mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-27 12:53:35 +00:00
feat(seq): 执行引擎 —— 组内并行 + 具名槽 + 条件求值(插件线 P2)
三个不变量(各有判据钉住):
1. **组内并行、组间串行**。parallel=true 时各工具并发执行。
2. ★ **合并按声明顺序**,不按完成顺序。
并行下完成顺序不确定;若按完成顺序合并,同样的输入产出不同的结果,
整条序列**不可复现**。做法:各工具把结果写进 `results[i]`(按索引),
组屏障处按 tools 数组顺序一次性合并。
顺序合并顺带解决了并发写 map —— **执行期完全不写共享 map**。
3. ★ **条件求值失败必须报错**,不得降级成"条件为假"。
把求值失败当作跳过 = 序列安静地少做一步,而模型以为跑完了
—— 与「静默吞工具」同族(那正是 P1 判据里刚堵上的同类问题)。
条件求值(设计文档 §5 的 L1+L2,不引表达式引擎):
· true/false、$args.key 裸引用(真值)
· == / != / > / < / >= / <= 、contains
· 字面量支持 "str" / 'str' / true / false / 数字 / 裸文本
· 布尔与字符串宽松比较(true == "true"),对齐 utils.getBool 的既有约定
变量插值两种形态(缺一不可):
· **整值引用** "$args.count" ⇒ 替换为**原始值并保留类型**
(数字仍是数字;否则模型收到字符串 "3")
· **文本内插值** "ssh $args.host" ⇒ 在字符串内替换
· 标量渲染:对象/数组用**紧凑 JSON**,绝不用 fmt.Sprintf("%v")
(那会产出 `map[k:v]` 这种模型读不懂的 Go 语法)
on_error:abort(默认)/ continue。失败时也留槽(记错误文本)——
否则后续组读到的是"缺失",而"缺失"与"值为空"在下游难以区分。
判据(exec_test.go,8 条):
· 具名槽写入正确
· ★ 结果按声明顺序合并(用 delay 让完成顺序**确实**打乱)
· array 槽同名 as 按声明顺序确定性追加
· 条件为假 ⇒ 整组跳过、槽**不赋值**、零工具被执行
· ★ 条件畸形(空键 / 引用未声明入参 / 语法不完整)⇒ 报错且**不执行任何工具**
· 条件为真 ⇒ 正常执行
· on_error 的 abort / continue 两种语义
· 插值:文本内替换 + 整值引用保留类型
过程中三次自伤:
1. ★ **toolRunner 接口第一版写成 call(name)**,不收 args ⇒ 插值判据成了
摆设(永远"通过")。改为 call(name, args) 后插值才真正可观察。
2. toolRunner / compactJSON 定义在了 _test.go 里,exec.go 引用不到 ⇒
build 失败。toolRunner 是**引擎的依赖契约**,必须在非测试文件。
3. fixture 里给 "slow" 配了不存在的返回值,误以为它该返回 "B" ——
是我没配就断言,不是实现错。
变异验证:把合并改为"按完成顺序 append"⇒ array 槽顺序判据 FAIL,
报错直指 `[C A ran:slow]` vs 期望 `[A ran:slow C]`。
(第一版变异用了一个 no-op 的 sort.SliceStable,等于什么都没测,
已改成真正模拟完成序的实现。)
`-race` 全绿;回归 internal/agent/... internal/plugins/... 全绿。
顺带修正设计文档:两处 tools 示例原写成 `{tool:cmd_run,...}`,
**不是合法 JSON**(P1 判据实测会解析失败)。已改为合法 JSON 并加注
「键要带引号,这是实现时判据跑出来的真实缺陷,不是假想」。
This commit is contained in:
@ -302,7 +302,7 @@ ParallelSafe bool `json:"parallel_safe,omitempty"`
|
||||
"in": { "host": "string", "verbose": "bool" },
|
||||
"out": { "summary": "string", "load": "string" },
|
||||
"when": "$args.verbose == true",
|
||||
"tools": "{tool:cmd_run,args:{command:\"ssh $args.host uptime\"},as:summary} ; {tool:cmd_run,args:{command:\"ssh $args.host top -bn1\"},as:load} ;"
|
||||
"tools": "{\"tool\":\"cmd_run\",\"args\":{\"command\":\"ssh $args.host uptime\"},\"as\":\"summary\"} ; {\"tool\":\"cmd_run\",\"args\":{\"command\":\"ssh $args.host top -bn1\"},\"as\":\"load\"} ;"
|
||||
},
|
||||
{
|
||||
"name": "巡检全部",
|
||||
@ -310,7 +310,7 @@ ParallelSafe bool `json:"parallel_safe,omitempty"`
|
||||
"in": { "hosts": "array" },
|
||||
"out": { "reports": "array", "errors": "array" },
|
||||
"parallel": true,
|
||||
"tools": "{tool:seq_call,args:{group:\"拉取单台\",args:{host:$args.hosts[0]}},as:reports} ; {tool:seq_call,args:{group:\"拉取单台\",args:{host:$args.hosts[1]}},as:reports} ;"
|
||||
"tools": "{\"tool\":\"seq_call\",\"args\":{\"group\":\"拉取单台\",\"args\":{\"host\":$args.hosts[0]}},\"as\":\"reports\"} ; {\"tool\":\"seq_call\",\"args\":{\"group\":\"拉取单台\",\"args\":{\"host\":$args.hosts[1]}},\"as\":\"reports\"} ;"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -377,6 +377,10 @@ ParallelSafe bool `json:"parallel_safe,omitempty"`
|
||||
而不是静默丢一个工具。但 `;` 本身**只有在 tool 被 `{…}` 包裹时才是安全的**——
|
||||
这一点是本设计的关键,不加包裹会立刻出问题(见下)。
|
||||
|
||||
⚠️ **每个 tool 必须是合法 JSON**(键要带引号):`{"tool":"cmd_run","args":{...},"as":"x"} ;`
|
||||
写成 `{tool:cmd_run,...}` 是**非法 JSON**,内核用 `encoding/json` 解析会直接失败。
|
||||
(这条是 P1 实现时判据跑出来的真实缺陷,不是假想。)
|
||||
|
||||
#### 切分规则(已实测)
|
||||
|
||||
`;` **仅在 brace/bracket 深度为 0 且不在字符串内**时才是分隔符:
|
||||
|
||||
Reference in New Issue
Block a user