PPysolate SpineREAD THE CODE · UNDERSTAND THE SYSTEM
课程与章节
IMPLEMENTATION / 11

扩展:报告与通知

一个 Python 程序,三个 Host 结果,沿调用链理解执行与状态信息。

本章目录

这个小 demo 用普通 Runner.Run 执行真实 CPython Wasm:Python 从 Host 读取本地报告,依据报告内容调用模拟通知服务,再根据 Host 返回的结构化状态打印结果。它适合用几分钟解释 Spine 的普通调用链与能力边界。

它是静态程序信息接口示例:没有 LLM、prompt、benchmark,也不代表新增了 runtime 语义。Host 工具、场景状态和评测记录都属于 cmd/walkthrough;Spine 仍只负责已有的 manifest 注入、Python 工具调用和普通 Run。

运行

需要已有 dist/spine.wasm;脚本不会构建或替换 Guest artifact。go run 在依赖未缓存时可能获取 Go 依赖,正式演示前先准备缓存。Guest 构建见 构建与来源。脚本从任意当前目录启动:

bash /path/to/pysolate-spine/demos/walkthrough.sh -scenario normal
bash /path/to/pysolate-spine/demos/walkthrough.sh -scenario denied
bash /path/to/pysolate-spine/demos/walkthrough.sh -scenario unknown
bash /path/to/pysolate-spine/demos/walkthrough.sh -scenario all
bash /path/to/pysolate-spine/demos/walkthrough.sh -h

参数遵循 Go flag 惯例:-scenario 接受 normal、denied、unknown、all,默认是 all;-wasm 可指向另一个已有 artifact。也可从仓库根目录运行 go run ./cmd/walkthrough -scenario all。帮助中的说明指出了默认 artifact 和“不自动构建/下载”的约束。

每次执行会展示同一段 Python source、简短 Host 调用事件、Guest 输出和结果。all 会按 normal、denied、unknown 顺序分别创建 Runner 并各自普通 Run。

逐步读代码

  1. examples/notification.py 中的 get_report() 和 send_notification(...) 是 Host 注入的具名 Python 函数;具体函数体不是 Python import,而是由 Runner 的 manifest 提供。Python 用报告的 title 和 items 拼出消息,并对 sent、denied、unknown 分支处理。未知状态显式报错;unknown 分支只说确认状态不明,并且不重试。
  2. cmd/walkthrough/main.go 的 notificationHost.manifest() 注册两项工具。get_report 返回结构化报告;send_notification 校验实际收到的参数,再按所选场景返回结构化状态。normal 表示本地模拟服务完成;denied 在 dispatch 前拒绝;unknown 表示模拟 dispatch 已发生但确认丢失。这里不调用真实通知服务,不产生外部效果。
  3. runScenario 用 spine.New 建 Runner,随后调用 runner.Run(ctx, source, inputs)。它打印 Host 事件、Guest stdout 和 Run 结果。普通 Run 没有开启 PLM、prefix 或 prepared 模式。
  4. serviceDelivered 是 Host 侧独立评测状态,只在最后以明确标记输出,不会放进工具返回值、Guest inputs 或 Guest result。因而 unknown 对 Guest 来说确实未知;演示者可以独立看到模拟服务是否已 dispatch。

关键源码:

  • cmd/walkthrough/main.go:CLI、Host manifest、场景状态和输出。
  • cmd/walkthrough/main_test.go:真实 Guest 场景测试、调用次数/参数和状态断言。
  • examples/notification.py:被真实 Guest 执行的同一份 Python 程序。
  • demos/walkthrough.sh:从任意当前目录定位仓库根并启动 Go CLI。
  • bridge.go:现有 Tool / ToolSpec / Manifest API 与 Host 调用边界。
  • runner.go:现有 New 和普通 Run 路径。

沿一次调用讲:文件打开顺序

先只运行 ./demos/walkthrough.sh -scenario normal,不要一开始展开 PLM、prefix、COW。

  1. 打开 notification.py:get_report 没有在 Python 里定义,它从哪来?消息拼接和结果分支仍在 Python 内执行。
  2. 打开 main.go 的 manifest() 和 runScenario():应用注册两个 Go 函数,把源码与输入交给 spine.New 和 Run。scenario 只给 Host,不作为 Guest 输入。
  3. 打开 runner.go 的 New:注册 WASI 与 spine.call/prepare/resolve imports,编译 CPython Wasm。这里编译解释器 artifact,不是用户 Python。
  4. 同文件的 Run → run:建立本轮状态,将 source、inputs 和工具 manifest 编码,创建 Guest,经 callWithBytes 调用 Guest execute。Runner 的编译代码可以复用,本轮 Guest 则单独创建和释放。
  5. 打开 runtime.c:execute 把 ABI 请求转入 CPython;python_call 把 Python 请求送到 Host import。这层处理字节、缓冲与 Python 对象,不作业务决策。
  6. 打开 bootstrap.py 的 execute:为 manifest 中每个名称构造 make_tool 代理,再 compile / exec 用户源码。invoke_tool 把 tool 名与 args 编码,交给 _spine.call。
  7. 打开 bridge.go 的 hostCall → readRequest → invoke:检查 ABI 内存范围,按 Host manifest 查工具并执行。Python 代理不是授权边界;业务权限检查放在注册的实现里。
  8. 结果沿 bridge → C → Python decode 返回,Python 继续执行,最终把 result 交回 Go。Runner 收尾并关闭 Guest。
