Skip to content

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_agent e payment_agent sã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

  1. Crie o arquivo em ifriend_agent/agents/meu_novo_agent.py
  2. Registre em ifriend_agent/agents/__init__.py
  3. Adicione ao AgentBuilder (always-on ou condicional com feature flag)
  4. Adicione rota no prompt (prompts/orchestrator_prompt.pyROUTING_HINTS)
  5. Crie testes em ifriend_agent/tests/
  6. Atualize AGENTS.md

Consulte a seção "Como Adicionar um Novo Agente" no AGENTS.md para 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 em ifriend_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 ao root_agent, não transferir entre pares
  • O IFriendAPIClient tem métricas built-in — use para diagnosticar problemas de performance
  • Para referência técnica detalhada de cada agente, consulte AGENTS.md na raiz do projeto

Anterior: ← Arquitetura · Próximo: Adaptadores de Mensageria →