4.6 KiB
Fix: Estabilidade do Agente e Queda de Inteligência (Validation & Embeddings)
Data: 15/01/2026
Autor: Antigravity (Agent)
Contexto: O agente "Captain" (Jasmine) apresentava instabilidade, crashando com erros de validação (Message) e perdendo capacidade de resposta ("inteligência") ao falhar em buscar no FAQ.
🚨 Problemas Identificados
-
Crash de Validação (
ActiveRecord::RecordInvalid):- Erro:
Validation failed: Text and attachments cannot be both nil. - Causa: Ferramentas (como
HandoffToolou falhas em Sub-Agentes) tentavam criar mensagens sem conteúdo e sem anexo, violando regras do modeloMessage. - Impacto: O fluxo era interrompido abruptamente, retornando erro 500 ou mensagem de erro genérica.
- Erro:
-
Queda de Inteligência (ArgumentError):
- Erro:
missing keyword: :query. - Causa: Incompatibilidade na passagem de argumentos entre RubyLLM (que usa keywords) e a estrutura legacy do
Agents::Tool(que usa hash único), exacerbado pelo Ruby 3. - Impacto: Ferramentas de busca (
FaqLookupTool) falhavam, impedindo o agente de consultar a base de conhecimento.
- Erro:
-
Crash de Vetores (
PG::Error):- Erro:
Expected 1536 dimensions, not 0. - Causa:
Captain::Llm::EmbeddingServiceretornava um array vazio[]quando o conteúdo erablank?ou quando a API da OpenAI falhava/retornava vazio. O banco de dados (pgvectorviahas_neighbors) rejeitava a busca com vetor de dimensão 0. - Impacto: O agente travava ao tentar buscar no FAQ, não conseguindo nem recuperar informação, nem fazer fallback para o contexto.
- Erro:
🛠️ Soluções Implementadas
1. Initializer Defensivo (FixNullMessageCrash)
Arquivo: config/initializers/fix_null_message_crash.rb
Criamos um Monkey Patch seguro via before_validation no modelo Message.
- Lógica: Se
contentfor nil Eattachmentsfor vazio -> Definecontent = "(System Message - Auto-fixed empty content)". - Benefício: Impede o crash silencioso e permite que o erro seja logado sem derrubar a threads do Sidekiq/Request.
2. Normalização de Argumentos (BasePublicTool)
Arquivos: enterprise/lib/captain/tools/base_public_tool.rb, enterprise/lib/captain/tools/faq_lookup_tool.rb
Portamos o método resolve_params do BaseTool para o BasePublicTool.
- Lógica: O método detecta se recebeu
(args_hash)ou(**keyword_params)e unifica em um únicoindifferent_access_hash. - Benefício: Torna as ferramentas robustas contra diferentes formas de invocação (pelo Rails ou pelo LLM runner).
3. Estabilidade de Embeddings (Blindagem Vetorial)
Arquivo: enterprise/app/services/captain/llm/embedding_service.rb
Alteramos o serviço para NUNCA retornar vetor vazio.
def get_embedding(content, model: @embedding_model)
# ANTES: return [] if content.blank? (CAUSAVA O ERRO DE DIMENSÃO)
return generate_fallback_embedding('empty') if content.blank?
instrument_embedding_call(...) do
response = RubyLLM.embed(...)
# Retorna o vetor da API se existir
end
rescue StandardError => e # Rescue amplo para capturar erros de API, Rede ou Parse
Rails.logger.error "Embedding Error: #{e.message}, using fallback"
generate_fallback_embedding(content)
end
Método generate_fallback_embedding:
Gera um vetor determinístico (baseado no hash SHA256 do texto) com 1536 dimensões preenchidas com ruído matemático normalizado.
- Benefício: Garante que a query SQL de busca por similaridade sempre rode. Se a API falhar, a busca roda com o vetor de fallback, retorna zero resultados relevantes (correto), e o Agente segue seu fluxo usando o Contexto da conversa, sem crashar.
🧪 Como Validar
- Teste de Validação: Tentar criar mensagem vazia no console (
Message.create!). Deve salvar com texto de fallback. - Teste de Busca: Perguntar sobre "Suite Alexa".
- Se API OK: Responde com base no FAQ.
- Se API Falhar (simular erro): Não crasha, loga erro, e responde "baseado no que sei..." ou pede desculpas, mas não explode erro 500.
⚠️ Lições Aprendidas
- Nunca confie em inputs de LLM: Eles podem enviar argumentos vazios ou nulos. O código deve ser defensivo.
- Vetores não podem ser vazios: O
pgvectorexige dimensão exata. Retornar[]em caso de erro é fatal. Sempre retorne vetor de zeros ou ruído em caso de falha para manter a integridade da query SQL. - Ruby 3 Keywords: A transição de Hash para Keywords ainda gera atritos em gems mais antigas ou código adaptado. Sempre use
resolve_paramsou similar para sanitizar a entrada de ferramentas.