T29 流式收尾:
- _stream_partial 每次流式调用前复位(防上次成功置位导致本次失败误抛不回退)
T31 dsh(deepseek-harness)功能对齐:
- OpenAICompatChat 重试退避:传输错误/408/429/5xx 指数退避(max_retries=2),4xx 不重试
- web_fetch 工具:公网 http/https 抓取,SSRF 防护(DNS 后拒绝私网/环回/链路本地/NAT64,
512KB/15s/12k 上限,二进制嗅探拒绝),agent.allow_net 开关(默认开)
- 原子写入:write_file/edit_file 临时文件 + os.replace(Windows EPERM 退避)
- 慢工具线程卸载:run_command/web_fetch/search_files 独立线程 + asyncio.sleep 轮询
- search_files:os.walk 修剪依赖目录(替代 rglob 全量物化),不跟随符号链接
- run_command:危险命令黑名单独立拦截 + 显式 COMSPEC/sh 解释器执行
- 重复调用提醒:同工具同参数第 3 次起回喂系统提示 + repeat_warning 事件
- 会话重命名:PATCH /agent/sessions/{sid} + 前端 ✎
- 前端:SettingsView 适配密钥打码(留空保留),SPA 重新构建
- 新增 tests/test_agent_features.py(9 项);全量 318 测试通过
180 lines
13 KiB
Markdown
180 lines
13 KiB
Markdown
# 实现方案 v4:模型池与工具智能体
|
||
|
||
> 编写日期:2026-09(git 记录)
|
||
> 状态:**当前权威增补方案**。在 v2(端云协同协作)/ v3(Web 应用化)基础上两个升级:
|
||
> ①「端云」叙事泛化为**多价位模型池**;② 新增 **zcode 式工具智能体**(模型操作工作区文件)。
|
||
> 前置:v3 已落地(异步 /chat + SSE + Vue SPA,262 项测试全绿)。
|
||
|
||
---
|
||
|
||
## 0. 一页速览
|
||
|
||
- **模型池**:`gateway/model_pool.py` 维护多价位异构模型条目(`local / budget / premium` 三档,
|
||
每条目 = 端点 + 凭据 + 模型名 + 单价 $/1M tokens),`roles` 把条目指派给
|
||
architect / worker / agent 三个角色;留空 = 沿用经典单模型设置(向后兼容)。
|
||
`build_v2_pipeline` 池指派优先;`V2Stats` 新增按模型 token/成本分账(`by_model`)。
|
||
- **工具智能体**:`router_system/tools.py`(纯标准库)提供 WorkspaceTools
|
||
(list_dir / read_file / write_file / **edit_file / search_files / run_command**,
|
||
路径关押在根目录内)与 ToolLoop(通用工具循环,轮数 + token 双护栏,逐事件回调);
|
||
`gateway/agent.py` 提供 OpenAI 兼容工具调用客户端与运行服务
|
||
(事件落盘 `agent_runs/{id}/events.jsonl`),`/agent` 端点 + SSE 实时推送。
|
||
- **可选择工作目录**(参考 deepseek-harness 的"打开文件夹"体验):智能体可作用于
|
||
用户从磁盘上选择的任意项目目录(`/agent/fs` 目录浏览 + `/agent/workspaces` 最近列表),
|
||
运行记录所用目录;`run_command` 默认关闭(allow_shell 开关),开启后在工作区内执行命令
|
||
(超时熔断 + 输出截断 + Windows CREATE_NO_WINDOW)。
|
||
- **工程约束不变**:`router_system` 零第三方依赖;测试全部封闭(httpx 注入/假实现);
|
||
不上分布式中间件;金额护栏沿用 api_token_cap 思想(agent 有独立 token_cap)。
|
||
|
||
## 1. 设计决策(D1–D6,不得推翻)
|
||
|
||
- **D1 价位为主轴,位置为属性**:系统按"模型成本档位"组织协作,不按"端/云"位置。
|
||
本地 llama.cpp 是 local(零边际成本)档;云端按单价分 budget / premium。
|
||
「端云协同」成为两档部署特例,命题叙事不改、系统叙事升级。
|
||
- **D2 池条目存配置不存权重**:云端模型只存端点/凭据/单价元数据;本地模型经
|
||
llama_manager 管理 .gguf。api_key 服务端持久化、接口打码返回(`api_key_set` + 前 6 位)。
|
||
- **D3 角色指派可热切换**:`PUT /pool/roles` 后 `rebuild_pipeline()` 立即生效;
|
||
测试注入 `worker_cfg_override` 优先级最高(保测试封闭)。
|
||
- **D4 工具被关押在根目录**:所有路径 join 后 resolve,必须仍位于工作区根内
|
||
(防 `../` 与绝对路径逃逸);读取/写入/编辑均有长度与唯一性约束
|
||
(edit_file 的 old_string 必须唯一命中,歧义即拒绝);search_files 跳过依赖/构建目录与
|
||
二进制大文件。工作区根本身可由用户选择("打开文件夹"),关押相对所选根生效。
|
||
- **D5 智能体双护栏 + shell 默认关**:轮数上限(agent.max_rounds,默认 8)+
|
||
token 熔断(agent.token_cap,默认 20000);`run_command` 受 allow_shell 开关(默认关),
|
||
开启后有 shell_timeout_s 超时与输出截断;触顶强制总结,不再给工具。
|
||
- **D6 事件文件为单一事实源**:智能体过程逐条追加 events.jsonl,SSE 生成器轮询增量推送
|
||
(晚加入者从头回放);前端收到 final 即关闭连接并忽略后续回放,防止重复渲染。
|
||
|
||
## 2. 接口清单(新增)
|
||
|
||
| 端点 | 说明 |
|
||
|---|---|
|
||
| `GET /pool` | 池全量(roles + entries,key 打码) |
|
||
| `POST /pool` | 新增/更新条目(api_key 留空 = 保留原值) |
|
||
| `DELETE /pool/{id}` | 删除条目(角色指派联动清空) |
|
||
| `PUT /pool/roles` | 角色指派(architect/worker/agent → 条目 id 或空串) |
|
||
| `POST /pool/{id}/test`、`GET /pool/{id}/models` | 条目连通测试 / 模型列表探测 |
|
||
| `POST /agent` | 提交任务 `{task, pool_id?, workspace?}`,workspace 为所选工作目录 |
|
||
| `GET /agent/{id}/status` `/events` `/stream`(SSE) | 状态(含所用工作目录)/ 全量事件 / 实时流 |
|
||
| `GET /agent/workspace?path=&root=`、`GET /agent/file?path=&root=` | 工作区浏览/读取(root 指定所选目录) |
|
||
| `GET /agent/fs?path=` | 目录选择器:浏览本地目录(只列子目录,空 path 列盘符) |
|
||
| `GET /agent/workspaces`、`POST /agent/workspaces` | 当前+最近工作区 / 打开(可 create 新建)并设为当前 |
|
||
|
||
`/config/models`、`/config/ping` 抽出共用 `_list_backend_models` / `_probe_backend`。
|
||
|
||
## 3. 前端(v3 四页基础上)
|
||
|
||
- 设置页新增「🗄️ 模型池」区块:三角色指派下拉、条目表格(档位徽标/单价/启用/测试/编辑/删除)、
|
||
内联添加表单(模型列表探测 datalist)。
|
||
- 新增「🤖 智能体」页(`/agent`):任务输入 + 模型选择(池条目)、工具调用时间轴
|
||
(round / tool_call / tool_result / usage / final)、左侧工作区文件浏览与预览。
|
||
- 指标页新增「按模型分账」卡片(by_model 表格)。
|
||
|
||
## 4. 任务登记(T16–T21,衔接 v2 表 T15 之后)
|
||
|
||
| T | 内容 | 状态 |
|
||
|---|------|------|
|
||
| T16 | 工具内核 `router_system/tools.py` + `tests/test_tools.py`(11 项) | ✅ |
|
||
| T17 | 模型池 `gateway/model_pool.py` + `/pool` 端点 + 管线池解析 + by_model 分账 + 测试 | ✅ |
|
||
| T18 | 智能体 `gateway/agent.py` + `/agent` 端点(SSE)+ 测试(含越界/校验/池指派) | ✅ |
|
||
| T19 | 前端 API 层 + 设置页模型池 UI | ✅ |
|
||
| T20 | 智能体页 AgentView + 指标页分账卡 + SSE 终态去重 | ✅ |
|
||
| T21 | 集成验证:真跑 3 轮工具任务(写/列/读)+ 全量 262 测试 + 浏览器实测 | ✅ |
|
||
| T22 | 工具扩展(harness 风格):edit_file 唯一替换 / search_files 跨文件搜索 / run_command(opt-in) | ✅ |
|
||
| T23 | 工作区选择:/agent/fs 目录浏览 + 最近工作区 + /agent 带 workspace + 状态记录目录 | ✅ |
|
||
| T24 | 前端:工作区选择栏(目录浏览器模态/最近/shell 开关)+ edit diff 卡 + 命令卡 | ✅ |
|
||
| T25 | 集成验证:选定真实目录跑"读→精确编辑→运行验证"全链路 + 274 测试全绿 | ✅ |
|
||
|
||
## 5. 实验设计建议(论文用)
|
||
|
||
- **位置无关性**:同一 Worker 角色分别绑 local(llama.cpp)与 budget(低价 API),
|
||
跑同一数据集对比 token/成本/质量——证明架构"价位驱动"而非"位置驱动"。
|
||
- **价位帕累托曲线**:agent/worker 角色在 local→budget→premium 间切换,
|
||
画成本–质量帕累托图(metrics 的 by_model 直接供数)。
|
||
|
||
## 6. 运行时目录(均 gitignore)
|
||
|
||
`config/model_pool.json`(池持久化)、`agent_runs/{id}/`(events.jsonl + status.json)、
|
||
`agent_workspace/`(智能体默认工作区,可在设置 agent.workspace_dir 调整)。
|
||
|
||
## 7. 增补:两级智能体(D7,T26)
|
||
|
||
- **决策 D7(价位协作延伸到智能体形态)**:`POST /agent` 支持 `executor_pool_id`——
|
||
规划者(大模型:agent 角色或经典 Architect 设置)负责拆解指令与审查裁决;
|
||
执行者(本地小模型:llama_server/openai 池条目)负责工具轮;中间状态写
|
||
`agent_runs/{id}/handoff.json`(智能体版"交流文本":instructions/acceptance/exchanges)。
|
||
- **协议**:规划者两阶段 JSON(plan: instructions+acceptance;review: verdict done|redo +
|
||
reply_to_executor + final_answer),解析失败回喂重试一次再降级(对齐仓库 JSON 纪律);
|
||
redo 时裁决意见作为执行者下一轮指令,交接上限 `agent.max_handoffs`(默认 2)。
|
||
- **护栏**:规划者与执行者不得为同一池条目;执行轮 token 与整体 token_cap 共享预算;
|
||
ToolLoop 内层循环 `emit_final=False`,终态事件由外层编排统一发出(防前端 SSE 提前收口)。
|
||
- **事件**:新增 `phase`(plan/execute/review,带模型名)与 `message`(planner/executor 正文)
|
||
两类事件,前端以阶段徽标 + 双色消息卡渲染。
|
||
|
||
## 8. 增补:会话式智能体(D8,T27)
|
||
|
||
- **决策 D8(对齐 dsh 的会话范式)**:智能体从"一次性任务"升级为**多轮会话**——
|
||
`POST /agent/sessions` 建会话(绑定工作区/执行者配置),后续任务带 `session_id`
|
||
递交,既往轮次折叠为对话历史注入模型(近 6 轮,每条截断 1500 字);
|
||
轮次(任务/答复/工具步数/token)持久化到 `agent_runs/sessions/{sid}.json`。
|
||
- **停止**:`POST /agent/{id}/cancel` 取消运行中任务(asyncio cancel + 状态标记
|
||
cancelled_by_user)。
|
||
- **前端**:AgentView 重构为 dsh 式对话界面——左侧会话列表、居中消息流
|
||
(用户蓝气泡 / 助手白卡)、底部 composer(工作区/执行者/命令开关收为 chips,
|
||
Enter 发送,运行中变红色停止钮);工具过程折叠收纳,历史轮次仅显示步数摘要。
|
||
- **工程**:ToolLoop 增 history 参数;工具步数统一在事件写入层统计;
|
||
会话存储测试隔离(防泄漏进真实 agent_runs/sessions/)。
|
||
|
||
## 9. 增补:审批流与流式输出(D9/D10,T28/T29)
|
||
|
||
- **决策 D9(操作审批,对齐 dsh)**:`agent.approval_policy` = off | dangerous(默认:
|
||
写/编辑/命令询问,只读自动放行)| all;工具执行前经 approval_hook 挂起等待,
|
||
UI 两按钮(拒绝 / 允许一次),超时 `approval_timeout_s`(默认 120s)自动拒绝
|
||
(fail-closed);拒绝结果回喂模型可改道;approval_request/decided 事件对入审计。
|
||
- **决策 D10(token 级流式)**:OpenAICompatChat 默认 stream=True(SSE 逐段解析,
|
||
tool_calls 碎片按 index 组装、不在正文展示;include_usage 计量);流式失败自动
|
||
回退非流式一次(已有部分增量输出时如实抛出);ToolLoop 经 on_delta 转发,
|
||
DeltaThrottle ≥48 字符节流落 delta 事件;前端打字机式实时渲染 + 光标。
|
||
- **配套修复**:Vue 响应式丢失 bug——闭包持有 push 前原始对象导致过程事件不渲染,
|
||
改取响应式代理对象。
|
||
|
||
## 10. 增补:安全基线(D11,T30,Mimosa 深度扫描驱动)
|
||
|
||
- **决策 D11(默认最小暴露面)**:
|
||
- **路径参数 ID 白名单**:`/runs|/agent|/agent/sessions` 的路径参数只允许
|
||
`[a-zA-Z0-9_-]{1,64}`(系统生成 ID 的字符集),非法一律 404——杀灭 Windows
|
||
反斜杠穿越变体(如 `/runs/x/artifacts/..%5C..%5C..%5C.env` 直读 .env)。
|
||
- **工件双重关押**:artifacts 端点解析后必须仍在 artifacts 目录内;pipeline
|
||
`_save_artifact`/`_read_artifact` 对模型输出的工件名消毒(剥路径成分)。
|
||
- **下载关押**:`/llama/download` 的 dest 解析后必须位于 models/ 内;URL 协议
|
||
白名单 http/https 在 HF 别名转换**之前**判定(防 file:/// 被拼成 HF 地址)。
|
||
- **密钥打码**:GET /config 的 architect.api_key 只回前 6 位 + api_key_set;
|
||
PUT 空串/缺省=保留原值(对齐 D2 池条目语义,前端"已设置,留空保留")。
|
||
- **Web 信任围栏**:TrustedHostMiddleware 默认只信 localhost/127.0.0.1/[::1]
|
||
(GATEWAY_TRUSTED_HOSTS 可覆盖,"*"=放行全部),防 DNS rebinding 打到本网关;
|
||
api.py `__main__` 默认绑定 127.0.0.1(scripts/serve.py 本就如此)。
|
||
- **独立命令拦截**:run_command 危险模式黑名单(format / rd /s / rm -rf / /
|
||
shutdown / curl|sh 等)先于审批独立拦截;shell 经显式 `"%COMSPEC%" /c` /
|
||
`/bin/sh -c` 执行(语义同 shell=True,但解释器路径受控)。
|
||
- **CSPRNG**:review 抽样缺省随机源改 random.SystemRandom。
|
||
|
||
## 11. 增补:dsh 功能对齐(D12,T31)
|
||
|
||
- **决策 D12(对齐 deepseek-harness 的健壮性功能)**:
|
||
- **重试退避**:OpenAICompatChat 非流式路径(含流式失败回退)对传输错误/
|
||
408/429/5xx 指数退避重试(max_retries=2、retry_delay_s=1.0);4xx 不重试。
|
||
- **web_fetch 工具**:抓公网 http/https 文本,dsh web_fetch 同款 SSRF 防护——
|
||
DNS 解析后拒绝私网/环回/链路本地/保留/NAT64 地址;512KB/15s/12k 字符上限、
|
||
二进制嗅探拒绝;受 `agent.allow_net` 开关(默认开),归入只读工具集。
|
||
- **原子写入**:write_file/edit_file 走同目录临时文件 + os.replace(Windows
|
||
EPERM 退避一次后降级直写),防崩溃留半截文件。
|
||
- **慢工具线程卸载**:run_command/web_fetch/search_files 在独立守护线程执行
|
||
(asyncio.sleep 轮询等完成——补丁版 TestClient 的每请求独立事件循环不推进
|
||
run_in_executor 桥接,轮询在生产/测试两端都可靠),快工具保持内联。
|
||
- **search 修剪**:os.walk 修剪依赖/构建目录(替代 rglob 全量物化),不跟随
|
||
符号链接目录,限量行为不变。
|
||
- **重复调用提醒**:同工具同参数第 3 次起在回喂结果附系统提示 + repeat_warning
|
||
事件(防模型原地打转,dsh repeat-tool-reminder 同款)。
|
||
- **会话重命名**:`PATCH /agent/sessions/{sid}` + 前端会话列表 ✎ 按钮。
|
||
- **D10 收尾修复**:_stream_partial 每次流式调用前复位(防上次成功置位导致
|
||
本次失败误抛而不回退)。
|