加载中...
  • 2026 年了,Agent 架构还要不要依赖大框架 loading

    吃透 AI Agent 开发 · 第 05 篇 · 第一章 · 认知校准

    框架可以节省样板代码,却不能替你决定权限、状态、失败恢复和观测边界。先理解原理,再决定抽象层。

    框架可以节省样板代码,却不能替你决定权限、状态、失败恢复和观测边界。先理解原理,再决定抽象层。

    先看你在购买什么

    团队说“要不要上大框架”时,常常把三件事揉成一个问题:模型 SDK 是否需要封装、工作流是否需要编排、整套 Agent 运行时是否交给第三方。它们的风险和收益完全不同。

    一个小项目可以直接调用模型 API,但只要出现工具、重试、会话和审批,就会自然形成自己的运行时。此时引入框架的价值,是把已经理解的协议实现得更快,而不是把尚未理解的决策交给黑盒。

    以一次换模型为例

    假设今天用供应商 A,明天要切到供应商 B。真正需要稳定的不是函数名,而是这些边界:消息 part 怎样保存、工具调用如何校验、reasoning 是否回传、错误是否可重试、用量如何计费。如果框架把这些都藏在一个 run() 里,迁移成本就会在最需要速度的时候突然出现。

    flowchart LR
      Intent[用户意图] --> Protocol[内部消息协议]
      Protocol --> ProviderA[模型适配 A]
      Protocol --> ProviderB[模型适配 B]
      Protocol --> Policy[工具与权限策略]
      Policy --> Effect[真实副作用]
      Effect --> Evidence[统一证据]
    

    把模型供应商放在协议边界之后,框架可以替换;把权限和证据放在框架回调里,业务就会被实现细节牵着走。

    做一次“空框架实验”

    不用追求完整产品,只实现一个任务:模型决定调用 read_file,应用校验路径,返回文件摘要,模型再回答。实验要刻意保留四个空白:没有自动重试、没有全局 Memory、没有隐藏的 prompt 拼接、没有把异常变成普通字符串。这样才能看见真正需要的抽象。

    type Step =
      | { kind: 'model'; messages: Message[] }
      | { kind: 'tool-intent'; name: string; args: unknown }
      | { kind: 'tool-result'; name: string; status: 'ok' | 'denied' | 'failed'; value: unknown }
      | { kind: 'final'; text: string }
    
    function nextStep(step: Step): Step {
      if (step.kind === 'tool-intent' && step.name === 'read_file') {
        return { kind: 'tool-result', name: step.name, status: 'denied', value: 'policy required' }
      }
      return step
    }
    

    这段切片的重点不是把模型调用写完,而是让“模型提出意图”和“应用允许执行”成为两个不同状态。框架如果能帮助我们维护这个边界,它就是加速器;如果它把两者合成一个不可观察的动作,就要谨慎。

    用取舍而不是品牌做决定

    你的现场 直接 SDK 工作流框架 完整运行时
    单一模型、只读回答 足够 可能过重 明显过重
    多工具、需要审批 要自己补边界 通常合适 视会话需求决定
    长任务、暂停恢复 需要大量基础设施 能解决部分编排 可能省下维护成本
    需要严格数据隔离 仍要自己做 不能默认替你做 仍需审查实现

    “框架能不能做”不是验收标准。“发生越权时我能否指出是哪一层放行的”才是。把团队最怕的三类故障写出来,再逐项确认框架提供的是机制、扩展点还是一段文档建议。

    q-code 参考:看抽象从哪里长出来

    只把 q-code 当作阅读材料,按这个顺序追踪:

    顺序 路径 观察点
    1. 循环协议 src/agent/loop.ts 哪些状态必须保留在框架之外
    2. 工具入口 src/tools/registry.ts 注册、授权和执行为何不能散落
    3. 压缩恢复 src/context/compressor.ts 框架何时应该暴露完整历史
    4. 审计证据 src/observability/audit.ts 如何验证“框架替我做了什么”

    阅读时把每个框架 API 翻译成职责名称。翻译不出来的 API,不要在生产路径上盲目依赖。

    一个真实的失败现场

    为了赶 demo 把所有能力交给黑盒 Agent 类,生产问题出现时无法定位责任边界。日志里只有“Agent failed”,没有当时的模型、工具参数、授权决定、外部状态和重试次数。团队只能重新跑一次,希望得到不同结果。

    修复顺序应该反过来:先为内部步骤留下事件,再把稳定的步骤交给框架。框架可以负责连接模型,但请求编号、策略结果和恢复句柄仍归应用所有。这样即使将来换掉框架,故障记录不会一起消失。

    把实验结果写成团队决策

    练习:把一个现有 Agent 任务拆成“协议、策略、编排、存储、界面”五列。每列写出当前实现、框架提供的能力、还缺的证据和退出框架的成本。最后用一个具体故障做验收:模型调用工具超时后,谁能判断动作是否已经发生?

    如果答案依赖某个黑盒对象的私有字段,暂时不要升级抽象;先补一个内部事件或适配层。好的框架决策不是“用了最先进的库”,而是未来还能解释和替换。

    用两周试点代替会议里的品牌辩论

    选择框架最容易陷入文档对比:A 支持图编排,B 支持 Memory,C 的生态更大。文档只能证明 API 存在,不能证明它适合你的权限、恢复和部署边界。更有效的做法是选一条真实但可控的任务,在候选方案里各做一个垂直切片。

    任务不要太简单。只问模型一句话,任何框架都很好看;直接拿生产支付流程,又会把试点变成大工程。一个合适切片可以是:“读取仓库版本,修改一处配置,运行指定测试,用户拒绝时不写入,进程中断后能看到已完成步骤。”

    试点只比较可观测结果:

    {
      "case": "bump-timeout",
      "signals": {
        "approval_before_write": true,
        "tool_trace_replayable": true,
        "resume_after_restart": false,
        "provider_swap_files_changed": 7,
        "cold_start_ms": 680
      },
      "unknowns": ["远程工具调用后的幂等恢复"]
    }
    

    resume_after_restart: false 比“支持 checkpoint”这句宣传更有决策价值。它告诉团队当前持久化粒度不足,而不是让大家继续争论术语。

    框架锁定通常藏在数据里

    大家常把锁定理解成 import 了多少 API。真正难迁移的往往是已经落盘的数据:消息序列化格式、checkpoint、工具结果、向量索引 metadata、追踪字段和用户自定义扩展。一旦这些数据只有框架内部能读,换框架就不只是重写代码,还要迁移历史。

    审查时可以列一份“带得走吗”清单:

    • 会话能否导出为公开、带版本的 JSONL。
    • tool call/result 是否保留标准字段和原始关联 ID。
    • checkpoint 能否在不启动框架的情况下检查。
    • 检索索引能否从源文档重建,而不是只剩不可解释向量。
    • trace 是否能落到本地开放格式,外部平台不可用时仍能排障。
    • Prompt、Skill 和工具 schema 是否属于应用仓库,而非供应商控制台里的隐藏配置。

    锁定并非一律坏事。托管服务替团队承担运维,付出迁移成本可能合理。问题在于要知道自己交换了什么,并给最关键的数据保留出口。

    不要把领域状态塞进编排节点对象

    图式工作流框架常提供一个可序列化 state,开发者很容易把所有东西都往里放:用户消息、数据库连接、临时文件句柄、权限对象、UI 状态和完整工具输出。短期传参方便,恢复时却会同时遇到序列化、敏感数据和版本升级问题。

    更稳的状态只保存恢复决策需要的事实与句柄:

    interface DurableRunState {
      schemaVersion: 3
      runId: string
      currentNode: string
      completedSteps: string[]
      messageLogRef: string
      artifactRefs: string[]
      pendingApproval?: { actionHash: string; requestedAt: string }
    }
    

    数据库连接由运行时重新注入,临时 UI 状态不持久化,大工具输出以 artifact 引用恢复。这样状态迁移有明确边界,框架的 checkpoint 只是载体,不是整个应用对象的冷冻柜。

    四种抽象,各自只解决一层重复

    直接 SDK、工具调用封装、工作流编排和完整 Agent Runtime 可以叠加,不必四选一。

    直接 SDK 解决与模型供应商通信;工具层解决 schema、调用和结果协议;工作流层解决有依赖的步骤、分支与恢复;Runtime 解决会话、配置、权限、事件和界面。项目可以使用供应商 SDK,加自己的工具注册表,再仅对长流程引入图编排,而不把所有请求都塞进图。

    判断是否该上更高抽象,可以看重复来自哪里。十个工具都在复制参数校验,需要 Registry;三个长流程都在复制 checkpoint,需要工作流;每个入口都在复制会话和日志,需要 Runtime。只有一个调用点时,提前抽象通常只是多了一层跳转。

    升级框架前先跑“逃生演习”

    升级版本常在 happy path 上通过,却在旧会话恢复、工具 schema 变化或异常序列化时出问题。演习可以准备四份 fixture:一段旧版会话、一个执行到一半的 checkpoint、一次失败工具结果、一个长输出 artifact。候选版本要能读取它们,或给出明确迁移错误,不能静默从头执行。

    尤其要防止恢复时重复副作用。旧 checkpoint 显示“调用支付工具后尚未写入下一节点”,新框架若无法判断调用是否完成,正确状态是等待核验,不是自动重放。框架提供的 retry 机制必须服从业务幂等边界。

    团队真正需要拥有的最小内核

    即使采用完整框架,应用最好仍自己拥有几项稳定协议:内部消息表示、工具授权结果、运行与会话 ID、artifact 引用、审计事件名称。它们不必很多,却要能在框架之外被测试和读取。

    这层最小内核让选择从“要不要依赖框架”变成更现实的问题:“哪些重复工作值得委托,哪些业务边界必须留在自己手里。”前者可以随生态变化,后者决定系统在事故中还能否解释自己。

    一次小版本升级暴露的退出成本

    某框架把 tool result 从 { name, output } 改成带 content parts 的新格式,happy path 只需改两行 adapter。上线后旧 checkpoint 无法反序列化,正在等待审批的任务全部从头开始,其中一个外部调用险些重复。

    问题不在“框架不稳定”,而在应用把 checkpoint 与消息格式完全交给框架,却没有自己的 schemaVersion、call receipt 和恢复测试。修复后把持久协议放进最小内核:框架消息进出都经过 adapter,checkpoint 只保存应用 state 与 artifact ref,旧 fixture 每次升级必跑。

    退出成本可以量化:替换 provider adapter 改多少文件;导出会话是否无需启动旧框架;权限与 Audit 是否仍工作;旧任务是迁移、只读展示还是必须放弃。每次升级试点先在隔离副本恢复四类 artifact,再决定推广。

    框架越大,越需要写“我们不交给它什么”。这不是抵触生态,而是让团队在依赖升级、停止维护或合规变化时仍有可走的退路。

    本文目录
    本文目录