fix: auto-convert reasoning_effort to reasoningEffort for openai-compatible providers#12831
fix: auto-convert reasoning_effort to reasoningEffort for openai-compatible providers#12831
Conversation
…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>
There was a problem hiding this comment.
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.reasoningEffort(undefined)覆盖,导致用户自定义参数被静默丢弃。
建议:将转换移出 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
- 影响面越小,引入新问题的风险越低
|
Side note: |
… 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>
|
Re: file-level comment about |
…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>
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>
There was a problem hiding this comment.
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
- Correct conversion logic placement: Following previous review suggestions, the conversion has been moved from the generic function
getCustomParameters()tobuildProviderOptions(), affecting only theopenai-compatibleprovider with minimal impact. - Proper boundary condition handling: When users configure both
reasoningEffortandreasoning_effort, the explicit camelCase version is prioritized, and the snake_case version is always removed (avoiding dead-weight parameters). - Comprehensive test coverage: Three test cases cover normal conversion, non-openai-compatible no conversion, and scenarios where both formats coexist.
- 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
volcengineProvideranddoubaoModelobjects are repeatedly defined in three test cases and can be extracted to a shareddescribeblock 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 — 修复方向正确,实现简洁,测试覆盖充分。
优点
- 转换逻辑位置正确:按照之前 review 的建议,已将转换从通用函数
getCustomParameters()移到buildProviderOptions()中,仅作用于openai-compatibleprovider,影响面最小化。 - 边界条件处理得当:当用户同时配置了
reasoningEffort和reasoning_effort时,优先保留显式的 camelCase 版本,并始终删除 snake_case 版本(避免 dead-weight 参数)。 - 测试覆盖全面:三个测试用例分别覆盖了正常转换、非 openai-compatible 不转换、以及两种格式共存的场景。
- 注释清晰:包含 issue 链接和原因说明,方便未来维护者理解这段 workaround 的背景。
Minor Suggestion (Non-blocking)
- 测试中
volcengineProvider和doubaoModel对象在三个测试用例中重复定义,可以抽到共享的describe块中减少冗余(详见 inline comment)。
Overall,这是一个干净、有针对性的 fix,解决了 AI SDK 的兼容性问题,没有引入额外风险。
| 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') | ||
| }) |
There was a problem hiding this comment.
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.
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-compatibleprovider, which overwrites it toundefined. Users get no warning and the feature simply doesn't work.After this PR:
reasoning_effortis automatically converted toreasoningEffort(camelCase) ingetCustomParameters()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-compatibleprovider explicitly filters outreasoning_effort(snake_case) but acceptsreasoningEffort(camelCase). Users following official API docs (e.g., Volcano Engine/Doubao) naturally use snake_case, causing silent failures.The following tradeoffs were made:
reasoningEffort(camelCase), preventing unexpected overwritesThe following alternatives were considered:
extra_body: More complex and less cleanBreaking changes
None. This is a backwards-compatible fix. If users already use
reasoningEffort(camelCase), behavior is unchanged.Special notes for your reviewer
getCustomParameters()(src/renderer/src/aiCore/utils/reasoning.ts) — the earliest point in the parameter pipelinereasoning_effort→reasoningEffortis converted; if both are specified, the explicitreasoningEfforttakes priorityRoot Cause:
Checklist
Release note