feat(llm): capture Claude/OpenAI reasoning as a real subtype:reasoning channel (ENG-1109) - #280
Open
torrmal wants to merge 1 commit into
Open
feat(llm): capture Claude/OpenAI reasoning as a real subtype:reasoning channel (ENG-1109)#280torrmal wants to merge 1 commit into
torrmal wants to merge 1 commit into
Conversation
…g channel (ENG-1109)
Anton had no way to distinguish the model's own reasoning/narration
("let me verify that first...") from the final answer -- both traveled
through StreamTextDelta, so narration got permanently baked into the
persisted answer instead of showing as a live, disappearing "current
thought" (which the frontend already supports via Hermes's existing
thought.progress + subtype channel -- Anton just never emitted it).
- provider.py: new StreamReasoningDelta event, added to StreamEvent.
- anthropic.py: output_config.effort already triggers server-side
adaptive thinking when reasoning_effort is configured; the missing
piece was parsing the resulting `thinking` content blocks instead of
misclassifying them as text / silently dropping thinking_delta.
signature_delta is intentionally ignored (a verification signature,
not text).
- openai.py (Responses API / FLAVOR_OPENAI): request reasoning.summary
alongside the existing effort, handle
response.reasoning_summary_text.delta.
- openai.py (chat.completions / FLAVOR_MINDS_PASSTHROUGH +
FLAVOR_OPENAI_COMPATIBLE_GENERIC -- the path the MindsHub-managed
gateway actually uses even for Claude models): read delta.reasoning_content
defensively, the de facto convention several OpenAI-compatible
reasoning gateways use. Best-effort -- needs verification against the
real mdb.ai gateway wire format.
- session.py: the synthetic "Thinking..." timer now also closes on the
first StreamReasoningDelta, matching its existing text/complete
behavior.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Anton has no way to distinguish the model's own reasoning/narration ("let me verify that first...", "confirmed — this changed, let me re-check...") from the final answer — both travel through
StreamTextDelta, the only text-carrying event that exists. That means narration gets permanently baked into the persisted answer instead of showing as a live, disappearing "current thought." The frontend already supports this correctly (built for cowork's ENG-1108) via athought.progress+subtype: reasoning|thinkingevent — the Hermes harness already emits it; Anton just never did.Confirmed via direct code reading, not assumption:
StreamTextDeltais the only text event inprovider.py'sStreamEventunion.thinking_delta/signature_deltasilently dropped).reasoning.summarywas missing from the request kwargs) and never handled in the streaming loop.FLAVOR_MINDS_PASSTHROUGH), not the Responses API — so a correct fix needs 3 code paths, not just "enable Claude thinking."Changes
provider.py: newStreamReasoningDeltaevent, added toStreamEvent.anthropic.py:output_config.effort(verified real — Claude 4.6/4.7's adaptive-thinking parameter, already sent wheneverreasoning_effortis configured for a compatible model) already triggers server-side thinking; the gap was purely on the parsing side. Addedcontent_block_starthandling forthinking/redacted_thinkingblock types, andcontent_block_deltahandling forthinking_delta→StreamReasoningDelta.signature_deltais intentionally a no-op (cryptographic verification signature, not user-facing text). No new request parameter — reuses the existing, safely-gatedreasoning_effortconfig.openai.py(Responses API /FLAVOR_OPENAI): added"summary": "auto"alongside the existingeffortin thereasoningkwarg (only sent whenreasoning_effortis already configured, same gating as today), plus a newresponse.reasoning_summary_text.deltabranch.openai.py(chat.completions /FLAVOR_MINDS_PASSTHROUGH+FLAVOR_OPENAI_COMPATIBLE_GENERIC): readsdelta.reasoning_contentdefensively viagetattr— the de facto convention several OpenAI-compatible reasoning gateways (DeepSeek, vLLM's reasoning parser) use. This one is best-effort — flagging clearly for review since I couldn't verify the actual mdb.ai gateway's wire format from this repo alone.session.py: the synthetic "Thinking..." timer (reasoning_start/reasoning_done) now also closes on the firstStreamReasoningDelta, matching its existing behavior for text/complete.Companion PR in cowork-server (maps
StreamReasoningDelta→ the wiresubtype: reasoningevent, matching hermes_harness's existing shape exactly): mindsdb/cowork-server#241 (opening next).No frontend changes needed — cowork's ENG-1108 already built the client-side handling for this exact
subtype: reasoning|thinkingshape.Linear: https://linear.app/mindsdb/issue/ENG-1109/anton-capture-claudeopenai-reasoning-as-a-real-subtypereasoning
Test plan
TestAnthropicProviderReasoningStream(thinking_delta → StreamReasoningDelta, signature_delta ignored, effort passed via extra_body),TestResponsesAPIReasoningSummary(kwargs gating, reasoning_summary_text.delta mapping),TestChatCompletionsReasoningContent(reasoning_content read + graceful absence)test_chat_scratchpad.py(subprocess sandbox execution issue), confirmed identical on unmodifiedstagingviagit stash, unrelated to this changeast.parsesanity check on all 4 edited modules;ruff checkclean (one pre-existing, unrelatedE741elsewhere insession.py)🤖 Generated with Claude Code