Skip to content

fix(model): apply thinkingBudget to OpenAI-compatible API request#1727

Open
lizhihao688 wants to merge 1 commit into
agentscope-ai:mainfrom
lizhihao688:fix/openai-thinking-budget-not-applied
Open

fix(model): apply thinkingBudget to OpenAI-compatible API request#1727
lizhihao688 wants to merge 1 commit into
agentscope-ai:mainfrom
lizhihao688:fix/openai-thinking-budget-not-applied

Conversation

@lizhihao688

@lizhihao688 lizhihao688 commented Jun 12, 2026

Copy link
Copy Markdown

Summary

GenerateOptions.thinkingBudget was stored in the options object but never mapped to OpenAIRequest, causing the thinking_budget parameter to be silently dropped from all OpenAI-compatible API calls.

Root Cause

In OpenAIChatFormatter.applyOptions(), all other GenerateOptions fields were correctly mapped to OpenAIRequest setters — but thinkingBudget was missing. Additionally, OpenAIRequest had no thinking_budget field at all.

Field Status (before fix)
temperaturerequest.setTemperature() ✅ handled
reasoningEffortrequest.setReasoningEffort() ✅ handled
maxCompletionTokensrequest.setMaxCompletionTokens() ✅ handled
thinkingBudgetNOT HANDLED — silently dropped ❌ missing

This breaks reasoning/thinking models (e.g. Qwen3, DeepSeek-R1): without the cap, the model exhausts all max_completion_tokens on reasoning_content and returns an empty content field.

Changes

  • OpenAIRequest.java: Add @JsonProperty("thinking_budget") field with getter/setter and Builder method
  • OpenAIChatFormatter.java: Map GenerateOptions.thinkingBudget in applyOptions()
  • OpenAIChatFormatterTest.java: Add ThinkingBudgetTests with 4 unit tests covering: direct options, default fallback, override, and absent cases

Testing

All 948 existing unit tests pass. spotless:check passes.

Before / After

BeforethinkingBudget silently ignored:

GenerateOptions.builder()
    .maxCompletionTokens(8192)
    .thinkingBudget(4096) // silently dropped — not in HTTP request
    .build()
// HTTP body: {"max_completion_tokens": 8192}

Afterthinking_budget correctly sent:

GenerateOptions.builder()
    .maxCompletionTokens(8192)
    .thinkingBudget(4096) // now included
    .build()
// HTTP body: {"max_completion_tokens": 8192, "thinking_budget": 4096}

Fixes #1697

@lizhihao688 lizhihao688 requested a review from a team June 12, 2026 04:56
@CLAassistant

CLAassistant commented Jun 12, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@lizhihao688 lizhihao688 force-pushed the fix/openai-thinking-budget-not-applied branch from fc2b0af to 396e88d Compare June 12, 2026 05:14
GenerateOptions.thinkingBudget was stored in the options object but never
mapped to OpenAIRequest, causing the thinking_budget parameter to be
silently dropped from all OpenAI-compatible API calls.

This breaks reasoning/thinking models (e.g. Qwen3, DeepSeek-R1) that
use thinking_budget to limit internal reasoning tokens: without the cap,
the model exhausts all max_completion_tokens on reasoning_content and
returns an empty content field.

Changes:
- Add thinking_budget field to OpenAIRequest DTO with @JsonProperty
- Map GenerateOptions.thinkingBudget in OpenAIChatFormatter.applyOptions()
- Add unit tests covering direct options, default fallback, override, and absent cases

Fixes agentscope-ai#1697
@lizhihao688 lizhihao688 force-pushed the fix/openai-thinking-budget-not-applied branch from 396e88d to 866a7c5 Compare June 12, 2026 05:17

@AgentScopeJavaBot AgentScopeJavaBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 AI Review

This is a clean, well-scoped bug fix that correctly addresses the missing thinkingBudgetOpenAIRequest mapping in OpenAIChatFormatter.applyOptions(). The implementation follows the exact same pattern used by all other option fields (temperature, reasoningEffort, topP, etc.), ensuring consistency. The new thinking_budget JSON property on OpenAIRequest is properly guarded by the class-level @JsonInclude(NON_NULL), so it will not leak into requests for providers that don't support it. The four unit tests cover the essential cases (direct, default-fallback, override, absent) and mirror the test structure used for other options. The Javadoc on the new field is informative and cites a concrete provider example. No logic, concurrency, or serialization issues found.

request.setReasoningEffort(reasoningEffort);
}

// Apply thinking budget

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[praise] Clean, minimal addition that follows the established apply-options pattern exactly. The null-guard and getOptionOrDefault usage are consistent with every other field in this method — easy to review and low risk.

* reasoning, leaving the remainder of {@code max_completion_tokens} for the
* visible response.
*/
@JsonProperty("thinking_budget")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[praise] Good Javadoc — citing a concrete provider (DashScope Qwen3) gives future maintainers the context they need to understand why this non-standard field exists on an OpenAI-compatible DTO.


GenerateOptions options = GenerateOptions.builder().temperature(0.7).build();

formatter.applyOptions(request, options, null);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[praise] Solid test coverage for a single-field addition. The four cases (direct set, default fallback, override, absent) mirror the pattern used by other option test groups and give confidence that getOptionOrDefault semantics are correct.

@AgentScopeJavaBot AgentScopeJavaBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 AI Review

This is a clean, well-scoped bug fix that correctly addresses the missing thinkingBudgetOpenAIRequest mapping in OpenAIChatFormatter.applyOptions(). The implementation follows the exact same pattern used by all other option fields (temperature, reasoningEffort, topP, etc.), ensuring consistency. The new thinking_budget JSON property on OpenAIRequest is properly guarded by the class-level @JsonInclude(NON_NULL), so it will not leak into requests for providers that don't support it. The four unit tests cover the essential cases (direct, default-fallback, override, absent) and mirror the test structure used for other options. The Javadoc on the new field is informative and cites a concrete provider example. No logic, concurrency, or serialization issues found.

request.setReasoningEffort(reasoningEffort);
}

// Apply thinking budget

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[praise] Clean, minimal addition that follows the established apply-options pattern exactly. The null-guard and getOptionOrDefault usage are consistent with every other field in this method — easy to review and low risk.

* reasoning, leaving the remainder of {@code max_completion_tokens} for the
* visible response.
*/
@JsonProperty("thinking_budget")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[praise] Good Javadoc — citing a concrete provider (DashScope Qwen3) gives future maintainers the context they need to understand why this non-standard field exists on an OpenAI-compatible DTO.


GenerateOptions options = GenerateOptions.builder().temperature(0.7).build();

formatter.applyOptions(request, options, null);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[praise] Solid test coverage for a single-field addition. The four cases (direct set, default fallback, override, absent) mirror the pattern used by other option test groups and give confidence that getOptionOrDefault semantics are correct.

@AgentScopeJavaBot AgentScopeJavaBot added bug Something isn't working area/core/model Model providers and formatters labels Jun 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core/model Model providers and formatters bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: GenerateOptions.thinkingBudget is never applied to OpenAI API request

3 participants