update at 2025-10-15 15:07:45
This commit is contained in:
287
docs/BUG_FIX_RECURSIVE_TOOL_CALLS.md
Normal file
287
docs/BUG_FIX_RECURSIVE_TOOL_CALLS.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# 🐛 Bug 修复:工具调用链递归处理
|
||||
|
||||
## 问题描述
|
||||
|
||||
AI 第二次调用了 `publish_content` 工具,但工具没有被实际执行。
|
||||
|
||||
## 问题现象
|
||||
|
||||
### Client 日志
|
||||
```javascript
|
||||
// 第一次 AI 调用
|
||||
🔧 AI 调用: mcp__check_login_status
|
||||
✅ 工具执行成功
|
||||
|
||||
// 第二次 AI 调用
|
||||
🔧 AI 调用: mcp__publish_content
|
||||
arguments: {"title":"家庭版酸菜鱼...", "content":"...", ...}
|
||||
|
||||
// 日志在这里停止,没有执行 publish_content
|
||||
⏱️ [callModelStream] 真流式总耗时: 40972.00 ms
|
||||
```
|
||||
|
||||
### Server 日志
|
||||
```
|
||||
[TOOL] check_login_status: Execution completed ← 只有第一个工具
|
||||
[CONNECTION] CONNECTION CLOSED
|
||||
|
||||
// publish_content 根本没有被调用!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 根本原因
|
||||
|
||||
在 `executeToolCalls` 方法中,代码调用 `sendChatRequestStream` 后就直接结束了,**没有检查 AI 是否再次调用了工具**!
|
||||
|
||||
### 问题代码
|
||||
|
||||
```typescript
|
||||
async executeToolCalls(...) {
|
||||
// 1. 执行工具
|
||||
const toolResults = [...]
|
||||
|
||||
// 2. 将结果发送给 AI
|
||||
await modelServiceManager.sendChatRequestStream(
|
||||
service.id,
|
||||
messages,
|
||||
selectedModel,
|
||||
onChunk,
|
||||
tools
|
||||
)
|
||||
|
||||
// ❌ 直接结束!没有检查 AI 是否再次调用工具
|
||||
}
|
||||
```
|
||||
|
||||
### 调用流程
|
||||
|
||||
```
|
||||
用户: "发布文章"
|
||||
↓
|
||||
AI 第一次调用: check_login_status
|
||||
↓
|
||||
executeToolCalls() 执行 check_login_status
|
||||
↓
|
||||
发送结果给 AI
|
||||
↓
|
||||
AI 第二次调用: publish_content ← 这里返回了 tool_calls
|
||||
↓
|
||||
❌ executeToolCalls() 结束,没有继续处理!
|
||||
↓
|
||||
工具调用链断裂
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 添加递归处理逻辑
|
||||
|
||||
```typescript
|
||||
async executeToolCalls(...) {
|
||||
// 1. 执行工具
|
||||
const toolResults = [...]
|
||||
|
||||
// 2. 将结果发送给 AI
|
||||
const result = await modelServiceManager.sendChatRequestStream(
|
||||
service.id,
|
||||
messages,
|
||||
selectedModel,
|
||||
onChunk,
|
||||
tools
|
||||
)
|
||||
|
||||
// 3. ✅ 递归处理:如果 AI 再次调用工具,继续执行
|
||||
if (result.data?.toolCalls && result.data.toolCalls.length > 0) {
|
||||
console.log('🔁 AI 再次调用工具,递归执行')
|
||||
await this.executeToolCalls(
|
||||
conversation,
|
||||
result.data.toolCalls,
|
||||
mcpServerId,
|
||||
model,
|
||||
onChunk,
|
||||
tools
|
||||
)
|
||||
} else {
|
||||
console.log('✅ 工具调用链完成')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 修复后的完整流程
|
||||
|
||||
```
|
||||
用户: "发布文章,主题:酸菜鱼"
|
||||
↓
|
||||
AI 第一次调用: check_login_status
|
||||
↓
|
||||
executeToolCalls() 第一次调用
|
||||
→ 执行 check_login_status
|
||||
→ 发送结果给 AI
|
||||
→ 检查 AI 响应
|
||||
→ 发现 AI 再次调用了 publish_content ✅
|
||||
↓
|
||||
executeToolCalls() 第二次调用(递归)
|
||||
→ 执行 publish_content
|
||||
→ 发送结果给 AI
|
||||
→ 检查 AI 响应
|
||||
→ 没有更多工具调用 ✅
|
||||
↓
|
||||
工具调用链完成
|
||||
↓
|
||||
AI 生成最终友好回复
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 支持的调用模式
|
||||
|
||||
### 1. 单次工具调用
|
||||
```
|
||||
AI → Tool → AI (完成)
|
||||
```
|
||||
|
||||
### 2. 两次工具调用
|
||||
```
|
||||
AI → Tool A → AI → Tool B → AI (完成)
|
||||
```
|
||||
|
||||
### 3. 多次工具调用链
|
||||
```
|
||||
AI → Tool A → AI → Tool B → AI → Tool C → AI (完成)
|
||||
```
|
||||
|
||||
### 4. 并行工具调用(待实现)
|
||||
```
|
||||
AI → [Tool A, Tool B, Tool C] → AI (完成)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 预期日志
|
||||
|
||||
```javascript
|
||||
// 第一次 AI 调用
|
||||
🔧 [makeChatRequestStream] 最终收集到工具调用: 1 个
|
||||
工具 [0]: {name: "mcp__check_login_status"}
|
||||
|
||||
🔧 [executeToolCalls] 执行 1 个工具调用
|
||||
✅ [MCPClientService.callTool] 工具调用成功
|
||||
|
||||
🤖 [executeToolCalls] 将工具结果发送给 AI
|
||||
🔧 [executeToolCalls] 继续传递工具列表: 3 个
|
||||
|
||||
// 第二次 AI 调用
|
||||
🔧 [makeChatRequestStream] 最终收集到工具调用: 1 个
|
||||
工具 [0]: {name: "mcp__publish_content", arguments: "{...}"}
|
||||
|
||||
🔁 [executeToolCalls] AI 再次调用工具,递归执行: 1 个 ← 新增!
|
||||
|
||||
// executeToolCalls 第二次调用(递归)
|
||||
🔧 [executeToolCalls] 执行 1 个工具调用
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🔧 [executeToolCalls] 工具调用详情:
|
||||
- 完整工具名: mcp__publish_content
|
||||
- 提取工具名: publish_content
|
||||
- 参数: {"title":"...", "content":"...", ...}
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🔧 [MCPClientService.callTool] 准备调用工具
|
||||
- 工具名称: publish_content
|
||||
- 参数: {...}
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
✅ [MCPClientService.callTool] 工具调用成功 ← 这次真的执行了!
|
||||
✅ [executeToolCalls] 工具调用链完成
|
||||
```
|
||||
|
||||
### Server 端日志
|
||||
|
||||
```
|
||||
[TOOL] check_login_status: Execution completed
|
||||
[TOOL] publish_content: Starting execution ← 现在能看到了!
|
||||
[TOOL] publish_content: Content published successfully
|
||||
[TOOL] publish_content: Execution completed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 技术要点
|
||||
|
||||
### 为什么需要递归?
|
||||
|
||||
1. **工具调用链是动态的**
|
||||
- AI 可能需要多步完成任务
|
||||
- 第一步:检查状态
|
||||
- 第二步:执行操作
|
||||
- 第三步:验证结果
|
||||
|
||||
2. **支持复杂业务流程**
|
||||
```
|
||||
用户: "查询账户余额,如果大于100,就发布一篇文章"
|
||||
|
||||
AI → check_balance (余额: 150)
|
||||
→ AI 判断: 余额够了
|
||||
→ publish_content (发布文章)
|
||||
→ AI 返回: "已发布"
|
||||
```
|
||||
|
||||
3. **符合 Function Calling 规范**
|
||||
- OpenAI API 支持多轮工具调用
|
||||
- 每次都需要检查是否有新的 tool_calls
|
||||
|
||||
### Cherry Studio 的实现
|
||||
|
||||
查看 Cherry Studio 源码,它也使用递归或循环处理工具调用链:
|
||||
|
||||
```typescript
|
||||
// Cherry Studio 的递归实现
|
||||
async function handleToolCalls(toolCalls) {
|
||||
const results = await executeTools(toolCalls)
|
||||
|
||||
const response = await sendMessage({
|
||||
messages: [...history, ...results],
|
||||
tools
|
||||
})
|
||||
|
||||
// 递归处理
|
||||
if (response.toolCalls) {
|
||||
return await handleToolCalls(response.toolCalls)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `/web/src/services/chatService.ts` - Line 1036-1050
|
||||
- 添加递归处理逻辑
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
| 项目 | 修复前 | 修复后 |
|
||||
|------|--------|--------|
|
||||
| 第一次工具调用 | ✅ 执行 | ✅ 执行 |
|
||||
| 第二次工具调用 | ❌ 不执行 | ✅ 执行 |
|
||||
| 第三次及更多 | ❌ 不执行 | ✅ 递归执行 |
|
||||
| 工具调用链 | ❌ 断裂 | ✅ 完整 |
|
||||
| Server 收到请求 | ❌ 第二次无 | ✅ 全部收到 |
|
||||
|
||||
**修复状态**: ✅ 已修复
|
||||
**测试状态**: ⏳ 待测试
|
||||
**版本**: v1.0.2+ Recursive Fix
|
||||
|
||||
---
|
||||
|
||||
**更新时间**: 2024-01-15
|
||||
Reference in New Issue
Block a user