Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/omit-temperature-when-unset.md
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.

😅 i haven't been too strict about including changesets, can provide some rationale for providing this as part of this PR? I've kind of been thinking we'll update this as part of our release process, since it requires localization.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"zoo-code": patch
---

OpenAI-Compatible provider: omit the `temperature` field when no custom temperature is set, so the model's server-side default applies instead of forcing `0` (#242). Model-required defaults (e.g. `deepseek-reasoner`) and the existing `supportsTemperature` capability gate are preserved, and a deliberately-set `0` is still sent.
19 changes: 16 additions & 3 deletions src/api/providers/__tests__/openai.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,13 +409,15 @@ describe("OpenAiHandler", () => {
expect(callArgs).not.toHaveProperty("temperature")
})

it("should include temperature by default when supportsTemperature is not set", async () => {
it("should omit temperature by default when no custom temperature is set (#242)", async () => {
// Option A: when "use custom temperature" is off (modelTemperature unset) and the model has no
// required default, omit `temperature` so the server's own default applies instead of forcing 0.
const stream = handler.createMessage(systemPrompt, messages)
for await (const _chunk of stream) {
}
expect(mockCreate).toHaveBeenCalled()
const callArgs = mockCreate.mock.calls[0][0]
expect(callArgs).toHaveProperty("temperature")
expect(callArgs).not.toHaveProperty("temperature")
})

it("should use the configured modelTemperature when supportsTemperature is not false", async () => {
Expand All @@ -438,6 +440,17 @@ describe("OpenAiHandler", () => {
expect(callArgs.temperature).toBe(DEEP_SEEK_DEFAULT_TEMPERATURE)
})

it("should still send temperature when the user sets a custom value of 0 (#242)", async () => {
// A deliberate 0 must be distinguished from "unset" — it is sent, not omitted.
const zeroTempHandler = new OpenAiHandler({ ...mockOptions, modelTemperature: 0 })
const stream = zeroTempHandler.createMessage(systemPrompt, messages)
for await (const _chunk of stream) {
}
expect(mockCreate).toHaveBeenCalled()
const callArgs = mockCreate.mock.calls[0][0]
expect(callArgs.temperature).toBe(0)
})

it("should include max_tokens when includeMaxTokens is true", async () => {
const optionsWithMaxTokens: ApiHandlerOptions = {
...mockOptions,
Expand Down Expand Up @@ -679,7 +692,7 @@ describe("OpenAiHandler", () => {
],
stream: true,
stream_options: { include_usage: true },
temperature: 0,
// #242 (option A): no custom temperature set → `temperature` is omitted.
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.

I think it's ok not to document the specific issue in code, it'll be handled in commit history.

tools: undefined,
tool_choice: undefined,
parallel_tool_calls: true,
Expand Down
12 changes: 8 additions & 4 deletions src/api/providers/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,14 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
// Some OpenAI-Compatible models (e.g. claude-opus-4-7) reject `temperature` as
// deprecated/unsupported. Honor the model's `supportsTemperature` flag and omit it
// when explicitly set to false (undefined still sends temperature, preserving behavior).
...(modelInfo.supportsTemperature !== false && {
temperature:
this.options.modelTemperature ?? (deepseekReasoner ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
}),
// Include `temperature` only when the model supports it (#233) AND either the user set a
// custom temperature or the model needs a specific default (deepseek-reasoner). When "use
// custom temperature" is off and the model has no required default, omit it so the server's
// own default applies instead of forcing 0 (#242 — option A).
...(modelInfo.supportsTemperature !== false &&
(this.options.modelTemperature != null || deepseekReasoner) && {
temperature: this.options.modelTemperature ?? DEEP_SEEK_DEFAULT_TEMPERATURE,
}),
messages: convertedMessages,
stream: true as const,
...(isGrokXAI ? {} : { stream_options: { include_usage: true } }),
Expand Down
Loading