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

Commit 9e96578

Browse files
committed
fix: add GLM model detection to OpenAI Compatible provider
This commit adds GLM model detection and related optimizations to the OpenAI Compatible provider (OpenAiHandler), which handles the "OpenAI Compatible" option in the UI when users set a custom base URL. Changes: - Import GLM detection utilities and Z.ai format converter - Add glmConfig property to track GLM model configuration - Detect GLM model on construction when model ID is available - Re-detect GLM model in createMessage if model ID changes - For GLM models: - Use convertToZAiFormat with mergeToolResultText to prevent conversation flow disruption - Disable parallel_tool_calls as GLM models may not support it - Add thinking parameter for GLM-4.7 models - Add console logging for detection results and applied optimizations This addresses the feedback in issue #11071 where the user clarified they are using the "OpenAI Compatible" provider, not the "OpenAI" provider. The previous changes in this PR only affected the base-openai-compatible-provider.ts (used by Z.ai, Groq, etc.) and lm-studio.ts, but not openai.ts which handles OpenAI Compatible.
1 parent 96b6845 commit 9e96578

1 file changed

Lines changed: 71 additions & 6 deletions

File tree

src/api/providers/openai.ts

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type { ApiHandlerOptions } from "../../shared/api"
1515
import { TagMatcher } from "../../utils/tag-matcher"
1616

1717
import { convertToOpenAiMessages } from "../transform/openai-format"
18+
import { convertToZAiFormat } from "../transform/zai-format"
1819
import { convertToR1Format } from "../transform/r1-format"
1920
import { ApiStream, ApiStreamUsageChunk } from "../transform/stream"
2021
import { getModelParams } from "../transform/model-params"
@@ -24,14 +25,16 @@ import { BaseProvider } from "./base-provider"
2425
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
2526
import { getApiRequestTimeout } from "./utils/timeout-config"
2627
import { handleOpenAIError } from "./utils/openai-error-handler"
28+
import { detectGlmModel, logGlmDetection, type GlmModelConfig } from "./utils/glm-model-detection"
2729

2830
// TODO: Rename this to OpenAICompatibleHandler. Also, I think the
2931
// `OpenAINativeHandler` can subclass from this, since it's obviously
3032
// compatible with the OpenAI API. We can also rename it to `OpenAIHandler`.
3133
export class OpenAiHandler extends BaseProvider implements SingleCompletionHandler {
3234
protected options: ApiHandlerOptions
3335
protected client: OpenAI
34-
private readonly providerName = "OpenAI"
36+
private readonly providerName = "OpenAI Compatible"
37+
private glmConfig: GlmModelConfig | null = null
3538

3639
constructor(options: ApiHandlerOptions) {
3740
super()
@@ -77,6 +80,13 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
7780
timeout,
7881
})
7982
}
83+
84+
// Detect GLM model on construction if model ID is available
85+
const modelId = this.options.openAiModelId || ""
86+
if (modelId) {
87+
this.glmConfig = detectGlmModel(modelId)
88+
logGlmDetection(this.providerName, modelId, this.glmConfig)
89+
}
8090
}
8191

