第 23 章 · 运行时架构设计
Harness 锚点|分层骨架——pi 的 packages/* 如何切层,支撑判别清单全部 10 项。
从“我电脑上能跑”到“给一万个卖家用”
那个跨境卖家的选品 Agent 越做越顺手,他动了个念头:干脆做成一个 SaaS,卖给同行的卖家们用。可真要动手,他立刻撞上一堵墙——原来那个在自己笔记本上跑得欢的脚本,一旦要同时服务成千上万个卖家,处处都是窟窿:换个更便宜的模型得改一堆业务代码;想加个网页版发现 Agent 逻辑和命令行搅在一起拆不开;某个卖家报“结果不对”却完全没法复现……
“在我电脑上能跑”和“能作为产品稳定服务成千上万用户”之间,隔着一条鸿沟。生产环境要求的是 Demo 不关心的东西:清晰的分层(哪块归哪块管)、可替换(换个模型不用重写业务)、可观测(出问题能查)、可控(成本、安全、权限)。
这一章我们先看最根本的一环——架构分层(layered architecture):一个能上生产的 Agent 运行时,应该怎么切分职责。
分层:让每层只管一件事
好的架构核心就一个字:分层。把复杂系统切成若干层,每层职责单一、通过清晰接口交互,层与层之间只靠约定好的接口通信,不互相伸手掏内部。对 Agent 运行时,一个通用的分层是四层:
- 模型层(Model Layer):与各家 LLM 打交道——统一接口、处理重试、计费、抹平多 provider 差异。它对上层只暴露一个“给我 prompt、还我 completion”的接口,至于底下接的是 OpenAI 还是 Anthropic,上层不关心。
- 运行时层(Runtime Layer):Agent 的核心循环(ReAct)、工具调用、状态管理、上下文/记忆。这是“Agent 之所以是 Agent”的地方。
- 编排层(Orchestration Layer):多 Agent 的协调、工作流、交接(handoff)。单 Agent 用不到,多 Agent 系统才需要。
- 接口层(Interface Layer):怎么和外界交互——CLI、Web、RPC、API。同一个 Agent,可以长出多张脸。
分层带来什么:换一层不动其他层
分层最实在的好处,是关注点分离(separation of concerns) 带来的可替换性:换一层不影响其他层。
- 换个模型 provider?只动模型层——业务逻辑纹丝不动(这正是第 34 章“模型分层”的前提)。
- 加个 Web 界面?只动接口层——运行时层完全复用。
- 想给多 Agent 换种编排策略?只动编排层——工具和模型都不用碰。
这背后是一条老到发亮的软件工程原则:依赖倒置(dependency inversion)——上层依赖的是下层的接口,而非下层的实现。模型层承诺“我提供一个统一的 LLM 调用接口”,运行时层就只管照这个接口用,底下换成谁家的模型都无所谓。没有这层解耦,换模型就意味着改遍全身;有了它,换模型只是改个配置。
用 pi 实现:pi 的包结构就是分层
pi 的架构是这套分层的一个真实样本——它的 packages 目录几乎就是照着层切的:
| pi 包 | 层 | 职责 |
|---|---|---|
@earendil-works/pi-ai | 模型层 | 统一多 provider LLM API、重试分类、定价 |
@earendil-works/pi-agent-core | 运行时层 | agent loop、工具调用、状态管理 |
@earendil-works/pi-orchestrator | 编排层 | 多 agent 编排(实验性) |
@earendil-works/pi-coding-agent | 接口层 | 交互式编码 Agent CLI |
@earendil-works/pi-tui | 接口层 | 终端 UI 渲染 |
这种切分带来的实际收益,你在前面的章节已经反复见到:
- 模型可替换(第 34 章):因为有独立的
pi-ai层,换模型只是换个配置。 - 能力可扩展(第 8 章):因为运行时层暴露了生命周期事件,扩展(extension)能挂载而不用改核心。
- 多种接口:同一个运行时,既能做 CLI,也能通过 RPC 远程驱动(第 24 章)。
量级有别:有的 harness 是单语言、单进程的轻量分层(如 pi),有的用多语言、多进程把编排/执行/推理拆到不同运行时——同样是分层思想,量级不同。理解了分层原则,你能看懂任何一种。
一项 AI 业务能力的参考架构
技术分层解决“代码如何组织”,业务能力架构还要解决“谁触发、需要什么状态、可以做什么、谁承担结果”。一个能进入生产的 Agent,至少要把下面六层打通:
| 层 | 核心职责 | 典型组件 |
|---|---|---|
| 业务触发层 | 定义经营目标、触发事件、业务对象与结果负责人 | 订单事件、线索进入、库存预警、人工发起 |
| 流程与状态层 | 表达正常路径、异常分支、暂停恢复和人机交接 | 状态机、DAG、审批流、任务队列 |
| Agent / Harness 层 | 管理推理循环、上下文、工具、记忆、编排和停止 | pi、Agno、LangGraph 或自研 Runtime |
| 企业上下文层 | 按权限装配必要的数据、规则、知识与历史 | RAG、数据服务、规则服务、身份权限 |
| 工具与集成层 | 把判断转成可校验、可审计的系统动作 | ERP、CRM、OMS、MCP、API、浏览器 |
| 治理与运营层 | 贯穿全链路记录质量、风险、成本和业务结果 | Tracing、评测、策略、预算、告警、反馈回流 |
这六层形成的最小完整闭环可以浓缩成:
业务触发 → 获取必要上下文 → Agent 判断 → 工具执行 → 高风险/异常转人工 → 记录业务结果 → 进入评测与复盘
第一版不需要把每层都建成公共平台。先围绕一个高价值、高频、边界清晰的流程,把这条链路完整跑通;当第二、第三个场景出现相同需求时,再把上下文连接、工具注册、策略和评测抽成公共能力。这样架构随着真实复用生长,而不是脱离业务提前建设。
分层之外:生产还需要什么
分层是骨架,但一个生产系统还要在这骨架上加几样东西,后面几章会逐一展开:
- 可观测性(observability):日志、追踪、成本统计(第 25 章)。
- 预算控制(budget control):别让一个失控的 Agent 烧光预算(第 26 章)。
- 安全边界(security boundary):不信任的代码要隔离(第 28 章)。
- 状态持久化(persistence):崩溃可恢复(第 12 章已讲)。
本章你只需记住一个顺序:先有清晰的分层,这些才好加。如果模型调用、业务逻辑、界面渲染搅成一团,你想加预算控制都不知道该往哪儿插——因为没有一个清晰的“模型层”来统一拦截调用。
真实案例:pi 生态的分层
pi 生态本身就是“分层 + 各司其职”的一个漂亮示例。它分成三块:
| 组件 | 角色 |
|---|---|
| pi(引擎/CLI) | Agent 运行时、多 LLM、工具调用、会话管理 |
| pi-app(Web + macOS) | Web 界面、桌面壳、可选远程访问 |
| 你的工作流 | 用 CLI、浏览器或桌面 App——都连到同一个 Agent |
关键在于三者共享同一份数据契约:~/.pi/agent/ 目录(auth.json 凭据、settings.json 设置、sessions/ 会话)。pi-app 明确规定“不自建存储、不复制凭据”——你在 CLI 里 /login,浏览器端立刻能用同一个 provider;你在 App 里建的会话,终端 pi 在同一目录下也列得出来。
这带来一句极精炼的分层原则,值得记住:
能力来自 pi,表达来自 pi-web。
也就是说:Agent 的能力(循环、工具、会话)只在引擎层实现一次;界面层(Web/桌面)只负责表达,绝不重新实现一套 Agent 逻辑。这正是本章“换一层不影响其他层”的现实印证——同一个引擎,既能长出 CLI,也能长出 Web 和 macOS App。假如哪天要接入手机,新加的也只是一层“表达”,引擎层一行都不用改。
动手看看
打开 pi 的 packages/ 目录,对照上面那张表,看看每个包的 package.json 里 dependencies 都依赖了谁——你会发现依赖是单向朝下的:接口层依赖运行时层,运行时层依赖模型层,反过来没有。这就是分层最直观的证据:上层认识下层,下层不认识上层。
再做个思考实验:如果你要给这个 Agent 加一个“企业微信机器人”入口,你觉得该改哪个包、新建哪个包?(提示:它是一张新的“脸”,属于接口层——运行时层和模型层应该一行都不用动。)
实战中的几个坑
坑一:业务逻辑写进了模型层。
- 现象:想换个模型,却发现 provider 调用那块混着一堆选品业务判断,牵一发动全身。
- 原因:没守住层的边界,把上层的事塞进了下层。
- 对策:模型层只做“统一调用 + 重试 + 计费”,业务判断一律上移到运行时层。
坑二:过度分层,简单需求也套四层。
- 现象:一个单 Agent 小工具,也硬塞进“编排层”,绕来绕去、样板代码一大堆。
- 原因:把“分层”当成越多越好,忽略了量级。
- 对策:分层数量随复杂度走——单 Agent 用不上编排层就别加;原则相同,量级要匹配。
坑三:接口层里偷偷实现了 Agent 逻辑。
- 现象:Web 端为了“快”,自己写了一段调模型的逻辑,和引擎层重复且很快不一致。
- 原因:违背了“能力来自引擎、表达来自界面”。
- 对策:接口层只负责表达,任何 Agent 能力都回到运行时层实现一次、各接口共用。
坑四:层间用具体实现而非接口耦合。
- 现象:运行时层直接
import了某家 provider 的 SDK,换模型要改运行时代码。 - 原因:上层依赖了下层的实现而非接口,违反依赖倒置。
- 对策:让下层暴露稳定接口,上层只依赖接口——换实现时上层无感。
对比:其他框架
四家框架各有自己的分层方式,本质都是“让每层只管一件事”,差别在层次划分与耦合度。
| 框架 | 架构分层怎么做 | 特点 |
|---|---|---|
| LangGraph | 模型层、链/图(编排)层、部署层清晰分离,Platform 管部署、LangSmith 管可观测 | 分层最显式,代价是层次多、样板多 |
| CrewAI | 主要暴露 Crew/Agent/Task 三层,底层运行时藏起来 | 抽象层次高、上手快,但分层是框架定死的、插自定义逻辑空间小 |
| PydanticAI | 不强加架构分层,鼓励用依赖注入和类型“像写普通 Python 一样”组织 | 分层由你的工程习惯决定,适合融入既有应用 |
| Agno | AgentOS 作运行时+监控层,上层 Agent/Team/Workflow,下层 Memory/Knowledge/Tools | 分层清晰且“电池全含”,适合快速搭完整应用 |
| pi | packages/* 一目了然:pi-ai/pi-agent-core/pi-orchestrator/coding-agent 各司一层 | 单语言轻量分层,你在其上构建而非被框架框住 |
一句话点破:分层的本质都是“关注点分离 + 换一层不动其他层”,各家差别只在层是框架替你定死的、还是你自己划的——理解了原则,原书 Shannon 那种多语言重型三层你也一样看得懂。
小结
- Demo 到生产的鸿沟,靠清晰的架构分层来跨:模型层、运行时层、编排层、接口层,每层职责单一、只靠接口通信。
- 分层的核心收益是“换一层不影响其他层”——背后是依赖倒置:上层依赖下层的接口而非实现,于是模型可替换、能力可扩展、接口可多样。
- pi 的
packages/*就是这套分层的真实样本;pi 生态“能力来自 pi、表达来自 pi-web”是“不重复实现能力”的现实印证;原书 Shannon 是更重的多语言三层——原则相同,量级不同。 - 分层是骨架,生产还需在其上加可观测、预算、安全、持久化——而“先有分层”是这些能干净落地的前提。
- 别过度分层:层数随复杂度走,单 Agent 用不上编排层就别硬加。
有了分层骨架,下一章解决生产的关键难题之一:怎么让 Agent 可靠地远程运行——pi 的 RPC 与远程驱动。