Fix research_topic loop when intent misses and main model calls tool directly
When the intent model doesn't classify a research request (low confidence,
long message, etc.), the main model (qwen3) would correctly identify
research_topic itself and call it via the streaming tool loop. But
execute_tool("research_topic") only returns a dummy research_pending
placeholder, causing the model to see the result and retry — looping
up to MAX_TOOL_ROUNDS times.
Fix: filter research_topic out of stream_tools (the tool list given to
the main model via stream_chat_with_tools). research_topic is an
intent-only routing tool; the main model should never call it directly.
The full tools list (including research_topic) is still passed to
classify_intent so intent routing continues to work.
The _INTENT_ONLY_TOOLS frozenset makes this pattern explicit and
extensible for future intent-only tools.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -237,9 +237,20 @@ async def run_generation(
|
||||
get_setting(user_id, "intent_model", ""),
|
||||
)
|
||||
intent_model = intent_model_setting or Config.OLLAMA_INTENT_MODEL or model
|
||||
|
||||
# research_topic is handled exclusively by the intent-first pipeline (round 0).
|
||||
# Remove it from the tool list given to the main model so it can never be called
|
||||
# directly via the streaming tool loop (which would just return a placeholder result
|
||||
# and cause the model to retry in a loop).
|
||||
_INTENT_ONLY_TOOLS = frozenset({"research_topic"})
|
||||
stream_tools = [
|
||||
t for t in tools
|
||||
if t.get("function", {}).get("name") not in _INTENT_ONLY_TOOLS
|
||||
]
|
||||
|
||||
logger.info(
|
||||
"Starting generation for conv %d: model=%s, intent_model=%s, tools=%d",
|
||||
conv_id, model, intent_model, len(tools),
|
||||
"Starting generation for conv %d: model=%s, intent_model=%s, tools=%d (stream_tools=%d)",
|
||||
conv_id, model, intent_model, len(tools), len(stream_tools),
|
||||
)
|
||||
|
||||
# Phase 2: Summarize long conversation history if needed.
|
||||
@@ -443,7 +454,7 @@ async def run_generation(
|
||||
buf.append_event("status", {"status": "Generating response..."})
|
||||
t_stream = time.monotonic()
|
||||
|
||||
async for chunk in _stream_with_retry(messages, model, tools, think):
|
||||
async for chunk in _stream_with_retry(messages, model, stream_tools, think):
|
||||
if buf.cancel_event.is_set():
|
||||
cancelled = True
|
||||
break
|
||||
@@ -515,7 +526,7 @@ async def run_generation(
|
||||
|
||||
buf.append_event("status", {"status": "Generating response..." if _round == 0 else "Composing response..."})
|
||||
t_stream = time.monotonic()
|
||||
async for chunk in _stream_with_retry(messages, model, tools, think):
|
||||
async for chunk in _stream_with_retry(messages, model, stream_tools, think):
|
||||
if buf.cancel_event.is_set():
|
||||
cancelled = True
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user