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

Commit 34d4383

Browse files
committed
feat: add GLM model detection with temperature=0.6 for LM Studio and OpenAI-compatible providers
- Add GLM model detection utility (src/api/providers/utils/glm-model-detection.ts) - Detects GLM family models (4.5, 4.6, 4.7 and variants) - Supports various model ID formats (official, LM Studio, GGUF, mlx-community) - Returns GLM-specific configuration: temperature=0.6, mergeToolResultText, disableParallelToolCalls - Includes diagnostic logging for detection results - Update LM Studio provider (src/api/providers/lm-studio.ts) - Detect GLM models on initialization - Apply temperature=0.6 for GLM models (ZAI_DEFAULT_TEMPERATURE) - Use convertToZAiFormat with mergeToolResultText for GLM models - Disable parallel_tool_calls for GLM models - Update OpenAI-compatible provider (src/api/providers/openai.ts) - Detect GLM models on initialization - Apply temperature=0.6 for GLM models - Use convertToZAiFormat with mergeToolResultText for GLM models - Disable parallel_tool_calls for GLM models - Add comprehensive tests for GLM detection Addresses Issue #11071: GLM models stuck repeating file reads
1 parent b5ae557 commit 34d4383

4 files changed

Lines changed: 563 additions & 14 deletions

File tree

src/api/providers/lm-studio.ts

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,21 @@ import { NativeToolCallParser } from "../../core/assistant-message/NativeToolCal
1010
import { TagMatcher } from "../../utils/tag-matcher"
1111

1212
import { convertToOpenAiMessages } from "../transform/openai-format"
13+
import { convertToZAiFormat } from "../transform/zai-format"
1314
import { ApiStream } from "../transform/stream"
1415

1516
import { BaseProvider } from "./base-provider"
1617
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
1718
import { getModelsFromCache } from "./fetchers/modelCache"
1819
import { getApiRequestTimeout } from "./utils/timeout-config"
1920
import { handleOpenAIError } from "./utils/openai-error-handler"
21+
import { detectGlmModel, logGlmDetection, type GlmModelConfig } from "./utils/glm-model-detection"
2022

2123
export class LmStudioHandler extends BaseProvider implements SingleCompletionHandler {
2224
protected options: ApiHandlerOptions
2325
private client: OpenAI
2426
private readonly providerName = "LM Studio"
27+
private glmConfig: GlmModelConfig
2528

2629
constructor(options: ApiHandlerOptions) {
2730
super()
@@ -35,17 +38,32 @@ export class LmStudioHandler extends BaseProvider implements SingleCompletionHan
3538
apiKey: apiKey,
3639
timeout: getApiRequestTimeout(),
3740
})
41+
42+
// Detect if this is a GLM model and apply optimizations
43+
this.glmConfig = detectGlmModel(this.options.lmStudioModelId)
44+
if (this.options.lmStudioModelId) {
45+
logGlmDetection(this.providerName, this.options.lmStudioModelId, this.glmConfig)
46+
}
3847
}
3948

4049
override async *createMessage(
4150
systemPrompt: string,
4251
messages: Anthropic.Messages.MessageParam[],
4352
metadata?: ApiHandlerCreateMessageMetadata,
4453
): ApiStream {
45-
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
46-
{ role: "system", content: systemPrompt },
47-
...convertToOpenAiMessages(messages),
48-
]
54+
// For GLM models, use Z.ai format with mergeToolResultText to prevent conversation flow disruption
55+
// For other models, use standard OpenAI format
56+
let openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[]
57+
if (this.glmConfig.isGlm && this.glmConfig.mergeToolResultText) {
58+
// Use Z.ai format converter which merges text after tool results into tool messages
59+
const convertedMessages = convertToZAiFormat(messages, { mergeToolResultText: true })
60+
openAiMessages = [{ role: "system", content: systemPrompt }, ...convertedMessages]
61+
} else {
62+
openAiMessages = [
63+
{ role: "system", content: systemPrompt },
64+
...convertToOpenAiMessages(messages),
65+
]
66+
}
4967

