mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Merge 0016aa033d into b867ec9145
This commit is contained in:
commit
4d0df8b7ce
4 changed files with 225 additions and 5 deletions
|
|
@ -14,6 +14,7 @@ import { BaseProvider } from "./base-provider"
|
|||
import { handleOpenAIError } from "./utils/openai-error-handler"
|
||||
import { calculateApiCostOpenAI } from "../../shared/cost"
|
||||
import { getApiRequestTimeout } from "./utils/timeout-config"
|
||||
import { getGlmModelOptions } from "./utils/glm-model-detection"
|
||||
|
||||
type BaseOpenAiCompatibleProviderOptions<ModelName extends string> = ApiHandlerOptions & {
|
||||
providerName: string
|
||||
|
|
@ -75,6 +76,9 @@ export abstract class BaseOpenAiCompatibleProvider<ModelName extends string>
|
|||
) {
|
||||
const { id: model, info } = this.getModel()
|
||||
|
||||
// Check if this is a GLM model and get recommended options
|
||||
const glmOptions = getGlmModelOptions(model)
|
||||
|
||||
// Centralized cap: clamp to 20% of the context window (unless provider-specific exceptions apply)
|
||||
const max_tokens =
|
||||
getModelMaxOutputTokens({
|
||||
|
|
@ -86,16 +90,24 @@ export abstract class BaseOpenAiCompatibleProvider<ModelName extends string>
|
|||
|
||||
const temperature = this.options.modelTemperature ?? info.defaultTemperature ?? this.defaultTemperature
|
||||
|
||||
// For GLM models, disable parallel_tool_calls as they may not support it
|
||||
const parallelToolCalls = glmOptions?.disableParallelToolCalls ? false : (metadata?.parallelToolCalls ?? true)
|
||||
|
||||
const params: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
|
||||
model,
|
||||
max_tokens,
|
||||
temperature,
|
||||
messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)],
|
||||
messages: [
|
||||
{ role: "system", content: systemPrompt },
|
||||
...convertToOpenAiMessages(messages, {
|
||||
mergeToolResultText: glmOptions?.mergeToolResultText ?? false,
|
||||
}),
|
||||
],
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
tools: this.convertToolsForOpenAI(metadata?.tools),
|
||||
tool_choice: metadata?.tool_choice,
|
||||
parallel_tool_calls: metadata?.parallelToolCalls ?? true,
|
||||
parallel_tool_calls: parallelToolCalls,
|
||||
}
|
||||
|
||||
// Add thinking parameter if reasoning is enabled and model supports it
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from ".
|
|||
import { getModelsFromCache } from "./fetchers/modelCache"
|
||||
import { getApiRequestTimeout } from "./utils/timeout-config"
|
||||
import { handleOpenAIError } from "./utils/openai-error-handler"
|
||||
import { getGlmModelOptions } from "./utils/glm-model-detection"
|
||||
|
||||
export class LmStudioHandler extends BaseProvider implements SingleCompletionHandler {
|
||||
protected options: ApiHandlerOptions
|
||||
|
|
@ -42,9 +43,16 @@ export class LmStudioHandler extends BaseProvider implements SingleCompletionHan
|
|||
messages: Anthropic.Messages.MessageParam[],
|
||||
metadata?: ApiHandlerCreateMessageMetadata,
|
||||
): ApiStream {
|
||||
const modelId = this.getModel().id
|
||||
|
||||
// Check if this is a GLM model and get recommended options
|
||||
const glmOptions = getGlmModelOptions(modelId)
|
||||
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
...convertToOpenAiMessages(messages),
|
||||
...convertToOpenAiMessages(messages, {
|
||||
mergeToolResultText: glmOptions?.mergeToolResultText ?? false,
|
||||
}),
|
||||
]
|
||||
|
||||
// -------------------------
|
||||
|
|
@ -83,14 +91,19 @@ export class LmStudioHandler extends BaseProvider implements SingleCompletionHan
|
|||
let assistantText = ""
|
||||
|
||||
try {
|
||||
// For GLM models, disable parallel_tool_calls as they may not support it
|
||||
const parallelToolCalls = glmOptions?.disableParallelToolCalls
|
||||
? false
|
||||
: (metadata?.parallelToolCalls ?? true)
|
||||
|
||||
const params: OpenAI.Chat.ChatCompletionCreateParamsStreaming & { draft_model?: string } = {
|
||||
model: this.getModel().id,
|
||||
model: modelId,
|
||||
messages: openAiMessages,
|
||||
temperature: this.options.modelTemperature ?? LMSTUDIO_DEFAULT_TEMPERATURE,
|
||||
stream: true,
|
||||
tools: this.convertToolsForOpenAI(metadata?.tools),
|
||||
tool_choice: metadata?.tool_choice,
|
||||
parallel_tool_calls: metadata?.parallelToolCalls ?? true,
|
||||
parallel_tool_calls: parallelToolCalls,
|
||||
}
|
||||
|
||||
if (this.options.lmStudioSpeculativeDecodingEnabled && this.options.lmStudioDraftModelId) {
|
||||
|
|
|
|||
115
src/api/providers/utils/__tests__/glm-model-detection.spec.ts
Normal file
115
src/api/providers/utils/__tests__/glm-model-detection.spec.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import { isGlmModel, getGlmModelOptions } from "../glm-model-detection"
|
||||
|
||||
describe("GLM Model Detection", () => {
|
||||
describe("isGlmModel", () => {
|
||||
describe("should detect GLM models", () => {
|
||||
const validGlmModels = [
|
||||
// Standard Z.ai format
|
||||
"glm-4.5",
|
||||
"glm-4.6",
|
||||
"glm-4.7",
|
||||
"glm-4.5-air",
|
||||
"glm-4.5v",
|
||||
// MLX format (from user's report)
|
||||
"mlx-community/GLM-4.5-4bit",
|
||||
"mlx-community/GLM-4.5-8bit",
|
||||
// GGUF format (from user's report)
|
||||
"GLM-4.5-UD-Q8_K_XL-00001-of-00008.gguf",
|
||||
"GLM-4.5-UD-Q4_K_M.gguf",
|
||||
// HuggingFace format
|
||||
"THUDM/glm-4-9b-chat",
|
||||
"THUDM/glm-4v-9b",
|
||||
// ChatGLM variants
|
||||
"chatglm-6b",
|
||||
"chatglm2-6b",
|
||||
"chatglm3-6b",
|
||||
"ChatGLM-6B",
|
||||
// Without hyphen
|
||||
"glm4",
|
||||
"GLM4",
|
||||
// Mixed case
|
||||
"GLM-4.5",
|
||||
"Glm-4.5",
|
||||
]
|
||||
|
||||
test.each(validGlmModels)('should detect "%s" as a GLM model', (modelId) => {
|
||||
expect(isGlmModel(modelId)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("should NOT detect non-GLM models", () => {
|
||||
const nonGlmModels = [
|
||||
// OpenAI models
|
||||
"gpt-4",
|
||||
"gpt-4-turbo",
|
||||
"gpt-3.5-turbo",
|
||||
"o1-preview",
|
||||
// Anthropic models
|
||||
"claude-3-opus",
|
||||
"claude-3.5-sonnet",
|
||||
// Llama models
|
||||
"llama-3.1-70b",
|
||||
"meta-llama/Llama-3.1-8B-Instruct",
|
||||
// Mistral models
|
||||
"mistral-7b",
|
||||
"mixtral-8x7b",
|
||||
// DeepSeek models
|
||||
"deepseek-coder",
|
||||
"deepseek-reasoner",
|
||||
// Qwen models
|
||||
"qwen-2.5-72b",
|
||||
"qwen-coder",
|
||||
// Empty/undefined
|
||||
"",
|
||||
]
|
||||
|
||||
test.each(nonGlmModels)('should NOT detect "%s" as a GLM model', (modelId) => {
|
||||
expect(isGlmModel(modelId)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
it("should return false for undefined modelId", () => {
|
||||
expect(isGlmModel(undefined)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getGlmModelOptions", () => {
|
||||
it("should return options for GLM models", () => {
|
||||
const options = getGlmModelOptions("glm-4.5")
|
||||
expect(options).toEqual({
|
||||
mergeToolResultText: true,
|
||||
disableParallelToolCalls: true,
|
||||
})
|
||||
})
|
||||
|
||||
it("should return options for MLX GLM models", () => {
|
||||
const options = getGlmModelOptions("mlx-community/GLM-4.5-4bit")
|
||||
expect(options).toEqual({
|
||||
mergeToolResultText: true,
|
||||
disableParallelToolCalls: true,
|
||||
})
|
||||
})
|
||||
|
||||
it("should return options for GGUF GLM models", () => {
|
||||
const options = getGlmModelOptions("GLM-4.5-UD-Q8_K_XL-00001-of-00008.gguf")
|
||||
expect(options).toEqual({
|
||||
mergeToolResultText: true,
|
||||
disableParallelToolCalls: true,
|
||||
})
|
||||
})
|
||||
|
||||
it("should return undefined for non-GLM models", () => {
|
||||
expect(getGlmModelOptions("gpt-4")).toBeUndefined()
|
||||
expect(getGlmModelOptions("llama-3.1")).toBeUndefined()
|
||||
expect(getGlmModelOptions("claude-3")).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should return undefined for undefined modelId", () => {
|
||||
expect(getGlmModelOptions(undefined)).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should return undefined for empty string", () => {
|
||||
expect(getGlmModelOptions("")).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
80
src/api/providers/utils/glm-model-detection.ts
Normal file
80
src/api/providers/utils/glm-model-detection.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* Utility functions for detecting GLM (General Language Model) models.
|
||||
*
|
||||
* GLM models from Z.ai/THUDM may require special handling:
|
||||
* - mergeToolResultText: true - prevents conversation flow disruption
|
||||
* - parallel_tool_calls: false - some GLM models do not support this parameter
|
||||
*/
|
||||
|
||||
/**
|
||||
* Pattern to detect GLM models in model IDs.
|
||||
*
|
||||
* This regex matches "glm" anywhere in the model ID (case-insensitive),
|
||||
* including common variations like:
|
||||
* - "glm-4.5" (standard Z.ai format)
|
||||
* - "glm4" (without hyphen)
|
||||
* - "chatglm" (ChatGLM variants)
|
||||
* - "mlx-community/GLM-4.5-4bit" (MLX format with prefix)
|
||||
* - "GLM-4.5-UD-Q8_K_XL-00001-of-00008.gguf" (GGUF format)
|
||||
* - "THUDM/glm-4-9b-chat" (HuggingFace format)
|
||||
*/
|
||||
const GLM_MODEL_PATTERN = /glm/i
|
||||
|
||||
/**
|
||||
* Detects if a model ID represents a GLM (General Language Model) model.
|
||||
*
|
||||
* @param modelId - The model ID to check (e.g., "glm-4.5", "mlx-community/GLM-4.5-4bit")
|
||||
* @returns true if the model ID indicates a GLM model, false otherwise
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* isGlmModel("glm-4.5") // true
|
||||
* isGlmModel("mlx-community/GLM-4.5-4bit") // true
|
||||
* isGlmModel("GLM-4.5-UD-Q8_K_XL.gguf") // true
|
||||
* isGlmModel("chatglm-6b") // true
|
||||
* isGlmModel("gpt-4") // false
|
||||
* isGlmModel("llama-3.1") // false
|
||||
* ```
|
||||
*/
|
||||
export function isGlmModel(modelId: string | undefined): boolean {
|
||||
if (!modelId) {
|
||||
return false
|
||||
}
|
||||
return GLM_MODEL_PATTERN.test(modelId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration options for GLM models when used via LM Studio
|
||||
* or OpenAI-compatible endpoints.
|
||||
*/
|
||||
export interface GlmModelOptions {
|
||||
/**
|
||||
* If true, merge text content after tool_results into the last tool message
|
||||
* instead of creating a separate user message. This prevents GLM models from
|
||||
* losing context or reasoning_content after tool results.
|
||||
*/
|
||||
mergeToolResultText: boolean
|
||||
|
||||
/**
|
||||
* If true, disable parallel_tool_calls parameter for GLM models
|
||||
* since they may not support it.
|
||||
*/
|
||||
disableParallelToolCalls: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the recommended configuration options for a GLM model.
|
||||
*
|
||||
* @param modelId - The model ID to check
|
||||
* @returns GlmModelOptions if GLM model detected, undefined otherwise
|
||||
*/
|
||||
export function getGlmModelOptions(modelId: string | undefined): GlmModelOptions | undefined {
|
||||
if (!isGlmModel(modelId)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
mergeToolResultText: true,
|
||||
disableParallelToolCalls: true,
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue