fix(agent): match non-English keywords in tool-RAG domain classifier#3779
fix(agent): match non-English keywords in tool-RAG domain classifier#3779max-freddyfire wants to merge 1 commit into
Conversation
_classify_agent_request only matched English domain keywords, so a
non-English agent request ("Vis meg de fem siste e-postene i innboksen
min.") matched no domain, was flagged low_signal, and tool retrieval was
skipped entirely: the model received only the always-available tools and
told the user it could not do the task, while the English equivalent was
offered every email tool.
Extend each domain keyword regex with high-precision Swedish / Norwegian /
Danish / German / Spanish / French / Italian nouns and verbs (email,
notes/calendar/tasks, documents, web, ui, files, settings, cookbook).
Gating semantics, ordering, and English behavior are unchanged; smalltalk
stays low-signal in every language. Tokens are chosen to avoid English
collisions (termin(?!a[lt]) blocks terminal/terminate, suche\w* avoids
"such", the modell suffix alternation avoids British "modelled", meteo
does not match "meteor").
Regression tests exercise the classifier directly per language and domain,
pin English classification, and pin smalltalk as low-signal.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
40d1ca6 to
d7bdfa7
Compare
|
Thanks for the detailed repro and the test coverage. The stall you are describing is real, but I am going to close this approach rather than merge it, for the same reason as #3802. Hardcoding per-language keyword and phrase lists into src/agent_loop.py is not maintainable. Every additional language means another set of verbs, nouns, and phrasing heuristics baked into the classifier, and those lists drift and go stale fast. The bigger problem is cross-language collision: matching an open-ended multilingual keyword set invites false positives from tokens that are innocuous in one language but a trigger in another, so the gate fires on input that was never that intent. The PR already has to hand-tune patterns to avoid colliding with common English words, which is exactly the fragility I want to avoid, and it gets worse with each language added. The direction we want for multilingual intent is a proper i18n layer rather than inline keyword grab-bags: detect or read the user's configured language, and match intent against English plus that one language only, sourced from a real i18n module instead of regex literals scattered through the agent code. That keeps the matching set small and predictable and removes the collision problem entirely. So this is a close on the approach, not on the goal. An i18n-backed version would be a welcome follow-up. |
Summary
The tool-RAG domain classifier
_classify_agent_request(src/agent_loop.py) only matched English keywords, so a non-English agent request matched no domain, was flaggedlow_signal, and tool retrieval was skipped entirely (low_signal = not continuation and not domains; the low-signal branch pins the tool set toALWAYS_AVAILABLE). Live repro: Norwegian "Vis meg de fem siste e-postene i innboksen min." got onlyask_user/manage_memory/update_planand the agent told the user to "check your email client or webmail provider directly", while the English phrasing was offered every email tool. This PR extends each domain regex (email, notes/calendar/tasks, documents, web, ui, files, settings, cookbook) with high-precision Swedish/Norwegian/Danish/German/Spanish/French/Italian keywords. Gating semantics, ordering, and English behavior are unchanged; smalltalk stays low-signal in every language.Target branch
dev, notmain. All PRs land indev;mainis curated by the maintainer at each release. If your PR is onmainby accident, click "Edit" on this PR and change the base.Linked Issue
Fixes #3713
Fixes #3766 (same bug reported from the Italian side; Italian is covered)
Related: #3605/#3606 (missing
imagesdomain in the same classifier — intentionally not touched here; it should get the same multilingual treatment once merged), #3771 (orthogonal: restores web tools for genuinely low-signal turns; no diff overlap), #3668/#3683 (sibling English-only pattern in_INTENT_RE).Type of Change
Details
Each domain
has(...)call gets one additional pattern argument with the non-English keywords, e.g. email:Tokens were checked one by one against English and cross-language collisions:
termin(?!a[lt])\w*(German "Termin") does not match "terminal"/"terminate".notiz(?!ie)\w*(German "Notiz") does not match Italian "notizie" (news) — found via live testing, pinned by a dedicated guard test.suche\w*, never baresuch.modell(?:e|en|er|ene|erna|o|i)?does not match British "modelled"/"modelling".meteodoes not match "meteor"; French/Spanish "activate" verbs are left out (active\w*/activa\w*would match English "active"/"activate").A false positive costs a few extra tool schemas offered to the model (same as existing loose English keywords like
buy→ notes), never an action.Checklist
imagesdomain to the same classifier and fix(agent_mode): restore web tools for low-signal agent turns #3771 changesALWAYS_AVAILABLEin src/tool_index.py; neither overlaps this diff.)devdocker compose uporuvicorn app:app) and verified the change works end-to-end. Type-checks and unit tests are not enough.End-to-end verification on a live instance (qwen3:30b-thinking via Ollama), same Norwegian request before/after:
and the agent goes from refusing ("check your email client ... directly") to actually calling
list_emails. The Italian repro from #3766 was also run live:How to Test
The new test file exercises
_classify_agent_requestdirectly (it is deterministic string matching): 52 non-English requests across seven languages and eight domains must classify into the same domain as their English equivalent withlow_signal=False; English phrasings are pinned to their current classification; greetings in all languages must staylow_signal=True; a guard test pins the notizie/Notiz cross-language collision.Manual check: in Agent Mode, send
Vis meg de fem siste e-postene i innboksen min.and watch the[agent-intent]log line flip fromlow_signal=True domains=[]tolow_signal=False domains=['email'].Visual / UI changes — REQUIRED if you touched anything that renders
N/A — backend tool-selection logic and tests only. No CSS, HTML, SVG, or
static/js/files changed.This is a single-purpose, AI-assisted fix following the issue-first flow: the bug was root-caused and confirmed on #3713 (with credit to the original reporter) before opening this PR.
Screenshots / clips
N/A — no visual rendering changes.
🤖 Generated with Claude Code