
很多人问我说现在现成的 Agent 框架那么多为什么还要自己搭一套我的回答是框架解决的是“通用问题”而你的业务解决的是“特定问题”。这中间的空隙就是你要不要自建 Agent 框架的真正分界线。这一章我不会去讲某个具体框架的 API 怎么调而是把“第七章 构建你的 Agent 框架”最核心的部分——7.1 框架整体架构设计——掰开揉碎讲清楚一个能支撑复杂业务、能演进、能维护的 Agent 框架它的架构到底应该怎么搭。这篇文章的定位是给已经跑通过几个 Demo、但一上生产就开始凌乱的团队和个人开发者看的。你不需要是资深系统架构师但你需要对 Python 比较熟最好已经写过几个基于大模型的应用。我会从框架的职责边界、核心抽象、编排机制、上下文管理、工程化落地这几个维度往下拆并且在关键设计决策上告诉你“为什么这样做”而不是只丢给你一张架构图。1. 自建 Agent 框架前先想清楚框架的职责边界很多人一说“自建 Agent 框架”第一反应就是“我们要写一个大而全的框架什么都要有”。这个思路基本上一开始就输了。真正能落地、能被团队长期维护的框架通常只做窄而深的事情把大模型调用、工具调用、状态流转、上下文管理这几件事抽象好然后剩下的全部交给业务层。1.1 现成框架很香但定制需求迟早会顶到天花板市面上有不少成熟的 Agent 框架它们解决了统一接口、内置工具链、插件生态等通用问题。但你一旦进入真实业务会遇到几个特别扎心的情况内置的 Agent 循环逻辑是黑盒你无法精确控制每一轮决策的触发条件工具调用的错误处理策略是通用的无法适配你业务的幂等性要求上下文管理机制偏通用遇到行业领域知识、长流程任务内存和 token 消耗双双失控团队定制了 Prompts 体系后框架升级一个版本行为就变了双休日加班做回归。这些问题的本质不是现成框架“做错了”而是“通用”和“定制”之间天生有张力。自建框架的意义不是什么都自己写而是把框架的底盘——也就是 Agent 的定义方式、执行循环、上下文管线和工具协议——握在自己手里。1.2 明确边界这个框架不做什么反而更重要我在设计自己的 Agent 框架时第一件事不是画架构图而是列了一份“框架不做什么”的清单框架职责做框架不职责不做Agent 的抽象定义与生命周期管理业务具体流程编排逻辑大模型 Provider 的统一接入具体 Prompt 内容的编写工具调用的协议与注册机制业务工具的具体实现上下文的分层管理与压缩策略存储基础设施选型如用什么数据库多 Agent 协作的编排原语具体任务的路线规划可观测性埋点与日志规范具体的监控大盘展示错误处理与重试策略的通用规则业务重试语义如支付场景的特殊处理这份清单最核心的价值是防止框架侵入业务。我见过太多团队把框架越写越厚最后框架里塞满了业务判断业务代码里也到处是框架钩子两边的耦合到谁都动不了的地步。提示自建框架的第一行代码建议从“Agent 抽象规范文档”开始写而不是从一个基类开始写。2. 核心抽象层Agent、LLM、工具与记忆的职责划分框架的底层是我最先稳定的四个核心抽象Agent、LLM Provider、Tool 和 Memory。这四个概念听起来简单但真正设计好了并不容易因为它们之间的交互方式决定了整个框架的复杂度和扩展性。2.1 Agent 对象不是“一个类”而是一份契约我刚做第一版框架时把 Agent 设计成了一个充满业务方法的大类结果每来一个新场景就要继承和重写。后来我重构了设计Agent 本质上是一份“契约”——一个对象只要满足以下条件框架就可以驱动它有一个系统提示词模板System Prompt可以接收上下文变量动态渲染有一个工具注册表标明它自己能调用哪些工具有明确的输入输出模型Input/Output Schema有一个终止条件定义即什么时候该结束本轮执行。这里我用 Python 的 Protocol 做一个简单示例这个设计思路不受语言限制from typing import Protocol, List, Optional from pydantic import BaseModel class AgentInput(BaseModel): user_message: str session_state: dict extra_vars: dict {} class AgentOutput(BaseModel): reply: str handoff_to: Optional[str] None state_updates: dict {} class ToolSpec(BaseModel): name: str description: str parameters_schema: dict class IAgent(Protocol): name: str system_prompt_template: str tools: List[ToolSpec] async def build_messages( self, agent_input: AgentInput, memory: MemoryManager ) - List[dict]: ... async def parse_response(self, response: dict) - AgentOutput: ...为什么我要把 Agent 设计成协议而不是基类因为在真实业务里有的 Agent 可能不需要走大模型它就是一条规则路由有的 Agent 需要调用外部 gRPC 服务有的 Agent 后续会替换成微调过的自研小模型。协议的方式允许这些异构实现“无痛接入”。这套设计的关键收益是行为参数化同一个 Agent 定义可以靠切换配置在不改代码的情况下给不同客户或者不同项目提供定制化的 Prompt、工具集和终止策略。2.2 工具调用协议与容错策略Agent 框架的第二个核心抽象是 Tool。这块如果设计不好后期加工具的时候会让每个人都抓狂。我在实践中总结了一套比较稳的工具协议工具必须描述清楚自己的“输入 Schema”通常用 JSON Schema工具必须声明自己的“副作用类别”只读、写入、副作用未知工具执行结果必须有标准包装成功、业务失败、系统异常三者必须区分开工具必须声明超时时间和幂等性策略。from dataclasses import dataclass from enum import Enum class ToolEffect(Enum): READ_ONLY read_only WRITE write UNKNOWN unknown class ToolResultKind(Enum): OK ok BUSINESS_FAILURE business_failure SYSTEM_ERROR system_error dataclass class ToolResult: kind: ToolResultKind data: dict duration_ms: float我踩过最大的坑是把业务失败当作系统异常来处理。结果就是LLM 看到工具返回的报错后开始“自作聪明”地反复重试同一个会业务失败的操作白白浪费 token 和时间。后来我强制工具返回三态结果后LLM 对失败原因的判断变得精确多了重试策略也能按类别独立配置。2.3 记忆的三种存储粒度与回收策略框架里记忆管理这个抽象一定要一开始就做进去不要等到“上下文不够用”了再来补。我的记忆设计分三种粒度记忆类型生命周期存储介质使用场景会话级工作记忆单次会话内存随 Agent 实例当前对话的轮次摘要任务级记忆一个任务从开始到结束Redis 或数据库多轮交互的任务参数、中间结果长期记忆跨会话、跨用户向量库/知识库用户偏好、历史决策回收策略上我建议做好关键信息保留而不是做全量覆盖。比如用户请求原文不要丢、已经确定的业务参数不要丢、失败的尝试记录不要全部保留但保留失败原因要点。这三条是长期跑业务后总结出来的确实很实用。3. 编排层单 Agent 循环与多 Agent 协作的执行骨架有了四个核心抽象之后真正让框架活起来的是编排层。这一层负责回答两个问题单个 Agent 内部怎么一步步执行多个 Agent 之间怎么协同完成复杂任务3.1 单 Agent 主循环九步执行链路单 Agent 的执行循环是整个 Agent 框架的心脏。我把它拆成了清晰的九个步骤每一步都有自己的职责方便出错时定位构建会话上下文从 Memory 拉取当前会话的历史和状态渲染系统提示词模板注入上下文变量和业务背景组装用户消息把当前输入与系统提示词拼接成模型输入调用大模型 API带上工具定义如果模型支持工具调用解析模型输出判断是普通的文本回复还是包含工具调用请求执行工具调用调用对应工具并拿到三态结果评估终止条件检查是否达到该 Agent 的终态或者是否需要中转给别的 Agent状态回写把本轮结果写入 Memory更新上下文变量循环继续或返回未终止则回到第 2 步已终止则返回最终输出。这套循环看起来不复杂但设计上有一个容易被忽视的点第 2 步的“重新渲染系统提示词”。如果你在每一轮循环里都用同一套静态提示词Agent 很容易在前几轮就跑偏。我的做法是每一轮都把“当前已执行过的工具结果摘要”注入提示词的“当前状态”区段相当于给 Agent 一份“实时战报”效果比让它自己揣测好得多。3.2 多 Agent 协作从链式调用到 Swarm 式 Handoff说到多 Agent就绕不开最近很热的 Swarm 框架概念Agent、Handoff 和上下文变量。如果要用一句话总结 Swarm 的贡献那就是把多 Agent 协作从“链式管道”变成“智能体之间互相转交主导权”。我的框架吸收了这个思路后多 Agent 协作模式从原来的一种变成三种链式流程编排适合固定顺序的业务比如工单审核——初审 Agent 完成后结果转给复审 Agent路由分发编排适合意图明确的任务比如用意图识别 Agent 判断该把请求交给客服 Agent、售后 Agent 还是技术支持 AgentHandoff 式编排适合需要动态决策的任务主 Agent 在与用户交互的过程中根据对话内容自主决定转交给另一个 Agent并且被转交方可以拿到前序的上下文变量。3.3 Handoff 的元协议设计消息即路由Swarm 里最让我觉得有价值的设计是“Handoff 就是消息的一种特殊类型”这个理念。传统做法是在两个 Agent 之间显式定义一条“路由”比如 if 检测到退款意图 then 跳转到退款 Agent。这个做法僵硬、难以维护而且绕过了 Agent 本身的自主决策权。我把 Handoff 设计成 Agent 输出结构中的一个可选字段{ reply: 好的退款问题我来帮您转接给专门负责的同事。, handoff_to: refund_specialist, context_updates: { customer_tier: vip, order_id: SO-2024-00123, refund_reason_collected: true } }框架收到带有 handoff_to 字段的输出后会做三件事先保存上下文变量再调用目标 Agent 的系统提示词渲染最后把“当前待处理的问题摘要”作为输入消息传给目标 Agent让目标 Agent 能从对话中间继续接手而不是对用户说“请问你有什么问题”。这个机制让我砍掉了大量硬编码路由逻辑。新的 Agent 只要注册一份“我能处理什么请求”的描述信息就能被其他 Agent 在需要时转接过来。新增业务 Agent 的速度提升相当明显。4. 上下文变量与会话状态避免上下文爆炸与状态漂移我把上下文管理单拎出来是因为在实际场景中上下文问题比模型选型问题更致命。很多 Agent 跑着跑着就对话混乱、重复提问、丢失关键参数十有八九是上下文变量设计得不合理。4.1 会话状态的分层设计我把 Agent 运行时的状态分成三层任何变量都必须属于其中一层不允许“游离变量”请求级Request Scope单次用户输入的携带参数比如用户当前的提问文本、附件ID会话级Session Scope整个会话过程中持续生效的变量比如用户身份、当前选中的业务ID、语言偏好全局级Global Scope框架级配置和业务全局参数比如默认超时时间、系统版本号。分层的价值体现在被 Handoff 转交时请求级变量自动丢弃会话级变量跟着走全局变量常驻。这样就天然防止了“ A Agent 留下的脏变量污染 B Agent 的判断”。4.2 上下文变量命名空间与生命周期命名空间这块跟大多数人的直觉不一样——不是“为了整洁”而是为了“防事故”。假设两个 Agent 都往上下文里写了一个名为“status”的字段主 Agent 读到的状态到底是哪个如果你不在框架层面强制命名空间这个问题迟早会变成线上事故。我制定了两条硬规则每个 Agent 写入上下文变量时必须带前缀格式是 agent_name.variable_nameAgent 只能声明“需要的输入变量范围”框架会在 Handoff 时自动剪裁上下文把无关 Agent 的变量过滤掉。这两条规则让上下文变量从一开始就处于可控状态。新 Agent 加入时只需要声明自己依赖哪些前缀下的哪些字段不会碰它不该碰的东西。4.3 压缩策略什么时候丢东西、什么时候留东西上下文窗口是有限资源压缩策略直接决定 Agent 长跑后的智商水平。我试过简单的“长度超标就截断最早消息”结果非常糟糕Agent 会把早期最重要的用户意图给忘掉。后来我采用的策略是“三分类处理法”内容类型处理策略核心不可变信息用户ID、业务ID、最初意图永久保留每次压缩时单独固定过程性信息工具调用记录、中间推理摘要化压缩只保留结论与关键数据噪声信息闲聊、重复表达、失败重试细节直接丢弃通过这套策略我的 Agent 在跑长流程任务时token 消耗降低了大约 40%准确率反而更稳定了。5. 工程化落地错误处理、可观测性与测试策略架构设计得再漂亮最终要过工程化这道坎。我遇到过不少框架Demo 阶段天下无敌上生产后寸步难行问题基本出在错误处理、可观测性和测试这三个环节。5.1 错误分类与重试边界大模型应用和传统后端有个很大的不同错误来源多而且很多错误没法从状态码上判断是否值得重试。我把错误分成三类并配置了不同的处理策略错误类型典型场景处理方式可重试瞬时错误网络超时、限流429、5xx指数退避重试最多3次不可重试输入错误参数校验失败、工具 Schema 不匹配记录日志后返回给 LLM 要求修正业务不可重试错误业务校验不通过、用户取消操作立即终止当前分支不重试这里有一个值得留意的教训不要把“LLM 输出格式非法”归类为可重试错误。我第一次遇到模型输出 JSON 解析失败时天真的对这次调用做了整体重试结果白烧了一轮 token模型再次输出的还是坏的。正确的做法是把解析失败当成“反馈信号”重新输入给模型告诉它“你刚才的 JSON 格式不对请重新输出”往往第二次就是对的。5.2 可观测性把 Agent 的“思考过程”变成可审计日志Agent 应用最大的运维难点是它不像传统的 B/S 架构那样“请求-响应”清晰直接。用户问一句背后可能发生了五次模型调用、四次工具调用、一次 Agent 转交每一步都可能出错。如果没有高质量的日志排查问题纯靠猜。我在框架里设计了结构化追踪体系每一次运行都记录如下信息运行 IDRun ID与父运行 ID用于串联整条处理链每个 Agent 的输入摘要与输出摘要每一轮 LLM 调用的 token 数、耗时和延迟工具调用参数和结果摘要敏感字段脱敏上下文变量的变化记录终止原因正常完成、达到最大轮数、被上/下游 Agent 转交、异常中断。有了这套数据我可以回到任何一次线上会话把它按时间轴重放出来看清 Agent 每一步在想什么、做了什么。这才是真正意义上的“可解释性”——不是模型自己解释“我为什么这么回”而是我们能看到它走过了哪条执行路径。5.3 测试策略从单测到场景回放Agent 框架的测试不能走传统应用的纯单测路线因为模型输出有随机性。我最终采用的测试金字塔是这样第一层组件单测。用 mock 的 LLM 响应来测工具注册、上下文渲染、状态回写这些纯逻辑第二层黄金数据集回归。准备 50~200 条典型业务输入和期望输出每次框架改动后跑一遍记录通过率第三层场景回放测试。把线上记录的真实会话序列收集为回放用例用历史日志驱动框架重放检验行为一致性。这套体系让我的框架在快速迭代的同时敢于小步发布不会每次都心惊胆战地担心线上崩掉。6. 我的实践体会与下一步演进方向文章写到这里技术上把框架的核心架构都过了一遍。最后说一点我在实际构建和使用这套框架时的真实感受也算给想自己动手的读者一盏灯。架构设计这件事在我看来最难的并不是某个复杂的算法而是忍得住不贪多。Agent 框架特别容易陷入“因为 AI 什么都能干所以框架什么都要支持”的幻觉里。我自己就吃过亏一开始加了视觉记忆、语音交互、异步多模态调度结果核心的纯文本工具调用链路反而不够稳。后来砍到只剩四个核心抽象和三个协作模式一切都回到简单可靠的状态。在上下文管理上我用“三分类处理法”之后长任务的稳定性和 token 成本都明显改善。在工具协议上强制三态返回让失败归因变得非常清晰这是 Agent 能做出正确处理决策的前提。这些经验都来自一次次线上事故和现场排查不是靠理论推出来的。如果接下来你要扩展这套框架我建议优先做两件事一是给上下文变量和运行追踪加上持久化存储的接口让整套状态管理可以跨进程、跨服务二是沉淀你的黄金数据集当你开始积累到一千条以上真实业务回放用例时框架演进的速度会快得超乎你的想象。Agent 框架构建这条路入门靠 Demo真正深入靠架构设计。这一节把整体架构定了性后面几节我会顺着这条主线逐层展开 Agent 定义、执行循环、编排机制和上下文细节的代码实现。跟着一路下来你最后拿到的会是一套真正能上生产的 Agent 体系而不是一堆零散的脚本。