5068
// -------------------------
5169
// Track token usage
@@ -83,14 +101,27 @@ export class LmStudioHandler extends BaseProvider implements SingleCompletionHan
83101
let assistantText = ""
84102

85103
try {
104+
// Determine temperature: use GLM default (0.6) for GLM models, otherwise LM Studio default (0)
105+
const temperature = this.options.modelTemperature ??
106+
(this.glmConfig.isGlm ? this.glmConfig.temperature : LMSTUDIO_DEFAULT_TEMPERATURE)
107+
108+
// For GLM models, disable parallel_tool_calls as GLM models may not support it
109+
const parallelToolCalls = this.glmConfig.isGlm && this.glmConfig.disableParallelToolCalls
110+
? false
111+
: (metadata?.parallelToolCalls ?? true)
112+
113+
if (this.glmConfig.isGlm && this.glmConfig.disableParallelToolCalls) {
114+
console.log(`[${this.providerName}] parallel_tool_calls disabled for GLM model`)
115+
}
116+
86117
const params: OpenAI.Chat.ChatCompletionCreateParamsStreaming & { draft_model?: string } = {
87118
model: this.getModel().id,
88119
messages: openAiMessages,
89-
temperature: this.options.modelTemperature ?? LMSTUDIO_DEFAULT_TEMPERATURE,
120+
temperature,
90121
stream: true,
91122
tools: this.convertToolsForOpenAI(metadata?.tools),
92123
tool_choice: metadata?.tool_choice,
93-
parallel_tool_calls: metadata?.parallelToolCalls ?? true,
124+
parallel_tool_calls: parallelToolCalls,
94125
}
95126

96127
if (this.options.lmStudioSpeculativeDecodingEnabled && this.options.lmStudioDraftModelId) {
@@ -187,11 +218,15 @@ export class LmStudioHandler extends BaseProvider implements SingleCompletionHan
187218

188219
async completePrompt(prompt: string): Promise<string> {
189220
try {
221+
// Determine temperature: use GLM default (0.6) for GLM models, otherwise LM Studio default (0)
222+
const temperature = this.options.modelTemperature ??
223+
(this.glmConfig.isGlm ? this.glmConfig.temperature : LMSTUDIO_DEFAULT_TEMPERATURE)
224+
190225
// Create params object with optional draft model
191226
const params: any = {
192227
model: this.getModel().id,
193228
messages: [{ role: "user", content: prompt }],
194-
temperature: this.options.modelTemperature ?? LMSTUDIO_DEFAULT_TEMPERATURE,
229+
temperature,
195230
stream: false,
196231
}
197232

src/api/providers/openai.ts

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { TagMatcher } from "../../utils/tag-matcher"
1616

1717
import { convertToOpenAiMessages } from "../transform/openai-format"
1818
import { convertToR1Format } from "../transform/r1-format"
19+
import { convertToZAiFormat } from "../transform/zai-format"
1920
import { ApiStream, ApiStreamUsageChunk } from "../transform/stream"
2021
import { getModelParams } from "../transform/model-params"
2122

@@ -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
3538

3639
constructor(options: ApiHandlerOptions) {
3740
super()
@@ -77,6 +80,12 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
7780
timeout,
7881
})
7982
}
83+
84+
// Detect if this is a GLM model and apply optimizations
85+
this.glmConfig = detectGlmModel(this.options.openAiModelId)
86+
if (this.options.openAiModelId) {
87+
logGlmDetection(this.providerName, this.options.openAiModelId, this.glmConfig)
88+
}
8089
}
8190

8291
override async *createMessage(
@@ -106,6 +115,10 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
106115

107116
if (deepseekReasoner) {
108117
convertedMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
118+
} else if (this.glmConfig.isGlm && this.glmConfig.mergeToolResultText) {
119+
// For GLM models, use Z.ai format with mergeToolResultText to prevent conversation flow disruption
120+
const zaiMessages = convertToZAiFormat(messages, { mergeToolResultText: true })
121+
convertedMessages = [systemMessage, ...zaiMessages]
109122
} else {
110123
if (modelInfo.supportsPromptCache) {
111124
systemMessage = {
@@ -152,16 +165,37 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
152165

153166
const isGrokXAI = this._isGrokXAI(this.options.openAiBaseUrl)
154167

168+
// Determine temperature: use GLM default (0.6) for GLM models, DeepSeek default for DeepSeek, otherwise 0
169+
let temperature: number | undefined
170+
if (this.options.modelTemperature !== undefined) {
171+
temperature = this.options.modelTemperature
172+
} else if (this.glmConfig.isGlm) {
173+
temperature = this.glmConfig.temperature
174+
} else if (deepseekReasoner) {
175+
temperature = DEEP_SEEK_DEFAULT_TEMPERATURE
176+
} else {
177+
temperature = 0
178+
}
179+
180+
// For GLM models, disable parallel_tool_calls as GLM models may not support it
181+
const parallelToolCalls = this.glmConfig.isGlm && this.glmConfig.disableParallelToolCalls
182+
? false
183+
: (metadata?.parallelToolCalls ?? true)
184+
185+
if (this.glmConfig.isGlm && this.glmConfig.disableParallelToolCalls) {
186+
console.log(`[${this.providerName}] parallel_tool_calls disabled for GLM model`)
187+
}
188+
155189
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
156190
model: modelId,
157-
temperature: this.options.modelTemperature ?? (deepseekReasoner ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
191+
temperature,
158192
messages: convertedMessages,
159193
stream: true as const,
160194
...(isGrokXAI ? {} : { stream_options: { include_usage: true } }),
161195
...(reasoning && reasoning),
162196
tools: this.convertToolsForOpenAI(metadata?.tools),
163197
tool_choice: metadata?.tool_choice,
164-
parallel_tool_calls: metadata?.parallelToolCalls ?? true,
198+
parallel_tool_calls: parallelToolCalls,
165199
}
166200

167201
// Add max_tokens if needed
@@ -221,15 +255,30 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
221255
yield this.processUsageMetrics(lastUsage, modelInfo)
222256
}
223257
} else {
258+
// Non-streaming: also apply GLM-specific settings
259+
let nonStreamingMessages
260+
if (deepseekReasoner) {
261+
nonStreamingMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
262+
} else if (this.glmConfig.isGlm && this.glmConfig.mergeToolResultText) {
263+
// For GLM models, use Z.ai format with mergeToolResultText
264+
const zaiMessages = convertToZAiFormat(messages, { mergeToolResultText: true })
265+
nonStreamingMessages = [systemMessage, ...zaiMessages]
266+
} else {
267+
nonStreamingMessages = [systemMessage, ...convertToOpenAiMessages(messages)]
268+
}
269+
270+
// For GLM models, disable parallel_tool_calls
271+
const nonStreamingParallelToolCalls = this.glmConfig.isGlm && this.glmConfig.disableParallelToolCalls
272+
? false
273+
: (metadata?.parallelToolCalls ?? true)
274+
224275
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = {
225276
model: modelId,
226-
messages: deepseekReasoner
227-
? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
228-
: [systemMessage, ...convertToOpenAiMessages(messages)],
277+
messages: nonStreamingMessages,
229278
// Tools are always present (minimum ALWAYS_AVAILABLE_TOOLS)
230279
tools: this.convertToolsForOpenAI(metadata?.tools),
231280
tool_choice: metadata?.tool_choice,
232-
parallel_tool_calls: metadata?.parallelToolCalls ?? true,
281+
parallel_tool_calls: nonStreamingParallelToolCalls,
233282
}
234283

235284
// Add max_tokens if needed

0 commit comments

Comments
 (0)