Skip to content

“强制格式化”选项将 reasoning 字段重命名为 reasoning_content:适用于 OpenAI 与 OpenRouter 渠道 - #2279

Closed
ZhaoZuohong wants to merge 4846 commits into
QuantumNous:mainfrom
ZhaoZuohong:main
Closed

“强制格式化”选项将 reasoning 字段重命名为 reasoning_content:适用于 OpenAI 与 OpenRouter 渠道#2279
ZhaoZuohong wants to merge 4846 commits into
QuantumNous:mainfrom
ZhaoZuohong:main

Conversation

@ZhaoZuohong

@ZhaoZuohong ZhaoZuohong commented Nov 23, 2025

Copy link
Copy Markdown

修改说明

  • OpenRouter 虽然与 OpenAI 兼容格式使用相同的 /v1/chat/completions 端点,然而其响应格式与一般的 OpenAI 兼容格式有些不同。除 OpenRouter 额外添加的字段外,一个明显的区别是:思考内容在 reasoning 字段而非常见的 reasoning_content 字段中。有些客户端(如 Cline)在添加“兼容 OpenAI”格式的客户端时,只处理 reasoning_content 字段、不处理 OpenRouter 的 reasoning 字段。
  • 另一方面,有些上游渠道(如 Cerebras.ai)目前不受 New API 支持,它也会将模型的思考内容放入 reasoning 字段。

本 PR 的修改内容为:当开启 OpenAI 与 OpenRouter 渠道上游“强制格式化”选项时,将 reasoning 字段转换为重命名为 reasoning_content 字段,以适配更多客户端。

image

本 PR 的功能不会和“思考内容转换”选项(将 reasoning_content 转换为 <think> 标签拼接到内容中)冲突:

  • 非流式下,当前的“思考内容转换”选项本来就没有效果,只有本选项生效;
  • 在流式下,只要开启“思考内容转换”选项,推理内容就在 <think> 标签中;关闭“思考内容转换”选项时,字段受本选项控制。

Summary by CodeRabbit

  • New Features

    • Force-format option now applies to both OpenAI and OpenRouter channels.
    • Responses (including streamed responses) will normalize reasoning into the reasoning-content field when force-format is enabled.
    • UI: the force-format control is now available for OpenRouter channels as well.
    • Improved inference of token usage when explicit usage data is missing.
  • Documentation

    • Updated help text and translations (EN/FR/JA/RU/VI/ZH) to reflect expanded support and reasoning-content conversion.

✏️ Tip: You can customize this high-level summary in your review settings.

feitianbubu and others added 30 commits November 4, 2025 18:29
修复即梦v30-pro视频生成失败问题
feat:  EditTokenModal 中针对用户创建的 token 默认无限额度
feat: add environment variable switch for critical rate limit
fix: trim suffix p for jimeng image model
fix(channel): 当没有可用密钥时返回错误而不是第一个密钥
… new sections for partners, acknowledgments, and deployment instructions
- 更新中文README.md中的语言链接
- 完全重写英文README.en.md,包含所有详细功能说明
- 完全重写法文README.fr.md,确保内容一致性
- 完全重写日文README.ja.md,提供完整的项目说明

所有语言版本现在具有:
- 相同的结构和格式
- 一致的语言导航
- 完整的功能特性和部署指南
- 统一的环境变量配置说明
修复viduq2不支持参考生视频的问题
Calcium-Ion and others added 18 commits December 13, 2025 01:05
fix(channel_select): adjust priority retry logic for cross-group
feat: support gpt tts series model quota calculate
feat(auth): enhance IP restriction handling with CIDR support

@coderabbitai coderabbitai 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.

Actionable comments posted: 0

🧹 Nitpick comments (2)
relay/channel/openai/relay-openai.go (2)

247-251: Consider moving conversion after error check for efficiency.

