From 91ee1428fa3bac30ca8e90155b33fa108442844b Mon Sep 17 00:00:00 2001 From: bonerush <96404351+bonerush@users.noreply.github.com> Date: Wed, 8 Apr 2026 18:08:59 +0800 Subject: [PATCH] =?UTF-8?q?Fix=20bug=20OpenAI=20tooluse,Improve=20error=20?= =?UTF-8?q?messaging=20for=20deferred-loading=20tools=20under=20OpenAI?= =?UTF-8?q?=E2=80=91compatible=20models.=20(#199)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: reorder tool and user messages for OpenAI API compatibility (#168) Fixes #168 OpenAI requires that an assistant message with tool_calls be immediately followed by tool messages. Previously, convertInternalUserMessage output user content before tool results, causing 400 errors. Now tool messages are pushed first. * fix: 修复OpenAI兼容层中deferred tools处理问题 提交描述: 修复了在使用OpenAI兼容API时TaskCreate工具调用失败的问题。 问题: - 当使用OpenAI兼容API模型时,调用TaskCreate工具出现"InputValidationError: The required parameter `subject` is missing"错误 - OpenAI兼容层没有正确处理deferred tools的过滤逻辑,导致工具schema没有被正确发送给模型 修复: 1. 在OpenAI兼容层中添加了与Anthropic API路径一致的deferred tools处理逻辑 2. 导入必要的工具搜索相关函数: isToolSearchEnabled, extractDiscoveredToolNames, isDeferredTool等 3. 实现工具过滤逻辑: - 检查工具搜索是否启用 - 构建deferred tools集合 - 过滤工具列表: 只包含非deferred工具或已发现的deferred工具 - 为deferred tools设置deferLoading标志 4. 修正了extractDiscoveredToolNames函数的导入路径错误 影响: - 解决了TaskCreate工具调用时的参数验证错误 - 确保OpenAI兼容层与Anthropic API路径在处理deferred tools时行为一致 - 支持工具搜索功能在OpenAI兼容模式下正常工作 修改的文件: - src/services/api/openai/index.ts - 主要修复文件 测试建议: 1. 使用OpenAI兼容API模型时,TaskCreate工具应该可以正常调用 2. 如果工具搜索功能启用,可能需要先使用ToolSearchTool来发现TaskCreate工具 3. 验证工具调用时不再出现"InputValidationError"错误 这个修复确保了当使用OpenAI兼容API(如Ollama、DeepSeek、vLLM等)时,deferred tools(如TaskCreate)能够被正确处理,解决了工具调用失败的问题。 * fix: 更新工具模式未发送提示,增加OpenAI兼容模型使用指南 --- docs/openai-task-tools.md | 190 ++++++++++++++++++++++++++++ src/services/tools/toolExecution.ts | 19 ++- 2 files changed, 206 insertions(+), 3 deletions(-) create mode 100644 docs/openai-task-tools.md diff --git a/docs/openai-task-tools.md b/docs/openai-task-tools.md new file mode 100644 index 000000000..22d415e4b --- /dev/null +++ b/docs/openai-task-tools.md @@ -0,0 +1,190 @@ +# OpenAI兼容模型中task工具使用指南 + +## 问题描述 + +当使用OpenAI兼容模型(如DeepSeek、Ollama、vLLM等)时,调用task工具(TaskGet、TaskCreate、TaskUpdate、TaskList)可能会出现以下错误: + +``` +Error: InputValidationError: TaskGet failed due to the following issues: + The required parameter `taskId` is missing + An unexpected parameter `task_id` was provided + + This tool's schema was not sent to the API — it was not in the discovered-tool set derived from message history. Without the schema in your prompt, typed parameters (arrays, numbers, booleans) get emitted as strings and the client-side parser rejects them. Load the tool first: call ToolSearch with query "select:TaskGet", then retry this call. +``` + +## 问题原因 + +### 1. 延迟加载工具(Deferred Tools) +task工具都是延迟加载的(`shouldDefer: true`),这意味着: +- 工具的模式(schema)不会在初始API调用中发送 +- 需要先通过`ToolSearch`工具发现 +- 只有在被发现后,工具模式才会被发送给API + +### 2. 参数名转换问题 +- task工具使用驼峰命名:`taskId` +- OpenAI兼容模型可能输出蛇形命名:`task_id` +- 当工具模式没有被发送时,模型会猜测参数名,可能导致不匹配 + +## 解决方案 + +### 方案1:先使用ToolSearch(推荐) +在使用task工具之前,先调用`ToolSearch`工具: + +```javascript +// 第一步:发现task工具 +ToolSearch("select:TaskGet,TaskCreate,TaskUpdate,TaskList") + +// 第二步:正常使用task工具 +TaskCreate({ subject: "任务标题", description: "任务描述" }) +TaskGet({ taskId: "1" }) +TaskUpdate({ taskId: "1", status: "completed" }) +TaskList() +``` + +### 方案2:批量发现所有task工具 +```javascript +// 一次性发现所有task工具 +ToolSearch("select:TaskGet,TaskCreate,TaskUpdate,TaskList") + +// 然后可以任意使用task工具 +const task = await TaskCreate({ subject: "新任务", description: "任务描述" }) +console.log(`创建的任务ID: ${task.id}`) + +const taskList = await TaskList() +console.log(`当前有 ${taskList.tasks.length} 个任务`) +``` + +### 方案3:单独发现特定工具 +```javascript +// 只发现需要的工具 +ToolSearch("select:TaskGet") + +// 然后使用该工具 +TaskGet({ taskId: "1" }) +``` + +## 参数名注意事项 + +在使用OpenAI兼容模型时,请注意参数名格式: + +### ✅ 正确(驼峰命名) +```javascript +TaskGet({ taskId: "1" }) +TaskCreate({ subject: "标题", description: "描述" }) +TaskUpdate({ taskId: "1", status: "completed" }) +``` + +### ❌ 错误(蛇形命名) +```javascript +TaskGet({ task_id: "1" }) // 错误:应该使用taskId +TaskCreate({ subject: "标题", description: "描述" }) // 正确 +TaskUpdate({ task_id: "1", status: "completed" }) // 错误:应该使用taskId +``` + +## 常见问题解答 + +### Q1: 为什么需要先使用ToolSearch? +A: task工具是延迟加载的,它们的模式只有在被`ToolSearch`工具发现后才会发送给API。没有工具模式,模型无法知道正确的参数名和类型。 + +### Q2: 每次会话都需要使用ToolSearch吗? +A: 是的,每次新的会话都需要先使用ToolSearch发现工具。工具发现状态不会在会话之间保留。 + +### Q3: 使用Anthropic官方模型也需要这样吗? +A: 通常不需要。Anthropic官方模型对延迟加载工具的处理更智能,但为了兼容性,建议在使用task工具前都先使用ToolSearch。 + +### Q4: 可以一次性发现所有工具吗? +A: 可以,使用`ToolSearch("select:TaskGet,TaskCreate,TaskUpdate,TaskList")`可以一次性发现所有task工具。 + +### Q5: 如果忘记使用ToolSearch会怎样? +A: 会收到参数验证错误,提示需要先使用ToolSearch。按照错误信息的指导操作即可。 + +## 最佳实践 + +1. **会话开始时发现工具**:在开始使用task工具前,先调用ToolSearch +2. **批量发现**:一次性发现所有需要的task工具 +3. **检查参数名**:确保使用正确的驼峰命名参数 +4. **查看错误信息**:如果遇到错误,仔细阅读错误信息中的指导 + +## 示例工作流 + +```javascript +// 1. 开始新会话 +// 2. 发现task工具 +ToolSearch("select:TaskGet,TaskCreate,TaskUpdate,TaskList") + +// 3. 创建任务 +const newTask = await TaskCreate({ + subject: "修复OpenAI兼容性问题", + description: "解决task工具在OpenAI兼容模型下的参数名问题" +}) + +// 4. 获取任务详情 +const taskDetails = await TaskGet({ taskId: newTask.id }) + +// 5. 更新任务状态 +await TaskUpdate({ + taskId: newTask.id, + status: "in_progress", + activeForm: "修复OpenAI兼容性问题" +}) + +// 6. 查看所有任务 +const allTasks = await TaskList() +console.log(`当前有 ${allTasks.tasks.length} 个任务`) + +// 7. 完成任务 +await TaskUpdate({ + taskId: newTask.id, + status: "completed" +}) +``` + +## 故障排除 + +### 错误:参数名不匹配 +**症状**:`taskId`参数缺失,发现`task_id`参数 +**解决**:确保使用驼峰命名的`taskId`,而不是蛇形命名的`task_id` + +### 错误:工具模式未发送 +**症状**:`This tool's schema was not sent to the API` +**解决**:先使用`ToolSearch("select:工具名")`发现工具 + +### 错误:工具不可用 +**症状**:工具调用失败,没有具体错误信息 +**解决**:检查工具是否启用(通过`isTodoV2Enabled()`),确保环境变量设置正确 + +## 相关配置 + +### 环境变量 +```bash +# 启用OpenAI兼容模式 +export CLAUDE_CODE_USE_OPENAI=1 +export OPENAI_API_KEY=your-api-key +export OPENAI_BASE_URL=https://api.deepseek.com + +# 配置模型映射 +export OPENAI_DEFAULT_SONNET_MODEL=deepseek-chat +export OPENAI_DEFAULT_OPUS_MODEL=deepseek-chat +export OPENAI_DEFAULT_HAIKU_MODEL=deepseek-chat +``` + +### 设置文件 +通过`/login`命令配置OpenAI兼容模式后,设置会保存在`~/.claude/settings.json`: +```json +{ + "modelType": "openai", + "openai": { + "baseURL": "https://api.deepseek.com", + "apiKey": "your-api-key", + "models": { + "haiku": "deepseek-chat", + "sonnet": "deepseek-chat", + "opus": "deepseek-chat" + } + } +} +``` + +## 总结 + +在使用OpenAI兼容模型时,task工具需要先通过`ToolSearch`发现才能正常使用。遵循"先发现,后使用"的原则,并注意参数名的正确格式(驼峰命名),可以确保task工具在OpenAI兼容模型下正常工作。 \ No newline at end of file diff --git a/src/services/tools/toolExecution.ts b/src/services/tools/toolExecution.ts index b4d9c81d1..71a6ea3b8 100644 --- a/src/services/tools/toolExecution.ts +++ b/src/services/tools/toolExecution.ts @@ -589,10 +589,23 @@ export function buildSchemaNotSentHint( if (!isDeferredTool(tool)) return null const discovered = extractDiscoveredToolNames(messages) if (discovered.has(tool.name)) return null + + const toolDisplayName = tool.userFacingName + ? tool.userFacingName(undefined) + : tool.name + return ( - `\n\nThis tool's schema was not sent to the API — it was not in the discovered-tool set derived from message history. ` + - `Without the schema in your prompt, typed parameters (arrays, numbers, booleans) get emitted as strings and the client-side parser rejects them. ` + - `Load the tool first: call ${TOOL_SEARCH_TOOL_NAME} with query "select:${tool.name}", then retry this call.` + `\n\nTool "${toolDisplayName}" is deferred-loading and needs to be discovered before use.\n` + + `When using OpenAI-compatible models (DeepSeek, Ollama, etc.), follow these steps:\n` + + `1. First discover the tool with ToolSearch: ${TOOL_SEARCH_TOOL_NAME}("select:${tool.name}")\n` + + `2. Then call ${toolDisplayName} tool\n` + + `\nExample:\n` + + `${TOOL_SEARCH_TOOL_NAME}("select:${tool.name}") → ${toolDisplayName}({ ... })\n` + + `\nImportant notes:\n` + + `• Use camelCase parameter names (e.g., taskId), not snake_case (task_id)\n` + + `• All task tools (TaskGet, TaskCreate, TaskUpdate, TaskList) need to be discovered first\n` + + `• You can discover them all at once: ${TOOL_SEARCH_TOOL_NAME}("select:TaskGet,TaskCreate,TaskUpdate,TaskList")\n` + + `\nSee docs/openai-task-tools.md for detailed guide.` ) }