
1. 七要素先给 Agent 画一张解剖图很多人聊 AI Agent一上来就争论“它到底是不是有自主意识”“未来会不会取代人”但落到工程上完全不是这么回事。我在一线做后端和 AI 应用最大的感受是Agent 不过是以大模型为推理核心、通过感知-规划-行动-反馈循环去完成目标任务的软件系统。听起来很玄拆开看就是七个要素。理解了这七块板子你再看任何框架、任何架构都不会懵。1.1 模型一切推理的起点第一个要素是大模型本身也就是推理引擎。无论是 GPT 系、Claude、还是开源模型Agent 所有智能行为都建立在这个底座上。工程选型时我一般看三个指标一是上下文窗口够不够用二是是否支持稳定的函数调用function calling三是服务商的限流策略和响应延迟。很多项目失败不是因为 Agent 逻辑写得不好而是模型选错了。比如工具调用支持差的模型你让它输出 JSON 参数它给你扯一段废话上下文窗口短的模型塞几轮工具结果就溢出了。所以模型是骨架后续每个要素都建立在它之上。1.2 规划把目标拆成可执行的步骤规划就是让模型想清楚“先做什么、再做什么”。常见的手法有 ReAct 模式也就是“思考-行动-观察”循环也有 Plan-and-Execute 模式先输出一个完整计划再逐步执行。我做个类比规划就像你写工作周报先列总目标再拆成“周一发布、周二验证、周三复盘”这样的细项。工程实现时规划不一定是独立模块。很多时候你只是在 Prompt 里加一句“请先分解问题再逐步解决”或者用一个规划节点单独生成任务列表。但要注意规划越自由越容易失控。所以给模型限定动作空间非常重要比如只能调用五类工具禁止无限自我发散。1.3 记忆短期状态和长期知识的区别记忆是 Agent 和单轮 Chat 最大的区别之一。短期记忆就是当前会话里的对话历史、中间状态长期记忆则是能被后续会话复用的用户偏好、事实知识、历史结果。我用一个容易踩坑的理解记忆不是越大越好而是要分层。短期记忆直接塞进上下文调用模型时拼到 Prompt 后面。长期记忆一般落到数据库或向量库通过语义检索把相关片段捞回来。很多 Agent 一开始做得很好跑一段时间就变傻就是因为只堆聊天记录没有做摘要和淘汰机制。之后我们在决策点里会专门聊怎么管记忆。1.4 工具Agent 的“手和脚”工具是 Agent 与外部世界交互的接口。搜索、计算器、数据库查询、发邮件、操作浏览器本质上都是一个个被模型调用的函数。没有工具的大模型只是一个“嘴强王者”能聊但干不了活。有了工具它才真正“下地干活”。工程上工具被描述为一段 JSON Schema 外加一个对应函数。模型看到 Schema 后决定“我需要调用哪个工具、传入什么参数”。这一步直接决定了 Agent 的能力边界。我的经验是工具不在多在于接口稳定、描述清楚。一个语义含糊的工具描述会诱导模型反复传错参数。1.5 行动把模型意图变成系统操作规划定好了方向工具定义了能力行动则是真正执行调用的那一下。行动模块负责解析模型的输出校验参数调用对应函数然后把结果拿回来。这里有个容易忽略的问题行动必须带容错。工具执行可能超时、可能返回异常你必须把这些异常包装成“模型能看懂的错误信息”再喂回去。否则模型下一次调用还会撞同一个坑。行动计划是模型对世界的假设行动结果才是世界的真相两者必须在这个环节对齐。1.6 反馈闭环是 Agent 的灵魂没有反馈的循环只能算“脚本”。Agent 每执行一步都要观察结果、检查是否完成目标再决定是继续修正还是结束。反馈可以是工具返回的数据可以是环境状态也可以是用户补充的信息。我见过很多把 Agent 做成“一次性输出”的项目本质没有循环那不叫 Agent。真正有用的 Agent 一定是把“行动-观察-再规划”这条回路跑起来的。这一步也是工程调试最投入精力的地方因为反馈未必符合预期你需要设计中止条件防止它无限循环。1.7 安全生产环境不容商量的一层最后一个要素常常被忽略但它决定了 Agent 能否上线。安全包括敏感操作之前的二次确认、输出内容的合规过滤、任务边界限制、对异常指令的拒答。这不是“加个敏感词库”那么简单而是要在 Agent 的控制流中插入护栏guardrail节点。我的建议是涉及真实世界后果的操作比如发邮件、转账、删数据必须在行动节点前加一层人工审批。模型可以无边无际地天马行空系统边界必须严格收敛。这七个要素不是可选项而是一个完整 Agent 的必备项。接下来我们把视角从解剖学切换到工程决策。2. 七个决策点动手前先回答的问题解剖学告诉你 Agent 有哪些零件工程实现告诉你这些零件怎么组装。我把它归纳成七个必须提前拍板的决策点。每个决策点没有绝对对错只有适不适合你的业务场景。下面逐个拆开讲。2.1 决策点一框架选型用 LangGraph 还是自研这是第一个要命的问题。市面上有 LangChain/LangGraph、Haystack、Spring AI Agent、Rust 生态里的 Rig 等还有低代码平台如扣子Coze。选型不能跟风要看你团队的技术栈和要解决的问题。如果你已经重度使用 Python且业务流程复杂、需要精细控制循环和状态LangGraph 是目前最顺手的方案。它把 Agent 画成一张有向图节点是逻辑执行块边是条件路由非常适合做“多步规划、工具调用、掉头重试”的循环。Spring AI Agent 适合 Java 团队能直接和 Spring Boot 生态融合但灵活度不如 LangGraph。Rust 适合对性能和并发要求极高的场景但模型生态和社区资料相对少开发成本偏高。我的观点是自研框架不推荐从零开始。你不会想自己维护 Prompt 拼接、消息历史、工具调用协议这些轮子。除非你的业务极其特殊且团队有充足精力否则站在成熟框架的肩上做二次开发更划算。选型的最终标准是“团队能否长期维护”不是“谁家 Star 最多”。2.2 决策点二控制流线性 Chain 还是状态图控制流是 Agent 的中枢神经。最早期大家用 Linear Chain一锤子买卖调用模型拿结果完事。后来发现任务经常要“根据中间结果决定下一步”于是 LangChain 发明了各种 Router。但这些组合越来越拧巴所以有了明确的图状态机像 LangGraph 那样把每个环节定义成节点节点之间的边代表转移条件。工程上我强烈建议复杂的 Agent 用状态图而不是互相嵌套的 Chain。因为图结构天然适合“循环”和“分支”比如“如果工具执行失败回到规划节点重新生成”“如果检测到任务完成走到结束节点”。图还容易可视化调试时你能看到当前卡在哪个节点数据流是怎样的。用图不是让所有逻辑都画成密密麻麻的节点。节点粒度要适中太粗没法复用太细调试难。我通常把“模型调用”“工具执行”“条件判断”“输出格式化”各设计成独立节点简单直接。2.3 决策点三记忆生命周期怎么存、怎么更新、怎么忘前文提到记忆要分层但真正落地时你会遇到更头疼的问题上下文窗口不够用了怎么办旧记忆和当前任务冲突怎么办不同用户的记忆怎么隔离我的实践是把记忆生命周期分成三个阶段。第一阶段是“工作记忆”也就是当前会话的上下文通常用消息列表保存加一个最大轮数截断第二阶段是“摘要记忆”当对话超过阈值让模型把前面内容压缩成摘要再接续后面的对话第三阶段是“长期记忆”把一些关键事实写入向量库或 KV 存储下次会话检索复用。“遗忘”同样重要。我会对长期记忆加时间戳和饱和度超过一段时间没被命中的内容定期淘汰。否则记忆库会堆满噪声检索时反而把无关内容捞回来干扰模型判断。记忆系统的设计目标是“在最恰当的时机给模型最少但最关键的信息”。2.4 决策点四工具协议统一 Schema 还是自由文本调用模型调用工具不是魔法它遵循一套协议。业界普遍推荐的协议是 OpenAI Function Calling也就是把每个工具定义成一段包含 name、description、parameters 的 JSON Schema。模型看到这些信息后选择工具并生成符合 Schema 的参数。千万别小看工具描述它决定了模型调用准不准。我踩过最大的坑是工具描述写得太简略模型频繁传错枚举值或者在参数里写“任意字符串”结果模型填了一堆废话。正确做法是给每个参数写示例、写约束、写默认值并且把潜在的常见误用写进描述里。服务端还要做统一的工具注册中心。每次新加工具只需注册一个函数加一个 Schema不用改控制流。工具执行异常时要捕获错误并转成“给模型的正常反馈”而不是直接让整个 Agent 崩溃。工具协议是接口工程稳定比丰富重要得多。2.5 决策点五Token 预算如何不烧钱又能保证效果Token 是 Agent 里绕不开的成本。一次复杂任务可能要调用模型十几次每条历史消息、每段工具结果都会重复计入上下文Token 消耗会肉眼可见地涨。很多团队上线前没算这笔账月底账单直接傻眼。我的成本控制有四个抓手。一是估算公式每次请求 Token 消耗 系统 Prompt 历史消息 工具描述 当前输出上限。工具描述往往是隐性大户工具数量越多每次请求都背着全量 Schema。二是裁剪策略历史消息按轮剪只保留最近的 N 条变动加上摘要兜底。三是结构化输出让模型用 JSON Schema 输出尽管会多花一点 Token但能显著减少“参数传错”导致的无效重试。四是缓存相同工具结果、相同上文前缀可以做缓存避免重复计算。预算不是死的。我会按用户等级或任务复杂度设置不同档位简单任务用轻量模型复杂任务才启用大模型。最终效果是“能省则省关键动作不省”。2.6 决策点六并发与状态隔离Agent 怎么扛得住流量“AI Agent 怎么扛并发”是近期很多人关心的问题。瓶颈不在于模型吞吐而在于 Agent 的状态管理。每个用户的会话都有自己的上下文、自己的待执行计划、自己的记忆如果把所有状态扔进一个全局变量并发一高必然串号。我的年轻团队现在用 FastAPI LangGraph 做部署。核心思路是把 Agent 状态做成可序列化的对象每个请求进来时创建一个独立的实例执行完把状态和最终结果持久化到数据库或 Redis。服务节点保持无状态这样横向上可以随意扩容扛并发靠加机器而不是靠单机硬撑。异步化也很关键。工具调用如果是网络请求要使用 async 方式避免进程阻塞。数据库连接池、Redis 连接池要单独管理不能让每个 Session 都新建连接。另外模型 API 本身的限流也要在网关层做排队和退避否则上游一限流下游所有请求都撞墙。2.7 决策点七可观测与评测没有监控就别上线最后一个决策点最容易被忽略你拿什么证明 Agent 表现好纯看用户反馈太慢必须建设可观测性和离线评测两套体系。可观测性要做三件事日志、追踪、指标。日志记录每一轮“规划了什么、调用了哪个工具、返回了什么结果”追踪把同一个任务的所有动作串起来方便回放指标则统计成功率、平均轮数、平均耗时时长。没有这些你根本无法判断是模型问题、工具问题还是 Prompt 问题。评测是我反复强调的重点。准备一套带标准答案的评估集覆盖主要任务路径和边界情况。每次改 Prompt、换模型、调参数都跑一遍评测集看完成任务的比例和工具调用的准确率。线上表现和离线评测常常不一致但离线评测依然是最快的回归手段。如果预算有限先跑三十条典型 case也比裸上线靠用户骂要强。3. 实操用 FastAPI LangGraph 搭一个能跑并发的 Agent讲完理论和选型我直接分享一套可复用的工程实现。这里混用了 Python 生态的代表性技术FastAPI 提供 HTTP 服务LangGraph 管理 Agent 流程LangChain 提供模型封装。这个组合不是唯一解但非常清晰适合中小团队快速落地。3.1 工程目录与依赖规划项目结构我会拆成四层避免所有逻辑塞进一个文件app/ main.py # FastAPI 入口负责会话创建和请求代理 agent/ graph.py # 定义 Agent 状态图 tools.py # 工具注册中心 state.py # 会话状态模型 memory/ store.py # 历史消息和长期记忆存储 service/ agent_runner.py # 独立执行 Agent 的封装依赖方面核心是fastapi、langchain、langgraph、openai。如果不需要复杂的封装其实可以直接用langgraph配合任何模型的 API。我不建议为了用框架而用框架代码里看到多少抽象取决于你实际需要多少控制。3.2 Agent 状态图的核心代码LangGraph 的核心是定义状态类型和节点函数。下面是一个简化但完整可运行的例子from typing import TypedDict, Annotated, Literal from langgraph.graph import StateGraph, END from langgraph.graph.message import add_messages class AgentState(TypedDict): # 使用 add_messages 注解LangGraph 会自动合并新消息到历史列表 messages: Annotated[list, add_messages] tool_plan: list[str] step: int done: bool def plan_node(state: AgentState): # 构造规划 Prompt让模型决定调用哪个工具、传入什么参数 # 实际代码可以调用 LLM 或直接基于规则路由 return {step: state[step] 1} def tool_node(state: AgentState): # 执行工具注册中心里被选中的函数 return {messages: [{role: assistant, content: 工具执行完成}]} def should_continue(state: AgentState) - Literal[tools, finish]: if state[done] or state[step] 5: return finish return tools def build_graph(): graph StateGraph(AgentState) graph.add_node(plan, plan_node) graph.add_node(tools, tool_node) graph.set_entry_point(plan) graph.add_conditional_edges(plan, should_continue, {tools: tools, finish: finish}) graph.add_edge(tools, plan) graph.add_edge(finish, END) return graph.compile()这段代码的关键点是add_messages它保证消息历史不会被覆盖而是追加。step字段用来兜底防止 Agent 陷入死循环最多跑五轮。实际项目中tool_node会解析模型选出的工具名和参数再调用工具最后把工具结果作为消息写回状态。3.3 FastAPI 接入与并发隔离FastAPI 接入时要注意每个请求必须独立创建 Agent 实例不能全局复用。LangGraph 的compile()返回一个可调用对象你可以为每个会话创建状态初始化值from fastapi import FastAPI from pydantic import BaseModel app FastAPI() class ChatRequest(BaseModel): session_id: str user_message: str class AgentSession: def __init__(self, session_id: str): self.session_id session_id self.history [] async def run(self, message: str) - str: graph build_graph() initial_state {messages: [{role: user, content: message}], tool_plan: [], step: 0, done: False} result await graph.ainvoke(initial_state) return result[messages][-1][content] sessions {} app.post(/chat) async def chat(req: ChatRequest): session sessions.setdefault(req.session_id, AgentSession(req.session_id)) answer await session.run(req.user_message) return {answer: answer}这里sessions是演示用的进程内字典生产环境应该换成 Redis。这里想强调的是每个 session 拥有独立的 Agent 状态流量再大也不会影响彼此。进程内字典的缺点是重启丢失、单机内存瓶颈但只要换成 Redis整个架构就变得可以横向扩展。FastAPI 的async还能撑住大量 IO 等待适合 Agent 这种“推理耗时长、算力占用小”的服务。3.4 部署时还需要注意的几个细节部署 Agent 服务和部署普通 Web 服务有区别。首先是超时设计模型推理可能耗时数十秒网关和负载均衡器要配置与之匹配的超时时间其次是流式输出如果前端希望打字机效果需要把后端的响应改成 SSEServer-Sent Events最后是 Graceful ShutdownAgent 执行到一半时进程退出正在进行的任务是直接丢弃还是标记重跑要提前设计。内存方面LangGraph 状态对象不能无界增长。每个 Session 结束前要把状态序列化后存到持久层并释放内存引用。我见过最典型的线上事故是Session 对象被 Hono 上全局引用后上下文越来越多内存直线上升最后 OOM。定期清理空闲 Session 是必须做的事。4. 真实踩坑实录常见问题与排查技巧最后这部分是我希望大家少走弯路的地方。下面这些坑都是我在生产环境里真实遇到过的直接给结论。4.1 模型无限循环工具调用停不下来现象是Agent 一直在“调用工具-观察结果-继续调用”永远不输出最终答案日志里 step 数值狂奔。原因通常有两个一是终止条件写得太松模型认为任务永远没完成二是工具结果里出现了它不理解的新信息导致它反复探索。解法有三个。第一给 Agent 的 Prompt 里明确写“如果你已经获取到关键信息请直接给出最终答案”第二设置最大迭代次数比如 5 次或 10 次超过后强制结束并返回已收集信息第三检查是不是工具描述不清晰某些工具返回错了结果导致模型误判。我一般先查流程日志看最后几轮“观察”的内容是什么再决定是改 Prompt 还是改终止条件。4.2 并发一高上下文串了现象是用户 A 的对话里突然冒出用户 B 的信息越查越诡异。十有八九是全局变量存了会话状态或者消息历史用了类级别的可变变量。Python 里特别容易踩这个坑因为默认参数是可变的多个请求如果共享同一个 List就会互相污染。解法很简单每个请求实例化状态消息历史用copy.deepcopy或者在进入节点时通过add_messages创建新列表。LangGraph 的Annotated和add_messages其实已经帮你处理了合并逻辑但如果你自己手写state[messages].append(...)并发时就会出事。记住状态不可变每次返回新状态是 LangGraph 的使用铁律。4.3 Token 消耗暴涨工具异常反复重试现象是某天调用量突然翻倍排查发现同一个工具失败后模型锲而不舍地重试了七八次。问题出在工具异常信息太笼统。如果返回“调用失败”模型并不知道怎么改参数只能盲试。解法是把异常转成“可行动的提示”。比如“天气接口超时建议 3 秒后重试或改用备用城市编码”。模型看到具体原因后才能做出正确的下一步决策。同时在工具节点加幂等和重试策略比如对只读接口最多重试两次对写接口必须人工确认。这个策略既省 Token也避免把线上系统打崩溃。4.4 上线前没有评测集改一个配置心里没底现象不是故障而是恐惧改了 Prompt 之后不知道效果是变好还是变坏。没有评测集你只能靠手动点几个 Case 靠感觉判断这在 Agent 系统里非常危险。我现在的做法是维护两个评测集。第一个是“核心路径”覆盖典型用户问题比如“帮我查快递”“帮我订会议”第二个是“边界与对抗”覆盖模糊指令、空缺参数、敏感操作等。每次改动先跑核心路径要求通过率不低于之前再跑边界集记录失败案例并人工判断。有了这套流程我敢每周迭代 Prompt 和模型版本。注意Agent 的评测不是一次性的。模型厂商升级版本、工具接口调整、业务规则变化都可能让此前通过率很高的评测集一夜失效。所以评测集本身也要维护定期检查是否还有效、是否覆盖了最新的业务场景。最后再分享一个小技巧给 Agent 的执行过程加可视化回放。无论是用 LangGraph 自带的绘图还是自己写一个日志面板能看到每一步“思考了什么、调用了哪个工具、返回了什么结果”能让你调试效率提升一个量级。我第一次做回放面板时才发现很多所谓的“模型不听话”其实是工具返回的数据格式和我预期不一致。Agent 工程是系统工程光盯着模型 Prompt 是不够的七个要素、七个决策点缺一不可。记住先拆解再决策最后用日志和评测去验证你的每一个选择。