第 2 卷
复制代码
| 1 | 模型请求──┬──①tools/pre-execute | allow/deny/ask | ←能放行 |
|---|---|---|---|
| 2 | ├──②守卫(guard) | deny/不反对 | ←不能主动放行 |
| 3 | ├──③tools/execute | 环绕:超时、重试、计时 | ←包装 |
| 4 | ├──④工具本体execute | 返回规范JSON值 | ←上一讲讲过 |
| 5 | ├──⑤output.render | 值→呈现(内容块) | ←上一讲讲过 |
| 6 | ├──⑥tools/post-execute | accept(可改写)/block ←能拦,能改结果 | |
| 7 | ├──⑦finalizeContent | 最后的内容不变式 | ←最后一次改呈现 |
| 8 | └──⑧tools/result | 只观察,不能改 | ←审计与埋点 |
前四关决定“要不要做、怎么做”,后四关决定“结果以什么面目呈现、出口交给谁记录”。
这套结构是固定的,不因你注不注册监听器或守卫而变。一个都不注册,也不过是没有守卫来拒绝、没有监听器去改写,这次调用按默认路径顺利走完。pipeline-demo.ts 第一段输出
就是它,后面每段都只加一个监听器或守卫,看结果怎么变:
复制代码 1--- 基线:什么关卡都没有 ---
2{ isError: false, content: [ { type: 'text', text: 'Hello, World!' } ], value: 'H
但上面画的是顺路走完的路。最要紧的岔路是被拒:① 或 ② 一旦 deny,④ 工具本体根本不跑,结果直接落到 ⑥(ask 会拐去审批、异常逃不过 ⑦)。
八道关里,真正留给部署方的插槽是 ①②③⑥⑧ 这五关。另外三关 ④⑤⑦ 归写工具的人,
- execute、⑤ output.render 上一讲讲过;⑦ finalizeContent 的说明见 post-
execute 一节结尾:
| ①tools/pre-execute | waterfall | 放行/拒绝/转审批 |
|---|---|---|
| ②守卫 | root.tools.guard() | 只拒绝或弃权 |
一句话记住:①②⑥ 能改结果,③ 只能换信号,⑧ 只观察。插槽是流水线的、不属于某个工具:换工具,规则照常生效;增删规则,不用改工具源码。
- 的“只能换信号”单独说一句:exec.signal 是工具收到的“停”通知——调用方想取消
就触发它,工具自己监听、尽快停手(框架不能强杀代码)。环绕代码唯一能做的,是在交棒前换一个新通知,比如“3 秒后自动触发”——超时就这么实现;但通知管“怎么停”、管
不了“跑出什么”。
八关里值得停下来细看的是①②⑥:① pre-execute(策略层,能主动放行)、② 守卫(约束层,最反直觉)、⑥ post-execute(结果出口,能拦能改),下面我们一个个来
看。
pre-execute:放行、拒绝,还是问人
进流水线先碰到的是第 ① 关 tools/pre-execute,它是策略层,返回官方类型 PreToolDecision:
复制代码 1type PreToolDecision =
| 2 | | {kind:'allow'} | *//放行* |
|---|---|---|
| 3 | | {kind:'deny';reason:string} | *//拒绝* |
| 4 | | {kind:'ask';reason?:string} | *//去问人* |
allow:放行,调用继续走 ②。
deny:拒绝,④ 不跑、结果落到 ⑥。
reason:
复制代码 1--- pre-execute 决定 ask(本进程没有审批服务)---
2{ isError: true, error: { message: '危险操作,需要人工确认' }, ... }
deny 则是策略层自己拿主意、主动拒绝:
复制代码 1--- pre-execute 决定 deny ---
2{ isError: true, error: { message: '该工具已对当前会话下线' }, ... }
守卫:拒绝或弃权
- 放行后,进的是第 ② 关——守卫。② 与① 不同,守卫只能拒绝或弃权。注册只用一个函
数 ctx.tools.guard:
复制代码
| 1 | constoffGuard = root.tools.guard((exec) =>{ |
|---|---|
| 2 | constargs = exec.argumentsas{name?:string} |
| 3 | returnargs?.name==='World'?'不允许问候"World"':undefined |
| 4 | }) |
返回的 offGuard 是注销函数,示例每段用完立刻调。
守卫的返回值只有两种:字符串 = 拒绝(字符串就是理由);undefined = 弃权。
注意:这里没有“允许”这个返回值。不是漏了,是刻意设计。它带来一条性质,叫单调性
本卷图片
本页图片
