fix(reasoning): skip duplicate callback for <think>-extracted reasoning during streaming (#3116)
Local models (Ollama, LM Studio) embed reasoning in <think> tags in delta.content. During streaming, _stream_delta() already displays these blocks. Then _build_assistant_message() extracts them again and fires reasoning_callback, causing duplicate display. Track whether reasoning came from structured fields (reasoning_content) vs <think> tag extraction. Only fire the callback for <think>-extracted reasoning when stream_delta_callback is NOT active. Structured reasoning always fires regardless. Salvaged from PR #2076 by dusterbloom (Fix A only — Fix B was already covered by PR #3013's _current_reasoning_callback centralization). Closes #2069.
This commit is contained in:
14
run_agent.py
14
run_agent.py
@@ -4494,6 +4494,7 @@ class AIAgent:
|
|||||||
so both the tool-call path and the final-response path share one builder.
|
so both the tool-call path and the final-response path share one builder.
|
||||||
"""
|
"""
|
||||||
reasoning_text = self._extract_reasoning(assistant_message)
|
reasoning_text = self._extract_reasoning(assistant_message)
|
||||||
|
_from_structured = bool(reasoning_text)
|
||||||
|
|
||||||
# Fallback: extract inline <think> blocks from content when no structured
|
# Fallback: extract inline <think> blocks from content when no structured
|
||||||
# reasoning fields are present (some models/providers embed thinking
|
# reasoning fields are present (some models/providers embed thinking
|
||||||
@@ -4509,10 +4510,15 @@ class AIAgent:
|
|||||||
logging.debug(f"Captured reasoning ({len(reasoning_text)} chars): {reasoning_text}")
|
logging.debug(f"Captured reasoning ({len(reasoning_text)} chars): {reasoning_text}")
|
||||||
|
|
||||||
if reasoning_text and self.reasoning_callback:
|
if reasoning_text and self.reasoning_callback:
|
||||||
try:
|
# Skip callback for <think>-extracted reasoning when streaming is active.
|
||||||
self.reasoning_callback(reasoning_text)
|
# _stream_delta() already displayed <think> blocks during streaming;
|
||||||
except Exception:
|
# firing the callback again would cause duplicate display.
|
||||||
pass
|
# Structured reasoning (from reasoning_content field) always fires.
|
||||||
|
if _from_structured or not self.stream_delta_callback:
|
||||||
|
try:
|
||||||
|
self.reasoning_callback(reasoning_text)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
msg = {
|
msg = {
|
||||||
"role": "assistant",
|
"role": "assistant",
|
||||||
|
|||||||
Reference in New Issue
Block a user