fix(provider): recognize thinking tags in compatible completions - #10116
Open
konodiodaaaaa1 wants to merge 1 commit into
Open
konodiodaaaaa1 wants to merge 1 commit into
konodiodaaaaa1 wants to merge 1 commit into
Conversation
buyun14
pushed a commit
to buyun14/AstrBot
that referenced
this pull request
Sep 17, 2026
…tible completions
buyun14
pushed a commit
to buyun14/AstrBot
that referenced
this pull request
Sep 17, 2026
…tible completions
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.
Problem and fix
An OpenAI-compatible Gemini gateway returned
<thinking>...</thinking>insidemessage.content. AstrBot's non-streaming parser only recognizes<think>...</think>, so the reasoning remains in the visible answer even withdisplay_reasoning_text=false.This adds the
thinkingspelling to the existing extraction and trailing-close-tag cleanup. Opening and closing tags must match. Extracted text goes intoLLMResponse.reasoning_content, preserving the existing precedence of the structured reasoning field (including an explicitly empty string).Example:
<thinking>Reasoning</thinking>Answernow yields answerAnswerand reasoningReasoning.The observed
<thinking>wrapper is gateway behavior; it is not presented here as Google's native Gemini wire protocol. This is a small compatibility fix, with no new dependency or configuration option.Upstream check
Checked on 2026-09-17 against official
masterate0aa8d386121ead06825fb6d1e423a41a3d14a83; latest published release at the time was v4.28.1.<think>blocks in streaming responses; its current diff does not handle<thinking>.<thinking>blocks.Those related changes address different extraction paths. I did not find an existing change covering this exact tag spelling in the reviewed master and PR diffs.
Validation
<thinking>support.python -m pytest tests/test_openai_thinking_tags.py tests/test_openai_source.py -q: 76 passed after the change.ruff check .,ruff format --check .using the repository-pinned Ruff 0.15.22, andgit diff --check: passed.Follow-up considerations for maintainers
This patch deliberately leaves broader policy and protocol decisions to separate work:
thinkingspelling and split-tag tests alongside fix: 移除流式响应中MiniMax思考内容的输出 #7314.<think>/<thinking>examples in prose or code also share the existing regex ambiguity. A future policy should decide whether filtering is provider-specific and how truncation is handled.reasoning_content,reasoning, content blocks, andreasoning_detailsneed protocol-aware extraction. fix(provider): make OpenAI-compatible reasoning field name configurable per channel (#9783) #9829 covers configurable field names, while structured arrays need their own treatment.Official protocol references reviewed (not a claim that this PR implements every format)
The same model can have different native, hosted, and gateway formats. References below describe specific interfaces/model families, rather than asserting that every model from a vendor uses one universal delimiter.
type: reasoningoutput items,summaryentries oftype: summary_text, and opaqueencrypted_contentPart.thoughtdistinguishes reasoning text;thought_signatureis opaque statesignatureand optionalsummary; streaming usesthought_summaryandthought_signaturedeltasthinkingblocks with athinkingfield;thinking_delta/signature_deltaevents;redacted_thinkinghas opaque datareasoning_contentbesidecontent; reasoning-history requirements depend on the API and tool-use modereasoning_content; Qwen3 raw generation:<think>...</think>reasoning_content; its tokenizer includes<think>and</think>reasoning_content; preserved-thinking mode requires unchanged reasoning historyreasoning_contentalongside tool calls; the documented tool workflow preserves it in subsequent messages<think>; withreasoning_split, separatereasoning_content/reasoning_detailsreasoning/reasoning_contentalias andreasoning_detailsentries such asreasoning.text,reasoning.summary,reasoning.encryptedmessage.thinkingfor chat, top-levelthinkingfor generate; answer stays inmessage.content/responsereasoning_content, Responses reasoning delta events, and optional encrypted reasoning state; availability varies by model<think>...</think>and<answer>...</answer>thinkingcontent blocks withthinkingtext, separate fromtextblocksanalysisandfinal; protocol parsing is channel-basedThese references support keeping structured provider handling separate from a small allowlist of observed inline tags. This patch adds only the confirmed
<thinking>spelling. It does not infer extra XML tags from generic words such as “analysis” or “reasoning”.Summary by Sourcery
Support
<thinking>reasoning tags in non-streaming OpenAI-compatible completions while preserving existing structured reasoning behavior.Bug Fixes:
<thinking>...</thinking>blocks in OpenAI-compatible completion content and move their text intoreasoning_contentinstead of the visible answer.<thinking>closing tags alongside existing<think>tags.Enhancements:
Tests: