diff --git a/src/gateway/chat/handle-chat.ts b/src/gateway/chat/handle-chat.ts index 69cc9be..b792713 100644 --- a/src/gateway/chat/handle-chat.ts +++ b/src/gateway/chat/handle-chat.ts @@ -261,6 +261,15 @@ async function handleChat( // explicit excel/spreadsheet keyword, same shape as the weather/legal gates above. Previously // ungated, so both tool schemas rode along on every single chat turn regardless of app. const hasExcelKeyword = /엑셀|xlsx|xls\b|스프레드시트|spreadsheet|excel/i.test(message); + // ollama_web_search/ollama_web_fetch are explicitly self-deprecating in their own descriptions + // ("web_search가 없는 환경에서만 fallback으로 사용") — and the provider fallback they exist for + // is already handled server-side inside executeWebSearch, so the model never needs to reach for + // them on its own. Advertising them cost 214 tokens per turn to tell the model not to use a + // tool. Gate on the same explicit phrasing personality-context.ts already uses for its + // ollama_web hint block, so the pair stays available when the user actually asks for it by name + // (otherwise that hint would describe tools no longer in the schema). + const ollamaWebToolNames = new Set(['ollama_web_search', 'ollama_web_fetch']); + const hasOllamaWebKeyword = /ollama[\s_]?(웹|web|검색|search)|올라마\s*(웹|검색|로\s*검색)/i.test(message); // Sub-agent/task-orchestration tools that tool_audit.log shows near-zero real usage for // (2026-05-04~2026-07-12: start_task/request_secondary_assist/subagent_spawn/ // delegate_to_specialist/spawn_subagent = 0 calls; task_control/parse_schedule_pattern/ @@ -311,6 +320,7 @@ async function handleChat( // model there has already generated the embed.windy.com URL to capture) — unlike the // other weather tools, not offered via the meteorologist skill in general chat. if (name === 'weather_map_screenshot' && !isWeatherAppSession) return false; + if (ollamaWebToolNames.has(name) && !hasOllamaWebKeyword) return false; if (legalToolNames.has(name) && !isLawyerAppSession && !(lawyerEnabled && hasLegalKeyword)) return false; if ((name === 'excel_read' || name === 'excel_write') && !isAccountantAppSession && !hasExcelKeyword) return false; if (coderToolNames.has(name) && !hasCodeKeyword) return false;