feat(MCP): Support GLM-4-alltools (#3304)

- Added Gemma3 as a vision model.
- Improved functioncall model check logic.
- Introduced a new method to clean tool call arguments, ensuring proper formatting and extraction of parameters.
- Adjusted tool call handling in OpenAIProvider to accommodate new GLM-4-alltools model checks and argument processing.
This commit is contained in:
SuYao 2025-03-14 12:30:26 +08:00 committed by GitHub
parent e89e27b0d7
commit 25f354c651
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 87 additions and 9 deletions

View File

@ -157,7 +157,8 @@ const visionAllowedModels = [
'chatgpt-4o(?:-[\\w-]+)?', 'chatgpt-4o(?:-[\\w-]+)?',
'o1(?:-[\\w-]+)?', 'o1(?:-[\\w-]+)?',
'deepseek-vl(?:[\\w-]+)?', 'deepseek-vl(?:[\\w-]+)?',
'kimi-latest' 'kimi-latest',
'gemma-3(?:-[\\w-]+)'
] ]
const visionExcludedModels = ['gpt-4-\\d+-preview', 'gpt-4-turbo-preview', 'gpt-4-32k', 'gpt-4-\\d+'] const visionExcludedModels = ['gpt-4-\\d+-preview', 'gpt-4-turbo-preview', 'gpt-4-32k', 'gpt-4-\\d+']
@ -178,14 +179,34 @@ export const EMBEDDING_REGEX = /(?:^text-|embed|bge-|e5-|LLM2Vec|retrieval|uae-|
export const NOT_SUPPORTED_REGEX = /(?:^tts|rerank|whisper|speech)/i export const NOT_SUPPORTED_REGEX = /(?:^tts|rerank|whisper|speech)/i
// Tool calling models // Tool calling models
export const FUNCTION_CALLING_MODELS = ['gpt-4o', 'gpt-4o-mini', 'gpt-4', 'gpt-4.5', 'claude', 'qwen'] export const FUNCTION_CALLING_MODELS = [
export const FUNCTION_CALLING_REGEX = new RegExp(`\\b(?:${FUNCTION_CALLING_MODELS.join('|')})\\b`, 'i') 'gpt-4o',
'gpt-4o-mini',
'gpt-4',
'gpt-4.5',
'claude',
'qwen',
'glm-4(?:-[\\w-]+)?',
'learnlm(?:-[\\w-]+)?',
'gemini(?:-[\\w-]+)?' // 提前排除了gemini的嵌入模型
]
const FUNCTION_CALLING_EXCLUDED_MODELS = ['aqa(?:-[\\w-]+)?', 'imagen(?:-[\\w-]+)?']
export const FUNCTION_CALLING_REGEX = new RegExp(
`\\b(?!(?:${FUNCTION_CALLING_EXCLUDED_MODELS.join('|')})\\b)(?:${FUNCTION_CALLING_MODELS.join('|')})\\b`,
'i'
)
export function isFunctionCallingModel(model: Model): boolean { export function isFunctionCallingModel(model: Model): boolean {
if (model.type?.includes('function_calling')) { if (model.type?.includes('function_calling')) {
return true return true
} }
if (['gemini', 'deepseek', 'anthropic'].includes(model.provider) && !EMBEDDING_REGEX.test(model.id)) { if (isEmbeddingModel(model)) {
return false
}
if (['deepseek', 'anthropic'].includes(model.provider)) {
return true return true
} }

View File

@ -284,6 +284,55 @@ export default class OpenAIProvider extends BaseProvider {
return model.id.startsWith('o1') || model.id.startsWith('o3') return model.id.startsWith('o1') || model.id.startsWith('o3')
} }
/**
* Check if the model is a Glm-4-alltools
* @param model - The model
* @returns True if the model is a Glm-4-alltools, false otherwise
*/
private isZhipuTool(model: Model) {
return model.id.includes('glm-4-alltools')
}
/**
* Clean the tool call arguments
* @param toolCall - The tool call
* @returns The cleaned tool call
*/
private cleanToolCallArgs(toolCall: ChatCompletionMessageToolCall): ChatCompletionMessageToolCall {
if (toolCall.function.arguments) {
let args = toolCall.function.arguments
const codeBlockRegex = /```(?:\w*\n)?([\s\S]*?)```/
const match = args.match(codeBlockRegex)
if (match) {
// Extract content from code block
let extractedArgs = match[1].trim()
// Clean function call format like tool_call(name1=value1,name2=value2)
const functionCallRegex = /^\s*\w+\s*\(([\s\S]*?)\)\s*$/
const functionMatch = extractedArgs.match(functionCallRegex)
if (functionMatch) {
// Try to convert parameters to JSON format
const params = functionMatch[1].split(',').filter(Boolean)
const paramsObj = {}
params.forEach((param) => {
const [name, value] = param.split('=').map((p) => p.trim())
if (name && value !== undefined) {
paramsObj[name] = value
}
})
extractedArgs = JSON.stringify(paramsObj)
}
toolCall.function.arguments = extractedArgs
}
args = toolCall.function.arguments
const firstBraceIndex = args.indexOf('{')
const lastBraceIndex = args.lastIndexOf('}')
if (firstBraceIndex !== -1 && lastBraceIndex !== -1 && firstBraceIndex < lastBraceIndex) {
toolCall.function.arguments = args.substring(firstBraceIndex, lastBraceIndex + 1)
}
}
return toolCall
}
/** /**
* Generate completions for the assistant * Generate completions for the assistant
* @param messages - The messages * @param messages - The messages
@ -443,11 +492,19 @@ export default class OpenAIProvider extends BaseProvider {
} }
if (finishReason === 'tool_calls') { if (finishReason === 'tool_calls') {
const toolCalls = Object.values(final_tool_calls) const toolCalls = Object.values(final_tool_calls).map(this.cleanToolCallArgs)
reqMessages.push({ console.log('start invoke tools', toolCalls)
role: 'assistant', if (this.isZhipuTool(model)) {
tool_calls: toolCalls reqMessages.push({
} as ChatCompletionAssistantMessageParam) role: 'assistant',
content: `argments=${JSON.stringify(toolCalls[0].function.arguments)}`
})
} else {
reqMessages.push({
role: 'assistant',
tool_calls: toolCalls
} as ChatCompletionAssistantMessageParam)
}
for (const toolCall of toolCalls) { for (const toolCall of toolCalls) {
const mcpTool = openAIToolsToMcpTool(mcpTools, toolCall) const mcpTool = openAIToolsToMcpTool(mcpTools, toolCall)