Skip to content

Comments

fix: auto-convert reasoning_effort to reasoningEffort for openai-compatible providers#12831

Open
DeJeune wants to merge 5 commits intomainfrom
DeJeune/fix-topic-naming
Open

fix: auto-convert reasoning_effort to reasoningEffort for openai-compatible providers#12831
DeJeune wants to merge 5 commits intomainfrom
DeJeune/fix-topic-naming

Conversation

@DeJeune
Copy link
Collaborator

@DeJeune DeJeune commented Feb 9, 2026

What this PR does

Before this PR:
When users configure reasoning_effort (snake_case) as a custom parameter following official API provider documentation, the parameter is silently dropped by the AI SDK's @ai-sdk/openai-compatible provider, which overwrites it to undefined. Users get no warning and the feature simply doesn't work.

After this PR:
reasoning_effort is automatically converted to reasoningEffort (camelCase) in getCustomParameters() before reaching the AI SDK, ensuring the parameter is correctly passed to the API.

Fixes #11987

Why we need it and why it was done in this way

The Vercel AI SDK's @ai-sdk/openai-compatible provider explicitly filters out reasoning_effort (snake_case) but accepts reasoningEffort (camelCase). Users following official API docs (e.g., Volcano Engine/Doubao) naturally use snake_case, causing silent failures.

The following tradeoffs were made:

  • Auto-conversion is done only when the user hasn't already specified reasoningEffort (camelCase), preventing unexpected overwrites

The following alternatives were considered:

  • Wrapping in extra_body: More complex and less clean
  • UI warning: Doesn't solve the problem, just alerts about it
  • Auto-conversion was chosen as it's the simplest fix that matches user expectations

Breaking changes

None. This is a backwards-compatible fix. If users already use reasoningEffort (camelCase), behavior is unchanged.

Special notes for your reviewer

  • The conversion happens in getCustomParameters() (src/renderer/src/aiCore/utils/reasoning.ts) — the earliest point in the parameter pipeline
  • Only reasoning_effortreasoningEffort is converted; if both are specified, the explicit reasoningEffort takes priority
  • This affects all OpenAI-compatible providers: Volcano Engine, Baichuan, Moonshot, Zhipu, Hunyuan, Silicon Flow, custom providers, etc.

Root Cause:

image

Checklist

  • PR: The PR description is expressive enough and will help future contributors
  • Code: Write code that humans can understand and Keep it simple
  • Refactor: You have left the code cleaner than you found it (Boy Scout Rule)
  • Upgrade: Impact of this change on upgrade flows was considered and addressed if required
  • Documentation: A user-guide update was considered and is present (link) or not required

Release note

fix: auto-convert `reasoning_effort` custom parameter to `reasoningEffort` for OpenAI-compatible providers, preventing silent parameter dropping (#11987)

…atible providers

The AI SDK's openai-compatible provider silently overwrites reasoning_effort
(snake_case) to undefined, causing user-configured parameters to be dropped
without warning. This auto-converts reasoning_effort to reasoningEffort
(camelCase) in getCustomParameters(), which the AI SDK accepts.

Fixes #11987

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Collaborator

@EurFelux EurFelux left a comment

Choose a reason for hiding this comment

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

Note

This issue/comment/review was translated by Claude.

Review Summary

The fix direction is correct, but it's recommended to adjust the conversion location to minimize the impact scope.

Root Cause Analysis ✅

The PR correctly identified the issue: @ai-sdk/openai-compatible's shape filter filters out reasoning_effort (snake_case), then overwrites with compatibleOptions.reasoningEffort (undefined), causing user custom parameters to be silently dropped.

Suggestion: Move Conversion Out of getCustomParameters()

getCustomParameters() is a common parameter extraction function shared by all providers. This issue only occurs with the @ai-sdk/openai-compatible provider, so the conversion logic should minimize the impact scope by being placed in the openai-compatible specific path (e.g., when merging custom parameters in buildProviderOptions(), only perform the conversion for the openai-compatible provider), rather than modifying the common function.

Reasons:

  • Other providers (or even scenarios that directly build API body) may expect reasoning_effort (snake_case), since OpenAI API itself uses snake_case
  • Common functions should remain side-effect-free and not do provider-specific workarounds
  • The smaller the impact scope, the lower the risk of introducing new issues

Original Content

Review Summary

修复方向正确,但建议调整转换位置以缩小影响面。

根因分析 ✅

PR 正确识别了问题:@ai-sdk/openai-compatible 的 shape filter 会过滤掉 reasoning_effort(snake_case),然后用 compatibleOptions.reasoningEffortundefined)覆盖,导致用户自定义参数被静默丢弃。

建议:将转换移出 getCustomParameters()

getCustomParameters() 是所有 provider 共用的通用参数提取函数。此问题仅发生在 @ai-sdk/openai-compatible provider 上,因此转换逻辑应尽可能缩小影响面,放到 openai-compatible 的专属路径中(例如 buildProviderOptions() 中合并自定义参数时,仅对 openai-compatible provider 执行转换),而非修改通用函数。

理由:

  • 其他 provider(甚至直接构建 API body 的场景)可能期望 reasoning_effort(snake_case),因为 OpenAI API 本身就用 snake_case
  • 通用函数应保持无副作用,不做 provider-specific 的 workaround
  • 影响面越小,引入新问题的风险越低

@EurFelux
Copy link
Collaborator

EurFelux commented Feb 9, 2026

Side note: getCustomParameters() doesn't seem to belong in reasoning.ts — it's a general-purpose utility for extracting user-defined custom parameters and has nothing to do with reasoning. Consider moving it to a more appropriate location (e.g., options.ts or a dedicated utils file) in a follow-up.

