diff --git a/src/lib/chat/provider.ts b/src/lib/chat/provider.ts index 4d76928..553f9ce 100644 --- a/src/lib/chat/provider.ts +++ b/src/lib/chat/provider.ts @@ -273,34 +273,65 @@ async function checkOllamaHealth(): Promise { } /** - * Tools to send to Ollama — a trimmed subset to keep context small. - * Smaller models struggle with 17 tool definitions, so we send only - * the most commonly used ones. Bulk tools are excluded (handled by - * the RBAC layer + confirmation flow anyway). + * Dynamic tool selection for Ollama — pick only the tools relevant to + * the user's request so smaller models aren't overwhelmed by 20 definitions. + * `search_entities` is always included (needed for name→ID resolution). */ -const OLLAMA_TOOL_ALLOWLIST = new Set([ - "search_entities", - "list_projects", - "get_project", - "list_deliverables", - "list_users", - "get_blocked_stages", - "list_overdue", - "get_workload", - "assign_artist", - "advance_stage", - "create_project", - "create_deliverable", -]); +const TOOL_GROUPS: Record = { + status: { + keywords: /status|overview|how.?s|progress|update me|what.?s going|summary|blocked|overdue|late|behind|bottleneck/i, + tools: ["list_projects", "get_project", "list_deliverables", "get_blocked_stages", "list_overdue"], + }, + workload: { + keywords: /workload|capacity|busy|bandwidth|availab|who.?s free|how many|assigned/i, + tools: ["get_workload", "list_users", "get_available_artists", "get_suggested_artists"], + }, + assign: { + keywords: /assign|reassign|move .* to|put .* on|give .* to|allocat/i, + tools: ["assign_artist", "remove_assignment", "get_available_artists", "get_suggested_artists", "list_users"], + }, + create: { + keywords: /create|new project|new deliverable|add.*project|add.*deliverable|set up|setup/i, + tools: ["create_project", "create_deliverable"], + }, + advance: { + keywords: /advance|approve|move.*stage|next stage|complete.*stage|mark.*done|progress.*stage|skip/i, + tools: ["advance_stage"], + }, + revision: { + keywords: /revision|review|feedback|note|comment|round/i, + tools: ["create_revision", "list_revisions"], + }, +}; + +function getOllamaTools(userMessage: string) { + // Always include search_entities for name resolution + const selected = new Set(["search_entities"]); + + // Match user message against keyword groups + let matched = false; + for (const group of Object.values(TOOL_GROUPS)) { + if (group.keywords.test(userMessage)) { + matched = true; + for (const tool of group.tools) selected.add(tool); + } + } + + // If nothing matched (generic question), include basic query tools + if (!matched) { + for (const t of ["list_projects", "get_project", "list_deliverables", "list_users"]) { + selected.add(t); + } + } + + console.log(`[Ollama] Selected ${selected.size} tools for: "${userMessage.slice(0, 60)}…"`); -function getOllamaTools() { return TOOL_DEFINITIONS - .filter((t) => OLLAMA_TOOL_ALLOWLIST.has(t.name)) + .filter((t) => selected.has(t.name)) .map((t) => ({ type: "function" as const, function: { name: t.name, - // Shorten descriptions for smaller context description: t.description.split(".")[0] + ".", parameters: t.input_schema, }, @@ -388,10 +419,18 @@ async function chatWithOllama( ollamaMessages[0].content = sp.trim() + "\n\nUse bullet points, not tables. Be concise."; } + // Extract the last user message for dynamic tool selection + const lastUserContent = [...messages].reverse().find((m) => m.role === "user"); + const userText = typeof lastUserContent?.content === "string" + ? lastUserContent.content + : Array.isArray(lastUserContent?.content) + ? lastUserContent.content.map((b: any) => b.text || b.content || "").join(" ") + : ""; + const requestBody = JSON.stringify({ model: getOllamaChatModel(), messages: ollamaMessages, - tools: getOllamaTools(), + tools: getOllamaTools(userText), stream: false, options: { temperature: 0.3,