8292
override async *createMessage(
@@ -91,6 +101,12 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
91101
const isAzureAiInference = this._isAzureAiInference(modelUrl)
92102
const deepseekReasoner = modelId.includes("deepseek-reasoner") || enabledR1Format
93103

104+
// Re-detect GLM model if not already done or if model ID changed
105+
if (!this.glmConfig || this.glmConfig.originalModelId !== modelId) {
106+
this.glmConfig = detectGlmModel(modelId)
107+
logGlmDetection(this.providerName, modelId, this.glmConfig)
108+
}
109+
94110
if (modelId.includes("o1") || modelId.includes("o3") || modelId.includes("o4")) {
95111
yield* this.handleO3FamilyMessage(modelId, systemPrompt, messages, metadata)
96112
return
@@ -106,6 +122,12 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
106122

107123
if (deepseekReasoner) {
108124
convertedMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
125+
} else if (this.glmConfig.isGlmModel) {
126+
// GLM models benefit from mergeToolResultText to prevent reasoning_content loss
127+
const glmConvertedMessages = convertToZAiFormat(messages, {
128+
mergeToolResultText: this.glmConfig.mergeToolResultText,
129+
})
130+
convertedMessages = [systemMessage, ...glmConvertedMessages]
109131
} else {
110132
if (modelInfo.supportsPromptCache) {
111133
systemMessage = {
@@ -152,6 +174,16 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
152174

153175
const isGrokXAI = this._isGrokXAI(this.options.openAiBaseUrl)
154176

177+
// Determine parallel_tool_calls setting
178+
// Disable for GLM models as they may not support it properly
179+
let parallelToolCalls: boolean
180+
if (this.glmConfig.isGlmModel && this.glmConfig.disableParallelToolCalls) {
181+
parallelToolCalls = false
182+
console.log(`[${this.providerName}] parallel_tool_calls disabled for GLM model`)
183+
} else {
184+
parallelToolCalls = metadata?.parallelToolCalls ?? true
185+
}
186+
155187
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
156188
model: modelId,
157189
temperature: this.options.modelTemperature ?? (deepseekReasoner ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
@@ -161,12 +193,19 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
161193
...(reasoning && reasoning),
162194
tools: this.convertToolsForOpenAI(metadata?.tools),
163195
tool_choice: metadata?.tool_choice,
164-
parallel_tool_calls: metadata?.parallelToolCalls ?? true,
196+
parallel_tool_calls: parallelToolCalls,
165197
}
166198

167199
// Add max_tokens if needed
168200
this.addMaxTokensIfNeeded(requestOptions, modelInfo)
169201

202+
// For GLM-4.7 models with thinking support, add thinking parameter
203+
if (this.glmConfig.isGlmModel && this.glmConfig.supportsThinking) {
204+
const useReasoning = this.options.enableReasoningEffort !== false // Default to enabled for GLM-4.7
205+
;(requestOptions as any).thinking = useReasoning ? { type: "enabled" } : { type: "disabled" }
206+
console.log(`[${this.providerName}] GLM-4.7 thinking mode: ${useReasoning ? "enabled" : "disabled"}`)
207+
}
208+
170209
let stream
171210
try {
172211
stream = await this.client.chat.completions.create(
@@ -221,20 +260,46 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
221260
yield this.processUsageMetrics(lastUsage, modelInfo)
222261
}
223262
} else {
263+
// Determine message conversion based on model type
264+
let nonStreamingMessages
265+
if (deepseekReasoner) {
266+
nonStreamingMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
267+
} else if (this.glmConfig.isGlmModel) {
268+
// GLM models benefit from mergeToolResultText to prevent reasoning_content loss
269+
const glmConvertedMessages = convertToZAiFormat(messages, {
270+
mergeToolResultText: this.glmConfig.mergeToolResultText,
271+
})
272+
nonStreamingMessages = [systemMessage, ...glmConvertedMessages]
273+
} else {
274+
nonStreamingMessages = [systemMessage, ...convertToOpenAiMessages(messages)]
275+
}
276+
277+
// Determine parallel_tool_calls setting for non-streaming
278+
let nonStreamingParallelToolCalls: boolean
279+
if (this.glmConfig.isGlmModel && this.glmConfig.disableParallelToolCalls) {
280+
nonStreamingParallelToolCalls = false
281+
} else {
282+
nonStreamingParallelToolCalls = metadata?.parallelToolCalls ?? true
283+
}
284+
224285
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = {
225286
model: modelId,
226-
messages: deepseekReasoner
227-
? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
228-
: [systemMessage, ...convertToOpenAiMessages(messages)],
287+
messages: nonStreamingMessages,
229288
// Tools are always present (minimum ALWAYS_AVAILABLE_TOOLS)
230289
tools: this.convertToolsForOpenAI(metadata?.tools),
231290
tool_choice: metadata?.tool_choice,
232-
parallel_tool_calls: metadata?.parallelToolCalls ?? true,
291+
parallel_tool_calls: nonStreamingParallelToolCalls,
233292
}
234293

235294
// Add max_tokens if needed
236295
this.addMaxTokensIfNeeded(requestOptions, modelInfo)
237296

297+
// For GLM-4.7 models with thinking support, add thinking parameter
298+
if (this.glmConfig.isGlmModel && this.glmConfig.supportsThinking) {
299+
const useReasoning = this.options.enableReasoningEffort !== false
300+
;(requestOptions as any).thinking = useReasoning ? { type: "enabled" } : { type: "disabled" }
301+
}
302+
238303
let response
239304
try {
240305
response = await this.client.chat.completions.create(

0 commit comments

Comments
 (0)