mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
Fix bug OpenAI tooluse,Improve error messaging for deferred-loading tools under OpenAI‑compatible models. (#199)
* 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兼容模型使用指南
This commit is contained in:
190
docs/openai-task-tools.md
Normal file
190
docs/openai-task-tools.md
Normal file
@@ -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兼容模型下正常工作。
|
||||
@@ -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.`
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user