Python send_notification(message)
  → bootstrap.invoke_tool:JSON 编码
  → C python_call:Python bytes / Wasm ABI
  → Go bridge.hostCall → invoke:Host manifest 查找
  → 应用 send_notification:校验参数、派发或拒绝
  ← JSON {value: {status: ...}}
  ← Python decode:取得结构化状态
  → Python 分支,写入 result
  → bootstrap.execute → C execute → Go Output

第一遍回答“控制流在哪里、数据怎样过边界、谁拥有权限”。第二遍切 denied 和 unknown:两者都没有确认成功,但只有前者可以确定没有派发。不要把业务状态返回与 Go/Python 异常混为一谈。

ASCII 调用流程

CLI -scenario
     |
     v
cmd/walkthrough: 选择场景,注册 Manifest
     |                            |
     |                             +--> Host 模拟器保存 serviceDelivered
     v
spine.New(existing Wasm, Manifest)
     |
     v
Runner.Run(同一份 Python source, {})
     |
     +--> Guest: get_report() --------> Host: 结构化本地报告
     |                                      |
     +<-------------------------------------+
     |
     +--> Guest: send_notification(message)
     |         |
     |         +--> Host 返回 sent / denied / unknown
     |              (denied 不 dispatch;unknown 已 dispatch、确认丢失)
     |
     +--> Guest 按 status 输出;unknown 不宣称成功且不重试
     |
     +--> Run result + Guest stdout

独立于 Guest:Host 输出 evaluation-only serviceDelivered

Mermaid(可选)

支持 Mermaid 的阅读器可用下面的图;上面的 ASCII 图不依赖网络或图渲染器。

flowchart TD
  A[CLI scenario] --> B[cmd 注册 Manifest]
  B --> C[spine.New + 普通 Runner.Run]
  C --> D[Guest get_report]
  D --> E[Host 返回结构化本地报告]
  E --> F[Guest 拼消息并调用 send_notification]
  F --> G{Host 模拟结果}
  G -->|normal| H[sent]
  G -->|denied| I[未 dispatch,denied]
  G -->|unknown| J[已 dispatch,确认丢失]
  H --> K[Guest 按状态输出]
  I --> K
  J --> K[Guest 不宣称成功,不重试]
  G -. 独立评测,不进入 Guest .-> L[serviceDelivered]

结果怎么理解

场景 Host 工具结果(Guest 可知) 模拟服务状态(Host 评测) Guest 的说法
normal sent 已 dispatch 通知已发送
denied denied 未 dispatch Host 拒绝,未发送
unknown unknown 已 dispatch,确认丢失 结果未知,不自动重试

Host 返回值结构化,是为了让 Python 根据真实可见信息做分支;它不是一套新的 Spine 错误/投递语义。这里的 sent、拒绝和确认丢失都只是本地 demo 约定。独立评测字段不能被解释为 Guest 知道了通知是否送达。

新 idea 的关联与边界

这个例子把“工具调用结果”和“模拟服务真实状态”放在不同视角。它连接未来的问题:固定工具能力和权限,给模型哪些效果契约与结果确定性信息、何时提供,才能减少误操作与无效重试?

  • 这里实际演示的是第一个准备步骤:通过已有 ABI 传递明确的 provider 状态,并保证测试真值不进入 Guest。
  • 当前 Python 的判断是人写好的,不能用这个例子声称模型学会了正确处理未知状态。
  • 效果契约、按需查询与自动反馈的对照尚未实现;后续应保持工具和恢复策略一致,再比较信息方案。
  • 不添加通用证据整理器,不让 runtime 推断业务策略。sent/denied/unknown 属于这个 Host provider 的约定。
  • unknown 由本地模拟器控制,不是断电、真实网络故障或进程重启实验。它不能证明 exactly-once,也不能消除外部结果不确定性。

它不演示 PLM 提前读取、prefix 流式源码、并发工具调用、真实通知发送、LLM agent 或 benchmark。实际工具错误会让 Guest 调用失败;示例不会把错误变成成功状态。运行时与教学 demo 的边界不变:新增的业务含义全部留在 cmd/walkthrough。