第一章 别再画那个同心圆了——AI Agent架构的真实图景

24 阅读10分钟

你一定见过那张图。

一个同心圆,最里面是"LLM",外面一圈是"Tools",再外面是"Memory",最外层是"Planning"。或者换个版本:一个循环箭头图,"Observe → Think → Act → Observe",周而复始。

这些图不是错的。它们只是没用

当你真正要设计一个能跑在生产环境的Agent系统时,你会发现这些图告诉你的东西,和你要解决的问题之间,隔着一道鸿沟。它们告诉你Agent"概念上怎么运作",但没告诉你"工程上怎么实现"。

这篇文章要画另一张图。一张能指导你写代码、做技术选型、排查问题的图。


同心圆图的问题在哪

先说清楚,同心圆图和ReAct循环图在教学层面是有价值的。它们帮初学者建立了一个心智模型:Agent不是魔法,是一个有输入、有处理、有输出的系统。

但当你从"理解概念"进入"设计系统"时,这些图就开始误导了。

问题一:掩盖了复杂度的真实分布

同心圆图暗示每一层是等价的、对称的。但真实系统中:

  • LLM调用是核心,但只占代码量的5%
  • 工具调度是重头,占代码量的30%,也是bug高发区
  • 记忆系统不是一层,是三层(工作记忆、短期记忆、长期记忆),每层的存储介质、检索策略、失效策略都不同
  • 规划不是独立模块,是渗透在每一层的决策逻辑

同心圆图让你以为"加个记忆层"就像给洋葱加一层皮。实际上,记忆系统涉及向量数据库选型、embedding模型选择、检索策略设计、记忆压缩、遗忘机制……每一个都是独立的技术决策点。 image.png (见配图1:同心圆图 vs 6层架构对比)

问题二:没有数据流

ReAct循环图告诉你Agent会"循环",但没告诉你:

  • 循环的触发条件是什么
  • 循环的终止条件是什么
  • 循环中的状态如何传递
  • 循环失败时如何回退

没有数据流,你就无法推理系统的行为。当Agent在生产环境中出现"死循环"或"过早退出"时,你拿着ReAct图根本无法定位问题。

问题三:没有边界

同心圆图没有告诉你在哪里划线。哪些是Agent系统的边界?哪些是外部系统?哪些是基础设施?

结果就是,很多Agent系统设计成了一锅粥:LLM调用、工具执行、状态存储、日志记录、监控告警全部耦合在一起。改一个工具的返回格式,可能牵连到记忆系统的解析逻辑。


生产级Agent的6层架构

现在我们来画一张有用的图。

这张图的核心思想是:Agent系统是一个分层架构,每一层有明确的职责和接口。你可以在每一层做技术选型、做单元测试、做性能优化,而不影响其他层。

┌─────────────────────────────────────────────────────────────────┐
│                        监控与可观测层                              │
│   Tracing / Logging / Metrics / Alerting                        │
└─────────────────────────────────────────────────────────────────┘
                                 ↑
┌─────────────────────────────────────────────────────────────────┐
│                          编排层                                   │
│   任务调度 / 流程控制 / 错误处理 / 重试策略                          │
└─────────────────────────────────────────────────────────────────┘
                                 ↑
┌─────────────────────────────────────────────────────────────────┐
│                         记忆系统                                  │
│   工作记忆(Context) / 短期记忆(Session) / 长期记忆(Vector/KG)       │
└─────────────────────────────────────────────────────────────────┘
                                 ↑
┌─────────────────────────────────────────────────────────────────┐
│                        工具调度层                                 │
│   工具注册 / 工具选择 / 参数绑定 / 执行 / 结果解析                    │
└─────────────────────────────────────────────────────────────────┘
                                 ↑
┌─────────────────────────────────────────────────────────────────┐
│                        LLM调用层                                  │
│   Prompt构建 / 请求发送 / 响应解析 / Token统计                       │
└─────────────────────────────────────────────────────────────────┘
                                 ↑
