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({ content.push({
type: 'tool_result', type: 'tool_result',
tool_use_id: message.tool_call_id, tool_use_id: message.tool_call_id || message.tool_use_id,
content: toolContent content: toolContent
}); });
claudeMessages.push({ role: 'user', content: content }); claudeMessages.push({ role: 'user', content: content });
@ -219,6 +219,33 @@ export class OpenAIConverter extends BaseConverter {
content.push({ type: 'text', text: `[Audio: ${audioUrl}]` }); content.push({ type: 'text', text: `[Audio: ${audioUrl}]` });
} }
break; 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,13 +303,26 @@ export class OpenAIConverter extends BaseConverter {
} }
if (openaiRequest.tools?.length) { if (openaiRequest.tools?.length) {
claudeRequest.tools = openaiRequest.tools.map(t => ({ claudeRequest.tools = openaiRequest.tools
.filter(t => t && ((t.function && t.function.name) || t.name))
.map(t => {
if (t.function) {
return {
name: t.function.name, name: t.function.name,
description: t.function.description || '', description: t.function.description || '',
input_schema: t.function.parameters || { type: 'object', properties: {} } 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); claudeRequest.tool_choice = this.buildClaudeToolChoice(openaiRequest.tool_choice);
} }
}
// Optional passthrough: request-side "thinking" controls for Claude/Kiro. // Optional passthrough: request-side "thinking" controls for Claude/Kiro.
// OpenAI-compatible clients can provide these via `extra_body.anthropic.thinking`. // OpenAI-compatible clients can provide these via `extra_body.anthropic.thinking`.
@ -596,9 +636,16 @@ export class OpenAIConverter extends BaseConverter {
const mapping = { auto: 'auto', none: 'none', required: 'any' }; const mapping = { auto: 'auto', none: 'none', required: 'any' };
return { type: mapping[toolChoice] }; return { type: mapping[toolChoice] };
} }
if (typeof toolChoice === 'object' && toolChoice.function) { 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 { type: 'tool', name: toolChoice.function.name };
} }
}
return undefined; 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 映射 // 构建 tool_call_id -> response 映射
@ -633,6 +688,14 @@ export class OpenAIConverter extends BaseConverter {
if (message.role === 'tool' && message.tool_call_id) { if (message.role === 'tool' && message.tool_call_id) {
toolResponses[message.tool_call_id] = message.content; 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 = []; const processedMessages = [];
@ -643,7 +706,7 @@ export class OpenAIConverter extends BaseConverter {
const role = message.role; const role = message.role;
const content = message.content; const content = message.content;
if (role === 'system') { if (role === 'system' || role === 'developer') {
// system -> system_instruction // system -> system_instruction
if (messages.length > 1) { if (messages.length > 1) {
if (typeof content === 'string') { if (typeof content === 'string') {
@ -790,6 +853,7 @@ export class OpenAIConverter extends BaseConverter {
const node = { role: 'model', parts: [] }; const node = { role: 'model', parts: [] };
// 处理文本内容 // 处理文本内容
const functionCallIds = [];
if (typeof content === 'string' && content) { if (typeof content === 'string' && content) {
node.parts.push({ text: content }); node.parts.push({ text: content });
} else if (Array.isArray(content)) { } else if (Array.isArray(content)) {
@ -797,6 +861,19 @@ export class OpenAIConverter extends BaseConverter {
if (!item) continue; if (!item) continue;
if (item.type === 'text' && item.text) { if (item.type === 'text' && item.text) {
node.parts.push({ 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) { } else if (item.type === 'image_url' && item.image_url) {
const imageUrl = typeof item.image_url === 'string' const imageUrl = typeof item.image_url === 'string'
? item.image_url ? item.image_url
@ -823,9 +900,8 @@ export class OpenAIConverter extends BaseConverter {
} }
} }
// 处理 tool_calls -> functionCall // 处理 OpenAI 格式 tool_calls -> functionCall
if (message.tool_calls && Array.isArray(message.tool_calls)) { if (message.tool_calls && Array.isArray(message.tool_calls)) {
const functionCallIds = [];
for (const tc of message.tool_calls) { for (const tc of message.tool_calls) {
if (tc.type !== 'function') continue; if (tc.type !== 'function') continue;
const fid = tc.id || ''; const fid = tc.id || '';
@ -851,6 +927,7 @@ export class OpenAIConverter extends BaseConverter {
functionCallIds.push(fid); functionCallIds.push(fid);
} }
} }
}
// 添加 model 消息 // 添加 model 消息
if (node.parts.length > 0) { if (node.parts.length > 0) {
@ -864,7 +941,6 @@ export class OpenAIConverter extends BaseConverter {
const name = tcID2Name[fid]; const name = tcID2Name[fid];
if (name) { if (name) {
let resp = toolResponses[fid] || '{}'; let resp = toolResponses[fid] || '{}';
// 确保 resp 是字符串
if (typeof resp !== 'string') { if (typeof resp !== 'string') {
resp = JSON.stringify(resp); resp = JSON.stringify(resp);
} }
@ -882,14 +958,36 @@ export class OpenAIConverter extends BaseConverter {
processedMessages.push(toolNode); processedMessages.push(toolNode);
} }
} }
} else { } else if (role === 'tool') {
// 没有 tool_calls直接添加 // 处理独立的 tool role 消息OpenAI 格式)
if (node.parts.length > 0) { // 转换为 Gemini 的 functionResponse 格式
processedMessages.push(node); 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);
}
toolNode.parts.push({
functionResponse: {
name: functionName,
response: {
result: responseContent
}
}
});
if (toolNode.parts.length > 0) {
processedMessages.push(toolNode);
} }
} }
} }
// tool 消息已经在 assistant 的 tool_calls 处理中合并了,这里跳过 // 其他 role 类型跳过
} }
// 构建 Gemini 请求 // 构建 Gemini 请求
@ -1025,6 +1123,12 @@ export class OpenAIConverter extends BaseConverter {
} }
functionDeclarations.push(fnDecl); 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 工具 // 处理 google_search 工具
@ -1334,6 +1438,8 @@ export class OpenAIConverter extends BaseConverter {
} }
}] }]
}; };
return result;
} }
// ========================================================================= // =========================================================================