Fix intent classifier missing Research button requests

Two fixes for the intent model failing to route 'Research: X' messages
to research_topic:

1. Fast-path in classify_intent: if the message matches ^Research:\s+.+
   (the exact format the UI Research button always sends), skip the LLM
   call entirely and return research_topic with high confidence. This is
   100% reliable and saves an unnecessary model call for this pattern.

2. Expanded research_topic rule examples in the system prompt to include
   "Research: X" prefix format, shopping-style queries ("research where
   to buy X"), and clarification that the topic is everything after the
   keyword — improves LLM routing for natural-language research requests
   that don't match the previous narrow examples.

Root cause: qwen2.5:1.5b misclassified "Research: where to buy three-
quarter sleeve tee shirts" as general chat (shopping query phrasing
combined with the colon confused the small model).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 13:07:39 -05:00
parent a95d17fc04
commit d1c7ce88df
+22 -2
View File
@@ -104,12 +104,17 @@ Rules:
("search for X", "look up X", "what is the latest version of X", "find X online",
"google X", "what is X" for quick factual answers — NOT when they want a comprehensive note)
- research_topic: user wants to research a topic and create a comprehensive note from web sources
("research X", "research X and make a note", "compile notes on X", "write a report on X",
"deep dive into X", "find everything about X", "comprehensive guide to X")
("research X", "Research: X", "research X and make a note", "compile notes on X", "write a report on X",
"deep dive into X", "find everything about X", "comprehensive guide to X",
"research where to buy X", "research how to X", "research X and ship to me" — the topic
is everything after "Research:" or "research")
- "ack": one short, natural sentence confirming the action (tool path only). Vary phrasing — do not always start with "Let me". Omit (null) for chat-only responses.
- Do NOT wrap the JSON in markdown code fences."""
_RESEARCH_PREFIX = re.compile(r"^[Rr]esearch:\s+(.+)", re.DOTALL)
async def classify_intent(
user_message: str,
tools: list[dict],
@@ -127,6 +132,21 @@ async def classify_intent(
if not tools:
return IntentResult()
# Fast-path: "Research: <topic>" is the canonical format sent by the Research
# button in the UI. It always means research_topic — skip the LLM call entirely.
valid_names = {t.get("function", {}).get("name") for t in tools}
if "research_topic" in valid_names:
m = _RESEARCH_PREFIX.match(user_message.strip())
if m:
topic = m.group(1).strip()
logger.info("Intent fast-path: 'Research:' prefix → research_topic, topic='%s'", topic[:80])
return IntentResult(
tool_name="research_topic",
arguments={"topic": topic},
confidence="high",
ack=f"I'll research that and compile a comprehensive note.",
)
tool_summary = _build_tool_summary(tools)
today = date_type.today().isoformat()