运行时行为验证 #1: 工具调用循环协议
🆕 本章通过实际 daemon 运行时行为验证补全
日期: 2026-06-25 | Daemon: v4.25.5 | Model: deepseek-v4-flash
1. 背景
此前静态分析发现了 ChatEvent 的 ToolStart/ToolOutput/ToolResult 事件变体,但从未在运行时验证过完整调用流程。本报告通过实际 HTTP 请求捕获完全的工具调用循环。
2. 工具调用完整 SSE 事件序列
通过 POST /chat 发送要求文件操作的消息,捕获到完整事件流:
消息: "在当前目录下创建一个test_runtime.md文件,内容是runtime test"
事件序列:
┌─────────────────────────────────────────────────────────────────────┐
│ 1. reasoning (多个碎片) ← 模型思考阶段 │
│ {"type":"reasoning","content":"The user wants me to create..."} │
│ │
│ 2. tokens (第一轮统计) │
│ {"type":"tokens","prompt":6204,"completion":91,"total":6295} │
│ │
│ 3. tool_start ← 工具调用开始 │
│ {"type":"tool_start", │
│ "id":"call_00_zm24QnryxjpAbEVEoSsx9133", │
│ "name":"write_file", │
│ "arguments":{"file_path":"test_runtime.md","content":"rt..."}} │
│ │
│ 4. tool_result ← 工具执行结果 │
│ {"type":"tool_result", │
│ "id":"call_00_zm24QnryxjpAbEVEoSsx9133", │
│ "name":"write_file", │
│ "output":"Created /path/test_runtime.md (12 bytes, 1 lines)", │
│ "success":true,"duration_ms":3} │
│ │
│ 5. reasoning (第二轮) ← 模型根据工具结果继续推理 │
│ {"type":"reasoning","content":"Done. The file has been..."} │
│ │
│ 6. text ← 最终文本回复 │
│ {"type":"text","content":"文件 `test_runtime.md` 已创建..."} │
│ │
│ 7. tokens (第二轮统计) │
│ {"type":"tokens","prompt":6394,"completion":27,"total":6421} │
│ │
│ 8. done ← 会话结束 │
│ {"type":"done","tokens":6421,"tool_calls":1, │
│ "session_id":"68ef5b9d-4287-40d2-ac36-3e9c4dc35961"} │
└─────────────────────────────────────────────────────────────────────┘2.1 关键发现
| 特性 | 发现 |
|---|---|
| tool_output 事件不存在 | Daemon 协议中没有 tool_output 流式输出事件。所有工具输出在完成后一次性通过 tool_result 返回 |
| permission_request 不触发 | 对于写文件这种危险操作,daemon 没有触发权限请求事件——工具直接自动执行 |
| tool_start 参数格式 | arguments 是 JSON 字符串(非对象),由模型决定参数内容 |
| tool_result 格式 | output 是纯文本,含完整文件路径和字节数。success: bool + duration_ms: u64 |
| 两轮 tokens 事件 | 工具调用前有一次 token 统计(模型第一次回答),工具执行后追加统计 |
| 多工具调用 | 复杂任务可能触发连续多个 tool_start → tool_result 对(见下文 glob 示例) |
2.2 多工具调用示例
当任务需要多次工具调用时,事件序列为:
1. reasoning → tokens
2. tool_start #1 → tool_result #1 (glob *.md)
3. tool_start #2 → tool_result #2 (glob **/*.md)
4. reasoning (下一轮)
5. text → tokens → done关键: 工具链中没有 tool_output,没有权限请求,没有人工确认。Daemon 自动串行执行所有工具。
3. 工具调用的 JSON 结构拆解
3.1 tool_start 事件
json
{
"type": "tool_start",
"id": "call_00_X1ja7qrtrIR9fvh1XbKn8073",
"name": "glob",
"arguments": "{\"pattern\": \"*.md\"}"
}| 字段 | 类型 | 说明 |
|---|---|---|
type | "tool_start" | 事件类型 |
id | string | 工具调用唯一 ID(格式: call_XX_<random>) |
name | string | 工具名(glob, write_file, read_file, bash 等) |
arguments | string | JSON 编码的参数字符串 |
3.2 tool_result 事件
json
{
"type": "tool_result",
"id": "call_00_X1ja7qrtrIR9fvh1XbKn8073",
"name": "glob",
"output": "59 files found:\nAGENT_EXECUTION_ENGINE.md\n...",
"success": true,
"duration_ms": 40
}| 字段 | 类型 | 说明 |
|---|---|---|
type | "tool_result" | 事件类型 |
id | string | 与 tool_start 相同的 ID |
name | string | 工具名 |
output | string | 工具调用输出(文本) |
success | bool | 是否成功 |
duration_ms | u64 | 执行耗时(毫秒) |
3.3 done 事件
json
{
"type": "done",
"tokens": 6421,
"tool_calls": 1,
"session_id": "68ef5b9d-4287-40d2-ac36-3e9c4dc35961"
}| 字段 | 类型 | 说明 |
|---|---|---|
type | "done" | 会话结束 |
tokens | u64 | 总 token 消耗 |
tool_calls | u64 | 本次会话工具调用次数 |
session_id | string | Daemon 分配的会话 UUID |
4. 权限系统运行时行为
重要发现: 在 daemon 的 /chat 接口中,所有工具调用均自动执行,不触发权限请求。
| 操作 | 权限请求 | 行为 |
|---|---|---|
| 读取文件 | 无 | 直接执行并返回结果 |
| 写入文件 | 无 | 直接写入 |
| 执行命令 | 无 | 直接执行 |
| 搜索文件 | 无 | 直接搜索 |
这与之前通过 PermissionDecisionRequest 分析得出的权限系统不同——/chat 端点是无权限检查的完全自动模式。权限请求仅存在于 WebUI 的 /live SSE 通道和 POST /chat/permission 端点。
5. 对代理实现的意义
| 之前假设 | 运行时验证结果 | 影响 |
|---|---|---|
| 存在 tool_output 事件 | ❌ 不存在 | Proxy 无需处理 tool_output → OpenAI chunk 的转换 |
| 需要处理 permission_request | ✅ 不触发 | Proxy 无需实现权限交互逻辑 |
| tool_start 参数是对象 | ✅ 是 JSON 字符串 | 需要 json.loads() 解析再传递 |
tool_result 含 output | ✅ 确认 | 可映射到 OpenAI 的 tool_call_id + content |
| 多工具间有 barrier | ✅ 串行执行 | 每个 tool_result 后模型重新推理 |
📅 最后更新: 2026-06-25🔍 验证方法: 实际 daemon 运行时 curl 抓包
逆向命令索引
bash
# 运行时捕获工具调用流
curl -N -X POST http://localhost:13456/chat \
-H "Authorization: Bearer $(cat ~/.atomcode/auth.toml | grep access_token | cut -d'"' -f2)" \
-H "Content-Type: application/json" \
-d '{"message":"写一个 hello.c 文件"}' 2>&1 | while IFS= read -r line; do
[[ "$line" == data:* ]] && echo "$line"
done