Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 041f1e8

Browse files
committed
fix: use getModel() temperature as single source of truth in OpenAI handler
The OpenAiHandler computed temperature independently in createMessage(), ignoring defaultTemperature from custom model info. This caused a 400 error with models like kimi-for-coding that only accept specific temperature values (e.g. 0.6). Changes: - getModel() now detects deepseek reasoner models and passes the correct defaultTemperature to getModelParams() - createMessage() streaming path uses temperature from getModel() instead of computing it separately - createMessage() non-streaming path now includes temperature (was missing) - completePrompt() now includes temperature (was missing) This makes getModelParams() the single source of truth for temperature, which respects: user overrides > model defaultTemperature > provider default. Closes #12142
1 parent cb83656 commit 041f1e8

2 files changed

Lines changed: 91 additions & 3 deletions

File tree

src/api/providers/__tests__/openai.spec.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,87 @@ describe("OpenAiHandler", () => {
499499
})
500500
})
501501

502+
describe("temperature handling", () => {
503+
const systemPrompt = "You are a helpful assistant."
504+
const testMessages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello!" }]
505+
506+
it("should use defaultTemperature from custom model info when no user temperature is set", async () => {
507+
const handlerWithModelDefault = new OpenAiHandler({
508+
...mockOptions,
509+
openAiCustomModelInfo: {
510+
...openAiModelInfoSaneDefaults,
511+
defaultTemperature: 0.6,
512+
},
513+
})
514+
const stream = handlerWithModelDefault.createMessage(systemPrompt, testMessages)
515+
for await (const _chunk of stream) {
516+
}
517+
expect(mockCreate).toHaveBeenCalled()
518+
const callArgs = mockCreate.mock.calls[0][0]
519+
expect(callArgs.temperature).toBe(0.6)
520+
})
521+
522+
it("should prefer user-set modelTemperature over defaultTemperature from model info", async () => {
523+
const handlerWithUserTemp = new OpenAiHandler({
524+
...mockOptions,
525+
modelTemperature: 0.3,
526+
openAiCustomModelInfo: {
527+
...openAiModelInfoSaneDefaults,
528+
defaultTemperature: 0.6,
529+
},
530+
})
531+
const stream = handlerWithUserTemp.createMessage(systemPrompt, testMessages)
532+
for await (const _chunk of stream) {
533+
}
534+
expect(mockCreate).toHaveBeenCalled()
535+
const callArgs = mockCreate.mock.calls[0][0]
536+
expect(callArgs.temperature).toBe(0.3)
537+
})
538+
539+
it("should default to 0 when no user temperature and no model defaultTemperature", async () => {
540+
const handlerNoTemp = new OpenAiHandler({
541+
...mockOptions,
542+
})
543+
const stream = handlerNoTemp.createMessage(systemPrompt, testMessages)
544+
for await (const _chunk of stream) {
545+
}
546+
expect(mockCreate).toHaveBeenCalled()
547+
const callArgs = mockCreate.mock.calls[0][0]
548+
expect(callArgs.temperature).toBe(0)
549+
})
550+
551+
it("should use defaultTemperature in non-streaming path", async () => {
552+
const handlerNonStreaming = new OpenAiHandler({
553+
...mockOptions,
554+
openAiStreamingEnabled: false,
555+
openAiCustomModelInfo: {
556+
...openAiModelInfoSaneDefaults,
557+
defaultTemperature: 0.6,
558+
},
559+
})
560+
const stream = handlerNonStreaming.createMessage(systemPrompt, testMessages)
561+
for await (const _chunk of stream) {
562+
}
563+
expect(mockCreate).toHaveBeenCalled()
564+
const callArgs = mockCreate.mock.calls[0][0]
565+
expect(callArgs.temperature).toBe(0.6)
566+
})
567+
568+
it("should use defaultTemperature in completePrompt", async () => {
569+
const handlerWithModelDefault = new OpenAiHandler({
570+
...mockOptions,
571+
openAiCustomModelInfo: {
572+
...openAiModelInfoSaneDefaults,
573+
defaultTemperature: 0.6,
574+
},
575+
})
576+
await handlerWithModelDefault.completePrompt("Hello")
577+
expect(mockCreate).toHaveBeenCalled()
578+
const callArgs = mockCreate.mock.calls[0][0]
579+
expect(callArgs.temperature).toBe(0.6)
580+
})
581+
})
582+
502583
describe("error handling", () => {
503584
const testMessages: Anthropic.Messages.MessageParam[] = [
504585
{
@@ -547,6 +628,7 @@ describe("OpenAiHandler", () => {
547628
expect(mockCreate).toHaveBeenCalledWith(
548629
{
549630
model: mockOptions.openAiModelId,
631+
temperature: 0,
550632
messages: [{ role: "user", content: "Test prompt" }],
551633
},
552634
{},
@@ -678,6 +760,7 @@ describe("OpenAiHandler", () => {
678760
expect(mockCreate).toHaveBeenCalledWith(
679761
{
680762
model: azureOptions.openAiModelId,
763+
temperature: 0,
681764
messages: [
682765
{ role: "system", content: systemPrompt },
683766
{ role: "user", content: "Hello!" },
@@ -701,6 +784,7 @@ describe("OpenAiHandler", () => {
701784
expect(mockCreate).toHaveBeenCalledWith(
702785
{
703786
model: azureOptions.openAiModelId,
787+
temperature: 0,
704788
messages: [{ role: "user", content: "Test prompt" }],
705789
},
706790
{ path: "/models/chat/completions" },

src/api/providers/openai.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
8484
messages: Anthropic.Messages.MessageParam[],
8585
metadata?: ApiHandlerCreateMessageMetadata,
8686
): ApiStream {
87-
const { info: modelInfo, reasoning } = this.getModel()
87+
const { info: modelInfo, reasoning, temperature } = this.getModel()
8888
const modelUrl = this.options.openAiBaseUrl ?? ""
8989
const modelId = this.options.openAiModelId ?? ""
9090
const enabledR1Format = this.options.openAiR1FormatEnabled ?? false
@@ -154,7 +154,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
154154

155155
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
156156
model: modelId,
157-
temperature: this.options.modelTemperature ?? (deepseekReasoner ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
157+
temperature,
158158
messages: convertedMessages,
159159
stream: true as const,
160160
...(isGrokXAI ? {} : { stream_options: { include_usage: true } }),
@@ -223,6 +223,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
223223
} else {
224224
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = {
225225
model: modelId,
226+
temperature,
226227
messages: deepseekReasoner
227228
? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
228229
: [systemMessage, ...convertToOpenAiMessages(messages)],
@@ -282,12 +283,14 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
282283
override getModel() {
283284
const id = this.options.openAiModelId ?? ""
284285
const info: ModelInfo = this.options.openAiCustomModelInfo ?? openAiModelInfoSaneDefaults
286+
const enabledR1Format = this.options.openAiR1FormatEnabled ?? false
287+
const isDeepSeekReasoner = id.includes("deepseek-reasoner") || enabledR1Format
285288
const params = getModelParams({
286289
format: "openai",
287290
modelId: id,
288291
model: info,
289292
settings: this.options,
290-
defaultTemperature: 0,
293+
defaultTemperature: isDeepSeekReasoner ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0,
291294
})
292295
return { id, info, ...params }
293296
}
@@ -300,6 +303,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
300303

301304
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = {
302305
model: model.id,
306+
temperature: model.temperature,
303307
messages: [{ role: "user", content: prompt }],
304308
}
305309

0 commit comments

Comments
 (0)