Skip to content

Commit 5421dce

Browse files
authored
feat: large tool call offload (#1276)
* docs(spec): add tool output guardrails * fix(agent): tool output guardrails * fix(agent): standardize tool offload extension * feat: extract path to session * fix: review issue * fix: error response on renderer * feat: add read_file pagination and whitelist-based tool offload - Raise offload threshold from 3000 to 5000 characters - Add whitelist for tools that require offload (execute_command, directory_tree, etc.) - Add offset/limit params to read_file for pagination support - Auto-truncate large files at 4500 chars with metadata hint - Prevents infinite offload loop when reading offloaded files * fix: independent reasoning time for each thinking block in agent loop
1 parent e8aec9a commit 5421dce

File tree

24 files changed

+835
-123
lines changed

24 files changed

+835
-123
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# 实施计划
2+
3+
## 现状梳理
4+
5+
- 真正的工具路由在 `src/main/presenter/toolPresenter`:
6+
- `ToolPresenter` + `ToolMapper`.
7+
- `agentPresenter/tool` 下的 `ToolRegistry`/`toolRouter` 目前未被运行路径使用.
8+
- `ToolCallProcessor` 会把工具结果直接拼接进 `conversationMessages`, 无大小控制.
9+
- `directory_tree` 实现为无限递归.
10+
11+
## 方案设计
12+
13+
### 1) `directory_tree` 深度控制
14+
15+
- 更新 schema:
16+
- `src/main/presenter/agentPresenter/acp/agentToolManager.ts`
17+
- `directory_tree` 增加 `depth?: number`(默认 1, 最大 3).
18+
- `src/main/presenter/agentPresenter/acp/agentFileSystemHandler.ts`
19+
- `DirectoryTreeArgsSchema` 同步增加 `depth`.
20+
- 递归限制:
21+
-`directoryTree` 内实现 `currentDepth` 控制.
22+
- root 深度为 0, 仅当 `currentDepth < depth` 时继续展开.
23+
24+
### 2) 工具输出 offload
25+
26+
- 触发阈值: 工具输出字符串长度 > 3000.
27+
- offload 存储:
28+
- 目录: `~/.deepchat/sessions/<conversationId>/`
29+
- 文件名: `tool_<toolCallId>.offload`
30+
- 内容: 原始完整工具输出(文本)
31+
- stub 内容:
32+
- 总字符数
33+
- 预览片段(1024 字符以内)
34+
- 完整文件绝对路径
35+
- 执行位置:
36+
-`ToolCallProcessor` 中对工具输出 string 化后做长度判断.
37+
- 仅替换 `tool_call_response` 和写入 `conversationMessages`.
38+
- 保持 `tool_call_response_raw` 不变, 避免影响 MCP UI/搜索结果.
39+
40+
### 3) 文件读取放行规则
41+
42+
- 文件类工具在读取 `~/.deepchat` 时需要额外校验:
43+
- 只放行 `~/.deepchat/sessions/<conversationId>` 下的文件.
44+
- 会话不匹配则拒绝访问.
45+
- 实现位置建议:
46+
-`AgentFileSystemHandler.validatePath` 增加路径前缀校验(读取时).
47+
- 路径安全:
48+
- 参考 `skillSyncPresenter/security.ts` 的路径规范化/安全校验逻辑.
49+
50+
### 4) 错误呈现
51+
52+
- 保证 error event 携带错误文本:
53+
- `AgentLoopHandler`/`StreamGenerationHandler`/`AgentPresenter` 的 error 事件
54+
统一包含 `error` 字段.
55+
- UI 侧:
56+
- `MessageBlockError.vue` 默认直接展示 raw text.
57+
- 不依赖 i18n key 时也能显示完整错误内容.
58+
59+
## 事件流
60+
61+
1. 工具调用完成 → `ToolCallProcessor` 取到输出.
62+
2. 输出超过 3000 字符 → offload 写文件 + 生成 stub.
63+
3. stub 进入 `conversationMessages``tool_call_response`.
64+
4. UI 展示 stub; 模型可用 file 工具读取完整路径.
65+
5. 出错时, error block 写入消息 + `STREAM_EVENTS.ERROR` 发送错误文本.
66+
67+
## 数据/文件结构
68+
69+
- `~/.deepchat/sessions/<conversationId>/tool_<toolCallId>.offload`
70+
- 原始完整工具输出文本
71+
72+
## 测试策略
73+
74+
- 单元测试:
75+
- `directory_tree` 深度限制(0/1/3/4).
76+
- tool output 超过 3000 字符时触发 offload, stub 格式正确.
77+
- 集成/手动:
78+
- 触发 `directory_tree` 大输出, 确认不再触发 10MB 失败.
79+
- 触发 provider error, UI 能直接看到 raw text.
80+
81+
## 风险与对策
82+
83+
- offload 文件增多:
84+
- 可在后续增加清理策略(按时间或数量).
85+
- conversationId 缺失场景:
86+
- 需定义降级行为(例如仅截断不 offload).
87+
- 若确认不存在此场景可忽略.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Agent 工具输出保护与错误呈现
2+
3+
> 状态: Draft
4+
> 日期: 2025-03-08
5+
6+
## 背景
7+
8+
当前 agent 运行中出现以下问题:
9+
10+
- Provider 报错会出现在主进程日志, 但 UI 未必能看到错误信息.
11+
- `directory_tree` 无深度限制, 可能产生巨量输出, 触发 10MB 限制.
12+
- 工具返回过大时会被直接注入到 LLM 上下文, 容易导致请求失败.
13+
14+
## 目标
15+
16+
- 让生成失败时的错误信息可见并可追溯.
17+
-`directory_tree` 增加深度控制, 最大不超过 3.
18+
- 对过大的工具输出做 offload, 用小的 stub 替代进入上下文.
19+
20+
## 非目标
21+
22+
- 不改动或替换 `agentPresenter/tool` 下的 `ToolRegistry`/`toolRouter`.
23+
- 不改变 MCP UI 资源与搜索结果的解析逻辑.
24+
25+
## 用户故事
26+
27+
1. 作为用户, 我希望生成失败时能在 UI 直接看到原始错误文本.
28+
2. 作为模型, 我希望能指定目录树深度, 避免一次输出过大.
29+
3. 作为系统, 我希望工具输出过大时自动 offload, 仍可在需要时读取完整内容.
30+
31+
## 验收标准
32+
33+
### 错误呈现
34+
35+
- 生成失败会将消息状态置为 `error`, 并写入一个 error block.
36+
- error block 直接显示 raw error 文本(不要求点击展开).
37+
- `STREAM_EVENTS.ERROR` 会携带错误文本, 便于 UI 展示或通知.
38+
39+
### `directory_tree` 深度控制
40+
41+
- `directory_tree` 增加 `depth` 可选参数, 默认值为 1.
42+
- depth 最大为 3, 超出直接校验失败.
43+
- 深度计数方式为 root=0.
44+
- depth=0: 仅返回根目录下条目, 不展开子目录.
45+
- depth=1: 展开一级子目录, 不包含孙级.
46+
- 响应格式保持不变: JSON 数组 `{ name, type, children? }`.
47+
48+
### 工具输出 offload
49+
50+
- 当工具输出字符串长度 > 3000 字符时触发 offload.
51+
- 完整内容写入:
52+
- `~/.deepchat/sessions/<conversationId>/tool_<toolCallId>.offload`
53+
- LLM 只收到 stub, 包含:
54+
- 总字符数
55+
- 预览片段
56+
- 完整文件的绝对路径
57+
- 模型可以通过文件类工具读取上述路径.
58+
- 文件类读取工具仅放行当前会话 `conversationId` 对应目录.
59+
- `tool_call_response_raw` 不被改写, 避免影响 MCP UI/搜索结果处理.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# 任务拆分
2+
3+
1. 更新 `directory_tree` schema 与描述, 增加 `depth`(默认 1, 最大 3).
4+
2.`AgentFileSystemHandler.directoryTree` 实现 depth 控制(root=0)并补充测试.
5+
3.`ToolCallProcessor` 增加工具输出长度检测:
6+
- 超过 3000 字符 → 写入 `~/.deepchat/sessions/<conversationId>/tool_<toolCallId>.offload`
7+
- 生成 stub 替换 `tool_call_response` 与上下文内容.
8+
4. 在文件工具读路径校验中放行 `~/.deepchat/sessions/<conversationId>`:
9+
- 仅限当前会话.
10+
5. 统一 error event 的 `error` 字段传递, 并确保写入 error block.
11+
6. 更新 `MessageBlockError.vue` 默认展示 raw text(不依赖 i18n key).
12+
7. 运行 `pnpm run format``pnpm run lint`.

0 commit comments

Comments
 (0)