Skip to content

Latest commit

 

History

History
192 lines (111 loc) · 8.54 KB

File metadata and controls

192 lines (111 loc) · 8.54 KB

第十五章:十二个可迁移的工程范式

从一个 Agent 产品中提炼出来的原则,适用于任何复杂系统的构建。

这十二个范式是全书内容的提炼。无论你用什么技术栈,无论你在构建什么类型的 Agent,这些范式都应该放在架构决策的核心位置。


范式一:声明式契约优于函数

原则:系统的每个能力单元,不只是"能做什么",还要声明"在什么情况下做"、"做的时候有什么约束"。

应用:工具不是函数,是声明式契约(isReadOnlyisConcurrencySaferequiresPermission)。 这个契约让调度器、权限系统、并发系统能做出正确的自动决策,不需要为每个工具写特殊处理逻辑。

反面:纯函数系统。每新增一个工具,调度器需要知道这个工具是否可以并发、是否需要确认、结果大了怎么处理……最终调度器里充满了 if tool.name === 'xxx'

可迁移:任何能力注册系统(插件、中间件、处理器)都应该有声明式元数据。


范式二:事件流作为系统边界

原则:核心业务逻辑和消费层之间,用结构化事件流解耦。

应用:Agent 循环输出 AgentStreamEvent 事件流。UI 消费事件流做渲染;日志系统消费事件流做记录;多 Agent 系统把子 Agent 的事件流 merge 进父 Agent 的流。

好处

  • 核心逻辑和 UI 独立演进(可以同时有多个 UI 消费同一个 Agent 循环)
  • 支持流式输出(事件驱动,天然支持"边生产边消费")
  • 可以给事件流插入中间件(日志、重放、测试)

可迁移:任何需要"实时更新外部状态"的核心逻辑都适用这个模式(游戏引擎、实时数据处理)。


范式三:分层压缩策略

原则:不同触发阈值对应不同成本的处理策略,从廉价到昂贵依次触发。

应用:上下文压缩的四层(Snip → MicroCompact → Collapse → AutoCompact),前两层无 API 成本,后两层才调用 LLM。

通用公式

如果 [资源使用率 > 60%]:启用廉价策略(本地算法)
如果 [资源使用率 > 80%]:启用中等策略(本地LLM / 简单API)
如果 [资源使用率 > 90%]:启用昂贵策略(强力API / 人工介入)

可迁移:缓存系统(L1/L2/L3 缓存策略)、数据库查询(索引→全表扫描→分布式计算)、服务降级(正常→简化→最小化)。


范式四:互斥守卫防止递归副作用

原则:所有"被动触发"的机制(定期任务、阈值触发)必须有防递归守卫。

应用:上下文压缩本身消耗 token → 可能触发再次压缩 → 无限循环。解决方案:压缩任务携带 querySource: 'compact' 标记,循环检测到标记后不再触发压缩。

通用模式

触发机制本身是否会产生"让下一次触发更容易发生"的副作用?
  如果是 → 需要互斥守卫

可迁移:任何自动触发的系统(日志系统、监控系统、自动备份)都要防止递归。


范式五:最小权限的结构化实现

原则:权限需要精确到每类操作的粒度,"用户同意一次"并不等于全部放行。

应用:五种权限模式 × 规则匹配 × AI 分类器 × 用户确认,形成责任链。每次决策都携带 reason,可审计。

关键细节:"允许了一次"(session 级记忆)和"永远允许"(规则级配置)是两个不同的层级,不能混淆。

可迁移:任何需要"授权"的系统(文件权限、API 权限、数据库权限)都应该有分层的、带原因的决策记录。


范式六:懒加载防止启动雪崩

原则:只在真正需要的时候才建立连接、加载数据、初始化组件。

应用:MCP Client 的懒连接。注册时只保存配置,第一次调用时才建立连接。这让 20 个 MCP Server 的配置不会让 Alice 的启动时间增加 20× 倍。

变体

  • 懒连接(连接)
  • 懒加载(数据)
  • 懒初始化(组件)

可迁移:任何"外部依赖多"的系统(数据库连接池、外部 API 客户端、重型组件)都应该考虑懒初始化。


