| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- /**
- * ORQUESTRADOR INTELIGENTE (Versão Multi-MCP)
- *
- * Agora suporta múltiplos servidores MCP!
- *
- * O que mudou?
- * - Antes: Um único cliente MCP
- * - Agora: Gerenciador com múltiplos MCPs
- * - Seleção automática do MCP baseado em idioma/domínio
- */
- import { buscarContexto, type ResultadoBuscaComContexto } from './rag';
- import { gerenciadorMCP } from './mcp-manager';
- // import { resolve } from 'bun';
- /**
- * Enum para definir a estratégia de busca
- *
- * Por que usar enum?
- * - Tipagem forte (TypeScript nos avisa de erros)
- * - Autodocumentação (fica claro quais são as opções)
- * - Fácil de estender no futuro
- */
- enum EstrategiaBusca {
- APENAS_RAG = 'apenas_rag',
- APENAS_MCP = 'apenas_mcp',
- HIBRIDO = 'hibrido',
- DIRETO = 'direto' // Sem contexto, chat direto
- }
- /**
- * Interface para o resultado da orquestração
- */
- interface ResultadoOrquestracao {
- estrategia: EstrategiaBusca;
- contextoRAG?: string;
- contextoMCP?: string;
- documentosRAG?: Array<{ nome: string; similaridade: number }>;
- ferramentasMCP?: string[];
- mcpUtilizado?: string; // NOVO: qual MCP foi usado
- promptFinal: string;
- }
- /**
- * BUSCADOR MCP (Versão Multi-MCP)
- *
- * Agora detecta automaticamente qual MCP usar!
- */
- async function buscarDadosMCP(mensagem: string): Promise<{ conteudo: string; mcpId: string }> {
- const mensagemLower = mensagem.toLowerCase();
- // Passo 1: Selecionar qual MCP usar
- const mcpId = gerenciadorMCP.selecionarMCP(mensagem);
-
- if (!mcpId) {
- console.warn('[ORQUESTRADOR] Nenhum MCP disponível');
- return {
- conteudo: 'Sistema de dados de exercícios temporariamente indisponível.',
- mcpId: 'nenhum'
- };
- }
- console.log(`[ORQUESTRADOR] Usando MCP: ${mcpId}`);
- try {
- // Passo 2: Decidir qual ferramenta chamar
- // As ferramentas podem ter nomes diferentes entre MCPs (pt vs en)
- const ferramentas = getFerramentaMapping(mcpId);
- // Estratégia 1: Busca por nome de exercício
- const nomesExercicios = ['supino', 'agachamento', 'rosca', 'levantamento', 'squat', 'bench'];
-
- for (const nome of nomesExercicios) {
- if (mensagemLower.includes(nome)) {
- try {
- const resultado = await gerenciadorMCP.chamarFerramenta(
- mcpId,
- ferramentas.buscarPorNome,
- { [ferramentas.paramNome]: nome }
- );
-
- if (!resultado.includes('Nenhum') && !resultado.includes('No exercise')) {
- return { conteudo: resultado, mcpId };
- }
- } catch (erro) {
- console.error('[ORQUESTRADOR] Erro ao buscar por nome:', erro);
- }
- }
- }
- // Estratégia 2: Busca por grupo muscular
- const grupos = detectarGrupoMuscular(mensagemLower);
- if (grupos.length > 0) {
- try {
- const resultado = await gerenciadorMCP.chamarFerramenta(
- mcpId,
- ferramentas.buscarPorGrupo,
- { [ferramentas.paramGrupo]: grupos[0] }
- );
- return { conteudo: resultado, mcpId };
- } catch (erro) {
- console.error('[ORQUESTRADOR] Erro ao buscar por grupo:', erro);
- }
- }
- // Estratégia 3: Listar todos
- const resultado = await gerenciadorMCP.chamarFerramenta(
- mcpId,
- ferramentas.listarTodos,
- {}
- );
- return { conteudo: resultado, mcpId };
- } catch (erro) {
- console.error('[ORQUESTRADOR] Erro ao buscar dados MCP:', erro);
- return {
- conteudo: 'Não foi possível acessar os dados de exercícios.',
- mcpId
- };
- }
- }
- /**
- * Mapeia nomes de ferramentas para cada MCP
- *
- * Por que criar esse mapeamento?
- * - MCPs diferentes podem ter nomes diferentes
- * - v1 usa português, v2 usa inglês
- * - Centraliza a lógica de compatibilidade
- */
- function getFerramentaMapping(mcpId: string): any {
- if (mcpId === 'academia-v1') {
- return {
- buscarPorNome: 'buscar_exercicio_por_nome',
- buscarPorGrupo: 'buscar_exercicios_por_grupo',
- listarTodos: 'listar_todos_exercicios',
- paramNome: 'nome',
- paramGrupo: 'grupo_muscular'
- };
- }
-
- if (mcpId === 'academia-v2') {
- return {
- buscarPorNome: 'buscar_exercicio_por_nome', // mesmo nome
- buscarPorGrupo: 'search_exercises_by_group',
- listarTodos: 'list_all_exercises',
- paramNome: 'nome',
- paramGrupo: 'muscle_group'
- };
- }
- // Padrão (v1)
- return {
- buscarPorNome: 'buscar_exercicio_por_nome',
- buscarPorGrupo: 'buscar_exercicios_por_grupo',
- listarTodos: 'listar_todos_exercicios',
- paramNome: 'nome',
- paramGrupo: 'grupo_muscular'
- };
- }
- /**
- * Detecta grupos musculares na mensagem
- */
- function detectarGrupoMuscular(mensagem: string): string[] {
- const grupos = [
- { palavras: ['perna', 'pernas', 'leg', 'legs'], pt: 'Pernas', en: 'Legs' },
- { palavras: ['peito', 'peitoral', 'chest'], pt: 'Peito (peitoral)', en: 'Chest' },
- { palavras: ['costas', 'dorsal', 'back'], pt: 'Costas (dorsais, lombar)', en: 'Back' },
- { palavras: ['ombro', 'ombros', 'shoulder'], pt: 'Ombros (deltoides)', en: 'Shoulders' },
- { palavras: ['braço', 'braços', 'arm', 'arms'], pt: 'Braços (Bíceps, Tríceps, Antebraço)', en: 'Arms' }
- ];
- const encontrados = [];
- for (const grupo of grupos) {
- if (grupo.palavras.some(p => mensagem.includes(p))) {
- // Retorna ambas as versões
- encontrados.push(grupo.pt, grupo.en);
- }
- }
- return encontrados;
- }
- function detectarPorSimilaridade(mensagem: string, exemplos: string[]): boolean {
- // Simples: verifica se alguma palavra da mensagem está em exemplos
- const palavrasMensagem = mensagem.toLowerCase().split(' ');
- return exemplos.some(ex => palavrasMensagem.some(p => ex.includes(p)));
- }
- /**
- * ANALISADOR DE INTENÇÃO
- *
- * Esta função analisa a pergunta do usuário e decide qual estratégia usar
- *
- * Como funciona?
- * 1. Procura por palavras-chave que indicam dados estruturados (MCP)
- * 2. Sempre tenta RAG para contexto documental
- * 3. Decide se usa ambos baseado na relevância
- *
- * Por que essa abordagem?
- * - Simples e eficaz (KISS)
- * - Pode evoluir para usar o próprio LLM para decidir
- * - Transparente para debug
- */
- function analisarIntencao(mensagem: string): EstrategiaBusca {
- const mensagemLower = mensagem.toLowerCase();
- console.log(`[ANALISADOR] Analisando: "${mensagem}"`);
- // Palavras que indicam necessidade de dados estruturados (MCP)
- const palavrasChaveMCP = [
- 'meu', 'meus', 'minhas', 'minha',
- 'my', 'mine',
- 'cadastrado', 'cadastrados', 'registered',
- 'tenho', 'possuo', 'have',
- 'listar', 'liste', 'list', 'mostrar', 'mostre', 'show',
- // Novos: adicionar sinônimos para mais flexibilidade
- 'quais', 'qual', 'que', 'existe', 'disponível', 'disponíveis',
- 'exercícios', 'exercicio', 'treino', 'treinos', 'rotina'
- ];
- // Palavras que indicam necessidade de conhecimento/técnica (RAG)
- const palavrasChaveRAG = [
- 'como fazer', 'como executar', 'how to',
- 'o que é', 'what is',
- 'explique', 'explain',
- 'técnica', 'technique',
- 'forma correta', 'correct form',
- // Novos: adicionar variações
- 'dica', 'dicas', 'passo', 'passos', 'guia', 'tutorial',
- 'benefício', 'benefícios', 'vantagem', 'vantagens'
- ];
- // Verifica indicadores MCP
- const indicadoresMCP = palavrasChaveMCP.filter(palavra =>
- mensagemLower.includes(palavra)
- );
- // Verifica indicadores RAG
- const indicadoresRAG = palavrasChaveRAG.filter(palavra =>
- mensagemLower.includes(palavra)
- );
- console.log(`[ANALISADOR] Indicadores MCP encontrados: [${indicadoresMCP.join(', ')}]`);
- console.log(`[ANALISADOR] Indicadores RAG encontrados: [${indicadoresRAG.join(', ')}]`);
- /*
- Este trecho de código visa
- tentar dar outra opção para a analise
- // Novo: exemplos de frases para detectar intenção
- const exemplosMCP = [
- 'liste meus exercícios',
- 'quais treinos tenho',
- 'mostre dados cadastrados'
- ];
- const exemplosRAG = [
- 'como fazer supino',
- 'explique técnica de agachamento',
- 'dicas para musculação'
- ];
- function detectarPorSimilaridade(mensagem: string, exemplos: string[]): boolean {
- // Simples: verifica se alguma palavra da mensagem está em exemplos
- const palavrasMensagem = mensagem.toLowerCase().split(' ');
- return exemplos.some(ex => palavrasMensagem.some(p => ex.includes(p)));
- }
- */
- // Novo: exemplos de frases para detectar intenção
- const exemplosMCP = [
- 'liste meus exercícios',
- 'quais treinos tenho',
- 'mostre dados cadastrados'
- ];
- const exemplosRAG = [
- 'como fazer supino',
- 'explique técnica de agachamento',
- 'dicas para musculação'
- ];
- // const temIndicadorMCP = indicadoresMCP.length > 0;
- // const temIndicadorRAG = indicadoresRAG.length > 0;
- const temIndicadorMCP = indicadoresMCP.length > 0 || detectarPorSimilaridade(mensagemLower, exemplosMCP);
- const temIndicadorRAG = indicadoresRAG.length > 0 || detectarPorSimilaridade(mensagemLower, exemplosRAG);
- // Lógica de decisão
- if (temIndicadorMCP && temIndicadorRAG) {
- console.log('[ANALISADOR] Decisão: HÍBRIDO (ambos indicadores presentes)');
- return EstrategiaBusca.HIBRIDO;
- }
- if (temIndicadorMCP) {
- console.log('[ANALISADOR] Decisão: APENAS MCP (dados estruturados)');
- return EstrategiaBusca.APENAS_MCP;
- }
- if (temIndicadorRAG) {
- console.log('[ANALISADOR] Decisão: APENAS RAG (conhecimento/técnica)');
- return EstrategiaBusca.APENAS_RAG;
- }
- // Padrão: Sem indicador
- console.log('[ANALISADOR] Decisão: sem parametrização');
- return EstrategiaBusca.DIRETO;
- }
- /**
- * ORQUESTRADOR PRINCIPAL (Versão Multi-MCP)
- */
- async function orquestrar(mensagem: string): Promise<ResultadoOrquestracao> {
- console.log('\n[ORQUESTRADOR] Analisando pergunta...');
-
- const estrategia = analisarIntencao(mensagem);
- console.log(`[ORQUESTRADOR] Estratégia: ${estrategia}`);
- let contextoRAG = '';
- let contextoMCP = '';
- let documentosRAG: Array<{ nome: string; similaridade: number }> = [];
- let ferramentasMCP: string[] = [];
- let mcpUtilizado: string | undefined;
- switch (estrategia) {
- case EstrategiaBusca.APENAS_RAG:
- const resultadoRAG = await buscarContexto(mensagem, 3, 0.3);
- if (resultadoRAG.resultados.length > 0) {
- contextoRAG = resultadoRAG.contexto;
- documentosRAG = resultadoRAG.resultados.map(doc => ({ nome: doc.nome, similaridade: doc.similaridade }));
- }
- break;
- case EstrategiaBusca.APENAS_MCP:
- const resultadoMCP = await buscarDadosMCP(mensagem);
- contextoMCP = resultadoMCP.conteudo;
- mcpUtilizado = resultadoMCP.mcpId;
- ferramentasMCP.push(mcpUtilizado);
- break;
- case EstrategiaBusca.HIBRIDO:
- const resultadoHibridoRAG = await buscarContexto(mensagem, 2, 0.3);
- if (resultadoHibridoRAG.resultados.length > 0) {
- contextoRAG = resultadoHibridoRAG.contexto;
- documentosRAG = resultadoHibridoRAG.resultados.map(doc => ({ nome: doc.nome, similaridade: doc.similaridade }));
- }
- const resultadoHibridoMCP = await buscarDadosMCP(mensagem);
- contextoMCP = resultadoHibridoMCP.conteudo;
- mcpUtilizado = resultadoHibridoMCP.mcpId;
- ferramentasMCP.push(mcpUtilizado);
- break;
- }
- const promptFinal = montarPromptFinal(mensagem, contextoRAG, contextoMCP);
- return {
- estrategia,
- contextoRAG: contextoRAG || undefined,
- contextoMCP: contextoMCP || undefined,
- documentosRAG,
- ferramentasMCP,
- mcpUtilizado,
- promptFinal
- };
- }
- /**
- * Monta o prompt final para a LLM
- *
- * Por que essa função separada?
- * - Facilita testar diferentes formatos de prompt
- * - Mantém a lógica de prompt isolada
- * - Fácil de ajustar sem mexer na orquestração
- */
- function montarPromptFinal(
- mensagem: string,
- contextoRAG: string,
- contextoMCP: string
- ): string {
- let prompt = 'Você é um assistente especializado em saúde, fitness e bem-estar.\n\n';
- // Adiciona contexto RAG se existir
- if (contextoRAG) {
- prompt += contextoRAG + '\n';
- }
- // Adiciona contexto MCP se existir
- if (contextoMCP) {
- prompt += 'DADOS ESTRUTURADOS:\n\n' + contextoMCP + '\n\n';
- }
- // Instruções para a LLM
- prompt += 'INSTRUÇÕES:\n';
- prompt += '- Use as informações fornecidas\n';
- prompt += '- Seja claro e objetivo\n';
- prompt += '- Responda no idioma da pergunta\n\n';
- prompt += `PERGUNTA: ${mensagem}\n\nRESPOSTA:`;
- return prompt;
- }
- export {
- orquestrar,
- EstrategiaBusca,
- type ResultadoOrquestracao
- };
|