The OpenRouter reasoning field conversion executes before checking for OpenAI errors (line 253). While this doesn't cause correctness issues—error responses return early at line 254—moving the conversion after the error check would avoid unnecessary work on error responses.

Apply this diff to optimize the flow:

 	err = common.Unmarshal(responseBody, &simpleResponse)
 	if err != nil {
 		return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
 	}
 
-	// OpenRouter reasoning 字段转换:reasoning -> reasoning_content
-	// 仅当启用转换为OpenAI兼容格式时执行(修改现有无条件转换)
-	if info.ChannelType == constant.ChannelTypeOpenRouter && info.ChannelOtherSettings.OpenRouterConvertToOpenAI {
-		convertOpenRouterReasoningFields(&simpleResponse)
-	}
-
 	if oaiError := simpleResponse.GetOpenAIError(); oaiError != nil && oaiError.Type != "" {
 		return nil, types.WithOpenAIError(*oaiError, resp.StatusCode)
 	}
+
+	// OpenRouter reasoning 字段转换:reasoning -> reasoning_content
+	// 仅当启用转换为OpenAI兼容格式时执行(修改现有无条件转换)
+	if info.ChannelType == constant.ChannelTypeOpenRouter && info.ChannelOtherSettings.OpenRouterConvertToOpenAI {
+		convertOpenRouterReasoningFields(&simpleResponse)
+	}

298-304: Consider clarifying the re-serialization comment.

The re-serialization logic for OpenRouter conversion is correct but the comment could better explain why this is in the else branch: when forceFormat is true, re-serialization already happens at line 293; when false but OpenRouter conversion was applied, we still need to re-serialize because the in-memory structure was modified.

Apply this diff to clarify the comment:

 		} else {
-			// 对于 OpenRouter,仅在执行转换后重新序列化
+			// 对于 OpenRouter,当执行字段转换后需要重新序列化
+			// (forceFormat=true时已在上方第293行重新序列化)
 			if info.ChannelType == constant.ChannelTypeOpenRouter && info.ChannelOtherSettings.OpenRouterConvertToOpenAI {
 				responseBody, err = common.Marshal(simpleResponse)
 				if err != nil {
 					return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
 				}
 			}
 			break
 		}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b4748f7 and 756464d.

📒 Files selected for processing (10)
  • dto/openai_request.go (1 hunks)
  • relay/channel/openai/helper.go (3 hunks)
  • relay/channel/openai/relay-openai.go (5 hunks)
  • web/src/components/table/channels/modals/EditChannelModal.jsx (11 hunks)
  • web/src/i18n/locales/en.json (1 hunks)
  • web/src/i18n/locales/fr.json (1 hunks)
  • web/src/i18n/locales/ja.json (1 hunks)
  • web/src/i18n/locales/ru.json (1 hunks)
  • web/src/i18n/locales/vi.json (1 hunks)
  • web/src/i18n/locales/zh.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • web/src/i18n/locales/ja.json
  • dto/openai_request.go
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/zh.json
  • web/src/components/table/channels/modals/EditChannelModal.jsx
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-08-21T06:31:11.073Z
Learnt from: jiajunly
Repo: QuantumNous/new-api PR: 1629
File: relay/channel/openai/relay-openai.go:170-174
Timestamp: 2025-08-21T06:31:11.073Z
Learning: In relay/channel/openai/relay-openai.go, the streaming logic for the AddThinkFirst feature is designed so that only the first chunk of a stream gets the "<think>\n" prefix. The final flush in the streaming handler intentionally uses addThink=false because the last chunk should never receive the prefix, even in single-chunk streams where the prefix would have been applied during normal processing.

Applied to files:

  • relay/channel/openai/helper.go
  • relay/channel/openai/relay-openai.go
📚 Learning: 2025-08-26T09:59:00.337Z
Learnt from: Sh1n3zZ
Repo: QuantumNous/new-api PR: 1659
File: relay/relay_task.go:285-305
Timestamp: 2025-08-26T09:59:00.337Z
Learning: In controller/task_video.go, data: URLs (containing base64 encoded video data) are prevented from being stored in task.FailReason by checking if the URL starts with "data:" before assignment. This same pattern should be applied consistently across the codebase.

