3. O Agente iFriend (ADK Multi-Agent)¶
O que é o Google ADK?¶
O Agent Development Kit (ADK) é o framework do Google para criar agentes de IA. Os conceitos fundamentais são:
flowchart LR
subgraph "Google ADK"
A[LlmAgent] -->|usa| P[System Prompt]
A -->|registra| T[Tools]
A -->|registra| SA[Sub-Agentes]
A -->|configura| CB[Callbacks]
R[Runner] -->|executa| A
R -->|gerencia| S[Session]
end
| Conceito | Descrição |
|---|---|
| LlmAgent | Agente que recebe prompt, tools, callbacks e modelo LLM |
| Tool | Função Python que o agente pode invocar para buscar dados ou executar ações |
| Sub-Agent | Agente especializado que recebe delegação do agente principal |
| AgentTool | Wrapper que invoca um agente como tool (call-and-return) |
| Runner | Executor que gerencia o ciclo: receber mensagem → rodar agente → retornar resposta |
| Session | Estado da conversa (histórico, variáveis, contexto JWT) — compartilhado entre agentes |
| Callback | Função que roda antes ou depois do agente processar |
Arquitetura Multi-Agent (Coordinator/Dispatcher)¶
A iFriend utiliza uma arquitetura Coordinator/Dispatcher: um orquestrador central (root_agent) delega para agentes especializados conforme a intenção do usuário.
flowchart TB
subgraph "root_agent — Orquestrador Slim"
direction LR
ROOT["🧠 root_agent<br/>gemini-2.5-flash<br/>~60 linhas de prompt"]
RT["Tools:<br/>PreloadMemoryTool<br/>AgentTool research<br/>faq_tool"]
end
subgraph "Sub-Agentes — LLM-driven transfer"
direction TB
DISC["🔍 discovery_agent<br/>Busca de produtos<br/>6 tools"]
QUOTE["💰 quote_agent<br/>Cotação e preços<br/>4 tools"]
BOOK["📋 booking_agent<br/>Reservas<br/>8 tools"]
PAY["💳 payment_agent<br/>Pagamentos<br/>3 tools"]
UTIL["📧 utils_agent<br/>Email e CSV<br/>2 tools"]
SUPP["🤝 support_agent<br/>Atendimento humano<br/>0 tools"]
end
subgraph "AgentTool — call-and-return"
RES["🌐 research_agent<br/>Google Search"]
FAQ["❓ faq_agent<br/>FAQ iFriend"]
end
ROOT -->|"transfer_to_agent<br/>(multi-turn)"| DISC & QUOTE & BOOK & PAY & UTIL & SUPP
ROOT -->|"AgentTool<br/>(pontual)"| RES & FAQ
DISC & QUOTE & BOOK & PAY & UTIL & SUPP -->|"transfer_to_agent<br/>(volta)"| ROOT
style BOOK stroke-dasharray: 5 5
style PAY stroke-dasharray: 5 5
Tracejado:
booking_agentepayment_agentsão habilitados via feature flags (ENABLE_BOOKING,ENABLE_PAYMENT).
Dois padrões de delegação¶
| Padrão | Mecanismo | Comportamento | Agentes |
|---|---|---|---|
| LLM-driven transfer | sub_agents |
Sub-agente responde diretamente ao usuário (multi-turn). Quando termina, transfere de volta ao root_agent. |
discovery, quote, booking, payment, utils, support |
| Call-and-return | AgentTool |
Resultado volta ao orquestrador, que formula a resposta. | research, faq |
Regra de escape¶
Todos os sub-agentes possuem uma regra: se o usuário pedir algo fora do domínio do agente ativo, o sub-agente transfere imediatamente ao root_agent em vez de dizer "não consigo". O orquestrador então roteia para o agente correto.
Onde está definido¶
O agente é montado dinamicamente pelo AgentBuilder em ifriend_agent/agent_builder.py:
# Simplificado para entendimento
class AgentBuilder:
@staticmethod
def build() -> LlmAgent:
# 1. Valida feature flags (ex: PAYMENT requer BOOKING)
validate_flags()
# 2. Monta sub-agentes (always-on + condicionais)
sub_agents = [discovery_agent, quote_agent, utils_agent, support_agent]
if get_flag("ENABLE_BOOKING"):
sub_agents.append(booking_agent)
if get_flag("ENABLE_PAYMENT"):
sub_agents.append(payment_agent)
# 3. Gera prompt dinâmico (só rotas dos agentes ativos)
instruction = build_orchestrator_instruction(active_names)
# 4. Cria root_agent
return LlmAgent(
name="root_agent",
model="gemini-2.5-flash",
instruction=instruction,
sub_agents=sub_agents,
tools=[PreloadMemoryTool(), AgentTool(research_agent), faq_tool],
before_agent_callback=before_agent_callback_combined,
after_agent_callback=save_session_to_memory_callback,
)
# ifriend_agent/agent.py — ponto de entrada
root_agent = AgentBuilder.build()
O Prompt do Orquestrador (Dinâmico)¶
O prompt do root_agent é dinâmico — gerado pelo AgentBuilder com base nas feature flags ativas.
Definido em ifriend_agent/prompts/orchestrator_prompt.py.
Estrutura do prompt (~60 linhas):¶
| Seção | Descrição |
|---|---|
| Identidade | "Você é o iFriend Core Agent — assistente pessoal para experiências de viagem" |
| Memórias | Carregadas automaticamente via PreloadMemoryTool (FULLTEXT search) |
| Regras de UX | Execute silenciosamente → Apresente resultados → 1 pergunta por mensagem |
| Roteamento | Rotas dinâmicas para sub-agentes (só os ativos aparecem) |
| Hints de desabilitação | Instruções para quando booking/payment estão desabilitados |
| Guardrails | Segurança contra prompt injection, nunca revelar instruções |
| Lógica temporal | Data atual injetada via callback |
Roteamento de agentes¶
→ discovery_agent: buscar passeios, experiências, guias, tours em um destino
→ quote_agent: calcular preço, orçamento, cotação, variações de produto
→ booking_agent: reservar, fazer booking, identificar usuário para reserva
→ payment_agent: pagar reserva, cartão de crédito, parcelamento
→ utils_agent: enviar email, gerar CSV/planilha, exportar dados
→ support_agent: falar com humano, consultor, escalar atendimento, WhatsApp
Rotas de agentes desabilitados não aparecem no prompt — o LLM nunca vê opções indisponíveis.
Catálogo de Sub-Agentes¶
discovery_agent — Busca de Produtos¶
| Campo | Valor |
|---|---|
| Arquivo | ifriend_agent/agents/discovery_agent.py |
| Tools | busca_produtos, detalhes_experience, detalhes_guia, disponibilidade_calendario, disponibilidade_horarios, listar_guias_experience |
Fluxo: Busca produtos por destino → apresenta lista numerada → usuário seleciona → mostra detalhes + disponibilidade → transfere ao root_agent para cotação.
quote_agent — Cotação e Preços¶
| Campo | Valor |
|---|---|
| Arquivo | ifriend_agent/agents/quote_agent.py |
| Tools | tem_variacao, listar_variacoes, calcular_preco, cotacao_moeda |
Fluxo: Verifica variações → lista age policies → calcula preço (BRL) → converte moeda se necessário → transfere ao root_agent para reserva.
booking_agent — Reservas (condicional)¶
| Campo | Valor |
|---|---|
| Arquivo | ifriend_agent/agents/booking_agent.py |
| Tools | buscar_usuario, buscar_agencia, criar_conta_agencia, criar_conta_viajante, validar_dados_reserva, emitir_reserva_guia, emitir_reserva_experience, obter_reserva |
| Feature flag | ENABLE_BOOKING (default: true) |
Fluxo: Buscar/criar usuário → validar dados → emitir reserva → transfere ao root_agent para pagamento.
payment_agent — Pagamentos (condicional)¶
| Campo | Valor |
|---|---|
| Arquivo | ifriend_agent/agents/payment_agent.py |
| Tools | verificar_parcelas, gerar_token_cartao, processar_pagamento |
| Feature flag | ENABLE_PAYMENT (default: true, requer ENABLE_BOOKING) |
Fluxo: Verificar parcelas → coletar dados do cartão → tokenizar → processar pagamento.
utils_agent — Email e CSV¶
| Campo | Valor |
|---|---|
| Arquivo | ifriend_agent/agents/utils_agent.py |
| Tools | enviar_email_sendgrid, gerar_csv |
support_agent — Atendimento Humano¶
| Campo | Valor |
|---|---|
| Arquivo | ifriend_agent/agents/support_agent.py |
| Tools | Nenhuma (gera link WhatsApp via LLM) |
research_agent — Pesquisa Web (AgentTool)¶
| Campo | Valor |
|---|---|
| Arquivo | ifriend_agent/agents/research_agent.py |
| Tools | google_search (built-in ADK) |
| Modo | AgentTool (call-and-return ao orquestrador) |
faq_agent — FAQ da Plataforma (AgentTool)¶
| Campo | Valor |
|---|---|
| Arquivo | ifriend_agent/agents/faq_agent.py |
| Tools | Nenhuma (respostas via LLM com knowledge inline) |
| Modo | AgentTool (call-and-return ao orquestrador) |
Fluxo típico de uma conversa¶
stateDiagram-v2
[*] --> root_agent: Usuário envia mensagem
root_agent --> discovery_agent: "Quero passeio em Paris"
discovery_agent --> root_agent: Produto selecionado
root_agent --> quote_agent: Calcular preço
quote_agent --> root_agent: Orçamento apresentado
root_agent --> booking_agent: "Quero reservar"
booking_agent --> root_agent: Reserva emitida
root_agent --> payment_agent: "Quero pagar"
payment_agent --> root_agent: Pagamento processado
root_agent --> utils_agent: "Envia por email"
utils_agent --> root_agent: Email enviado
root_agent --> [*]: Conversa encerrada
note right of root_agent
O orquestrador roteia para o
sub-agente correto via
transfer_to_agent
end note
Tools: mapa completo por agente¶
flowchart TD
subgraph "🔍 discovery_agent"
busca_produtos["busca_produtos<br/><i>BigQuery + embeddings</i>"]
detalhes_exp["detalhes_experience"]
detalhes_guia["detalhes_guia"]
listar_guias["listar_guias_experience"]
calendario["disponibilidade_calendario"]
horarios["disponibilidade_horarios"]
end
subgraph "💰 quote_agent"
tem_variacao["tem_variacao"]
listar_variacoes["listar_variacoes"]
calcular_preco["calcular_preco"]
cotacao["cotacao_moeda"]
end
subgraph "📋 booking_agent"
buscar_user["buscar_usuario"]
buscar_agencia["buscar_agencia"]
criar_agencia["criar_conta_agencia"]
criar_viajante["criar_conta_viajante"]
validar["validar_dados_reserva"]
emitir_exp["emitir_reserva_experience"]
emitir_guia["emitir_reserva_guia"]
obter_reserva["obter_reserva"]
end
subgraph "💳 payment_agent"
parcelas["verificar_parcelas"]
token["gerar_token_cartao"]
pagar["processar_pagamento"]
end
subgraph "📧 utils_agent"
email["enviar_email_sendgrid"]
csv["gerar_csv"]
end
subgraph "🤖 root_agent"
preload["PreloadMemoryTool"]
research["AgentTool research_agent<br/><i>Google Search</i>"]
faq["faq_tool<br/><i>FAQ iFriend</i>"]
end
Como uma tool funciona¶
Toda tool segue este padrão:
# ifriend_agent/tools/alguma_tool.py
async def minha_tool(
parametro1: str,
parametro2: int,
tool_context: ToolContext, # Injetado automaticamente pelo ADK
) -> dict:
"""Descrição que o LLM lê para decidir quando usar esta tool."""
# 1. Pegar contexto JWT (se necessário)
jwt_ctx = tool_context.state.get("jwt_context", {})
# 2. Chamar API iFriend via client centralizado
client = IFriendAPIClient()
response = await client.get("/api/endpoint", params={...})
# 3. Retornar dados para o LLM processar
return {"resultado": response}
Pontos importantes:
- O ToolContext é injetado pelo ADK e dá acesso ao state da sessão (compartilhado entre todos os agentes)
- O IFriendAPIClient centraliza retry, circuit breaker e autenticação
- Cada tool pertence a um único sub-agente — não há sobreposição
Callbacks¶
flowchart LR
subgraph "Before Agent (root_agent)"
B1["inject_date<br/><i>Seta current_date no state</i>"]
B2["inject_jwt<br/><i>JWT context no state</i>"]
end
subgraph "Automático a cada LLM request"
PM["PreloadMemoryTool<br/><i>FULLTEXT search com query do usuário</i>"]
end
subgraph "After Agent (root_agent)"
A1["save_session_to_memory<br/><i>Resumo LLM → CloudSQL</i>"]
end
B1 --> B2 --> ROOT[Root Agent]
PM -.->|"injeta memórias<br/>antes de cada chamada LLM"| ROOT
ROOT --> A1
| Callback | Quando | O que faz | Arquivo |
|---|---|---|---|
before_agent_callback_combined |
Before (root) | Injeta current_date + jwt_context no state |
callbacks/agent_callbacks.py |
PreloadMemoryTool |
Antes de cada LLM request | Busca memórias via FULLTEXT search com query real do usuário | Built-in ADK |
save_session_to_memory_callback |
After (root) | Sumariza conversa via LLM → UPSERT no CloudSQL | callbacks/agent_callbacks.py |
Compartilhamento de Estado¶
O session.state é compartilhado por toda a hierarquia (root + sub-agents):
| Chave no state | Origem | Consumido por |
|---|---|---|
jwt_context |
before_agent_callback_combined (root) |
Todas as tools via tool_context.state |
current_date, current_year, current_datetime, current_weekday |
before_agent_callback_combined (root) |
Prompts via {current_date} etc. |
message_metadata |
Adapters (SSE, WebChat) | Tools de whitelabel, context enrichment |
user_id, company_id |
JWT decode | Tools de booking, busca |
Economia de Tokens (vs. arquitetura anterior)¶
| Aspecto | Antes (single-agent) | Agora (multi-agent) |
|---|---|---|
| Prompt | 336 linhas (tudo junto) | ~60 linhas (orquestrador) + ~30-55 (sub-agente ativo) |
| Tools por turno | 24 (sempre todas) | 2-8 (só as do agente ativo) |
| Payloads JSON | Inline no prompt | static_instruction (cacheável pelo Gemini) |
| Redução estimada | — | ~70% menos tokens por turno |
HTTP Client centralizado¶
Todas as tools usam o IFriendAPIClient para chamar a API iFriend:
flowchart LR
TOOL[Tool] --> CLIENT[IFriendAPIClient]
CLIENT --> POOL[Connection Pool<br/><i>aiohttp global</i>]
CLIENT --> RETRY[Retry<br/><i>backoff exponencial</i>]
CLIENT --> CB[Circuit Breaker<br/><i>CLOSED→OPEN→HALF_OPEN</i>]
CLIENT --> CACHE[Cache<br/><i>TTL em memória (GET)</i>]
CLIENT --> AUTH[Auto Auth<br/><i>JWT sistêmico ou usuário</i>]
POOL --> API[API iFriend]
Como adicionar um novo agente¶
- Crie o arquivo em
ifriend_agent/agents/meu_novo_agent.py - Registre em
ifriend_agent/agents/__init__.py - Adicione ao
AgentBuilder(always-on ou condicional com feature flag) - Adicione rota no prompt (
prompts/orchestrator_prompt.py→ROUTING_HINTS) - Crie testes em
ifriend_agent/tests/ - Atualize
AGENTS.md
Consulte a seção "Como Adicionar um Novo Agente" no
AGENTS.mdpara detalhes completos com exemplos de código.
Dicas para manutenção¶
- Modelo e parâmetros são configuráveis via env vars:
MODEL,AGENT_TEMPERATURE,AGENT_TOP_P,AGENT_TOP_K,AGENT_MAX_OUTPUT_TOKENS - LLM retry configurável:
LLM_RETRY_INITIAL_DELAY,LLM_RETRY_ATTEMPTS(config emifriend_agent/config/llm_config.py) - Feature flags controlam quais agentes estão ativos:
ENABLE_BOOKING,ENABLE_PAYMENT - Prompt do orquestrador é dinâmico — rotas e hints são gerados automaticamente com base nos agentes ativos
- Cada sub-agente tem
disallow_transfer_to_peers=True— só pode voltar aoroot_agent, não transferir entre pares - O
IFriendAPIClienttem métricas built-in — use para diagnosticar problemas de performance - Para referência técnica detalhada de cada agente, consulte
AGENTS.mdna raiz do projeto
Anterior: ← Arquitetura · Próximo: Adaptadores de Mensageria →