范式七:可撤销的不可逆操作

原则:对于有副作用的操作,在执行前快照状态,提供撤销能力。

应用:L2 自进化操作的 UndoStack。修改 Widget 配置前,把相关文件的内容快照到磁盘,用户可以随时恢复到任意历史版本。

实现要点

  • 快照在操作之前做(不是之后)
  • 快照持久化到磁盘(重启后依然可撤销)
  • UI 展示撤销历史,主动展示给用户

可迁移:任何会修改持久化状态的"AI 自动操作"(代码生成、文件修改、配置更改)都应该有撤销能力。


范式八:细化的可观测性

原则:记录对问题诊断有价值的结构化信息,而非无差别记录一切。

应用:LLM 调用日志里的 caller 字段,记录的是"谁(主对话/记忆提取/压缩/分类器)调用的 LLM"。这让成本分析可以定位到"某个场景贡献了 70% 的 token 消耗",而不只停留在总量数字上。

可迁移:任何系统的监控都应该有"来源"标记,记录"这个操作由什么流程触发",而非只记录"操作发生了"。


范式九:读写分离的并发安全

原则:只读操作可以并行;写操作需要串行或加锁。

应用:工具的 isConcurrencySafe = isReadOnly。多个并行的只读工具一批执行,写操作单独执行。

MCP 工具的保守默认:新加入的 MCP 工具默认 isConcurrencySafe = false,需要显式标记才能并发。宁可慢,不可出错。

可迁移:数据库事务(MVCC)、文件系统(读锁/写锁)、任务队列(并发读、独占写)。


范式十:分层记忆对应分层生命周期

原则:不同生命周期的信息应该住在不同的存储层,不要混在一起。

应用:五层记忆(在线上下文 / 项目文件 / 向量库 / SQLite / 用户画像),每层的读写频率、生命周期、访问模式都不同,分开存储,按需读取。

判断标准

  • 生命周期 < 1 次对话 → 内存
  • 生命周期 = 1 个项目 → 项目文件系统
  • 生命周期 = 永久,需要语义检索 → 向量库
  • 生命周期 = 永久,需要精确查询 → SQL 数据库

可迁移:任何有"热数据/温数据/冷数据"之分的系统(CDN/对象存储/磁带,Redis/MySQL/归档存储)。


范式十一:接口稳定,实现可丢弃

原则:确定接口,不断迭代实现。接口改变的成本远高于实现改变的成本。

应用:LLM 路由层的统一接口(stream(messages, tools) → StreamChunk)。Provider 的实现随时可以替换(新的 Provider 出现、老的 Provider 关闭),但接口不变,上层代码零修改。

接口设计的标准

  • 接口改变的成本是 O(调用方数量)
  • 实现改变的成本是 O(1)
  • 当 O(调用方数量) 很大时,投资设计一个稳定接口是值得的

可迁移:任何有多个实现的系统(支付接口 / 消息推送 / 存储后端)都应该有稳定的抽象层。


范式十二:防呆优于依赖正确性

原则:不要假设系统(包括 AI)总是做正确的事;设计机制来检测和纠正偏差。

应用:渠道 Fallback。如果 AI 生成了回复但"忘记"调用发送工具,自动补一次发送调用。

更多例子

  • sanitizeToolCallPairs:发给 LLM 之前修复孤立的 tool_call
  • 压缩互斥守卫:防止压缩机制相互干扰
  • 加密时拒绝未解锁写入:防止意外覆盖加密数据

核心洞察:AI 是概率性的,框架是确定性的。应该由确定性的框架来捕获和纠正概率性行为的偏差,不能假设 AI 每次都做正确的事。


总结

这十二个范式并不是 Agent 专属的,它们适用于任何需要处理不确定性、并发、长时任务、外部依赖的复杂系统。

Agent 系统只是把这些挑战放大了(因为 AI 是不确定性的核心来源),所以这些范式在 Agent 系统里特别重要、特别明显。

最重要的一条(如果只记一条的话):

*状态管理是复杂系统的核心难题。明确每块状态的边界、生命周期和所有权,系统的其余部分会变得简单很多。


上一章:Prompt 工程 · 下一章:活人感设计