┌─────────────────────────────────────────────────────────────────┐
│                        输入输出层                                 │
│   用户输入解析 / 输出格式化 / 流式输出 / 多模态处理                    │
└─────────────────────────────────────────────────────────────────┘

第1层:输入输出层

职责:处理用户交互的"最后一公里"

这层看起来简单,实际上坑很多:

  • 输入解析:用户输入是纯文本?结构化指令?多模态(图片+文本)?需要做意图分类吗?
  • 输出格式化:返回纯文本?Markdown?JSON?需要做格式校验吗?
  • 流式输出:LLM的streaming输出如何传递给前端?SSE?WebSocket?
  • 多模态处理:如果Agent要返回图片/文件,如何处理?

技术选型示例

  • 简单场景:直接字符串处理
  • 复杂场景:Pydantic模型做输入校验,FastAPI的StreamingResponse做流式输出

第2层:LLM调用层

职责:封装LLM API调用,提供统一接口

这层的核心是抽象。你不想让业务代码直接依赖OpenAI的SDK,因为:

  • 模型会换(GPT-4 → Claude → DeepSeek)
  • API会变(OpenAI的接口改过好几次)
  • 调用策略会调整(重试、超时、降级)

关键设计

class LLMClient(ABC):
    @abstractmethod
    async def complete(self, messages: list[Message], tools: list[Tool] | None = None) -> Response:
        pass

class OpenAIClient(LLMClient):
    async def complete(self, messages, tools=None):
        # 实际调用OpenAI API
        ...

class MockClient(LLMClient):
    async def complete(self, messages, tools=None):
        # 测试用的mock
        ...

技术选型示例

  • 轻量级:自己封装(200行代码足够)
  • 重量级:LiteLLM(统一多家API,但引入额外依赖)

第3层:工具调度层

职责:管理工具的生命周期,执行工具调用

这是Agent系统的核心复杂度所在。工具调度不只是"调用API",还包括:

  • 工具注册:如何描述工具?OpenAI的function calling格式?JSON Schema?
  • 工具选择:LLM返回的工具调用请求,如何路由到实际实现?
  • 参数绑定:LLM返回的参数是JSON字符串,如何解析并绑定到函数参数?
  • 执行:同步还是异步?超时怎么处理?并发控制?
  • 结果解析:工具返回的结果如何格式化回传给LLM?

一个真实的bug案例

某Agent在调用天气API时,LLM返回了 {"city": "北京"},但API实际需要 {"location": "Beijing"}。问题出在哪?

  • 不是LLM的错(它按工具描述返回了)
  • 不是API的错(它按文档要求参数)
  • 工具描述和API实现不一致

这个bug在"同心圆图"里根本无法定位。在6层架构里,你知道这是"工具调度层"的问题——工具描述(schema)和实际实现(handler)的契约不匹配。

第4层:记忆系统

职责:管理Agent的"状态"和"经验"

记忆不是一层,是三层: image.png (见配图2:记忆系统3层架构)

层级存储介质生命周期典型实现
工作记忆LLM上下文窗口单次推理消息列表
短期记忆内存/Redis单次会话会话状态字典
长期记忆向量数据库跨会话Pinecone/Milvus/PGVector

关键问题

  • 记忆写入:什么信息值得记住?全部记住会撑爆上下文窗口
  • 记忆检索:如何检索相关记忆?纯向量相似度?混合检索?
  • 记忆更新:用户说"我之前说的那个城市改成上海",如何更新已有记忆?
  • 记忆遗忘:过时信息如何清理?错误推理链如何删除?

一个反直觉的设计

很多Agent系统的问题不是"记不住",而是"记得太多"。上下文窗口塞满了无关信息,导致LLM推理质量下降。

好的记忆系统,遗忘机制比记忆机制更重要

第5层:编排层

职责:控制Agent的执行流程

这是"规划"真正发生的地方。但不是让LLM"自己规划自己执行",而是:

  • 任务分解:复杂任务如何拆成子任务?
  • 流程控制:串行执行?并行执行?条件分支?
  • 错误处理:某一步失败了,重试?跳过?回滚?
  • 终止条件:什么时候算"完成"?什么时候算"失败"?

编排模式对比