Applied to files:

  • relay/channel/openai/helper.go
📚 Learning: 2025-08-08T17:12:43.157Z
Learnt from: RedwindA
Repo: QuantumNous/new-api PR: 1537
File: relay/gemini_handler.go:330-342
Timestamp: 2025-08-08T17:12:43.157Z
Learning: In the new-api repository, the `GeminiEmbeddingHandler` function in `relay/gemini_handler.go` is designed specifically for native Gemini embedding requests and therefore does not require the `ConvertGeminiRequest` step that is used in the chat handler. The embedding requests are already in the native Gemini format and don't need conversion.

Applied to files:

  • relay/channel/openai/relay-openai.go
📚 Learning: 2025-06-21T03:37:41.726Z
Learnt from: 9Ninety
Repo: QuantumNous/new-api PR: 1273
File: relay/channel/gemini/relay-gemini.go:97-116
Timestamp: 2025-06-21T03:37:41.726Z
Learning: In relay/channel/gemini/relay-gemini.go, the thinking budget calculation logic (including the MaxOutputTokens multiplication) was introduced in PR #1247. PR #1273 focused specifically on decoupling the thoughts summary feature from thinking budget settings and did not modify the existing thinking budget behavior.

Applied to files:

  • relay/channel/openai/relay-openai.go
🧬 Code graph analysis (1)
relay/channel/openai/helper.go (7)
relay/common/relay_info.go (1)
  • RelayInfo (81-129)
constant/channel.go (1)
  • ChannelTypeOpenRouter (24-24)
dto/channel_settings.go (1)
  • ChannelOtherSettings (26-35)
dto/openai_response.go (2)
  • ChatCompletionsStreamResponse (159-167)
  • ResponsesStreamResponse (385-390)
common/json.go (2)
  • Unmarshal (9-11)
  • Marshal (21-23)
logger/logger.go (1)
  • LogError (65-67)
relay/helper/common.go (1)
  • ResponseChunkData (75-79)
🔇 Additional comments (8)
web/src/i18n/locales/en.json (1)

1367-1368: LGTM! Clear and accurate translations for the OpenRouter to OpenAI conversion feature.

The English translations are well-written and clearly describe the functionality. The label is concise and the description provides helpful detail about the specific field conversion (reasoning → reasoning_content). These translation keys have been consistently added across all supported locales (en, zh, fr, ja, ru, vi) with appropriate translations for each language.

relay/channel/openai/helper.go (4)

5-5: LGTM: Imports are used appropriately.

The new imports support error logging (fmt) and channel type checking (constant) in the OpenRouter conversion logic.

Also applies to: 9-9


23-45: LGTM: Streaming conversion logic is well-implemented.

The OpenRouter-to-OpenAI reasoning field conversion for streaming responses is correctly guarded by channel type and setting checks, includes comprehensive error logging as requested in previous reviews, and gracefully falls back to the original data on failure.


278-285: LGTM: Helper function is clean and well-documented.

The sendResponsesStreamData helper provides a clear abstraction with an appropriate early-return guard for empty data.


287-300: LGTM: Streaming reasoning field converter is correctly implemented.

The function properly guards against nil responses and empty choices, iterates through all choices, and applies the generic ConvertReasoningField to each Delta. The implementation is consistent with the non-streaming version.

relay/channel/openai/relay-openai.go (3)

196-214: LGTM: Comprehensive function documentation.

The docstring clearly explains the handler's behavior, including the new OpenRouter reasoning field conversion and re-serialization logic.


644-647: LGTM: Function documentation added.

The docstring clearly explains the extraction logic and return semantics for this helper function.


679-692: LGTM: Non-streaming reasoning field converter is correctly implemented.

