diff --git a/src/fabledassistant/services/generation_task.py b/src/fabledassistant/services/generation_task.py index 7a6fca0..2f727cd 100644 --- a/src/fabledassistant/services/generation_task.py +++ b/src/fabledassistant/services/generation_task.py @@ -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