feat(api): integrate MCP tools support in Ollama chat route

Add support for Model Context Protocol (MCP) tools in the Ollama chat API. Detect model capabilities to use native tools or prompt engineering. Handle tool calls, execute them via MCPExecutor, and inject results back into the conversation for seamless tool-assisted responses. This enhances chat functionality by enabling external tool integration.
This commit is contained in:
Evandro Fonseca Junior committed 2025-10-28 22:50:03 -03:00
1 parent 745746afa9
commit 4739a13458
18 files changed
+1782 -10

No files matched your search

+210
View File
@@ -0,0 +1,210 @@
/**
* Serviço para detectar intenções do usuário através de palavras-chave
*/
import type { DetectedIntent } from "@/lib/types/mcp-chat";
/**
* Padrões de keywords para cada tipo de intent
*/
const INTENT_PATTERNS = {
web_search: {
keywords: [
"buscar",
"pesquisar",
"procurar",
"google",
"encontrar",
"search",
"look up",
"find",
"o que é",
"quem é",
"onde fica",
],
regex:
/\b(buscar|pesquisar|procurar|google|encontrar|search|look up|find)\b/i,
},
time: {
keywords: [
"que horas",
"data",
"horário",
"hoje",
"agora",
"quando",
"calendário",
"time",
"date",
"current time",
"what time",
],
regex:
/\b(que horas|data|horário|hoje|agora|quando|time|date|current time)\b/i,
},
weather: {
keywords: [
"clima",
"tempo",
"temperatura",
"chuva",
"previsão",
"weather",
"forecast",
"temperature",
],
regex:
/\b(clima|tempo|temperatura|chuva|previsão|weather|forecast|temperature)\b/i,
},
file: {
keywords: [
"arquivo",
"pasta",
"diretório",
"file",
"folder",
"directory",
"ler arquivo",
"read file",
"listar",
"list",
],
regex:
/\b(arquivo|pasta|diretório|file|folder|directory|ler arquivo|read file)\b/i,
},
code: {
keywords: [
"código",
"programar",
"executar",
"run",
"execute",
"compile",
"debug",
"git",
"commit",
],
regex:
/\b(código|programar|executar|run|execute|compile|debug|git|commit)\b/i,
},
database: {
keywords: [
"banco de dados",
"database",
"query",
"sql",
"select",
"insert",
"update",
"delete",
],
regex:
/\b(banco de dados|database|query|sql|select|insert|update|delete)\b/i,
},
};
/**
* Detecta a intenção do usuário na mensagem
* @param message Mensagem do usuário
* @returns Intent detectado ou null
*/
export function detectIntent(message: string): DetectedIntent | null {
const messageLower = message.toLowerCase();
for (const [type, pattern] of Object.entries(INTENT_PATTERNS)) {
const matches = pattern.regex.test(messageLower);
if (matches) {
// Calcular confiança baseado em quantas keywords foram encontradas
const foundKeywords = pattern.keywords.filter((keyword) =>
messageLower.includes(keyword.toLowerCase())
);
const confidence = Math.min(0.5 + foundKeywords.length * 0.2, 1.0);
return {
type: type as DetectedIntent["type"],
confidence,
keywords: foundKeywords,
suggestedTools: getSuggestedToolsForIntent(
type as DetectedIntent["type"]
),
};
}
}
return null;
}
/**
* Retorna tools sugeridos para um tipo de intent
* @param intentType Tipo de intent
* @returns Array de nomes de tools sugeridos
*/
function getSuggestedToolsForIntent(
intentType: DetectedIntent["type"]
): string[] {
const toolMapping: Record<DetectedIntent["type"], string[]> = {
web_search: ["web_search", "duckduckgo_search", "google_search", "search"],
time: ["get_current_time", "get_date", "current_time", "time"],
weather: ["get_weather", "weather", "forecast"],
file: ["read_file", "list_files", "filesystem", "file"],
code: ["execute_code", "git", "run_command"],
database: ["query", "sql", "database"],
other: [],
};
return toolMapping[intentType] || [];
}
/**
* Verifica se um tool name corresponde a algum intent detectado
* @param toolName Nome do tool
* @param intent Intent detectado
* @returns true se o tool é relevante para o intent
*/
export function isToolRelevantForIntent(
toolName: string,
intent: DetectedIntent
): boolean {
const toolNameLower = toolName.toLowerCase();
return intent.suggestedTools.some((suggested) =>
toolNameLower.includes(suggested.toLowerCase())
);
}
/**
* Filtra tools com base em um intent detectado
* @param tools Array de tools disponíveis
* @param intent Intent detectado
* @returns Tools filtrados e ordenados por relevância
*/
export function filterToolsByIntent(
tools: Array<{ name: string; description: string }>,
intent: DetectedIntent
): Array<{ name: string; description: string; relevance: number }> {
return tools
.map((tool) => {
let relevance = 0;
// Verificar se o tool name contém alguma keyword sugerida
const toolNameLower = tool.name.toLowerCase();
for (const suggested of intent.suggestedTools) {
if (toolNameLower.includes(suggested.toLowerCase())) {
relevance += 2;
}
}
// Verificar se a descrição contém keywords do intent
const descriptionLower = tool.description.toLowerCase();
for (const keyword of intent.keywords) {
if (descriptionLower.includes(keyword.toLowerCase())) {
relevance += 1;
}
}
return { ...tool, relevance };
})
.filter((tool) => tool.relevance > 0)
.sort((a, b) => b.relevance - a.relevance);
}