@oliverlessa/gemini-agent-lib
Version:
Uma biblioteca NodeJS para criar agentes de IA com Gemini LLM
1,091 lines (946 loc) • 52.4 kB
JavaScript
const Agent = require('./agent');
const VertexAILLM = require('./vertex-ai-llm');
const FunctionDeclarationSchemaType = require('./function-declaration-schema-type');
const AgentRegistry = require('./agent-registry');
const SemanticMemory = require('./memory/semantic-memory'); // Importar SemanticMemory (necessário para instanceof check)
const { v4: uuidv4 } = require('uuid');
const debug = require('./debug');
/**
* Classe ChatAgent - Agente especializado em manter conversas com histórico
*
* Esta classe estende a classe Agent base e adiciona funcionalidades para
* manter o histórico de conversa entre interações com o usuário.
* Opcionalmente, pode delegar tarefas para agentes especialistas e
* persistir o histórico de conversas, fatos e resumos usando diferentes
* adaptadores de memória. Inclui suporte opcional para RAG via SemanticMemory.
*
* Permite o uso de um ID de conversa personalizado, facilitando a integração
* com sistemas externos que já possuem seus próprios identificadores.
*/
class ChatAgent extends Agent {
/**
* Construtor da classe ChatAgent
*
* @param {Object} config - Configuração do agente
* @param {string} config.role - Papel/função do agente (ex: "Assistente Pessoal")
* @param {string} config.objective - Objetivo principal do agente
* @param {string} config.context - Contexto ou instruções para o agente
* @param {Object} [config.llm] - Instância do modelo de linguagem a ser usado.
* Se não fornecido, um VertexAILLM padrão será instanciado automaticamente no modo "chat".
* Se fornecido e não estiver no modo "chat", um aviso será emitido, mas o agente continuará funcionando.
* @param {Array} config.tools - Array de ferramentas disponíveis para o agente
* @param {boolean} config.enableGoogleSearch - Ativa o Google Search para o agente
* @param {boolean} config.enableSpecialistDelegation - Ativa a delegação para agentes especialistas
* @param {AgentRegistry} config.agentRegistry - Instância de AgentRegistry (opcional). Se a delegação estiver habilitada e este parâmetro não for fornecido,
* um novo AgentRegistry será criado usando specialistAgentsConfig.
* @param {Object} config.specialistAgentsConfig - Configuração dos agentes especialistas (opcional). Usado para criar um AgentRegistry
* se agentRegistry não for fornecido e a delegação estiver habilitada.
* @param {Object} [config.conversationMemory=null] - Instância de ConversationMemory para persistência do histórico de conversas
* @param {Object} [config.factMemory=null] - Instância de FactMemory para persistência de fatos
* @param {Object} [config.summaryMemory=null] - Instância de SummaryMemory para persistência de resumos
* @param {boolean} [config.autoManageFactMemory=false] - Ativa o gerenciamento automático de fatos extraídos das conversas
* @param {boolean} [config.autoManageSummaryMemory=false] - Ativa o gerenciamento automático de resumos das conversas
* @param {string} [config.chatId=null] - ID opcional para a conversa. Se não fornecido e a memória estiver ativa, um UUID será gerado.
* Se fornecido, deve ser uma string não vazia, caso contrário um novo UUID será gerado.
* @param {SemanticMemory} [config.semanticMemory=null] - Instância opcional da memória semântica para busca RAG.
* @param {number} [config.ragTopK=3] - Número de chunks a serem recuperados da memória semântica (se semanticMemory for fornecida).
* @param {string} [config.ragContextPrefix="CONTEXTO RECUPERADO:\n---\n"] - Prefixo para o contexto RAG no prompt (se semanticMemory for fornecida).
* @param {string} [config.ragContextSuffix="\n---"] - Sufixo para o contexto RAG no prompt (se semanticMemory for fornecida).
* @param {string} [config.ragInstruction="Baseie sua resposta nas informações do CONTEXTO RECUPERADO, se relevantes. Use também o histórico da conversa e outras informações de memória."] - Instrução adicional para o LLM sobre como usar o contexto RAG (se semanticMemory for fornecida).
* @param {Object} [config.ragFilter=null] - Filtro opcional a ser aplicado na busca da SemanticMemory (formato depende do adaptador, se semanticMemory for fornecida).
*/
constructor({
role,
objective,
context,
llm,
tools = [],
enableGoogleSearch = false,
enableSpecialistDelegation = false,
agentRegistry = null,
specialistAgentsConfig = {},
conversationMemory = null,
factMemory = null,
summaryMemory = null,
autoManageFactMemory = false,
autoManageSummaryMemory = false,
chatId = null,
semanticMemory = null, // Adiciona semanticMemory
// Adiciona parâmetros de configuração RAG, com valores padrão
ragTopK = 3,
ragContextPrefix = "CONTEXTO RECUPERADO:\n---\n",
ragContextSuffix = "\n---",
ragInstruction = "Baseie sua resposta nas informações do CONTEXTO RECUPERADO, se relevantes. Use também o histórico da conversa e outras informações de memória.",
ragFilter = null
}) {
// Se o LLM não for fornecido, cria uma instância padrão do VertexAILLM no modo chat
let llmInstance = llm;
if (!llmInstance) {
debug.chatAgent("LLM não fornecido. Criando instância padrão do VertexAILLM no modo chat.");
llmInstance = new VertexAILLM({
projectId: process.env.GOOGLE_CLOUD_PROJECT_ID,
credentialsPath: process.env.GOOGLE_APPLICATION_CREDENTIALS,
modelName: "gemini-2.0-flash-001",
mode: "chat",
generationConfig: {
maxOutputTokens: 2048,
temperature: 0.2
}
});
}
// Verifica se o LLM está no modo chat
if (llmInstance.mode !== "chat") {
console.warn("AVISO: O LLM não está configurado no modo 'chat'. O histórico de conversa pode não funcionar corretamente.");
}
// Preparar variáveis para configuração de delegação
let allTools = [...tools];
let finalContext = context;
// Chama o construtor da classe pai (Agent) PRIMEIRO
super({
role,
objective,
context: finalContext,
task: "", // Inicialmente vazio, será preenchido a cada interação
llm: llmInstance,
tools: allTools,
enableGoogleSearch
});
// Configuração de memória
this.conversationMemory = conversationMemory;
this.factMemory = factMemory;
this.summaryMemory = summaryMemory;
this.autoManageFactMemory = autoManageFactMemory;
this.autoManageSummaryMemory = autoManageSummaryMemory;
this.semanticMemory = semanticMemory; // Atribui a memória injetada
// Armazena as configurações de RAG passadas via config
// e valida o tipo de semanticMemory
if (this.semanticMemory) {
if (!(this.semanticMemory instanceof SemanticMemory)) {
throw new Error("A configuração 'semanticMemory' deve ser uma instância de SemanticMemory.");
}
this.ragTopK = ragTopK;
this.ragContextPrefix = ragContextPrefix;
this.ragContextSuffix = ragContextSuffix;
this.ragInstruction = ragInstruction;
this.ragFilter = ragFilter;
debug.chatAgent("Memória Semântica (RAG) configurada e habilitada.");
} else {
// Garante que as propriedades RAG existam mesmo se a memória não for fornecida
// para evitar erros em _prepareEnrichedContext se acessadas inadvertidamente.
this.ragTopK = 0;
this.ragContextPrefix = '';
this.ragContextSuffix = '';
this.ragInstruction = '';
this.ragFilter = null;
}
// Lógica de atribuição do chatId
// A presença de SemanticMemory também deve ativar a geração de chatId se necessário
const hasMemory = this.conversationMemory || this.factMemory || this.summaryMemory || this.semanticMemory;
if (chatId && typeof chatId === 'string' && chatId.trim() !== '') {
// Usa o chatId fornecido se for uma string não vazia
this.chatId = chatId.trim(); // Usar trim() para remover espaços extras
debug.chatAgent(`Usando chatId fornecido: ${this.chatId}`);
} else if (hasMemory) {
// Gera um novo chatId se alguma memória estiver ativa e nenhum ID válido foi fornecido
this.chatId = uuidv4();
debug.chatAgent(`Gerando novo chatId: ${this.chatId}`);
} else {
// Nenhum chatId necessário se não houver memória
this.chatId = null;
}
// Inicializa o histórico de conversas como um array vazio PRIMEIRO
this.conversationHistory = [];
if (this.conversationMemory && this.chatId) {
// Se tiver memória persistente, carrega o histórico
this._loadConversationHistory();
}
// Configurar delegação para especialistas (se habilitada) DEPOIS do super()
this.specialistDelegationEnabled = false; // Por padrão, a delegação está desabilitada
if (enableSpecialistDelegation) {
this._setupSpecialistDelegation(agentRegistry, specialistAgentsConfig, role, objective);
}
}
/**
* Configura a delegação para agentes especialistas
* @private
* @param {AgentRegistry} agentRegistry - Instância de AgentRegistry (opcional)
* @param {Object} specialistAgentsConfig - Configuração dos agentes especialistas
* @param {string} role - Papel/função do agente
* @param {string} objective - Objetivo principal do agente
*/
_setupSpecialistDelegation(agentRegistry, specialistAgentsConfig, role, objective) {
try {
// Configurar o registry de agentes especialistas
if (agentRegistry && agentRegistry instanceof AgentRegistry) {
this.agentRegistry = agentRegistry;
} else {
// Criar uma nova instância com a configuração fornecida
this.agentRegistry = new AgentRegistry(specialistAgentsConfig);
}
// Verificar se há especialistas registrados
const availableRoles = this.agentRegistry.getAvailableSpecialistRoles();
if (availableRoles.length === 0) {
console.warn('[ChatAgent] Aviso: Delegação de especialistas habilitada, mas nenhum especialista registrado.');
} else {
// Se não foi fornecido um contexto personalizado, criar um com informações sobre especialistas
if (!this.context) {
this.context = `Você é um ${role} com o objetivo de ${objective}.
Sua principal função é conversar com o usuário. Responda diretamente sempre que possível.
Você tem acesso aos seguintes especialistas para tarefas específicas:
${availableRoles.map(r => `- ${r}`).join('\n')}
Instruções para delegação:
1. Analise a solicitação do usuário.
2. Se for uma pergunta geral ou algo que você pode responder, FAÇA ISSO DIRETAMENTE.
3. **SOMENTE SE** a solicitação exigir CLARAMENTE uma das especialidades listadas acima, use a ferramenta 'delegate_task_to_specialist'.
4. Ao delegar:
- Forneça o 'specialist_role' EXATO da lista.
- Crie uma 'task_for_specialist' CLARA e AUTOCONTIDA, passando TODO o contexto necessário.
5. Após receber a resposta do especialista, integre-a NATURALMENTE na sua resposta final ao usuário.
6. NÃO invente especialistas ou use a ferramenta de delegação para tarefas simples.`;
}
// Adicionar a ferramenta de delegação
const delegationTool = this._createDelegationTool(availableRoles);
this.tools.unshift(delegationTool);
// Habilitar a delegação
this.specialistDelegationEnabled = true;
debug.chatAgent(`Delegação para especialistas habilitada com ${availableRoles.length} especialistas disponíveis.`);
}
} catch (error) {
console.warn(`[ChatAgent] Aviso: Não foi possível habilitar a delegação para especialistas: ${error.message}`);
}
}
/**
* Cria a ferramenta de delegação para especialistas
* @private
* @param {Array<string>} availableRoles - Lista de papéis de especialistas disponíveis
* @returns {Object} Objeto de configuração da ferramenta de delegação
*/
_createDelegationTool(availableRoles) {
return {
name: "delegate_task_to_specialist",
description: `Delega uma tarefa específica para um agente especialista quando a pergunta do usuário requer conhecimento ou ação especializada.`,
parameters: {
type: FunctionDeclarationSchemaType.OBJECT,
properties: {
specialist_role: {
type: FunctionDeclarationSchemaType.STRING,
description: `O papel do agente especialista necessário. Deve ser um dos seguintes: ${availableRoles.join(', ')}.`
},
task_for_specialist: {
type: FunctionDeclarationSchemaType.STRING,
description: "A descrição clara e completa da tarefa a ser executada pelo agente especialista."
}
},
required: ["specialist_role", "task_for_specialist"]
}
};
}
/**
* Carrega o histórico de conversas da memória persistente
* @private
*/
async _loadConversationHistory() {
if (!this.conversationMemory || !this.chatId) {
this.conversationHistory = [];
return;
}
try {
debug.chatAgent(`Carregando histórico de conversa para chatId: ${this.chatId}`);
this.conversationHistory = await this.conversationMemory.loadHistory(this.chatId);
debug.chatAgent(`Histórico carregado: ${this.conversationHistory.length} mensagens`);
} catch (error) {
console.error(`[ChatAgent] Erro ao carregar histórico de conversa:`, error);
// Em caso de erro, inicializa um array vazio
this.conversationHistory = [];
}
}
/**
* Prepara um contexto enriquecido com informações das memórias (Fatos, Resumo, RAG).
* A ordem de adição ao prompt é: Fatos -> Contexto RAG -> Resumo.
* @private
* @returns {Promise<string>} Contexto enriquecido com informações das memórias.
*/
async _prepareEnrichedContext() {
// Começa com o contexto original do agente
let enrichedContext = this.context || '';
// Variáveis para armazenar as informações das memórias
let memoryInfo = '';
let hasFacts = false;
let hasSummary = false;
let hasRagContext = false; // Nova flag para RAG
let memoryHeaderAdded = false; // Flag para adicionar o cabeçalho apenas uma vez
// Função auxiliar para adicionar o cabeçalho da memória
const addMemoryHeader = () => {
if (!memoryHeaderAdded) {
memoryInfo += '\n\n--- Informações da Memória ---\n';
memoryHeaderAdded = true;
}
};
// 1. Recupera fatos da factMemory (se disponível)
if (this.factMemory && this.chatId) {
try {
const facts = await this.getAllFacts();
if (facts && Object.keys(facts).length > 0) {
addMemoryHeader();
memoryInfo += 'Fatos Conhecidos:\n';
for (const [key, value] of Object.entries(facts)) {
memoryInfo += `- ${key}: ${JSON.stringify(value)}\n`;
}
hasFacts = true;
}
} catch (error) {
console.error(`[ChatAgent] Erro ao recuperar fatos para contexto:`, error);
}
}
// 2. Recupera contexto da SemanticMemory (RAG - se disponível e houver uma task/mensagem atual)
if (this.semanticMemory && this.task) {
try {
debug.chatAgent(`[RAG] Buscando contexto para: "${this.task}" (topK=${this.ragTopK})`);
const searchResults = await this.semanticMemory.search(this.task, this.ragTopK, this.ragFilter);
debug.chatAgent(`[RAG] Encontrados ${searchResults.length} chunks relevantes.`);
if (searchResults.length > 0) {
addMemoryHeader();
const ragContextFormatted = this._formatRagContext(searchResults); // Usa o método interno
memoryInfo += `\n${ragContextFormatted}\n`; // Adiciona o contexto RAG formatado
hasRagContext = true;
}
} catch (error) {
console.error(`[ChatAgent] Erro ao buscar na SemanticMemory para contexto RAG:`, error);
}
}
// 3. Recupera o resumo mais recente da summaryMemory (se disponível)
if (this.summaryMemory && this.chatId) {
try {
const latestSummary = await this.getLatestSummary();
if (latestSummary) {
addMemoryHeader();
memoryInfo += `\nResumo Anterior:\n${latestSummary}\n`;
hasSummary = true;
}
} catch (error) {
console.error(`[ChatAgent] Erro ao recuperar resumo para contexto:`, error);
}
}
// Adiciona o rodapé e instruções se alguma informação de memória foi adicionada
if (memoryHeaderAdded) { // Verifica se o cabeçalho foi adicionado
memoryInfo += '--- Fim das Informações da Memória ---\n';
// Instruções combinadas para todas as memórias
let memoryInstructions = `
INSTRUÇÕES PARA USO DAS INFORMAÇÕES DA MEMÓRIA (Fatos, Resumos, Contexto Recuperado):
1. Use as informações acima como conhecimento de fundo para enriquecer sua resposta.`;
if (hasRagContext) {
memoryInstructions += `
2. Priorize o CONTEXTO RECUPERADO (RAG) se ele for diretamente relevante para a pergunta atual.`;
} else {
memoryInstructions += `
2. Use Fatos e Resumos para contexto geral e consistência.`;
}
memoryInstructions += `
3. NÃO repita explicitamente informações da memória se não for necessário.
4. Evite redundâncias. Integre o conhecimento das memórias de forma natural e concisa.`;
if (hasRagContext) {
memoryInstructions += `
5. Se o CONTEXTO RECUPERADO contradisser o histórico ou outras memórias, use seu julgamento ou peça esclarecimento.`;
// Adiciona instrução específica do RAG se configurada
if (this.ragInstruction) {
memoryInstructions += `\nInstrução Adicional RAG: ${this.ragInstruction}`;
}
}
memoryInfo += memoryInstructions;
// Adiciona as informações da memória ao contexto original
enrichedContext += memoryInfo;
const memoryTypes = [hasFacts && 'fatos', hasSummary && 'resumo', hasRagContext && 'contexto RAG'].filter(Boolean).join(', ');
debug.chatAgent(`Contexto enriquecido com: ${memoryTypes}`);
}
return enrichedContext;
}
/**
* Formata os resultados da busca semântica em uma string de contexto para o prompt.
* @param {import('./memory/semantic-memory').SearchResult[]} searchResults - Resultados da busca.
* @returns {string} O contexto formatado.
* @private
*/
_formatRagContext(searchResults) {
// Este método agora pertence ao ChatAgent e usa as propriedades ragContextPrefix/Suffix
if (!searchResults || searchResults.length === 0) {
return "";
}
const contextParts = searchResults.map((result, index) => {
// Tenta acessar metadados com segurança
const metadata = result.metadata || {};
const source = metadata.source || 'Desconhecida';
const chunkIndex = metadata.chunkIndex ?? index; // Usa index como fallback
const score = result.score?.toFixed(4) ?? 'N/A'; // Usa N/A como fallback
let part = `Fonte: ${source} (Chunk: ${chunkIndex}, Score: ${score})\n`;
part += result.content;
return part;
});
// Usa os prefixos/sufixos definidos no agente (this.ragContextPrefix/Suffix)
return `${this.ragContextPrefix || ''}${contextParts.join('\n---\n')}${this.ragContextSuffix || ''}`;
}
/**
* Processa uma mensagem do usuário e gera uma resposta.
* Inclui a preparação do contexto enriquecido antes de chamar o LLM.
* @param {string} message - Mensagem do usuário
* @returns {Promise<Object>} - Resposta do LLM ou objeto com sinal interno
*/
async processUserMessage(message) {
debug.chatAgent(`\nProcessando mensagem do usuário: "${message}"`);
// Define a mensagem do usuário como a tarefa atual (usada por _prepareEnrichedContext)
this.task = message;
// Adiciona a mensagem do usuário ao histórico ANTES de preparar o contexto
await this._addToConversationHistory("user", message);
// Prepara o histórico para o LLM (inclui a mensagem atual do usuário)
const history = this.prepareHistoryForLLM();
debug.chatAgent(`Histórico atual para LLM: ${history.length} turnos`);
// Prepara o contexto enriquecido com informações das memórias (incluindo RAG se aplicável)
const enrichedContext = await this._prepareEnrichedContext();
// Gera a resposta usando o LLM no modo chat
let response = await this.llm.generateContent({
// O prompt principal pode ser apenas a última mensagem do usuário,
// pois o histórico completo é passado separadamente.
// Ou, dependendo do LLM, pode ser necessário incluir a última mensagem aqui também.
// Vamos manter a mensagem aqui por enquanto, mas isso pode ser ajustado.
prompt: message,
context: enrichedContext, // Contexto base + memórias (fatos, RAG, resumo)
tools: this.prepareToolsForLLM(),
history: history // Histórico completo da conversa
});
let lastToolResult = null; // Rastreia o resultado da última ferramenta
// Loop para lidar com function calls
while (response && response.functionCall) {
const functionCall = response.functionCall;
debug.chatAgent(`\nLLM solicitou Function Call: ${functionCall.name}`);
debug.chatAgent("Argumentos: %o", functionCall.args);
// Adiciona a resposta com function call ao histórico (representa a "intenção" do modelo)
await this._addToConversationHistory("model", response.text || `[Function Call: ${functionCall.name}]`);
let functionResult;
// Verifica se é uma chamada para delegação
if (functionCall.name === "delegate_task_to_specialist" && this.specialistDelegationEnabled) {
try {
// ... (lógica de delegação existente) ...
const { specialist_role, task_for_specialist } = functionCall.args;
if (!specialist_role || !task_for_specialist) {
throw new Error("Argumentos 'specialist_role' ou 'task_for_specialist' ausentes ou inválidos.");
}
const availableRoles = this.agentRegistry.getAvailableSpecialistRoles();
if (!availableRoles.includes(specialist_role)) {
throw new Error(`Papel de especialista inválido: '${specialist_role}'. Papéis disponíveis: ${availableRoles.join(', ')}`);
}
debug.chatAgent(`Delegando tarefa para: ${specialist_role}`);
debug.chatAgent(`Tarefa para o especialista: ${task_for_specialist}`);
const specialistAgent = this.agentRegistry.getSpecialistAgent(specialist_role);
specialistAgent.task = task_for_specialist;
const specialistResponse = await specialistAgent.executeTask();
const specialistResponseText = typeof specialistResponse === 'object' && specialistResponse.text ? specialistResponse.text : specialistResponse;
debug.chatAgent(`Resposta do especialista ${specialist_role}: ${specialistResponseText.substring(0, 200)}${specialistResponseText.length > 200 ? '...' : ''}`);
// Adiciona a resposta do especialista ao histórico como a resposta final do modelo
// (Isso pode ser debatido - talvez devesse ser uma mensagem 'user' simulada?)
// Por ora, mantém como 'model' para indicar que foi a resposta final da cadeia.
await this._addToConversationHistory("model", specialistResponseText);
// Retorna diretamente a resposta do especialista
return { text: specialistResponseText };
} catch (error) {
console.error(`[ChatAgent] Erro durante delegação:`, error);
functionResult = `Erro ao processar delegação: ${error.message}`;
// Não retorna aqui, permite que o loop continue para informar o LLM sobre o erro
}
} else {
// Lógica para outras ferramentas
const functionToExecute = this.findToolFunction(functionCall.name);
if (!functionToExecute) {
const errorMessage = `Função '${functionCall.name}' não encontrada nas tools do agente.`;
console.error(errorMessage);
functionResult = `Erro: ${errorMessage}`;
} else {
// Executar a função e obter o resultado
try {
const functionArgs = functionCall.args || {};
debug.chatAgent(`Executando função '${functionCall.name}' com argumentos: %o`, functionArgs);
functionResult = await functionToExecute(functionArgs);
debug.chatAgent(`Resultado da Function Call '${functionCall.name}': %o`, functionResult);
} catch (error) {
console.error(`[ChatAgent] Erro ao executar a função '${functionCall.name}':`, error);
functionResult = `Erro ao executar a função '${functionCall.name}': ${error.message}`;
}
}
}
lastToolResult = functionResult; // Armazena o resultado da ferramenta
// Formata a mensagem com o resultado da função para o LLM
// Nota: Passar o resultado como 'user' é uma convenção comum para function calling
const functionResultMessage = JSON.stringify({
function_name: functionCall.name,
result: functionResult
});
// Adiciona o resultado da função ao histórico como uma mensagem 'user' (ou 'function' se o LLM suportar)
await this._addToConversationHistory("user", `[Resultado da Função: ${functionCall.name}]\n${functionResultMessage}`);
// Prepara o contexto enriquecido novamente (pode ter mudado se a função alterou fatos/resumos)
const nextEnrichedContext = await this._prepareEnrichedContext();
// Gera uma nova resposta com base no resultado da função
response = await this.llm.generateContent({
prompt: `Use o resultado da função ${functionCall.name} para continuar a conversa. Resultado: ${functionResultMessage}`, // Prompt pode ser mais direcionado
context: nextEnrichedContext,
tools: this.prepareToolsForLLM(),
history: this.prepareHistoryForLLM() // Passa o histórico atualizado
});
}
// Adiciona a resposta final do LLM ao histórico (se não for uma function call)
if (response && !response.functionCall && response.text) {
await this._addToConversationHistory("model", response.text);
// Avalia a conversa para gerenciar memórias automáticas
if ((this.autoManageFactMemory && this.factMemory) ||
(this.autoManageSummaryMemory && this.summaryMemory)) {
// Passa a mensagem original do usuário e a resposta final do agente
await this._evaluateAndManageMemories(message, response.text);
}
// Verifica se a última ferramenta chamada retornou um sinal interno
const isSignal = lastToolResult && lastToolResult._signal_type;
if (isSignal) {
debug.chatAgent("Anexando resultado de sinal à resposta final.");
// Retorna um objeto combinado com o texto e o sinal
return {
text: response.text,
_internalToolSignal: lastToolResult
};
}
} else if (response && !response.text && !response.functionCall) {
debug.chatAgent("LLM retornou uma resposta vazia sem function call.");
// Considerar retornar uma resposta padrão ou erro? Por enquanto, retorna a resposta vazia.
}
// Retorna a resposta final do LLM (pode ser texto ou vazia se houve erro ou só function call inicial)
return response || { text: "[Ocorreu um erro interno ao processar a resposta]" }; // Fallback
}
/**
* Processa o resultado de uma function call e gera uma nova resposta
* (Este método pode se tornar redundante com a lógica integrada em processUserMessage,
* mas mantido por ora para compatibilidade ou casos de uso específicos)
* @param {string} functionName - Nome da função chamada
* @param {Object} result - Resultado da função
* @returns {Promise<Object>} - Nova resposta do LLM
*/
async processFunctionResult(functionName, result) {
debug.chatAgent(`\nProcessando resultado da função ${functionName} (via processFunctionResult)`);
// Formata a mensagem com o resultado da função
const functionResultMessage = JSON.stringify({
function_name: functionName,
result: result
});
// Adiciona o resultado da função ao histórico como uma mensagem do usuário
await this._addToConversationHistory("user", `[Resultado da Função: ${functionName}]\n${functionResultMessage}`);
// Prepara o contexto enriquecido com informações das memórias
const enrichedContext = await this._prepareEnrichedContext();
// Gera uma nova resposta com base no resultado da função
const response = await this.llm.generateContent({
prompt: `Use o resultado da função ${functionName} para continuar. Resultado: ${functionResultMessage}`,
context: enrichedContext,
tools: this.prepareToolsForLLM(),
history: this.prepareHistoryForLLM()
});
// Adiciona a resposta ao histórico (se houver texto)
if (response && response.text) {
await this._addToConversationHistory("model", response.text);
}
return response;
}
/**
* Prepara o histórico no formato esperado pelo LLM
*
* @returns {Array<{role: string, parts: Array<{text: string}>}>} - Histórico formatado para o LLM
*/
prepareHistoryForLLM() {
return this.conversationHistory.map(item => ({
role: item.role,
parts: [{ text: item.content }]
}));
}
/**
* Adiciona uma mensagem ao histórico de conversa (volátil e persistente)
*
* @param {string} role - Papel (user ou model)
* @param {string} content - Conteúdo da mensagem
* @private
*/
async _addToConversationHistory(role, content) {
// Adiciona à memória volátil
this.conversationHistory.push({ role, content });
debug.chatAgent(`Adicionado ao histórico: ${role} - ${content.substring(0, 50)}${content.length > 50 ? '...' : ''}`);
// Se tiver memória persistente configurada, adiciona também lá
if (this.conversationMemory && this.chatId) {
try {
await this.conversationMemory.appendMessage(this.chatId, role, content);
} catch (error) {
console.error(`[ChatAgent] Erro ao persistir mensagem:`, error);
// Continua mesmo se falhar a persistência
}
}
}
/**
* Limpa o histórico de conversa (volátil e persistente)
*/
async clearHistory() {
debug.chatAgent(`Histórico limpo (${this.conversationHistory.length} mensagens removidas)`);
// Limpa a memória volátil
this.conversationHistory = [];
// Se tiver memória persistente configurada, limpa também lá
if (this.conversationMemory && this.chatId) {
try {
await this.conversationMemory.clearHistory(this.chatId);
} catch (error) {
console.error(`[ChatAgent] Erro ao limpar histórico persistente:`, error);
// Continua mesmo se falhar a limpeza da persistência
}
}
}
/**
* Define um fato na memória de fatos (se configurada)
*
* @param {string} key - Chave do fato
* @param {any} value - Valor do fato
* @returns {Promise<boolean>} - true se o fato foi armazenado, false caso contrário
*/
async setFact(key, value) {
if (!this.factMemory || !this.chatId) {
debug.chatAgent(`Memória de fatos não configurada. Fato '${key}' não armazenado.`);
return false;
}
try {
await this.factMemory.setFact(this.chatId, key, value);
debug.chatAgent(`Fato armazenado: ${key} = %o`, value);
return true;
} catch (error) {
console.error(`[ChatAgent] Erro ao armazenar fato '${key}':`, error);
return false;
}
}
/**
* Recupera um fato da memória de fatos (se configurada)
*
* @param {string} key - Chave do fato
* @returns {Promise<any|null>} - Valor do fato ou null se não encontrado ou se a memória não estiver configurada
*/
async getFact(key) {
if (!this.factMemory || !this.chatId) {
debug.chatAgent(`Memória de fatos não configurada. Não foi possível recuperar '${key}'.`);
return null;
}
try {
const value = await this.factMemory.getFact(this.chatId, key);
debug.chatAgent(`Fato recuperado: ${key} = %o`, value);
return value;
} catch (error) {
console.error(`[ChatAgent] Erro ao recuperar fato '${key}':`, error);
return null;
}
}
/**
* Recupera todos os fatos da memória de fatos (se configurada)
*
* @returns {Promise<Object|null>} - Objeto com todos os fatos ou objeto vazio se a memória não estiver configurada
*/
async getAllFacts() {
if (!this.factMemory || !this.chatId) {
debug.chatAgent(`Memória de fatos não configurada. Não foi possível recuperar fatos.`);
return {};
}
try {
const facts = await this.factMemory.getAllFacts(this.chatId);
debug.chatAgent(`Fatos recuperados: ${Object.keys(facts).length} fatos`);
return facts;
} catch (error) {
console.error(`[ChatAgent] Erro ao recuperar todos os fatos:`, error);
return {};
}
}
/**
* Adiciona um resumo à memória de resumos (se configurada)
*
* @param {string} summaryContent - Conteúdo do resumo
* @param {Date} [timestamp=new Date()] - Timestamp do resumo
* @returns {Promise<boolean>} - true se o resumo foi armazenado, false caso contrário
*/
async addSummary(summaryContent, timestamp = new Date()) {
if (!this.summaryMemory || !this.chatId) {
debug.chatAgent(`Memória de resumos não configurada. Resumo não armazenado.`);
return false;
}
try {
await this.summaryMemory.addSummary(this.chatId, summaryContent, timestamp);
debug.chatAgent(`Resumo armazenado: ${summaryContent.substring(0, 50)}${summaryContent.length > 50 ? '...' : ''}`);
return true;
} catch (error) {
console.error(`[ChatAgent] Erro ao armazenar resumo:`, error);
return false;
}
}
/**
* Recupera o resumo mais recente da memória de resumos (se configurada)
*
* @returns {Promise<string|null>} - Conteúdo do resumo mais recente ou null se não encontrado ou se a memória não estiver configurada
*/
async getLatestSummary() {
if (!this.summaryMemory || !this.chatId) {
debug.chatAgent(`Memória de resumos não configurada. Não foi possível recuperar o resumo mais recente.`);
return null;
}
try {
const summary = await this.summaryMemory.getLatestSummary(this.chatId);
if (summary) {
debug.chatAgent(`Resumo mais recente recuperado: ${summary.substring(0, 50)}${summary.length > 50 ? '...' : ''}`);
} else {
debug.chatAgent(`Nenhum resumo encontrado.`);
}
return summary;
} catch (error) {
console.error(`[ChatAgent] Erro ao recuperar resumo mais recente:`, error);
return null;
}
}
/**
* Recupera todos os resumos da memória de resumos (se configurada)
*
* @param {number} [limit] - Número máximo de resumos a serem retornados
* @returns {Promise<Array<{summaryContent: string, timestamp: Date}>|[]>} - Array de resumos ou array vazio se a memória não estiver configurada
*/
async getAllSummaries(limit) {
if (!this.summaryMemory || !this.chatId) {
debug.chatAgent(`Memória de resumos não configurada. Não foi possível recuperar resumos.`);
return [];
}
try {
const summaries = await this.summaryMemory.getAllSummaries(this.chatId, limit);
debug.chatAgent(`Resumos recuperados: ${summaries.length} resumos`);
return summaries;
} catch (error) {
console.error(`[ChatAgent] Erro ao recuperar todos os resumos:`, error);
return [];
}
}
/**
* Configura o registro de agentes especialistas
*
* @param {Object} specialistAgentsConfig - Configuração dos agentes especialistas
* @returns {ChatAgent} A própria instância para encadeamento de métodos
*/
setSpecialistAgentsConfig(specialistAgentsConfig) {
if (!this.specialistDelegationEnabled) {
console.warn('[ChatAgent] Aviso: Tentativa de configurar especialistas, mas a delegação não está habilitada.');
return this;
}
// Atualiza a configuração no registry
this.agentRegistry.setSpecialistAgentsConfig(specialistAgentsConfig);
// Atualiza a ferramenta de delegação com os novos papéis disponíveis
const availableRoles = this.agentRegistry.getAvailableSpecialistRoles();
// Encontra e atualiza a ferramenta de delegação na lista de ferramentas
const delegationToolIndex = this.tools.findIndex(tool => tool.name === 'delegate_task_to_specialist');
if (delegationToolIndex >= 0) {
this.tools[delegationToolIndex] = this._createDelegationTool(availableRoles);
} else {
// Adiciona a ferramenta se não existir
this.tools.unshift(this._createDelegationTool(availableRoles));
}
return this;
}
/**
* Registra um novo agente especialista
*
* @param {string} role - O papel/identificador do agente especialista
* @param {Object} config - Configuração do agente especialista
* @returns {ChatAgent} A própria instância para encadeamento de métodos
*/
registerSpecialist(role, config) {
if (!this.specialistDelegationEnabled) {
console.warn('[ChatAgent] Aviso: Tentativa de registrar especialista, mas a delegação não está habilitada.');
return this;
}
// Registra o especialista no registry
this.agentRegistry.registerSpecialist(role, config);
// Atualiza a ferramenta de delegação com os novos papéis disponíveis
const availableRoles = this.agentRegistry.getAvailableSpecialistRoles();
// Encontra e atualiza a ferramenta de delegação na lista de ferramentas
const delegationToolIndex = this.tools.findIndex(tool => tool.name === 'delegate_task_to_specialist');
if (delegationToolIndex >= 0) {
this.tools[delegationToolIndex] = this._createDelegationTool(availableRoles);
}
return this;
}
/**
* Remove um agente especialista do registro
*
* @param {string} role - O papel/identificador do agente especialista a ser removido
* @returns {ChatAgent} A própria instância para encadeamento de métodos
*/
unregisterSpecialist(role) {
if (!this.specialistDelegationEnabled) {
console.warn('[ChatAgent] Aviso: Tentativa de remover especialista, mas a delegação não está habilitada.');
return this;
}
// Remove o especialista do registry
this.agentRegistry.unregisterSpecialist(role);
// Atualiza a ferramenta de delegação com os papéis disponíveis atualizados
const availableRoles = this.agentRegistry.getAvailableSpecialistRoles();
// Encontra e atualiza a ferramenta de delegação na lista de ferramentas
const delegationToolIndex = this.tools.findIndex(tool => tool.name === 'delegate_task_to_specialist');
if (delegationToolIndex >= 0) {
this.tools[delegationToolIndex] = this._createDelegationTool(availableRoles);
}
return this;
}
/**
* Obtém a lista de papéis de especialistas disponíveis
*
* @returns {Array<string>} Array com os papéis dos especialistas registrados
*/
getAvailableSpecialistRoles() {
if (!this.specialistDelegationEnabled || !this.agentRegistry) {
return [];
}
return this.agentRegistry.getAvailableSpecialistRoles();
}
/**
* Obtém a configuração de um especialista específico
*
* @param {string} role - O papel/identificador do agente especialista
* @returns {Object|null} A configuração do especialista ou null se não encontrado
*/
getSpecialistConfig(role) {
if (!this.specialistDelegationEnabled || !this.agentRegistry) {
return null;
}
return this.agentRegistry.getSpecialistConfig(role);
}
/**
* Habilita a delegação para especialistas
*
* @param {Object} specialistAgentsConfig - Configuração dos agentes especialistas (opcional)
* @returns {ChatAgent} A própria instância para encadeamento de métodos
*/
enableSpecialistDelegation(specialistAgentsConfig = null) {
if (this.specialistDelegationEnabled) {
debug.chatAgent('Delegação para especialistas já está habilitada.');
// Se forneceu nova configuração, atualiza
if (specialistAgentsConfig) {
this.setSpecialistAgentsConfig(specialistAgentsConfig);
}
return this;
}
try {
// Criar uma nova instância de AgentRegistry com a configuração fornecida
this.agentRegistry = new AgentRegistry(specialistAgentsConfig || {});
// Verificar se há especialistas registrados
const availableRoles = this.agentRegistry.getAvailableSpecialistRoles();
if (availableRoles.length === 0) {
console.warn('[ChatAgent] Aviso: Delegação de especialistas habilitada, mas nenhum especialista registrado.');
} else {
// Adicionar a ferramenta de delegação
const delegationTool = this._createDelegationTool(availableRoles);
// Verifica se a ferramenta já existe
const delegationToolIndex = this.tools.findIndex(tool => tool.name === 'delegate_task_to_specialist');
if (delegationToolIndex >= 0) {
this.tools[delegationToolIndex] = delegationTool;
} else {
this.tools.unshift(delegationTool);
}
debug.chatAgent(`Delegação para especialistas habilitada com ${availableRoles.length} especialistas disponíveis.`);
}
// Habilitar a delegação
this.specialistDelegationEnabled = true;
} catch (error) {
console.warn(`[ChatAgent] Aviso: Não foi possível habilitar a delegação para especialistas: ${error.message}`);
}
return this;
}
/**
* Desabilita a delegação para especialistas
*
* @returns {ChatAgent} A própria instância para encadeamento de métodos
*/
disableSpecialistDelegation() {
if (!this.specialistDelegationEnabled) {
debug.chatAgent('Delegação para especialistas já está desabilitada.');
return this;
}
// Remove a ferramenta de delegação
const delegationToolIndex = this.tools.findIndex(tool => tool.name === 'delegate_task_to_specialist');
if (delegationToolIndex >= 0) {
this.tools.splice(delegationToolIndex, 1);
}
// Desabilita a delegação
this.specialistDelegationEnabled = false;
debug.chatAgent('Delegação para especialistas desabilitada.');
return this;
}
// O método executeTool foi removido pois duplicava a funcionalidade
// já existente na classe base Agent através do método findToolFunction.
// Use chatAgent.findToolFunction(functionCall.name) para obter a função da ferramenta
// e depois execute-a diretamente com os argumentos.
/**
* Avalia a última interação da conversa e gerencia automaticamente as memórias de fatos e resumos
*
* @param {string} userMessage - Mensagem do usuário
* @param {string} agentResponse - Resposta do agente
* @private
*
* Este método é chamado automaticamente após cada interação completa quando autoManageFactMemory
* ou autoManageSummaryMemory estão habilitados. Ele usa o LLM para:
*
* 1. Analisar a interação entre usuário e agente
* 2. Extrair fatos relevantes e armazená-los na factMemory (se autoManageFactMemory=true)
* 3. Gerar ou atualizar resumos da conversa na summaryMemory (se autoManageSummaryMemory=true)
*
* O método envia um prompt específico para o LLM que solicita a extração de fatos e/ou geração de resumo,
* dependendo de quais sistemas de memória automática estão habilitados.
*/
async _evaluateAndManageMemories(userMessage, agentResponse) {
debug.chatAgent(`Avaliando conversa para gerenciamento automático de memórias...`);
try {
// Prepara o prompt para o LLM
let prompt = `Analise a seguinte interação entre um usuário e um assistente:
Usuário: "${userMessage}"
Assistente: "${agentResponse}"
`;
// Adiciona instruções específicas para extração de fatos se necessário
if (this.autoManageFactMemory && this.factMemory) {
prompt += `
EXTRAÇÃO DE FATOS:
Identifique fatos importantes mencionados nesta interação. Um fato é uma informação discreta e específica que pode ser útil para referência futura.
Para cada fato identificado, forneça:
1. Uma chave única e descritiva (sem espaços, use_underscores)
2. O valor do fato (pode ser string, número, booleano ou objeto JSON simples)
Formato de resposta para fatos:
{
"fatos": [
{"chave": "nome_do_usuario", "valor": "João Silva"},
{"chave": "preferencia_cor", "valor": "azul"},
...
]
}
Se não houver fatos relevantes para extrair, retorne um array vazio: {"fatos": []}.
`;
}
// Adiciona instruções específicas para geração de resumo se necessário
if (this.autoManageSummaryMemory && this.summaryMemory) {
prompt += `
GERAÇÃO DE RESUMO:
Com base nesta interação, decida se é necessário gerar ou atualizar um resumo da conversa.
Um resumo deve capturar os pontos principais da conversa de forma concisa.
Formato de resposta para resumo:
{
"resumo": "Texto do resumo aqui, ou null se não for necessário atualizar"
}
`;
}
// Finaliza o prompt com instruções de formato
prompt += `
Responda APENAS com um objeto JSON contendo os campos solicitados, sem texto adicional.`;
// Chama o LLM para avaliar a conversa
const response = await this.llm.generateContent({
prompt,
context: "Você é um sistema de gerenciamento de memória que analisa conversas para extrair fatos e gerar resumos.",
tools: [], // Sem ferramentas para esta chamada
history: [] // Sem histórico para esta chamada
});
if (!response || !response.text) {
console.warn(`[ChatAgent] Avaliação de memória: resposta vazia do LLM.`);
return;
}
// Tenta extrair o JSON da resposta