Sistema multi-agente de debate entre torcedores brasileiros usando Google ADK
(Agent Development Kit) + protocolo A2A oficial (a2a-sdk) + LLMs via
OpenRouter (LiteLLM). Quatro torcedores (Flamengo, Fluminense, Corinthians,
Palmeiras) discutem qual é o maior; um Supervisor sorteia dois deles por
rodada e atua como mestre de cerimônia; um Pesquisador com busca web em
tempo real fornece fatos; e um Julgador (especialista em retórica,
psicologia cognitiva e neuro-persuasão) avalia os argumentos ao final e devolve
o veredito ao Supervisor, que anuncia o resultado.
┌──────────────┐
│ Streamlit │ (porta 8501)
└──────┬───────┘
│ A2A JSON-RPC
┌────────────┬────────┼────────┬─────────────┐
▼ ▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ … ┌──────────┐ ┌──────────┐
│Supervisor│ │ Torcedor │ │Pesquisad.│ │ Julgador │
│ (8002) │ │8003/4/6/7│ │ (8005) │ │ (8008) │
└──────────┘ └──────────┘ └────┬─────┘ └──────────┘
│ web search :online
▼
🌐 internet
- 7 agentes A2A (
google-adk+a2a-sdkoficiais) rodando como servidores ASGI independentes via uvicorn — cada um expõe seuAgentCardem/.well-known/agent-card.json. - Sorteio aleatório do par de torcedores que vão debater (12 combinações possíveis); os outros dois clubes ficam visíveis na UI como "🪑 fora da rodada".
- Pesquisa real na web no Pesquisador: usa
openai/gpt-oss-20b:free:onlineno OpenRouter — o sufixo:onlineaciona o plugin de Web Search e o agente retorna respostas com URLs reais citadas. - Tag
[PESQUISA]…[/PESQUISA]: quando um torcedor não tem certeza de um número, ele inclui essa tag e a UI dispara um A2A para o Pesquisador antes do próximo turno. O resultado vira um card destacado no chat. - Julgador especialista em retórica, psicologia cognitiva, análise de debate e neuro-persuasão. Avalia os argumentos por 5 critérios (logos/ethos/pathos/neuro/resposta) e devolve veredito JSON ao Supervisor.
- Supervisor como mestre de cerimônia: sorteia, abre o debate, monitora tempo e anuncia o veredito que recebe do Julgador. Não julga argumentos por conta própria.
- Streamlit com design system próprio (
ui/theme.py+ui/components.py): paleta por clube, cards de agente, bolhas de chat coloridas por time, banner "X VS Y" após sorteio, painel "Bastidores A2A" com o log JSON-RPC. - Export JSON da transcrição completa do debate.
| Camada | Tecnologia |
|---|---|
| Agentes | google-adk ≥ 1.8 (LlmAgent, Runner, FunctionTool, LiteLlm) |
| Protocolo A2A | a2a-sdk 1.x — AgentExecutor, DefaultRequestHandler, create_jsonrpc_routes, A2ACardResolver, Client |
| LLM provider | OpenRouter via litellm |
| Modelo torcedores + supervisor + julgador | google/gemini-3.1-flash-lite |
| Modelo pesquisador | openai/gpt-oss-20b:free:online (web search habilitado) |
| HTTP server | uvicorn (ASGI) + Starlette |
| UI | Streamlit |
| Gerenciador deps | uv |
- Python 3.12+
uvinstalado (brew install uvoucurl -LsSf https://astral.sh/uv/install.sh | sh)- Conta no OpenRouter e uma API key
(
sk-or-v1-…)
git clone https://github.com/seu-usuario/FlaFludeAgentes.git
cd FlaFludeAgentes
uv synccp .env.example .env
# edite .env e cole sua OPENROUTER_API_KEYConteúdo mínimo do .env:
OPENROUTER_API_KEY=sk-or-v1-...
FAN_MODEL=google/gemini-3.1-flash-lite
RESEARCHER_MODEL=openai/gpt-oss-20b:free:online
OR_APP_NAME=FlaFludeAgentes
OR_SITE_URL=http://localhost:8501uv run python start_a2a_servers.pySaída esperada:
🤖 FlaFludeAgentes — 7 servidores A2A (Google ADK + a2a-sdk)
⚖️ Subindo Supervisor (porta 8002)…
🔴⚫ Subindo Flamengo (porta 8003)…
🟢🔴 Subindo Fluminense (porta 8004)…
🔎 Subindo Pesquisador (porta 8005)…
⚫⚪ Subindo Corinthians (porta 8006)…
🟢⚪ Subindo Palmeiras (porta 8007)…
🧠 Subindo Julgador (porta 8008)…
🔍 Health check via /.well-known/agent-card.json
⚖️ Supervisor :8002 🟢 OK
🔴⚫ Flamengo :8003 🟢 OK
🟢🔴 Fluminense :8004 🟢 OK
🔎 Pesquisador :8005 🟢 OK
⚫⚪ Corinthians :8006 🟢 OK
🟢⚪ Palmeiras :8007 🟢 OK
🧠 Julgador :8008 🟢 OK
uv run streamlit run app.py- Defina a duração do debate (2–15 minutos) na barra lateral.
- Clique Atualizar status A2A para conferir que os 7 cards estão verdes.
- Clique 🎬 Iniciar debate — o Supervisor sorteia dois clubes (banner VS aparece no centro).
- Clique ➡️ Turno do para gerar o próximo argumento. Se o
torcedor incluir
[PESQUISA]…[/PESQUISA], a UI consulta o Pesquisador automaticamente e mostra o card com fontes (URLs) antes de avançar. - Quando o timer zerar (ou ao clicar ⏹ Encerrar e analisar agora), o Julgador analisa o debate por logos/ethos/pathos/neuro/resposta, devolve o veredito ao Supervisor, e este faz o anúncio público com pontuação por critério. O botão para exportar a transcrição em JSON aparece em seguida.
# unit tests (LiteLlm wrapper, sorteio, parsing de fontes, fan specs)
uv run pytest -q
# smoke A2A end-to-end (requer servidores no ar e OpenRouter válido)
uv run python scripts/smoke_a2a.pyPara validar manualmente um agent card individual:
curl http://localhost:8002/.well-known/agent-card.json | jqFlaFludeAgentes/
├── a2a_runtime.py # LiteLlm helper + ADKAgentExecutor + builder Starlette
├── a2a_client.py # A2AClientRegistry + A2ASyncRegistry (consumido pelo Streamlit)
├── fan_agent.py # FanSpec + build_fan_app(...) compartilhado pelos 4 torcedores
├── supervisor_agent/agent.py # Mestre de cerimônia (sorteia, abre, anuncia veredito)
├── researcher_agent/agent.py # Web search via plugin :online do OpenRouter
├── judge_agent/agent.py # Julgador: retórica, psicologia, neuro-persuasão
├── flamengo_agent/agent.py
├── fluminense_agent/agent.py
├── corinthians_agent/agent.py
├── palmeiras_agent/agent.py
├── ui/
│ ├── theme.py # Design tokens + inject_css()
│ └── components.py # debate_header, agent_card, chat_bubble, research_card, …
├── app.py # Streamlit (não instancia agentes localmente)
├── start_a2a_servers.py # Spawna os 7 uvicorns em processos paralelos
├── scripts/smoke_a2a.py # Descobre os 7 e dispara 1 debate
├── tests/test_runtime.py # Pytest cobrindo a fiação
├── utils/ # Logging estruturado (mantido)
├── pyproject.toml
├── .env.example
├── CLAUDE.md # Instruções específicas para Claude Code
└── README.md # (este arquivo)
| Agente | Porta | Modelo | Tools principais |
|---|---|---|---|
| Supervisor | 8002 | google/gemini-3.1-flash-lite |
pick_starting_pair, start_debate, announce_winner, get_time_status |
| Flamengo | 8003 | google/gemini-3.1-flash-lite |
initial_argument, counter_argument, request_research |
| Fluminense | 8004 | google/gemini-3.1-flash-lite |
idem |
| Pesquisador | 8005 | openai/gpt-oss-20b:free:online |
(sem tool — usa Web Search direto) |
| Corinthians | 8006 | google/gemini-3.1-flash-lite |
idem torcedores |
| Palmeiras | 8007 | google/gemini-3.1-flash-lite |
idem torcedores |
| Julgador | 8008 | google/gemini-3.1-flash-lite |
judge_debate (logos/ethos/pathos/neuro/resp) |
Cada agente expõe:
GET /.well-known/agent-card.json→ metadados (nome, descrição, skills, capabilities, URL).POST /→ JSON-RPC do protocolo A2A (message/send,message/stream, etc.).
uv run python -m supervisor_agent.agent # 8002 — mestre de cerimônia
uv run python -m flamengo_agent.agent # 8003
uv run python -m fluminense_agent.agent # 8004
uv run python -m researcher_agent.agent # 8005
uv run python -m corinthians_agent.agent # 8006
uv run python -m palmeiras_agent.agent # 8007
uv run python -m judge_agent.agent # 8008 — julgador técnico- UI → Supervisor (A2A): "Inicie um debate de N minutos chamando
_start_debate_tool." - Supervisor roda a tool, sorteia 2 clubes entre os 4, devolve JSON
com
starting_team,opponent_team,benched, etc. - UI desenha o
team_picker_panel(banner VS) e ativa o card do torcedor sorteado. - UI → Torcedor sorteado (A2A): "Argumento inicial contra ,
200–350 palavras, use
[PESQUISA]para dado incerto." - Se a resposta tem
[PESQUISA]q[/PESQUISA]:- UI → Pesquisador (A2A): consulta
q. - Pesquisador chama o LLM
:online→ OpenRouter busca na web → resposta com URLs. - UI renderiza
research_cardcom a query e as fontes.
- UI → Pesquisador (A2A): consulta
- UI → Oponente (A2A): "Contra-argumente o último argumento."
- Turnos alternam até o cronômetro zerar (ou usuário encerrar).
- UI → Julgador (A2A):
judge_debate(history)→ veredito técnico pontuando cada torcedor por logos/ethos/pathos/neuro/resposta e declarando o vencedor. - UI → Supervisor (A2A):
announce_winner(verdict_json)→ o Supervisor recebe o veredito e formata o anúncio público de encerramento.
O Supervisor não julga argumentos por conta própria — ele apenas modera o fluxo e anuncia o veredito que o Julgador entrega.
Todo o tráfego A2A é registrado em A2AClientRegistry.message_log() e exibido
no expander 🎛️ Bastidores A2A da UI.
| Variável | Obrigatória | Default | Função |
|---|---|---|---|
OPENROUTER_API_KEY |
✅ | — | Autenticação no OpenRouter |
FAN_MODEL |
❌ | google/gemini-3.1-flash-lite |
Modelo dos torcedores, supervisor e julgador |
RESEARCHER_MODEL |
❌ | openai/gpt-oss-20b:free:online |
Modelo do pesquisador (sufixo :online ativa Web Search) |
OR_APP_NAME |
❌ | — | Aparece no leaderboard do OpenRouter |
OR_SITE_URL |
❌ | — | Referer enviado ao OpenRouter |
SUPERVISOR_PORT, RESEARCHER_PORT, FLAMENGO_PORT, JUDGE_PORT, … |
❌ | 8002–8008 | Sobrescrever portas individuais |
"OPENROUTER_API_KEY não encontrado" — confira que .env existe na raiz e
que está no formato KEY=valor sem aspas.
address already in use ao subir o launcher — uma instância anterior ficou
de pé. Limpe com:
lsof -ti :8002,8003,8004,8005,8006,8007,8008 | xargs kill -9Pesquisador retorna respostas sem fontes — o plugin :online pode estar
desativado no provedor; confirme o slug em https://openrouter.ai/models e que
RESEARCHER_MODEL termina com :online.
Streamlit não acha agentes (todos vermelhos) — clique Atualizar status
A2A na sidebar. Se continuar, valide manualmente com curl http://localhost:8002/.well-known/agent-card.json.
InvalidAgentResponseError: Agent should enqueue Task before … — versão
incompatível do a2a-sdk. O projeto foi testado com a2a-sdk==1.0.2; rode
uv sync para garantir.
- Nunca comite
.env. O.gitignorejá cobre. - Se você expor uma
OPENROUTER_API_KEYpor engano (chat, screenshot, log), rotacione-a no painel do OpenRouter — chaves vazadas devem ser consideradas comprometidas. - O Pesquisador faz fetch real à internet via OpenRouter; cada turno com
[PESQUISA]consome créditos do plugin de Web Search.
MIT — veja LICENSE.
⚽ FlaFludeAgentes — Fla, Flu, Timão e Verdão debatem; Claude (via OpenRouter) faz o agenciamento.