The function properly guards against nil responses and empty choices, iterates through all choices, and applies the generic ConvertReasoningField to each Message. The implementation mirrors the streaming version and is consistent with the overall design.

…outer channels

- Remove OpenRouter-specific OpenRouterConvertToOpenAI setting
- Consolidate reasoning field conversion logic under force_format flag
- Support both OpenAI and OpenRouter channels for reasoning content transformation
- Update UI to show force format option for both channel types
- Remove duplicate conversion toggle from channel settings
- Update localization strings across all supported languages

@coderabbitai coderabbitai 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.

Actionable comments posted: 0

🧹 Nitpick comments (2)
web/src/i18n/locales/vi.json (1)

805-805: LGTM! Translation accurately reflects the expanded channel support.

The Vietnamese translation correctly conveys that the forced formatting feature now supports both OpenAI and OpenRouter channels, which aligns with the PR objectives. The terminology is consistent with other translations in the file (e.g., "suy luận" for reasoning).

Optional style note: Most translations in this file don't end with a period, but this one does. Consider removing the trailing period for consistency:

-"强制将响应格式化为 OpenAI 标准格式,支持推理内容字段转换(适用于OpenAI和OpenRouter渠道)": "Buộc định dạng phản hồi theo định dạng chuẩn OpenAI, hỗ trợ chuyển đổi trường nội dung suy luận (cho các kênh OpenAI và OpenRouter).",
+"强制将响应格式化为 OpenAI 标准格式,支持推理内容字段转换(适用于OpenAI和OpenRouter渠道)": "Buộc định dạng phản hồi theo định dạng chuẩn OpenAI, hỗ trợ chuyển đổi trường nội dung suy luận (cho các kênh OpenAI và OpenRouter)"
relay/channel/openai/relay-openai.go (1)

667-680: LGTM!

The function correctly iterates over choices using pointer access for in-place modification and delegates to the generic ConvertReasoningField helper. The nil/empty defensive check ensures safe operation.

Minor note: The function name convertOpenRouterReasoningFields references only OpenRouter, but it's now used for both OpenRouter and OpenAI channels. Consider renaming to convertReasoningFieldsToContent or similar for clarity, though this is optional.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 756464d and c84eeff.

📒 Files selected for processing (9)
  • relay/channel/openai/helper.go (3 hunks)
  • relay/channel/openai/relay-openai.go (4 hunks)
  • web/src/components/table/channels/modals/EditChannelModal.jsx (2 hunks)
  • web/src/i18n/locales/en.json (1 hunks)
  • web/src/i18n/locales/fr.json (1 hunks)
  • web/src/i18n/locales/ja.json (1 hunks)
  • web/src/i18n/locales/ru.json (1 hunks)
  • web/src/i18n/locales/vi.json (1 hunks)
  • web/src/i18n/locales/zh.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • relay/channel/openai/helper.go
  • web/src/components/table/channels/modals/EditChannelModal.jsx
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/zh.json
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-21T06:31:11.073Z
Learnt from: jiajunly
Repo: QuantumNous/new-api PR: 1629
File: relay/channel/openai/relay-openai.go:170-174
Timestamp: 2025-08-21T06:31:11.073Z
Learning: In relay/channel/openai/relay-openai.go, the streaming logic for the AddThinkFirst feature is designed so that only the first chunk of a stream gets the "<think>\n" prefix. The final flush in the streaming handler intentionally uses addThink=false because the last chunk should never receive the prefix, even in single-chunk streams where the prefix would have been applied during normal processing.

Applied to files:

  • relay/channel/openai/relay-openai.go
📚 Learning: 2025-08-08T17:12:43.157Z
Learnt from: RedwindA
Repo: QuantumNous/new-api PR: 1537
File: relay/gemini_handler.go:330-342
Timestamp: 2025-08-08T17:12:43.157Z
Learning: In the new-api repository, the `GeminiEmbeddingHandler` function in `relay/gemini_handler.go` is designed specifically for native Gemini embedding requests and therefore does not require the `ConvertGeminiRequest` step that is used in the chat handler. The embedding requests are already in the native Gemini format and don't need conversion.