… comments

Address review feedback:
- Always delete reasoning_effort even when reasoningEffort exists, since the
  AI SDK drops it anyway for openai-compatible providers
- Simplify misleading comment in options.test.ts integration test
- Update test assertion to reflect the new behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@DeJeune
Copy link
Collaborator Author

DeJeune commented Feb 10, 2026

Re: file-level comment about getCustomParameters() placement — agreed, it's a general-purpose utility and doesn't belong in reasoning.ts. Worth a follow-up refactor to move it to a more appropriate location (e.g., options.ts or a dedicated utils file). Keeping this PR focused on the bug fix for now.

DeJeune and others added 2 commits February 10, 2026 13:29
…ider only

Move the reasoning_effort → reasoningEffort conversion from
getCustomParameters() (shared by all providers) into buildProviderOptions(),
scoped to openai-compatible providers only. This keeps getCustomParameters()
side-effect-free and avoids affecting providers that legitimately use
reasoning_effort in snake_case (e.g., native OpenAI).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@DeJeune DeJeune requested a review from EurFelux February 10, 2026 05:44
Remove leftover formatting changes (const params / as Record<string, any>)
from the earlier conversion logic that was moved to buildProviderOptions().
getCustomParameters() is now identical to main.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Collaborator

@EurFelux EurFelux left a comment

Choose a reason for hiding this comment

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

Note

This review was translated by Claude.

Review Summary

Approve — The fix direction is correct, the implementation is concise, and test coverage is adequate.

Pros

  1. Correct conversion logic placement: Following previous review suggestions, the conversion has been moved from the generic function getCustomParameters() to buildProviderOptions(), affecting only the openai-compatible provider with minimal impact.
  2. Proper boundary condition handling: When users configure both reasoningEffort and reasoning_effort, the explicit camelCase version is prioritized, and the snake_case version is always removed (avoiding dead-weight parameters).
  3. Comprehensive test coverage: Three test cases cover normal conversion, non-openai-compatible no conversion, and scenarios where both formats coexist.
  4. Clear comments: Includes issue links and rationale, making it easier for future maintainers to understand the background of this workaround.

Minor Suggestion (Non-blocking)

  • The volcengineProvider and doubaoModel objects are repeatedly defined in three test cases and can be extracted to a shared describe block to reduce redundancy (see inline comment).

Overall, this is a clean, targeted fix that resolves the AI SDK compatibility issue without introducing additional risks.


Original Content

Review Summary

Approve — 修复方向正确,实现简洁,测试覆盖充分。

优点

  1. 转换逻辑位置正确:按照之前 review 的建议,已将转换从通用函数 getCustomParameters() 移到 buildProviderOptions() 中,仅作用于 openai-compatible provider,影响面最小化。
  2. 边界条件处理得当:当用户同时配置了 reasoningEffortreasoning_effort 时,优先保留显式的 camelCase 版本,并始终删除 snake_case 版本(避免 dead-weight 参数)。
  3. 测试覆盖全面:三个测试用例分别覆盖了正常转换、非 openai-compatible 不转换、以及两种格式共存的场景。
  4. 注释清晰:包含 issue 链接和原因说明,方便未来维护者理解这段 workaround 的背景。

Minor Suggestion (Non-blocking)

  • 测试中 volcengineProviderdoubaoModel 对象在三个测试用例中重复定义,可以抽到共享的 describe 块中减少冗余(详见 inline comment)。

Overall,这是一个干净、有针对性的 fix,解决了 AI SDK 的兼容性问题,没有引入额外风险。

Comment on lines +1121 to +1155
it('should auto-convert reasoning_effort to reasoningEffort for openai-compatible provider (issue #11987)', async () => {
const { getCustomParameters } = await import('../reasoning')

// Simulate Volcano Engine (Doubao) or similar OpenAI-compatible provider
const volcengineProvider = {
id: 'openai-compatible',
name: 'Volcano Engine',
type: 'openai',
apiKey: 'test-key',
apiHost: 'https://ark.cn-beijing.volces.com/api/v3',
models: [] as Model[]
} as Provider

const doubaoModel: Model = {
id: 'doubao-seed-1.8-thinking',
name: 'Doubao Seed 1.8 Thinking',
provider: 'openai-compatible'
} as Model

// User configures reasoning_effort (snake_case) following API docs
vi.mocked(getCustomParameters).mockReturnValue({
reasoning_effort: 'high'
})

const result = buildProviderOptions(mockAssistant, doubaoModel, volcengineProvider, {
enableReasoning: false,
enableWebSearch: false,
enableGenerateImage: false
})

// buildProviderOptions converts reasoning_effort → reasoningEffort for openai-compatible
expect(result.providerOptions['openai-compatible']).toHaveProperty('reasoningEffort')
expect(result.providerOptions['openai-compatible'].reasoningEffort).toBe('high')
expect(result.providerOptions['openai-compatible']).not.toHaveProperty('reasoning_effort')
})
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Test provider setup could be extracted to reduce duplication

The volcengineProvider and doubaoModel objects are duplicated across three test cases (this one, the "both forms" test, and the "not overwrite" test). Consider extracting them into a shared describe block:

describe('reasoning_effort auto-conversion', () => {
  const volcengineProvider = { /* ... */ } as Provider
  const doubaoModel = { /* ... */ } as Model

  it('should auto-convert for openai-compatible', ...)
  it('should NOT convert for non-openai-compatible', ...)
  it('should not overwrite existing reasoningEffort', ...)
})

This reduces ~30 lines of duplication and makes the intent clearer. Non-blocking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Custom parameter reasoning_effort silently filtered when using OpenAI-compatible providers, causing user configurations to fail without warning

2 participants