fix: buffer split reasoning wrapper deltas
maintainer edit: native reasoning streams can split <thinking> wrapper tags across chunks. Buffer the stream and emit only cleaned incremental reasoning so raw partial tags do not reach WebUI.
This commit is contained in:
@@ -771,18 +771,24 @@ class AgentRunner:
|
|||||||
await live_file_edits.update(delta)
|
await live_file_edits.update(delta)
|
||||||
|
|
||||||
if wants_streaming:
|
if wants_streaming:
|
||||||
|
thinking_buf = ""
|
||||||
|
|
||||||
async def _stream(delta: str) -> None:
|
async def _stream(delta: str) -> None:
|
||||||
if delta:
|
if delta:
|
||||||
context.streamed_content = True
|
context.streamed_content = True
|
||||||
await hook.on_stream(context, delta)
|
await hook.on_stream(context, delta)
|
||||||
|
|
||||||
async def _thinking(delta: str) -> None:
|
async def _thinking(delta: str) -> None:
|
||||||
|
nonlocal thinking_buf
|
||||||
if not delta:
|
if not delta:
|
||||||
return
|
return
|
||||||
delta = strip_reasoning_tags(delta)
|
prev_clean = strip_reasoning_tags(thinking_buf)
|
||||||
if delta:
|
thinking_buf += delta
|
||||||
|
new_clean = strip_reasoning_tags(thinking_buf)
|
||||||
|
incremental = new_clean[len(prev_clean):]
|
||||||
|
if incremental:
|
||||||
context.streamed_reasoning = True
|
context.streamed_reasoning = True
|
||||||
await hook.emit_reasoning(delta)
|
await hook.emit_reasoning(incremental)
|
||||||
|
|
||||||
async def _stream_recover() -> None:
|
async def _stream_recover() -> None:
|
||||||
await hook.on_stream_end(context, resuming=True)
|
await hook.on_stream_end(context, resuming=True)
|
||||||
|
|||||||
@@ -134,10 +134,15 @@ def strip_reasoning_tags(text: object) -> str:
|
|||||||
"""Remove wrapper tags from text that is already known to be reasoning."""
|
"""Remove wrapper tags from text that is already known to be reasoning."""
|
||||||
if not isinstance(text, str):
|
if not isinstance(text, str):
|
||||||
return ""
|
return ""
|
||||||
|
partial_reasoning_tag = (
|
||||||
|
r"</?(?:t|th|thi|thin|think|thinki|thinkin|thinking|tho|thou|thoug|though|thought)>?"
|
||||||
|
)
|
||||||
|
text = re.sub(rf"^\s*(?:{partial_reasoning_tag})$", "", text)
|
||||||
text = re.sub(r"^\s*<(?:think|thinking|thought)/>\s*", "", text)
|
text = re.sub(r"^\s*<(?:think|thinking|thought)/>\s*", "", text)
|
||||||
text = re.sub(r"\s*<(?:think|thinking|thought)/>\s*$", "", text)
|
text = re.sub(r"\s*<(?:think|thinking|thought)/>\s*$", "", text)
|
||||||
text = re.sub(r"^\s*<(?:think|thinking|thought)>\s*", "", text)
|
text = re.sub(r"^\s*<(?:think|thinking|thought)>\s*", "", text)
|
||||||
text = re.sub(r"\s*</(?:think|thinking|thought)>\s*$", "", text)
|
text = re.sub(r"\s*</(?:think|thinking|thought)>\s*$", "", text)
|
||||||
|
text = re.sub(rf"\s*(?:{partial_reasoning_tag})$", "", text)
|
||||||
return text.strip()
|
return text.strip()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -381,8 +381,8 @@ async def test_runner_strips_thinking_tags_from_native_thinking_deltas():
|
|||||||
*, on_content_delta=None, on_thinking_delta=None, **kwargs
|
*, on_content_delta=None, on_thinking_delta=None, **kwargs
|
||||||
):
|
):
|
||||||
if on_thinking_delta:
|
if on_thinking_delta:
|
||||||
await on_thinking_delta("<thinking>")
|
await on_thinking_delta("<thinking")
|
||||||
await on_thinking_delta("Preparing final response")
|
await on_thinking_delta(">Preparing final response")
|
||||||
await on_thinking_delta("</thinking>")
|
await on_thinking_delta("</thinking>")
|
||||||
if on_content_delta:
|
if on_content_delta:
|
||||||
await on_content_delta("done")
|
await on_content_delta("done")
|
||||||
|
|||||||
Reference in New Issue
Block a user