image.png (见配图3:编排模式对比)

模式适用场景复杂度
单次调用简单查询
ReAct循环多步推理
Plan-and-Execute复杂任务
状态机固定流程
DAG编排并行任务

一个踩坑案例

某Agent用ReAct循环处理用户请求,但LLM在"Think"步骤里反复纠结,形成了死循环。问题出在哪?

  • 不是LLM的错(它只是在"思考")
  • 编排层缺少终止条件

解决方案:在编排层加一个"最大步数限制"和"重复检测"——连续3次相同的思考就强制终止。

这个设计在"ReAct循环图"里是看不到的。它属于编排层,不属于"推理"。

第6层:监控与可观测层

职责:让Agent系统"可调试"

Agent系统是黑盒吗?不应该是。

但很多Agent系统确实是黑盒——你只知道"输入"和"输出",中间发生了什么,完全不知道。LLM调了几次?选了什么工具?工具返回了什么?为什么选这个工具而不是那个?记忆检索到了什么?

需要记录什么

  • 推理链:每一步的思考过程
  • 工具调用:调用参数、返回结果、耗时
  • 状态变迁:记忆的读写、会话状态的变化
  • 性能指标:每一步的耗时、token消耗

技术选型示例

  • LangSmith:LangChain生态,开箱即用
  • Arize Phoenix:开源,支持多种框架
  • 自建:OpenTelemetry + Grafana

从"单次对话"到"长期运行系统"

理解了6层架构,你就能理解一个关键转变:

Agent系统不是"对话系统",是"长期运行的自动化系统"

image.png (见配图5:Demo到生产的关键转变)

对话系统:用户发一句话,系统回一句话,结束。状态可以不保存。

Agent系统:用户发一个任务,系统可能要执行几分钟、几小时、甚至几天。中间可能调用几十次工具、读写几百条记忆、产生几千条日志。系统崩溃后要能恢复,执行失败后要能重试,用户中途要能干预。

这个转变意味着:

  • 状态管理变得至关重要(不能全放内存)
  • 错误处理要系统化设计(不能只靠try-catch)
  • 可观测性是必需品(不是锦上添花)
  • 成本控制要纳入架构设计(token消耗、API调用次数)

一张图胜过千言万语

现在,我们来看一张真实的Agent架构图。

image.png (见配图:生产级Agent架构图)

这张图和同心圆图的区别:

  1. 有层次:每一层职责清晰,可以独立设计、测试、优化
  2. 有数据流:箭头表示数据流向,可以推理系统行为
  3. 有边界:虚线框表示系统边界,明确哪些是内部、哪些是外部
  4. 有基础设施:监控、日志、存储不是"外挂",是架构的一部分

这张图怎么用

当你设计一个Agent系统时,按这个流程:

  1. 从上到下梳理:用户输入是什么?输出是什么?需要哪些工具?需要记忆吗?执行流程是什么?如何监控?
  2. 逐层做技术选型:每一层有哪些方案?各有什么trade-off?
  3. 逐层写代码:先写LLM调用层,再写工具调度层,再写记忆系统……不要一上来就写"Agent"
  4. 逐层测试:每一层可以独立单元测试,不需要端到端跑起来才能测

当你排查一个Agent系统的bug时,按这个流程:

image.png (见配图4:Agent故障排查路径图)

  1. 定位层级:问题出在哪一层?
  2. 检查接口:这一层的输入输出是否符合预期?
  3. 检查实现:这一层的内部逻辑是否有问题?
  4. 检查依赖:这一层依赖的下层是否正常?

小结

同心圆图和ReAct循环图是教学工具,不是设计工具

当你从"理解Agent是什么"进入"设计Agent系统"时,你需要的是一张能指导工程实践的架构图。

6层架构不是唯一正确的架构,但它是一个可操作的起点

  • 每一层有明确的职责
  • 每一层有成熟的技术选型
  • 每一层可以独立测试和优化

下一篇文章,我们讨论一个更实际的问题:什么场景该用Agent,什么场景不该用。不是所有问题都需要Agent,过度Agent化是当前最大的坑。