Skip to content

Commit 01eb23c

Browse files
authored
fix(openai): avoid setting max_tokens and max_completion_tokens together (#906)
Fixes #905 ## Summary - Add `GenerateOptions.maxCompletionTokens` - Update `OpenAIChatFormatter` to set `max_tokens` and `max_completion_tokens` independently, without mirroring ## Behavior - If only `maxTokens` is set -> only `max_tokens` is sent - If only `maxCompletionTokens` is set -> only `max_completion_tokens` is sent - If both are set -> both fields are sent as-is (SDK does not enforce exclusivity; provider can decide) ## Notes - Affects `OpenAIChatFormatter` and its subclasses (e.g. `OpenAIMultiAgentFormatter`, `DeepSeekFormatter`, `GLMFormatter`) - Other providers (Gemini/DashScope/Anthropic/Ollama, etc.) continue to use `maxTokens` only
1 parent 5d4fad6 commit 01eb23c

2 files changed

Lines changed: 54 additions & 7 deletions

File tree

agentscope-core/src/main/java/io/agentscope/core/formatter/openai/OpenAIChatFormatter.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,7 @@ public void applyOptions(
101101
request.setPresencePenalty(presencePenalty);
102102
}
103103

104-
// Apply max tokens
105-
Integer maxTokens =
106-
getOptionOrDefault(options, defaultOptions, GenerateOptions::getMaxTokens);
107-
if (maxTokens != null) {
108-
request.setMaxCompletionTokens(maxTokens);
109-
request.setMaxTokens(maxTokens);
110-
}
104+
applyMaxTokens(request, options, defaultOptions);
111105

112106
// Apply seed
113107
Long seed = getOptionOrDefault(options, defaultOptions, GenerateOptions::getSeed);
@@ -123,6 +117,22 @@ public void applyOptions(
123117
applyAdditionalBodyParams(request, options);
124118
}
125119

120+
protected void applyMaxTokens(
121+
OpenAIRequest request, GenerateOptions options, GenerateOptions defaultOptions) {
122+
Integer maxTokens =
123+
getOptionOrDefault(options, defaultOptions, GenerateOptions::getMaxTokens);
124+
if (maxTokens != null) {
125+
request.setMaxTokens(maxTokens);
126+
}
127+
128+
Integer maxCompletionTokens =
129+
getOptionOrDefault(
130+
options, defaultOptions, GenerateOptions::getMaxCompletionTokens);
131+
if (maxCompletionTokens != null) {
132+
request.setMaxCompletionTokens(maxCompletionTokens);
133+
}
134+
}
135+
126136
@Override
127137
public void applyTools(OpenAIRequest request, List<ToolSchema> tools) {
128138
if (tools == null || tools.isEmpty()) {

agentscope-core/src/main/java/io/agentscope/core/model/GenerateOptions.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public class GenerateOptions {
3939
private final Double temperature;
4040
private final Double topP;
4141
private final Integer maxTokens;
42+
private final Integer maxCompletionTokens;
4243
private final Double frequencyPenalty;
4344
private final Double presencePenalty;
4445
private final Integer thinkingBudget;
@@ -65,6 +66,7 @@ private GenerateOptions(Builder builder) {
6566
this.temperature = builder.temperature;
6667
this.topP = builder.topP;
6768
this.maxTokens = builder.maxTokens;
69+
this.maxCompletionTokens = builder.maxCompletionTokens;
6870
this.frequencyPenalty = builder.frequencyPenalty;
6971
this.presencePenalty = builder.presencePenalty;
7072
this.thinkingBudget = builder.thinkingBudget;
@@ -184,6 +186,20 @@ public Integer getMaxTokens() {
184186
return maxTokens;
185187
}
186188

189+
/**
190+
* Gets the maximum number of completion tokens to generate.
191+
*
192+
* <p>This is an alternative to {@link #getMaxTokens()} for OpenAI-compatible APIs that support
193+
* {@code max_completion_tokens}. Some providers/models treat {@code max_tokens} and
194+
* {@code max_completion_tokens} as mutually exclusive; this SDK does not enforce exclusivity
195+
* and will forward exactly what the caller sets.
196+
*
197+
* @return the maximum completion tokens limit, or null if not set
198+
*/
199+
public Integer getMaxCompletionTokens() {
200+
return maxCompletionTokens;
201+
}
202+
187203
/**
188204
* Gets the frequency penalty.
189205
*
@@ -388,6 +404,10 @@ public static GenerateOptions mergeOptions(GenerateOptions primary, GenerateOpti
388404
primary.temperature != null ? primary.temperature : fallback.temperature);
389405
builder.topP(primary.topP != null ? primary.topP : fallback.topP);
390406
builder.maxTokens(primary.maxTokens != null ? primary.maxTokens : fallback.maxTokens);
407+
builder.maxCompletionTokens(
408+
primary.maxCompletionTokens != null
409+
? primary.maxCompletionTokens
410+
: fallback.maxCompletionTokens);
391411
builder.frequencyPenalty(
392412
primary.frequencyPenalty != null
393413
? primary.frequencyPenalty
@@ -450,6 +470,7 @@ public static class Builder {
450470
private Double temperature;
451471
private Double topP;
452472
private Integer maxTokens;
473+
private Integer maxCompletionTokens;
453474
private Double frequencyPenalty;
454475
private Double presencePenalty;
455476
private Integer thinkingBudget;
@@ -560,6 +581,22 @@ public Builder maxTokens(Integer maxTokens) {
560581
return this;
561582
}
562583

584+
/**
585+
* Sets the maximum number of completion tokens to generate.
586+
*
587+
* <p>This is an alternative to {@link #maxTokens(Integer)} for OpenAI-compatible APIs that
588+
* support {@code max_completion_tokens}. This builder does not enforce exclusivity with
589+
* {@code maxTokens}; both may be set and will be forwarded as-is by formatters that support
590+
* both fields.
591+
*
592+
* @param maxCompletionTokens the maximum completion tokens limit
593+
* @return this builder instance
594+
*/
595+
public Builder maxCompletionTokens(Integer maxCompletionTokens) {
596+
this.maxCompletionTokens = maxCompletionTokens;
597+
return this;
598+
}
599+
563600
/**
564601
* Sets the frequency penalty.
565602
*

0 commit comments

Comments
 (0)