@oliverlessa/gemini-agent-lib
Version:
Uma biblioteca NodeJS para criar agentes de IA com Gemini LLM
334 lines (298 loc) • 17.4 kB
JavaScript
/**
* ChatManager - Gerenciador de múltiplas sessões de ChatAgent
*
* Esta classe permite que uma única configuração ou instância da biblioteca
* possa gerenciar conversas separadas para diferentes usuários, mantendo
* seus históricos e memórias isolados.
*/
const ChatAgent = require('./chat-agent');
const VertexAILLM = require('./vertex-ai-llm');
const GenerativeAILLM = require('./generative-ai-llm'); // Adicionado import
const AgentRegistry = require('./agent-registry');
const { v4: uuidv4 } = require('uuid');
const debug = require('./debug');
// Criar um logger específico para o ChatManager
debug.chatManager = debug.create('chat-manager');
class ChatManager {
/**
* Gerencia múltiplas sessões de ChatAgent para diferentes usuários.
*
* @param {Object} config - Configuração para criar novas instâncias de ChatAgent.
* @param {Object} config.llmConfig - Configuração para instanciar o VertexAILLM (ou um LLM factory).
* @param {string} config.llmConfig.projectId - Obrigatório.
* @param {string} [config.llmConfig.credentialsPath] - Caminho para as credenciais.
* @param {string} [config.llmConfig.apiKey] - API Key alternativa.
* @param {string} [config.llmConfig.modelName="gemini-2.0-flash-001"] - Nome do modelo.
* @param {Object} [config.agentConfig={}] - Configuração base para cada ChatAgent (role, objective, context, tools, etc.).
* @param {Object} [config.memoryConfig={}] - Configuração dos adaptadores de memória a serem usados.
* @param {Object} [config.memoryConfig.conversation] - Configuração do adaptador de memória de conversação.
* @param {string} [config.memoryConfig.conversation.type] - Ex: 'SQLiteConversationMemoryAdapter'.
* @param {Object} [config.memoryConfig.conversation.dbConfig] - Config específica do adaptador.
* @param {Object} [config.memoryConfig.fact] - Configuração do adaptador de memória de fatos.
* @param {string} [config.memoryConfig.fact.type] - Ex: 'MongoDBFactMemoryAdapter'.
* @param {Object} [config.memoryConfig.fact.dbConfig] - Config específica.
* @param {Object} [config.memoryConfig.summary] - Configuração do adaptador de memória de resumos.
* @param {string} [config.memoryConfig.summary.type] - Ex: 'SQLiteSummaryMemoryAdapter'.
* @param {Object} [config.memoryConfig.summary.dbConfig] - Config específica.
* @param {boolean} [config.shareMemoryInstances=true] - Se true, cria uma única instância de cada adaptador de memória e a compartilha entre os ChatAgents. Se false, cria novas instâncias para cada sessão (menos eficiente, pode ser necessário para DBs como SQLite em cenários específicos).
* @param {Object} [config.delegationConfig={}] - Configuração para delegação de especialistas.
* @param {boolean} [config.delegationConfig.enabled=false] - Habilita delegação.
* @param {Object} [config.delegationConfig.specialistAgentsConfig] - Configs dos especialistas.
* @param {AgentRegistry} [config.delegationConfig.agentRegistry] - Instância compartilhada opcional.
*/
constructor({
llmConfig,
agentConfig = {},
memoryConfig = {},
delegationConfig = {},
shareMemoryInstances = true
}) {
// Validação condicional do projectId
if (!llmConfig) {
throw new Error("ChatManager requer 'llmConfig'.");
}
// Só exige projectId se não for GenerativeAILLM (ou se llmType não for especificado, assume Vertex)
if (llmConfig.llmType !== 'GenerativeAILLM' && !llmConfig.projectId) {
throw new Error("ChatManager requer 'llmConfig' com 'projectId' para LLMs baseados em Vertex AI.");
}
// Validação da apiKey para GenerativeAILLM
if (llmConfig.llmType === 'GenerativeAILLM' && !llmConfig.apiKey) {
throw new Error("ChatManager requer 'llmConfig' com 'apiKey' para GenerativeAILLM.");
}
this.baseLlmConfig = { ...llmConfig, mode: 'chat' }; // Garante modo chat
this.baseAgentConfig = agentConfig;
this.memoryConfig = memoryConfig;
this.delegationConfig = delegationConfig;
this.shareMemoryInstances = shareMemoryInstances;
/** @type {Map<string, ChatAgent>} */
this.activeSessions = new Map();
// Pré-instanciar componentes compartilhados (se aplicável)
// Instancia o LLM correto com base no llmType
if (this.baseLlmConfig.llmType === 'GenerativeAILLM') {
this.sharedLlm = new GenerativeAILLM(this.baseLlmConfig);
debug.chatManager("Instância compartilhada de GenerativeAILLM criada.");
} else {
// Assume VertexAILLM como padrão se llmType não for especificado ou for diferente
this.sharedLlm = new VertexAILLM(this.baseLlmConfig);
debug.chatManager("Instância compartilhada de VertexAILLM criada.");
}
this.sharedMemoryInstances = {};
this.sharedAgentRegistry = null;
if (this.shareMemoryInstances) {
this._initializeSharedMemories();
}
if (this.delegationConfig.enabled) {
// Se não for fornecido um registry compartilhado, criamos um com a config global
this.sharedAgentRegistry = this.delegationConfig.agentRegistry ||
new AgentRegistry(this.delegationConfig.specialistAgentsConfig || {});
}
debug.chatManager("ChatManager inicializado.");
if (this.shareMemoryInstances) debug.chatManager("Instâncias de memória serão compartilhadas.");
if (this.sharedAgentRegistry) debug.chatManager("Registro de Agentes será compartilhado.");
}
/**
* Inicializa as instâncias de memória compartilhadas
* @private
*/
_initializeSharedMemories() {
debug.chatManager("Inicializando instâncias de memória compartilhada...");
const memoryTypes = ['conversation', 'fact', 'summary']; // Adicionar 'semantic' quando pronto
memoryTypes.forEach(memType => {
const config = this.memoryConfig[memType];
if (config && config.type && config.dbConfig) {
try {
// Usa um factory ou switch para instanciar o adaptador correto
const AdapterClass = this._getMemoryAdapterClass(config.type);
this.sharedMemoryInstances[memType] = new AdapterClass({ dbConfig: config.dbConfig });
debug.chatManager(`Memória compartilhada '${memType}' (${config.type}) inicializada.`);
} catch (error) {
console.error(`[ChatManager] Falha ao inicializar memória compartilhada ${memType} (${config.type}):`, error);
// Decide se quer continuar sem essa memória ou lançar erro fatal
}
}
});
}
/**
* Obtém a classe do adaptador de memória pelo nome
* @private
* @param {string} adapterType - Nome do tipo de adaptador
* @returns {Class} Classe do adaptador
*/
_getMemoryAdapterClass(adapterType) {
// Importar os adaptadores de memória
const {
SQLiteConversationMemoryAdapter,
MongoDBConversationMemoryAdapter,
SQLiteFactMemoryAdapter,
MongoDBFactMemoryAdapter,
SQLiteSummaryMemoryAdapter,
MongoDBSummaryMemoryAdapter
} = require('./memory');
switch (adapterType) {
case 'SQLiteConversationMemoryAdapter': return SQLiteConversationMemoryAdapter;
case 'MongoDBConversationMemoryAdapter': return MongoDBConversationMemoryAdapter;
case 'SQLiteFactMemoryAdapter': return SQLiteFactMemoryAdapter;
case 'MongoDBFactMemoryAdapter': return MongoDBFactMemoryAdapter;
case 'SQLiteSummaryMemoryAdapter': return SQLiteSummaryMemoryAdapter;
case 'MongoDBSummaryMemoryAdapter': return MongoDBSummaryMemoryAdapter;
default: throw new Error(`Tipo de adaptador de memória desconhecido: ${adapterType}`);
}
}
/**
* Obtém ou cria uma sessão de ChatAgent para um determinado ID.
* @param {string} sessionId - Identificador único para a sessão/usuário. Deve ser não vazio.
* @param {Object} [sessionOptions={}] - Opções específicas para esta sessão, que podem sobrescrever a configuração base do agente (ex: context personalizado).
* @returns {Promise<ChatAgent>} A instância de ChatAgent para a sessão.
* @throws {Error} Se sessionId for inválido.
*/
async getOrCreateSession(sessionId, sessionOptions = {}) {
if (!sessionId || typeof sessionId !== 'string' || !sessionId.trim()) {
throw new Error("sessionId inválido fornecido para getOrCreateSession.");
}
const trimmedSessionId = sessionId.trim();
if (this.activeSessions.has(trimmedSessionId)) {
debug.chatManager(`Retornando sessão existente para ID: ${trimmedSessionId}`);
return this.activeSessions.get(trimmedSessionId);
}
debug.chatManager(`Criando nova sessão para ID: ${trimmedSessionId}`);
// Preparar configuração final para esta instância do ChatAgent
const agentFinalConfig = { ...this.baseAgentConfig, ...sessionOptions };
// Lógica para instanciar/obter memórias para esta sessão
let conversationMemoryInstance = null, factMemoryInstance = null, summaryMemoryInstance = null;
if (this.shareMemoryInstances) {
conversationMemoryInstance = this.sharedMemoryInstances.conversation || null;
factMemoryInstance = this.sharedMemoryInstances.fact || null;
summaryMemoryInstance = this.sharedMemoryInstances.summary || null;
} else {
// Criar instâncias de memória dedicadas (menos comum, mais complexo)
// TODO: Implementar lógica para criar instâncias não compartilhadas se shareMemoryInstances for false
console.warn("[ChatManager] Criação de instâncias de memória não compartilhadas não implementada, usando compartilhadas se disponíveis.");
conversationMemoryInstance = this.sharedMemoryInstances.conversation || null;
factMemoryInstance = this.sharedMemoryInstances.fact || null;
summaryMemoryInstance = this.sharedMemoryInstances.summary || null;
}
// Criar a nova instância do ChatAgent
const newAgentInstance = new ChatAgent({
// Passar configurações base e sobrescritas
...agentFinalConfig,
// Passar LLM (compartilhado)
llm: this.sharedLlm,
// Passar instâncias de memória (compartilhadas ou não)
conversationMemory: conversationMemoryInstance,
factMemory: factMemoryInstance,
summaryMemory: summaryMemoryInstance,
// Passar configuração de delegação
enableSpecialistDelegation: this.delegationConfig.enabled || false,
agentRegistry: this.sharedAgentRegistry, // Passa o registry compartilhado
// specialistAgentsConfig NÃO é necessário aqui se agentRegistry é fornecido
// Passar o sessionId como chatId OBRIGATORIAMENTE
chatId: trimmedSessionId
});
// Armazenar a nova instância
this.activeSessions.set(trimmedSessionId, newAgentInstance);
debug.chatManager(`Nova sessão criada e armazenada para ID: ${trimmedSessionId}`);
// Carregar histórico (se a memória for persistente e a instância acabou de ser criada)
// A lógica _loadConversationHistory no construtor do ChatAgent já deve cuidar disso.
return newAgentInstance;
}
/**
* Processa uma mensagem de um usuário em uma sessão específica.
* @param {string} sessionId - O ID da sessão/usuário.
* @param {string} message - A mensagem do usuário.
* @param {Object} [sessionOptions={}] - Opções para passar para getOrCreateSession se a sessão precisar ser criada.
* @returns {Promise<Object>} A resposta do ChatAgent (geralmente { text: "..." }).
* @throws {Error} Se sessionId for inválido ou ocorrer erro no processamento.
*/
async processMessage(sessionId, message, sessionOptions = {}) {
debug.chatManager(`Processando mensagem para sessão ID: ${sessionId}`);
try {
const agentInstance = await this.getOrCreateSession(sessionId, sessionOptions);
const response = await agentInstance.processUserMessage(message);
return response;
} catch (error) {
console.error(`[ChatManager] Erro ao processar mensagem para sessão ${sessionId}:`, error);
// Retorna um erro padronizado ou relança
return { text: "[Desculpe, ocorreu um erro ao processar sua mensagem.]" };
// Ou: throw error;
}
}
/**
* Encerra uma sessão específica, removendo a instância do agente da memória ativa.
* Nota: Isso NÃO limpa o histórico persistente.
* @param {string} sessionId - O ID da sessão a ser encerrada.
* @returns {boolean} True se a sessão foi encontrada e removida, false caso contrário.
*/
endSession(sessionId) {
if (!sessionId || typeof sessionId !== 'string') return false;
const trimmedSessionId = sessionId.trim();
const agentInstance = this.activeSessions.get(trimmedSessionId);
if (agentInstance) {
// Se as memórias NÃO forem compartilhadas, chamar agentInstance.close() aqui seria importante.
// Se forem compartilhadas, o fechamento deve ser gerenciado centralmente quando o ChatManager for destruído.
this.activeSessions.delete(trimmedSessionId);
debug.chatManager(`Sessão encerrada e removida para ID: ${trimmedSessionId}`);
return true;
} else {
debug.chatManager(`Tentativa de encerrar sessão inexistente: ${trimmedSessionId}`);
return false;
}
}
/**
* Limpa o histórico de uma sessão específica (na memória persistente, se houver).
* @param {string} sessionId - O ID da sessão cujo histórico deve ser limpo.
* @returns {Promise<void>}
* @throws {Error} Se a sessão não for encontrada.
*/
async clearSessionHistory(sessionId) {
if (!sessionId || typeof sessionId !== 'string' || !sessionId.trim()) {
throw new Error("sessionId inválido fornecido para clearSessionHistory.");
}
const trimmedSessionId = sessionId.trim();
const agentInstance = this.activeSessions.get(trimmedSessionId);
if (!agentInstance) {
// Se a sessão não está ativa, tenta limpar diretamente pela memória (se compartilhada)
const conversationMemory = this.sharedMemoryInstances.conversation;
if (conversationMemory) {
debug.chatManager(`Limpando histórico persistente para sessão inativa ID: ${trimmedSessionId}`);
await conversationMemory.clearHistory(trimmedSessionId);
// Limpar outras memórias também?
if (this.sharedMemoryInstances.fact) await this.sharedMemoryInstances.fact.deleteAllFacts(trimmedSessionId);
if (this.sharedMemoryInstances.summary) await this.sharedMemoryInstances.summary.deleteAllSummaries(trimmedSessionId);
return;
} else {
throw new Error(`Sessão com ID ${trimmedSessionId} não encontrada para limpar histórico.`);
}
}
debug.chatManager(`Limpando histórico para sessão ativa ID: ${trimmedSessionId}`);
await agentInstance.clearHistory();
// Limpar outras memórias também?
if (agentInstance.factMemory) await agentInstance.factMemory.deleteAllFacts(agentInstance.chatId);
if (agentInstance.summaryMemory) await agentInstance.summaryMemory.deleteAllSummaries(agentInstance.chatId);
}
/**
* Fecha todas as conexões de recursos compartilhados (LLM, Memórias).
* Deve ser chamado quando a aplicação que usa o ChatManager está encerrando.
*/
async shutdown() {
debug.chatManager("Iniciando desligamento do ChatManager...");
// Fechar LLM compartilhado (se o SDK tiver método close)
// if (this.sharedLlm && typeof this.sharedLlm.close === 'function') {
// await this.sharedLlm.close();
// }
// Fechar memórias compartilhadas
for (const memType in this.sharedMemoryInstances) {
const memoryInstance = this.sharedMemoryInstances[memType];
if (memoryInstance && typeof memoryInstance.close === 'function') {
try {
debug.chatManager(`Fechando memória compartilhada ${memType}...`);
await memoryInstance.close();
} catch (error) {
console.error(`[ChatManager] Erro ao fechar memória ${memType}:`, error);
}
}
}
this.activeSessions.clear(); // Limpa o mapa de sessões ativas
debug.chatManager("ChatManager desligado.");
}
}
module.exports = ChatManager;