Merge pull request #369 from simonsmh/main

Fix: OpenAI tool conversion issues
This commit is contained in:
何夕2077 2026-03-08 17:48:37 +08:00 committed by GitHub
commit 4a1a382dc2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -161,7 +161,7 @@ export class OpenAIConverter extends BaseConverter {
}
content.push({
type: 'tool_result',
tool_use_id: message.tool_call_id,
tool_use_id: message.tool_call_id || message.tool_use_id,
content: toolContent
});
claudeMessages.push({ role: 'user', content: content });
@ -219,6 +219,33 @@ export class OpenAIConverter extends BaseConverter {
content.push({ type: 'text', text: `[Audio: ${audioUrl}]` });
}
break;
case 'input_audio':
// OpenAI 官方 input_audio 格式
if (item.input_audio) {
// Claude 不直接支持音频输入,转换为文本描述
content.push({ type: 'text', text: `[Audio Input: ${item.input_audio.format || 'audio'}]` });
}
break;
case 'tool_use':
content.push({
type: 'tool_use',
id: item.id,
name: item.name,
input: typeof item.input === 'string' ? safeParseJSON(item.input) : (item.input || {})
});
break;
case 'tool_result': {
let resultContent = item.content;
if (typeof resultContent === 'object' && resultContent !== null) {
resultContent = JSON.stringify(resultContent);
}
content.push({
type: 'tool_result',
tool_use_id: item.tool_use_id || item.id,
content: resultContent
});
break;
}
}
});
}
@ -276,12 +303,25 @@ export class OpenAIConverter extends BaseConverter {
}
if (openaiRequest.tools?.length) {
claudeRequest.tools = openaiRequest.tools.map(t => ({
name: t.function.name,
description: t.function.description || '',
input_schema: t.function.parameters || { type: 'object', properties: {} }
}));
claudeRequest.tool_choice = this.buildClaudeToolChoice(openaiRequest.tool_choice);
claudeRequest.tools = openaiRequest.tools
.filter(t => t && ((t.function && t.function.name) || t.name))
.map(t => {
if (t.function) {
return {
name: t.function.name,
description: t.function.description || '',
input_schema: t.function.parameters || { type: 'object', properties: {} }
};
}
return {
name: t.name,
description: t.description || '',
input_schema: t.input_schema || { type: 'object', properties: {} }
};
});
if (claudeRequest.tools.length > 0) {
claudeRequest.tool_choice = this.buildClaudeToolChoice(openaiRequest.tool_choice);
}
}
// Optional passthrough: request-side "thinking" controls for Claude/Kiro.
@ -596,8 +636,15 @@ export class OpenAIConverter extends BaseConverter {
const mapping = { auto: 'auto', none: 'none', required: 'any' };
return { type: mapping[toolChoice] };
}
if (typeof toolChoice === 'object' && toolChoice.function) {
return { type: 'tool', name: toolChoice.function.name };
if (typeof toolChoice === 'object') {
// Claude 原生格式:{ type, name }
if (toolChoice.type && toolChoice.name) {
return { type: toolChoice.type, name: toolChoice.name };
}
// OpenAI 格式:{ function: { name } }
if (toolChoice.function) {
return { type: 'tool', name: toolChoice.function.name };
}
}
return undefined;
}
@ -625,6 +672,14 @@ export class OpenAIConverter extends BaseConverter {
}
}
}
// Claude 格式content 数组中的 tool_use
if (message.role === 'assistant' && Array.isArray(message.content)) {
for (const item of message.content) {
if (item && item.type === 'tool_use' && item.id && item.name) {
tcID2Name[item.id] = item.name;
}
}
}
}
// 构建 tool_call_id -> response 映射
@ -633,6 +688,14 @@ export class OpenAIConverter extends BaseConverter {
if (message.role === 'tool' && message.tool_call_id) {
toolResponses[message.tool_call_id] = message.content;
}
// Claude 格式user content 数组中的 tool_result
if (message.role === 'user' && Array.isArray(message.content)) {
for (const item of message.content) {
if (item && item.type === 'tool_result' && item.tool_use_id) {
toolResponses[item.tool_use_id] = item.content;
}
}
}
}
const processedMessages = [];
@ -643,7 +706,7 @@ export class OpenAIConverter extends BaseConverter {
const role = message.role;
const content = message.content;
if (role === 'system') {
if (role === 'system' || role === 'developer') {
// system -> system_instruction
if (messages.length > 1) {
if (typeof content === 'string') {
@ -790,6 +853,7 @@ export class OpenAIConverter extends BaseConverter {
const node = { role: 'model', parts: [] };
// 处理文本内容
const functionCallIds = [];
if (typeof content === 'string' && content) {
node.parts.push({ text: content });
} else if (Array.isArray(content)) {
@ -797,6 +861,19 @@ export class OpenAIConverter extends BaseConverter {
if (!item) continue;
if (item.type === 'text' && item.text) {
node.parts.push({ text: item.text });
} else if (item.type === 'tool_use') {
// Claude 格式 tool_use -> Gemini functionCall
const fid = item.id || '';
const fname = item.name || '';
const argsObj = typeof item.input === 'string' ? (() => { try { return JSON.parse(item.input); } catch(e) { return {}; } })() : (item.input || {});
node.parts.push({
functionCall: {
name: fname,
args: argsObj
},
thoughtSignature: OpenAIConverter.GEMINI_OPENAI_THOUGHT_SIGNATURE
});
if (fid) functionCallIds.push(fid);
} else if (item.type === 'image_url' && item.image_url) {
const imageUrl = typeof item.image_url === 'string'
? item.image_url
@ -823,22 +900,21 @@ export class OpenAIConverter extends BaseConverter {
}
}
// 处理 tool_calls -> functionCall
// 处理 OpenAI 格式 tool_calls -> functionCall
if (message.tool_calls && Array.isArray(message.tool_calls)) {
const functionCallIds = [];
for (const tc of message.tool_calls) {
if (tc.type !== 'function') continue;
const fid = tc.id || '';
const fname = tc.function?.name || '';
const fargs = tc.function?.arguments || '{}';
let argsObj;
try {
argsObj = typeof fargs === 'string' ? JSON.parse(fargs) : fargs;
} catch (e) {
argsObj = {};
}
node.parts.push({
functionCall: {
name: fname,
@ -846,50 +922,72 @@ export class OpenAIConverter extends BaseConverter {
},
thoughtSignature: OpenAIConverter.GEMINI_OPENAI_THOUGHT_SIGNATURE
});
if (fid) {
functionCallIds.push(fid);
}
}
// 添加 model 消息
if (node.parts.length > 0) {
processedMessages.push(node);
}
// 添加 model 消息
if (node.parts.length > 0) {
processedMessages.push(node);
}
// 添加对应的 functionResponse作为 user 消息)
if (functionCallIds.length > 0) {
const toolNode = { role: 'user', parts: [] };
for (const fid of functionCallIds) {
const name = tcID2Name[fid];
if (name) {
let resp = toolResponses[fid] || '{}';
if (typeof resp !== 'string') {
resp = JSON.stringify(resp);
}
toolNode.parts.push({
functionResponse: {
name: name,
response: {
result: resp
}
}
});
}
}
if (toolNode.parts.length > 0) {
processedMessages.push(toolNode);
}
}
} else if (role === 'tool') {
// 处理独立的 tool role 消息OpenAI 格式)
// 转换为 Gemini 的 functionResponse 格式
const toolNode = { role: 'user', parts: [] };
// 从 tool_call_id 查找对应的函数名
const toolCallId = message.tool_call_id;
const functionName = tcID2Name[toolCallId];
if (functionName) {
let responseContent = message.content;
if (typeof responseContent !== 'string') {
responseContent = JSON.stringify(responseContent);
}
// 添加对应的 functionResponse作为 user 消息)
if (functionCallIds.length > 0) {
const toolNode = { role: 'user', parts: [] };
for (const fid of functionCallIds) {
const name = tcID2Name[fid];
if (name) {
let resp = toolResponses[fid] || '{}';
// 确保 resp 是字符串
if (typeof resp !== 'string') {
resp = JSON.stringify(resp);
}
toolNode.parts.push({
functionResponse: {
name: name,
response: {
result: resp
}
}
});
toolNode.parts.push({
functionResponse: {
name: functionName,
response: {
result: responseContent
}
}
if (toolNode.parts.length > 0) {
processedMessages.push(toolNode);
}
}
} else {
// 没有 tool_calls直接添加
if (node.parts.length > 0) {
processedMessages.push(node);
});
if (toolNode.parts.length > 0) {
processedMessages.push(toolNode);
}
}
}
// tool 消息已经在 assistant 的 tool_calls 处理中合并了,这里跳过
// 其他 role 类型跳过
}
// 构建 Gemini 请求
@ -1013,7 +1111,7 @@ export class OpenAIConverter extends BaseConverter {
name: String(func.name || ''),
description: String(func.description || '')
};
// 处理 parameters -> parametersJsonSchema
if (func.parameters) {
fnDecl.parametersJsonSchema = cleanJsonSchema(func.parameters);
@ -1023,8 +1121,14 @@ export class OpenAIConverter extends BaseConverter {
properties: {}
};
}
functionDeclarations.push(fnDecl);
} else if (t.name) {
functionDeclarations.push({
name: String(t.name),
description: String(t.description || ''),
parametersJsonSchema: cleanJsonSchema(t.input_schema || { type: 'object', properties: {} })
});
}
// 处理 google_search 工具
@ -1334,6 +1438,8 @@ export class OpenAIConverter extends BaseConverter {
}
}]
};
return result;
}
// =========================================================================