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", ("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) "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_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", ("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") "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. - "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.""" - Do NOT wrap the JSON in markdown code fences."""
_RESEARCH_PREFIX = re.compile(r"^[Rr]esearch:\s+(.+)", re.DOTALL)
async def classify_intent( async def classify_intent(
user_message: str, user_message: str,
tools: list[dict], tools: list[dict],
@@ -127,6 +132,21 @@ async def classify_intent(
if not tools: if not tools:
return IntentResult() 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) tool_summary = _build_tool_summary(tools)
today = date_type.today().isoformat() today = date_type.today().isoformat()