mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-30 22:42:51 +00:00
docs: 两份 toolcall 文档对齐实现与部署实况
## 契约文档:状态头从「尚未实现」改为「已实现并部署」
生产已注册 7 个 `seq_*` 工具,但文档仍写着"设计定稿,尚未实现" ——
实现者(和读者)会以为 seq 还不存在。
新增 §9.3「实现落点」:设计稿 §8 写的是**六个** `seq_*` 工具,实现时
多了一个 `seq_when_call`(跨序列条件调用独立成工具,否则模型要手写
"先 seq_list 再挑目标再 seq_call",多一次往返且容易挑错),并记下三项
设计之外的修正(`seq_create` O(n²)、`Store.List()` 误认任意 `.json`、
存储用 AST 而非原始文本)。
同时点明:设计条款**仍是契约**,实现与本文冲突时以本文为准并修实现。
## 修一处预先存在的失效引用
§7 末尾 `见 §4.5` —— §4 只到 4.4,该小节不存在。改为按标题名引用
(`§4「结果契约」的 ErrToolNotFound 哨兵`):将来增删小节时不会再次失效。
自查脚本第一版把 8 个**存在**的章节误报成失效引用 —— 标题格式是
`## 1. 背景`(编号后跟 `.`),而我的正则要求编号后是空格。判据自己错了,
改成 `(\d+(?:\.\d+)*)\.?\s` 后才得到真实结果。
## 并行计划文档:部署小节 + 白名单专节
- 原「⚠ 部署前置条件(未完成)」改为「✅ 部署(已完成)」,补实际验证数据
- 记下"实例自述没有编排工具"不是说谎:生产二进制构建于 06:36、seq 引入
于 `da8841e`(更晚)⇒ `strings | grep -c internal/plugins/seq` 为 0。
这类"实例自述与代码状态不一致"应先查二进制构建时间,别急着怀疑提示词
- 新增「设备命令白名单改为可配置」:起因、替换语义、daemon 路径的疏漏
- 明确写下**已知局限**:白名单只匹配命令名、不看参数,
`find -delete` / `sed -i` / `sort -o` 仍放行 ⇒ **不要把它叫"只读白名单"**,
那会让人以为写操作被挡住了
- 记 106 此前无 `online` 日志的成因(旧 waiter 落在未 bind 时收 ping 会断连的
缺陷窗口),以及 `ssh` 吃掉 `read` 输入导致"喂了 yes 却说已取消"
删掉了初稿里一段"反引号内 `+=` 写进 heredoc 导致赋值落到子 shell"的说法 ——
脚本与 git 历史里都没有这种写法,属凭记忆误记,不能留。
文中数字均与现场核对:seq 工具 7、二进制 86784400 / 12691402、白名单 22 条。
This commit is contained in:
@ -3,7 +3,15 @@
|
||||
> **前置**:本文建立在《输入调度器设计》(`docs/zh/input-scheduler-design.md`)与
|
||||
> 《驻留式子 Agent 设计》(`docs/zh/resident-subagent-design.md`)之上。
|
||||
>
|
||||
> **状态**:本文是**设计定稿**,尚未实现。实现顺序见 §9。
|
||||
> **状态**:**已实现并部署**(2026-09-27)。
|
||||
> - 内核线(批内并行 + `ParallelSafe`/`Serial` 声明 + 保序落消息)与
|
||||
> 插件线 P1–P4(`seq` 插件 + 七个 `seq_*` 工具)均已完成,
|
||||
> 生产实例已注册 7 个 `seq_*` 工具。
|
||||
> - 实现过程中的实测结论(压测数据、踩坑、修法)见
|
||||
> 《toolcall-parallel-execution-plan》文末「附:更新前后全面压测结果」,
|
||||
> 实现落点见本文 §9.3。
|
||||
> - 本文的**设计条款仍是契约**:若实现与本文冲突,以本文为准并修实现。
|
||||
>
|
||||
> 标记:**[已定]**= 明确拍板;**[默认]**= 可逆取值,实现时在提交信息标注;
|
||||
> **[待定]**= 需决策后才动手。
|
||||
|
||||
@ -614,7 +622,7 @@ if ret, err := parent.ExecuteTool(name, args); err == nil {
|
||||
(`toolcall.go:104`)——这是**约定**不是契约:插件错误文案若恰好含该子串
|
||||
即被误判,并错误 fallback 到 io。
|
||||
⇒ 引入 `ErrToolNotFound` 哨兵(`errors.Is` 判别),见 **D5**。
|
||||
本次**内核侧一并实施**(见 §4.5)。
|
||||
本次**内核侧一并实施**(见 §4「结果契约」的 `ErrToolNotFound` 哨兵)。
|
||||
|
||||
#### 规则 5:`target` 形态非法
|
||||
|
||||
@ -703,6 +711,31 @@ if ret, err := parent.ExecuteTool(name, args); err == nil {
|
||||
**插件线可以复用内核并行面**,但**不要求内核新增任何接口**。
|
||||
若日后发现必须由内核代做(如统一授权闸),那是一次**单独的 SDK 扩展讨论**,
|
||||
不在本设计范围内。
|
||||
|
||||
### 9.3 实现落点(实际交付的七个工具)
|
||||
|
||||
设计稿 §8 写的是"六个 `seq_*` 工具",实现时**多了一个 `seq_when_call`** ——
|
||||
因为跨序列的条件调用(§5)在实现中被独立成一个工具,否则模型要手写
|
||||
"先 `seq_list` 再挑目标再 `seq_call`",多一次往返且容易挑错。
|
||||
|
||||
| 工具 | 作用 |
|
||||
| --- | --- |
|
||||
| `seq_create` | 保存序列(创建时即解析 + 静态校验 + 跨序列调用图环检测) |
|
||||
| `seq_run` | 执行序列(组内并行、组间串行) |
|
||||
| `seq_list` | 列出已存序列 |
|
||||
| `seq_call` | 按名调用序列 |
|
||||
| `seq_when_call` | 条件成立时才调用目标序列 |
|
||||
| `seq_delete` | 删除序列 |
|
||||
| `seq_help` | 写序列前的集中入口(示例均经真实解析器验证) |
|
||||
|
||||
实现期的三项**设计之外的修正**(都是判据跑出来的,不是拍脑袋):
|
||||
|
||||
1. **`seq_create` 的 O(n²)**:`CheckGraph` 原先每次都 `List()+Load()` 全部序列。
|
||||
改为调用图缓存 + `Save`/`Delete` 增量维护。
|
||||
★ 性能优化**不得**把校验挪到运行期 —— 目标存在性与环检测仍必须在保存时做。
|
||||
2. **`Store.List()` 把任意 `.json` 当序列**:改用专属 `.seq.json` 后缀。
|
||||
3. **存储用 AST 而非原始文本**:执行期不重新解析,避免解析器与执行器语义漂移。
|
||||
|
||||
## 10. 待定项
|
||||
|
||||
| 编号 | 问题 | 建议 |
|
||||
|
||||
@ -380,7 +380,25 @@ P3 落地时暴露的**真实缺陷**(不是新需求):
|
||||
压测脚本(4)、SDK(3)、io(3)、cmd 插件(2)。**未触及** `distill.go` /
|
||||
`onnx.go` / `nlp`,而工具调用路径本身不经过 ONNX ⇒ 上面的结论对生产成立。
|
||||
|
||||
### ⚠ 部署前置条件(未完成)
|
||||
### ✅ 部署(已完成 2026-09-27)
|
||||
|
||||
生产实例已更新到 `d3eaff4`+ 并验证通过:
|
||||
|
||||
| 项 | 结果 |
|
||||
| --- | --- |
|
||||
| 二进制 | 86,496,624 → **86,784,400** 字节(onnxruntime) |
|
||||
| 服务 | `active`、`kernel ready`、LLM 可达(unreachable=0) |
|
||||
| 多模态空间 | `provider=chineseclip dim=512 modalities=[text image]` |
|
||||
| `seq_*` 工具 | **7 个全部注册** |
|
||||
| 适配器 | md5 **完全一致**(`bf1dff86…`)—— 无 `.bundled` 清单 ⇒ 首次升级不覆盖已有文件 |
|
||||
| 备份 | `/var/tmp/homed-backup-20260927-194554`(含 `ROLLBACK.sh`) |
|
||||
|
||||
> 注:部署前生产二进制构建于**当天 06:36**,而 `seq` 引入于 `71c894c`(更晚)
|
||||
> ⇒ 旧实例的 `strings /usr/local/bin/homed | grep -c internal/plugins/seq` 为 **0**。
|
||||
> 它在 QQ 上如实回答"没有编排工具"**并不是说谎**,是确实没有。
|
||||
> 这类"实例自述与代码状态不一致"应先查二进制构建时间,别急着怀疑提示词。
|
||||
|
||||
### ⚠ 部署前置条件
|
||||
|
||||
生产二进制是 **`-tags=onnxruntime`** 构建(strip 后 75MB、`.rodata` 62.5MB),
|
||||
普通 `go build` 只有 28MB。`deploy/packaging/package-linux.sh:139` 会显式拒绝
|
||||
@ -391,3 +409,97 @@ P3 落地时暴露的**真实缺陷**(不是新需求):
|
||||
|
||||
⇒ **必须走 `deploy/packaging/build.sh`(需 `libonnxruntime.so` 与
|
||||
`CHINESECLIP_BUNDLE_DIR` 资产)才能部署**,否则依存句法分析与多模态向量化失效。
|
||||
|
||||
---
|
||||
|
||||
## 附:设备命令白名单改为可配置(2026-09-27)
|
||||
|
||||
与上面的 toolcall 并行是同一次排查的**另一条线**,记在这里是因为它同样属于
|
||||
"能力声明不该硬编码"这个主题。
|
||||
|
||||
### 起因
|
||||
|
||||
agent 通过 `device_ctl_cmdrun` 下发命令,命令在**设备侧**执行
|
||||
(`cmd/waiter/device.go` 的 `exec.CommandContext`),而白名单是**源码里
|
||||
硬编码的正则**(18 个命令:`ls/pwd/cat/df/…`)。`waiter.yaml` 里**没有任何键
|
||||
能改它** ⇒ `find` / `grep` / `sed` / `sort` / `tr` 这些排查问题最常用的
|
||||
**只读**命令一律被拒:
|
||||
|
||||
device_ctl_cmdrun device_id:waiter-fnnas error: command not in whitelist
|
||||
|
||||
注意 `device_authorized` 当时**已经是 `true`**、两台 token 相同、进程正常 ——
|
||||
所以"没开启设备桥授权"这个判断是错的,问题在白名单。
|
||||
|
||||
### 改动
|
||||
|
||||
`waiter.yaml` 新增 `device_cmd_allowlist`(字符串数组):
|
||||
|
||||
```yaml
|
||||
device_cmd_allowlist:
|
||||
- ls
|
||||
- find
|
||||
- grep
|
||||
- sed
|
||||
```
|
||||
|
||||
- **替换**默认集而非追加:避免"以为加了 find、结果还留着 `python3 -c` 任意执行"
|
||||
- 留空 ⇒ 用内置默认集(★ **绝不能变成"全放行"**,那等于静默拆掉闸门)
|
||||
- 只取命令名**第一段**再整词匹配:`grep -rn x .` 能过,而 `grepXxx` / `mygrep`
|
||||
不会因 `contains` 蒙混过关
|
||||
|
||||
### ★ 一次真实的疏漏
|
||||
|
||||
waiter 有**两条**设备桥启动路径:
|
||||
|
||||
| 路径 | 场景 |
|
||||
| --- | --- |
|
||||
| `main.go` 的 `startDeviceBridge` | 交互 / 一次性模式 |
|
||||
| `daemon.go` 的 `startDaemonDeviceBridge` | **`waiter --daemon`(生产两台都这么跑)** |
|
||||
|
||||
最初只在 `main.go` 里赋值 ⇒ daemon 路径不经过那里 ⇒ 配置**完全不生效**。
|
||||
症状极难定位:**配置写了、启动也打了招呼、命令照样被拒** ——
|
||||
看起来像"配置没读到",实际是"那条路径没接线"。
|
||||
已加 `TestDaemonPathAppliesAllowlist` 守住。
|
||||
|
||||
### ★ 已知局限:只匹配命令名,不看参数
|
||||
|
||||
实测(22 条白名单下):
|
||||
|
||||
| 命令 | 结果 | 实际副作用 |
|
||||
| --- | --- | --- |
|
||||
| `find . -name x.go` | 放行 | 只读 ✓ |
|
||||
| `find . -delete` | **放行** | ★ 删文件 |
|
||||
| `find . -exec rm {} ;` | **放行** | ★ 执行删除 |
|
||||
| `sed -i s/a/b/ f` | **放行** | ★ 原地改文件 |
|
||||
| `sort -o out.txt in.txt` | **放行** | ★ 写文件 |
|
||||
|
||||
即:**白名单是"命令名清单",不是"只读保证"**。用户已知悉并选择先下发
|
||||
(`ship_now`),参数级拦截(拒绝 `-i` / `-delete` / `-exec` / `-o` / `> 重定向`)
|
||||
作为后续项。
|
||||
|
||||
⇒ 写文档时不要把这一层叫"只读白名单",那会让人以为写操作被挡住了。
|
||||
|
||||
### 部署
|
||||
|
||||
| | 106 (fnnas) | 30 (mainnas) |
|
||||
| --- | --- | --- |
|
||||
| 二进制 | 11,388,177 → **12,691,402** | 同 |
|
||||
| 版本 | 8月27日 → `1.4.0` | 同 |
|
||||
| 白名单 | 22 条(启动日志确认读到) | 同 |
|
||||
| 备份 | `waiter.bak-20260927-194730` | `waiter.bak-20260927-194750` |
|
||||
|
||||
**顺带解答了一个悬案**:106 此前一直没有 `online` 日志,而 30 正常。
|
||||
两台配置与 token 完全相同 ⇒ 差异只可能在旧 waiter 二进制。8月27日那版
|
||||
落在"未 bind 时收到 ping 会关连接"的缺陷窗口里 ⇒ 更新后已正常:
|
||||
|
||||
19:46:12 device waiter-fnnas online → 输出通道 device-waiter-fnnas
|
||||
19:47:30 device waiter-fnnas offline → online (更新二进制时重连)
|
||||
|
||||
### 部署脚本的一个坑
|
||||
|
||||
`deploy-waiter.sh` 里 `ssh` 会从 stdin 读,把后续 `read -p "确认更新"` 的输入吃掉:
|
||||
|
||||
bash deploy-waiter.sh deploy <ip> <<< "yes" # 喂了 yes 却打印「已取消」
|
||||
|
||||
脚本本身完全正常、备份逻辑没问题,只是"明明喂了 yes 却什么也没发生"。
|
||||
已给 5 处 `ssh` 统一加 `-n`。
|
||||
|
||||
Reference in New Issue
Block a user