Applied to files:

  • relay/channel/openai/relay-openai.go
📚 Learning: 2025-06-21T03:37:41.726Z
Learnt from: 9Ninety
Repo: QuantumNous/new-api PR: 1273
File: relay/channel/gemini/relay-gemini.go:97-116
Timestamp: 2025-06-21T03:37:41.726Z
Learning: In relay/channel/gemini/relay-gemini.go, the thinking budget calculation logic (including the MaxOutputTokens multiplication) was introduced in PR #1247. PR #1273 focused specifically on decoupling the thoughts summary feature from thinking budget settings and did not modify the existing thinking budget behavior.

Applied to files:

  • relay/channel/openai/relay-openai.go
🧬 Code graph analysis (1)
relay/channel/openai/relay-openai.go (5)
constant/channel.go (2)
  • ChannelTypeOpenRouter (24-24)
  • ChannelTypeOpenAI (5-5)
dto/openai_response.go (2)
  • GetOpenAIError (393-432)
  • OpenAITextResponse (39-47)
types/error.go (1)
  • WithOpenAIError (277-300)
relay/channel/openai/reasoning_converter.go (1)
  • ConvertReasoningField (20-35)
dto/openai_request.go (1)
  • Message (283-294)
🔇 Additional comments (5)
web/src/i18n/locales/ja.json (1)

805-805: LGTM: Translation accurately reflects expanded feature scope.

The Japanese translation correctly updates the forced formatting feature description to include:

  • Both OpenAI and OpenRouter channel support (previously OpenAI only)
  • Explicit mention of reasoning content field conversion support

The translation is clear and aligns with the PR's objective to extend reasoning field conversion (reasoningreasoning_content) to OpenRouter channels.

web/src/i18n/locales/fr.json (1)

857-857: LGTM: Translation accurately reflects expanded feature scope.

The French translation correctly updates the forced formatting feature description to include:

  • Both OpenAI and OpenRouter channel support (previously OpenAI only)
  • Explicit mention of reasoning content field conversion support ("la conversion des champs de contenu de raisonnement")

The translation is clear and consistent with the same update in ja.json and other locale files, properly reflecting the PR's objective to extend reasoning field conversion to OpenRouter channels.

relay/channel/openai/relay-openai.go (3)

196-214: LGTM!

The docstring is comprehensive and accurately documents the function's behavior, including the new reasoning field conversion for OpenRouter/OpenAI channels, usage inference, and relay format adaptation.


247-255: LGTM!

The reasoning conversion correctly applies to both OpenRouter and OpenAI channels when forceFormat is enabled, aligning with the PR objectives. The placement before error checking is safe because convertOpenRouterReasoningFields has defensive nil/empty checks that make it a no-op for error responses.


632-665: LGTM!

The function correctly uses pointer types to distinguish between "field not present" vs "field is 0", has proper empty body and unmarshal error handling, and checks multiple possible field locations in a sensible priority order.

@ZhaoZuohong ZhaoZuohong changed the title OpenAI 渠道“强制格式化”选项将 reasoning 字段重命名为 reasoning_content 并为 OpenRouter 上游渠道开启 “强制格式化”选项将 reasoning 字段重命名为 reasoning_content:适用于 OpenAI 与 OpenRouter 渠道 Dec 16, 2025
@ZhaoZuohong
ZhaoZuohong marked this pull request as ready for review December 16, 2025 10:28
@ZhaoZuohong
ZhaoZuohong requested a review from seefs001 December 16, 2025 10:29
@seefs001 seefs001 added the ready to merge It will eventually merge, requiring a final check. label Dec 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready to merge It will eventually merge, requiring a final check.

Projects

None yet

Development

Successfully merging this pull request may close these issues.