Files
hermes-agent/agent/reasoning_summaries.py
Teknium 37fb7adfd6 fix: structured reasoning no longer breaks chat consumers
Normalize incoming reasoning at the shared heading boundary and completed
extraction, and flatten auxiliary content and reasoning before accumulation.
Reuse the existing text flattener with no implicit fragment separators.

Combine the earliest related work from zsuroy (#85791), the diagnosis and
patch from 2025hcsmile2010-hue (#104711, #104848), and completed extraction
work from liuhao1024 (#104717) as a slim redo, not a verbatim cherry-pick.

Two invariant tests exercise the real SDK and local HTTP fixture across
main streaming, Relay collection, auxiliary sync/async and completed output.
The standalone matrix improves from 32/84 to 84/84, preserving answers.

Co-authored-by: suroy <suroy@qq.com>
Co-authored-by: 2025hcsmile2010-hue <2025hcsmile2010@gmail.com>
Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
2026-09-07 08:24:54 -07:00

32 lines
1.6 KiB
Python

"""Boundary repair for providers that stream reasoning as discrete summary parts.
Reasoning-summary models (OpenAI gpt-5.x and Responses-API relays onto the chat wire) emit one
``reasoning_content`` delta per *completed* summary part, each opening with a bold heading.
The chat wire lacks the Responses API's ``summary_index`` delimiter (verified live on Nous
Portal ``openai/gpt-5.6-sol``), so plain concatenation glues ``**One****Two**`` into one
half-bold paragraph. The boundary is re-derived from a delta opening a bold heading, matching
the blank-line join Hermes' own Responses adapter does.
"""
from __future__ import annotations
from typing import Any
from agent.message_content import flatten_message_text
__all__ = ["separate_glued_reasoning_blocks"]
def separate_glued_reasoning_blocks(previous: str, delta: Any) -> str:
"""Return *delta*, prefixed with a paragraph break when it glues onto *previous*.
A break is inserted when *delta* opens a *closed* bold heading and *previous* is mid-line
(heading butting heading, or prose butting heading). Token-streamed reasoning is left
alone: its deltas carry their own whitespace, and a fragment that merely opens emphasis
(``**`` alone) is not a part boundary — summary parts carry the whole heading in one delta.
"""
# Relays also emit content-part lists/dicts; fragments carry their own whitespace.
delta = flatten_message_text(delta, sep="")
glued = previous and delta and not previous[-1].isspace() and delta.startswith("**") and "**" in delta[2:]
return f"\n\n{delta}" if glued else delta