Files
map-client-vue/docs/BUG_FIX_RECURSIVE_TOOL_CALLS.md
2025-10-15 15:07:45 +08:00

288 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 🐛 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