Skip to content

fix(provider): make OpenAI-compatible reasoning field name configurable per channel (#9783) - #9829

Open
wwwwyy1 wants to merge 2 commits into
AstrBotDevs:masterfrom
wwwwyy1:fix/openai-reasoning-key
Open

fix(provider): make OpenAI-compatible reasoning field name configurable per channel (#9783)#9829
wwwwyy1 wants to merge 2 commits into
AstrBotDevs:masterfrom
wwwwyy1:fix/openai-reasoning-key

Conversation

@wwwwyy1

@wwwwyy1 wwwwyy1 commented Aug 26, 2026

Copy link
Copy Markdown

Note: PR #9828 was closed automatically when its head branch was recreated (an accidental line-ending noise made the first diff enormous). This is the clean version: 3 files, +85/-3, rebased onto the latest master.

Fixes #9783

Problem / 问题

The generic OpenAI-compatible provider hard-codes the thinking field name in astrbot/core/provider/sources/openai_source.py:

self.reasoning_key = "reasoning_content"

Different upstreams name this field differently: DeepSeek / Moonshot and most OpenAI-compatible providers use reasoning_content, while OpenRouter and OpenRouter-style relay channels (e.g. cline-pass) use reasoning. As a result, when such a relay serves kimi-k3, thinking content is silently dropped in AstrBot even though the same API key works fine in other frontends. The repo already adapts specific sources by subclass overrides (groq_source.py / openrouter_source.py set reasoning_key = "reasoning"), but the generic openai_chat_completion channel has no way to benefit.

问题:openai_source.py 将思考内容字段名硬编码为 reasoning_content,而 OpenRouter 系中转渠道使用 reasoning 字段,导致思考内容完全丢失。

Solution / 修复方案

Implements Option 1 from the issue (per-channel config item, minimal change and most flexible):

  • self.reasoning_key = provider_config.get("reasoning_key") or "reasoning_content" — reads the field name from provider config; the default is unchanged, so existing users see zero behavior change
  • New reasoning_key entry in the WebUI config schema (default.py, CONFIG_METADATA_2) with description / type / hint
  • Multi-turn history round-trip also respects the configured key (see below)

方案:按 issue 方案 1 新增渠道配置项 reasoning_key,默认值不变(存量用户零回归),WebUI 配置档可见可填。

Notable detail (hidden fix) / 隐藏点修复

_finally_convert_payload re-emitted assistant think history as message["reasoning_content"] — also hard-coded. With only the extraction side fixed, a relay expecting reasoning would still receive the next-turn history under the wrong key. This PR emits history under the same configured self.reasoning_key.

隐藏点:多轮对话历史回传同样硬编码了 reasoning_content——只修提取侧时,第二轮起上游仍收到错误字段名的思考历史。本 PR 让历史回传使用同一配置字段名。

Scope notes / 范围说明

  • Auto-detection (Option 2, candidate-key list) intentionally not implemented: the project already adapts via explicit subclass overrides, a config key covers all remaining cases, and Option 1 is what the issue author prefers.
  • The model-specific reasoning_content handling in the DeepSeek-v4 / MiMo branches is intentionally untouched — that is the official DeepSeek API protocol (missing the field causes HTTP 400), unrelated to relay field dialects.

Testing / 测试

3 new tests in tests/test_openai_source.py:

  • test_reasoning_key_configurable_extracts_alias_field — with reasoning_key: reasoning, thinking content is extracted from the alias field
  • test_reasoning_key_default_keeps_reasoning_content_behavior — without the config, behavior is unchanged (alias ignored, reasoning_content still works)
  • test_reasoning_key_applied_to_assistant_history_payload — assistant think history is emitted under the configured key (and reasoning_content is not)

pytest results:

$ python -m pytest tests/test_openai_source.py -k reasoning -q
6 passed, 60 deselected in 4.38s   (3 new + 3 existing reasoning tests)

$ python -m pytest tests/test_openai_source.py -q
3 failed, 63 passed in 5.60s

The 3 failures are the pre-existing test_file_uri_to_path_* Windows-only path tests — verified identical on the unmodified baseline via git stash; project CI runs on Linux.

Changes / 改动文件

  • astrbot/core/provider/sources/openai_source.py — configurable reasoning_key for extraction + history round-trip
  • astrbot/core/config/default.py — reasoning_key config schema
  • tests/test_openai_source.py — 3 new tests

3 files, +85 / -3.

Summary by Sourcery

Make the OpenAI-compatible provider’s reasoning field configurable per channel while retaining the existing default behavior.

New Features:

  • Add a per-channel configuration option for selecting the reasoning field name used by OpenAI-compatible providers.

Bug Fixes:

  • Preserve reasoning content for providers and relays that use reasoning instead of reasoning_content, including across multi-turn assistant history.

Tests:

  • Add coverage for configurable reasoning extraction, default behavior, and assistant history serialization.

@dosubot dosubot Bot added size:S This PR changes 10-29 lines, ignoring generated files. area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. labels Aug 26, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="astrbot/core/provider/sources/openai_source.py" line_range="1047" />
<code_context>
-                    message["reasoning_content"] = reasoning_content
+                    # Emit the thinking history under the configured key so
+                    # channels that expect `reasoning` (issue #9783) accept it.
+                    message[self.reasoning_key] = reasoning_content

             if (
</code_context>
<issue_to_address>
**issue (broader_impact):** When `reasoning_key` is configured as `reasoning`, `_finally_convert_payload` writes a think-only assistant history message under `reasoning` with empty `content`, but the later `_sanitize_assistant_messages` check only recognizes `reasoning_content` and removes the message as empty. The relay therefore loses assistant reasoning history on subsequent turns.

**Triggers:** When a configured alias channel sends a subsequent request containing an assistant history message made entirely of think blocks.

**Suggested fix:** Make `_sanitize_assistant_messages` use the configured reasoning key, and invoke it as an instance method or pass `self.reasoning_key` into it.
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: astrbot/core/provider/sources/openai_source.py:1047


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread astrbot/core/provider/sources/openai_source.py Outdated
_sanitize_assistant_messages still hardcoded the reasoning_content field
name, so think-only assistant history was dropped as garbage when the
provider configured a different reasoning_key (AstrBotDevs#9783). Pass the
configured key through (falling back to reasoning_content for legacy
history) and cover it with tests.

Address PR AstrBotDevs#9829 review feedback.
@dosubot dosubot Bot added size:XS This PR changes 0-9 lines, ignoring generated files. and removed size:S This PR changes 10-29 lines, ignoring generated files. labels Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. size:XS This PR changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] 希望OpenAI 兼容渠道的思考字段名(reasoning key)支持自定义/可配置

1 participant