mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-17 02:05:22 -04:00
Tools: match keyword hints on word boundaries
`get_tools_for_query` force-includes whole tool families when the query
mentions an intent keyword, but matched with a raw substring test
(`kw in ql`). Short hints therefore fired inside unrelated words, bloating
the tool set with irrelevant tools:
- "fix" matched "prefix" -> document tools
- "line" matched "deadline"/"online" -> document tools
- "serve" matched "observe"/"reserve" -> cookbook serve tools
- "reply" matched "replying" -> all email tools
- "unread" matched "unreadable" -> all email tools
Match each keyword on word boundaries instead
(`re.search(rf"\b{re.escape(kw)}\b", ql)`), the same fix already applied to
the keyword matcher in topic_analyzer.py. Genuine intent keywords
("reply to this email", "edit the document", "serve the model") still match.
This only removes substring-inside-a-word matches; it does not change whole
-word matches (so e.g. an unrelated whole word like "tell" is a separate
keyword-choice question, left untouched here).
Checks: python -m pytest tests/test_tool_index_keyword_boundaries.py (4 passed;
3 of them fail on the pre-fix substring code), python -m py_compile
src/tool_index.py, git diff --check.
This commit is contained in:
+6
-2
@@ -431,10 +431,14 @@ class ToolIndex:
|
|||||||
base = set(always_include or ALWAYS_AVAILABLE)
|
base = set(always_include or ALWAYS_AVAILABLE)
|
||||||
retrieved = self.retrieve(query, k=k)
|
retrieved = self.retrieve(query, k=k)
|
||||||
base.update(retrieved)
|
base.update(retrieved)
|
||||||
# Keyword-based force-include for common intents
|
# Keyword-based force-include for common intents. Match on word
|
||||||
|
# boundaries, not raw substrings, so short hints like "fix", "line",
|
||||||
|
# "serve", "reply" or "unread" don't fire inside unrelated words
|
||||||
|
# ("prefix", "deadline"/"online", "observe"/"reserve", "replying",
|
||||||
|
# "unreadable"). Same word-boundary matching used in topic_analyzer.
|
||||||
ql = query.lower()
|
ql = query.lower()
|
||||||
for keywords, tools in self._KEYWORD_HINTS.items():
|
for keywords, tools in self._KEYWORD_HINTS.items():
|
||||||
if any(kw in ql for kw in keywords):
|
if any(re.search(rf"\b{re.escape(kw)}\b", ql) for kw in keywords):
|
||||||
base.update(tools)
|
base.update(tools)
|
||||||
# Structural scheduling-intent detection — typo-resilient (the literal
|
# Structural scheduling-intent detection — typo-resilient (the literal
|
||||||
# keyword "every day" misses "every dya"). Catches "every <word>",
|
# keyword "every day" misses "every dya"). Catches "every <word>",
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
"""Keyword-hint force-include must match on word boundaries, not substrings.
|
||||||
|
|
||||||
|
`get_tools_for_query` force-includes whole tool families when a query mentions
|
||||||
|
an intent keyword. The match used a raw substring test (`kw in ql`), so short
|
||||||
|
hints fired inside unrelated words: "fix" in "prefix", "line" in "deadline"/
|
||||||
|
"online", "serve" in "observe"/"reserve", "reply" in "replying", "unread" in
|
||||||
|
"unreadable". That bloated the tool set with irrelevant email/document/serve
|
||||||
|
tools for queries that have nothing to do with them. Same substring-vs-word
|
||||||
|
pitfall already fixed in topic_analyzer.py.
|
||||||
|
|
||||||
|
`retrieve` (which needs a chroma collection) is stubbed out so these tests
|
||||||
|
exercise only the keyword-hint loop.
|
||||||
|
"""
|
||||||
|
from src.tool_index import ToolIndex
|
||||||
|
|
||||||
|
|
||||||
|
def _index():
|
||||||
|
ti = ToolIndex.__new__(ToolIndex)
|
||||||
|
ti.retrieve = lambda query, k=8: [] # no chroma; isolate the keyword loop
|
||||||
|
return ti
|
||||||
|
|
||||||
|
|
||||||
|
def test_substring_inside_word_does_not_force_email_tools():
|
||||||
|
ti = _index()
|
||||||
|
# "replying" contains "reply"; "unreadable" contains "unread".
|
||||||
|
for q in ("i am replying to your github comment", "this document is unreadable"):
|
||||||
|
tools = ti.get_tools_for_query(q)
|
||||||
|
assert "send_email" not in tools, q
|
||||||
|
assert "reply_to_email" not in tools, q
|
||||||
|
|
||||||
|
|
||||||
|
def test_substring_inside_word_does_not_force_document_tools():
|
||||||
|
ti = _index()
|
||||||
|
# "prefix" contains "fix"; "deadline"/"online" contain "line".
|
||||||
|
for q in ("prefix the output with a label", "the deadline is online already"):
|
||||||
|
tools = ti.get_tools_for_query(q)
|
||||||
|
assert "edit_document" not in tools, q
|
||||||
|
assert "update_document" not in tools, q
|
||||||
|
|
||||||
|
|
||||||
|
def test_substring_inside_word_does_not_force_serve_tools():
|
||||||
|
ti = _index()
|
||||||
|
# "observe"/"reserve" contain "serve".
|
||||||
|
tools = ti.get_tools_for_query("please observe the reserve levels")
|
||||||
|
assert "serve_model" not in tools
|
||||||
|
assert "serve_preset" not in tools
|
||||||
|
|
||||||
|
|
||||||
|
def test_genuine_keywords_still_force_include():
|
||||||
|
ti = _index()
|
||||||
|
assert "reply_to_email" in ti.get_tools_for_query("reply to this email")
|
||||||
|
assert "edit_document" in ti.get_tools_for_query("edit the document")
|
||||||
|
assert "serve_model" in ti.get_tools_for_query("serve the model")
|
||||||
Reference in New Issue
Block a user