5.1 KiB
5.1 KiB
Correção e Arquitetura: Delegação via Scenarios (Captain AI)
Data: 07/01/2026 Contexto: Correção do fluxo onde a agente principal (Jasmine) consulta sub-agentes (Scenarios) para obter informações especializadas sem transferir o atendimento.
1. O Problema Original
O sistema falhava ao tentar acionar a ferramenta de delegação consultar_[cenario].
Os erros observados nos logs eram:
ArgumentError: wrong number of arguments: A ferramenta esperavakeyword arguments(pergunta_interna:), mas oToolRunnerenviava um objeto de contexto (Agents::ToolContext) e um hash de parâmetros.unknown keyword: :api_key: A chamada interna aoRubyLLMtentava passar a chave de API manualmente, mas a biblioteca não aceitava esse argumento (a configuração é global).- Retorno de Objeto Sujo: A ferramenta retornava uma instância de
RubyLLM::Message(ex:#<RubyLLM::Message:0x...>) para o agente principal, em vez do texto da resposta. Isso fazia com que a Jasmine não soubesse o que responder ao cliente.
Ponto de Quebra: Ocorria exatamente dentro do método execute da classe ScenarioDelegatorTool, tanto na entrada (assinatura do método) quanto na saída (retorno para a Jasmine).
2. Decisão de Arquitetura
Modelo Escolhido: Delegação por Proxy (Tool)
- Não usamos Handoff: Não transferimos o
contextoda conversa para o sub-agente. O cliente nunca fala diretamente com a Daniela (Reservas). - Jasmine como Interface Única: A Jasmine continua sendo a "dona" da conversa. Ela "vira para o lado", pergunta para a Daniela (via ferramenta), recebe a resposta técnica e a "traduz" ou repassa para o cliente com a voz dela.
- Tool como Proxy: A classe
ScenarioDelegatorToolatua como um wrapper que encapsula uma chamada LLM isolada. Para o sistema, é apenas uma ferramenta que retorna texto, igual a uma consulta de API.
3. O que foi Alterado
Arquivo principal: enterprise/lib/captain/tools/scenario_delegator_tool.rb
Antes (Quebrado)
class ScenarioDelegatorTool < Agents::Tool
def schema ... end # Definição manual de schema
def execute(pergunta_interna:) # Assinatura incompatível com ToolRunner
# Chamada incorreta com api_key
RubyLLM::Chat.new(...).ask(..., api_key: ...)
# Retorno implícito do objeto Message
end
end
Depois (Correto)
# 1. Herança correta para aproveitar a infraestrutura do projeto
class ScenarioDelegatorTool < Captain::Tools::BasePublicTool
# 2. Uso da DSL padrão para definição de parâmetros
param :pergunta_interna, type: 'string', desc: '...'
# 3. Assinatura padrão 'perform' que recebe o contexto e args nomeados
def perform(_tool_context, pergunta_interna:)
# ... lógica de prompt ...
# 4. Chamada RubyLLM sem api_key (usa config global do Runner)
response = RubyLLM::Chat.new(model: assistant.send(:agent_model))
.ask(prompt)
# 5. Extração explícita do conteúdo de texto
response.respond_to?(:content) ? response.content : response.to_s
rescue StandardError => e
"Erro ao consultar departamento: #{e.message}"
end
end
4. Fluxo Final (Estado Correto)
- Detecção: A
JasmineBrain(ou o LLM principal) detecta que o usuário quer algo específico de um cenário (ex: "quero reservar"). - Decisão: O LLM decide chamar a ferramenta
consultar_daniela_reservascom o argumentopergunta_interna="cliente quer reservar para semana que vem". - Execução (ToolRunner):
- Instancia
ScenarioDelegatorTool. - Seta a API Key do assistente globalmente no
RubyLLM(viaAgentRunnerService). - Chama
perform.
- Instancia
- Sub-agente (Proxy):
- A ferramenta monta um prompt "Você é Daniela..." com a pergunta da Jasmine.
- Chama o LLM (síncrono).
- Retorno: A ferramenta devolve apenas a string com a resposta da Daniela (ex: "Para reservar, use o link X...").
- Resposta ao Cliente: A Jasmine recebe essa string como
tool_output, incorpora ao contexto e gera a resposta final para o WhatsApp do cliente.
5. Pegadinhas (O que NÃO fazer)
- NUNCA implementar
executemanualmente em ferramentas que herdam deBasePublicTool. O métodoperformé o contrato correto que recebe tratamento de erros e logs. - NUNCA passar
api_keypara o métodoaskdo RubyLLM. OAgentRunnerServicegerencia a chave através deAgents.configureouRubyLLM.config. Passar manualmente gera erro de argumento. - CUIDADO com o retorno do LLM. O
RubyLLMretorna objetos complexos. Sempre extraia.contentou.to_santes de devolver para o agente principal, senão o agente "alucina" com o ID do objeto Ruby.
6. Como Validar
Nos logs da aplicação (docker logs ou console):
- Procure por
[DEBUG V2] Running with agents: jasmine. - Veja o
Agent result. - Dentro de
messages, localize o item comrole: :tool. - Verificação de Sucesso: O
contentda tool deve ser um texto legível (ex: "O link é...") e NÃO algo como#<RubyLLM::Message...>. - Se houver erro, aparecerá em
error: ...no log doAgent result.