从七要素到七个决策点我用这套方法才真正把一个 AI Agent 从demo做成能上线做 AI Agent 的工程实现最尴尬的一个阶段就是你说它智能吧它连个天气查询都能答错你说它没用吧它确实能帮你自动写邮件、查文档、调接口。我踩了很长时间的坑最后发现问题的关键不在模型选得够不够大也不在 Prompt 写得够不够花而在于我脑子里根本没有一套完整的Agent 工程化框架。直到我把整个项目拆成七要素的静态骨架再靠七个决策点去控制它的运行走向才终于把一个能跑通 demo 的 Agent变成了一套可以扛住真实业务请求的工程系统。这篇文章就是我基于实际项目经验做的完整拆解。适合正在做或者准备做 AI Agent 的人无论是你打算基于 LangChain、LangGraph 这类框架还是想用 Rust 或者直接调原生 API 手搓一套。我会讲清楚 Agent 工程落地时要关注的构件以及为什么很多项目死在了第七层决策上。1. 两种拆法七要素看清骨架七个决策点看清运行逻辑1.1 先理解七要素Agent 不是模型是一台需要七个部件的机器很多人把 AI Agent 误会成一个更强的模型这是第一个认知偏差。模型只是 Agent 这个系统里的一个计算核心而已。我的经验里任何一个合格的 Agent 工程实现都需要齐备七个基本要素缺一个后期都会出事。第一个要素是大模型接口。它负责把用户的自然语言请求转成可执行的意图。这个接口可以是 GPT 这类闭源 API也可以是开源模型本地部署甚至还可以是更底层的自研推理服务。这个层的工程重点在于要能统一管理模型供应商、模型版本、超参配置。不要在一个 Agent 服务里写死某个模型的名字因为 A/B 测试、成本优化、模型故障切换都需要在接口层做抽象。第二个要素是提示词系统。真正工程化的提示词绝不是一条你是一个助手的字符串。它是一套模板组织方式包含系统角色约束、任务目标、工具描述、对话历史、用户当前输入、输出格式要求。这个要素的工程挑战在于模板的版本管理和动态拼接。我见过太多项目提示词全写在代码段落里换个说法就不得不改代码重新部署。第三个要素是工具集。Agent 要和外部世界交互就得有手。工具可能是内置函数、业务 API、代码解释器、数据库查询器。工程上最忌讳的是让模型闭眼乱调工具。每个工具都应该尽可能附带上作用描述、参数 Schema、返回值结构、调用失败返回什么错误。说白了工具层做得越规整模型越不容易瞎编参数。第四个要素是记忆系统。记忆分成短期记忆当前会话上下文和长期记忆跨会话的知识、用户偏好、历史状态。工程实现里记忆不是一个存到变量里的概念而是包含读写接口、存储介质内存、Redis、数据库、向量库、淘汰策略Token 太长怎么办、持久化策略Agent 重启后记忆还在不在的一整套方案。第五个要素是执行器。执行器就是决定下一步做什么的东西。它负责解析模型输出中的意图和动作然后调度对应工具执行再把执行结果反馈给模型。在简单场景里执行器可以是一个 if-else 分支在复杂场景里执行器就是一个状态机或者图结构。执行器的质量直接决定了 Agent 是按部就班地工作还是陷入死循环。第六个要素是安全与控制。这是最容易被忽略的。Agent 一旦拿到工具权限它就可能执行删除操作、支付操作、外发消息。工程上必须有权限分层、敏感操作二次确认、输入校验、输出审计。我见过有团队让 Agent 直连生产库模型把 where 条件写错了一次直接导致整表更新。所以控制永远要前置。第七个要素是观测与评估体系。Agent 这种系统输出是自然语言行为是动态的如果不观测出了问题你根本不知道是哪一步错了。观测要包含每一次模型请求的耗时、Token 消耗、调用链追踪、工具执行结果、用户满意度反馈。评估则要包含离线测试集和在线指标没有这两样你能把它调好全靠运气。这七个要素不是七种插件而是同一个系统里必须同时存在的七个层。我见过有人写了个 200 行的 Python 脚本就把 Agent 跑起来了对那东西根本没有工程实现只是一个玩具。真正的工程化必须把这七个要素先定义清楚再去考虑怎么做代码架构。1.2 再看七个决策点Agent 运行时的灵魂七问如果说七要素是 Agent 的静态结构那么七个决策点就是动态运行时的岔路口。我在项目里把这七个决策点变成了一套中间件式的流程这也是我认为全文最核心的干货。第一个决策点是意图识别用户这句话是要执行任务、还是单纯聊天、还是需要澄清不要小看这个决策。很多 Agent 变成人工智障就是因为把帮我看看订单和我今天心情不好都丢给了同一个执行流程结果一个瞎调工具一个强行回复。第二个决策点是上下文选择当前这轮对话应该带哪些历史消息全部带进去还是只带最近三条还是需要把一周前的某个关键偏好也追加进来这个决策直接关系到 Token 成本更关系到模型会不会被无关历史干扰。第三个决策点是工具选取如果意图是执行任务那么选哪几个候选工具是一次性把所有工具描述全给模型还是先做一次召回工具越多模型的选择错误率越高所以这里至少要做一个粗糙的过滤。第四个决策点是参数生成与校验选定了工具模型要生成调用参数。参数合法吗类型对吗枚举值对吗有没有超出业务范围的危险值这一关必须在调用工具之前拦住。不要信任模型每次都能生成对的参数。第五个决策点是执行策略工具是同步执行还是异步执行如果工具调外部 API超时怎么算调用失败要不要自动重试重试几次如果工具耗时太长用户等得受不了应该怎么处理这一步是把能用变成好用的关键。第六个决策点是结果评估与反馈循环工具返回的结果中有没有冲突信息信息够不够回答用户不够的话要不要再补一轮工具调用这决定了 Agent 是一次问答还是多轮自主任务的分水岭。第七个决策点是输出生成与终态判定最终要不要生成自然语言回复还是直接把结构化结果返回调用方对话会话是继续维持还是结束如果后续还有任务要不要更新长期记忆很多 Agent 崩溃在这里因为它永远不知道自己什么时候该停下来。我强烈建议你把这七个决策点做成一张清单贴在代码注释里。每次你调试一个 Agent 行为异常就对着清单逐个排查。80% 的问题都能定位到具体决策点上而不是玄学般的模型不够聪明。2. Agent 工程落地的四个核心环节2.1 上下文管理与 Token 预算你的 Agent 不是在对话是在做内存管理上下文管理是 Agent 工程里第一个被低估的难点。真实项目里模型能接收的 Token 是有限的就算现在有了长上下文模型也有成本和延迟的约束。你不能天真地把所有历史都塞进去也不能只带一条用户最新消息那样模型会失去前后文语义。我在生产系统里采用了一套多级上下文策略。第一级是系统提示词它永远在第二级是任务相关的固定背景知识比如行业术语、企业规则这些内容通常比较长但不会频繁变化第三级是滚动历史窗口也就是最近 N 轮对话第四级是从长期记忆里召回的相关片段。这里有一个非常重要的工程细节**Token 预算是要在请求发出之前就计算好的。**我的做法是先用一个快速的 Tokenizer 估算各个部分的长度再根据模型的最大 Token 限制逆推滚动窗口保留多少轮。举个例子模型最大上下文是 128K系统提示词占了 2K背景知识占了 5K长期记忆召回了 3K那留给滚动历史加用户输入的预算就是 118K。假设每一轮对话平均消耗 2K那滚动窗口最多保留 59 轮。实际上我会预留 20% 的缓冲因为它还有工具返回结果要放进去。再往深一层上下文里最脏的东西是工具返回结果。有时候一个数据库查询返回 100 行数据模型根本用不了那么多还会把 Token 撑爆。所以我在工具层做了一个返回压缩机制数据超过一定行数就做聚合统计、截断字段、或者直接让模型先用代码处理一遍再拿结果。之前我踩过一个很疼的坑客户让我把订单列表交给 Agent 做分析结果一次查询返回了 3000 条记录Token 瞬间耗尽Agent 直接报错。后来我在工具返回层加了一个摘要器把 3000 条记录先算好总金额、类目分布、异常订单数再让 Agent 基于这些指标做解读问题立刻就解决了。Token 预算管理还有一块容易忽略就是输出 Token 限制。模型生成回复的时候如果最大输出 Token 设置太小回复会被截断用户看到半句话设置太大用户等待时间长。我的经验是纯对话场景 max_tokens 设置在 512 到 1024 之间涉及代码生成、长文总结的场景才调到 2048 以上。别什么都给 4096那只会让你的并发能力雪上加霜。2.2 工具调用与 Function Calling别让模型当齐天大圣你得给它画圈工具调用是 Agent 和外部系统交互的桥梁。现在主流模型都支持 Function Calling / Tool Use但工程化之后的用法和你直接调 API 试玩完全是两个世界。先说工具注册。我在代码里定义了一套工具描述规范每个工具有一个唯一的 name一个详细的 description以及一个严格的 parameters JSON Schema。description 一定要写清楚这个工具是干什么的、什么时候用、什么时候不要用。很多人偷懒description 就写一句获取天气模型分不清它到底能查今天的还是明天的是查国内城市的还是全球的。描述越准确模型工具选择的准确率越高。再说参数 Schema。不要写得模棱两可要写清楚每个参数的类型、必填性、取值范围、枚举值。比如一个发送邮件工具如果收件人参数没有格式校验模型给你造一个根本不存在的邮箱工具调用必定失败。我遇到过最离谱的一次模型调一个删除用户工具时把用户 ID 填成了all幸好我在决策点五的执行策略里给危险操作设置了二次确认否则后果真是不敢想。工具调用的执行也分同步和异步。同步工具适合耗时短、立即返回结果的场景比如查库存、算价格。异步工具适合耗时长的场景比如生成一份 PDF 报告、批量发送通知。异步执行的难点是要给用户一个进度反馈机制不能让他干等。我实现过最简单的方案Agent 告诉用户任务已提交处理中然后通过轮询或者回调把最终结果再推给用户。还有一类必须重视的是工具调用失败的自恢复能力。模型调用工具失败很常见网络超时、服务端 5xx、参数被业务系统拒绝。一个成熟的 Agent 不能一失败就摆烂。我在执行层加入了重试 降级 告知三级策略。第一级网络类错误重试最多三次用指数退避第二级如果工具本身不可用看有没有同类工具可以替代第三级如果确实无法完成Agent 要把失败原因转化为用户能听懂的话而不是抛出一段英文 Traceback。2.3 记忆系统设计短期用 Redis长期用向量库落地别整花活记忆系统这块市场上把它讲得太玄了。什么记忆是 Agent 的灵魂记忆决定了人格落到工程上就是存储和检索的问题。短期记忆就是指当前会话内的上下文。如果你的服务是单机部署存在内存里也没问题如果你的服务是多实例部署这就是现在大家爱问的AI Agent 怎么扛并发那短期记忆必须外置到 Redis 这类共享存储中。因为用户请求被负载均衡打到不同实例上如果记忆跟着实例走同一用户下一次请求落到另一台机器上下文就断了。我之前就吃过这个亏上线第一天线上会话连续性一塌糊涂后来把 session 上下文全部迁到 Redis设置 TTL 自动过期才彻底好。长期记忆的存储方案我推荐向量数据库配元数据索引的方案。流程长这样用户产生一条值得记住的信息比如偏好、历史事实先经过一个判定器判断值不值得记再通过嵌入模型生成向量存入向量库同时保留原始文本和业务元数据用户 ID、时间、来源。需要召回的时候以当前用户请求作为查询做向量相似度检索取 Top-K 条作为上下文注入提示词。但是这里要提醒一个细节**不要把所有信息都塞进长期记忆。**记忆是要做筛选的。我给系统设了一个记忆价值评分例如用户主动告知的偏好信息、对当前任务有直接影响的状态信息才写长期记忆日常的闲聊、临时查询不写。否则记忆库会变成垃圾场每次召回一堆无用信息反而污染上下文。长期记忆还有一个坑是数据时效性。用户一年前说过我喜欢喝咖啡现在可能早就改喝茶了。所以你存的记忆最好带上时间戳和衰退机制。时间太久、或者用户已明确推翻的记忆应该降权或者清除。这块需要你根据业务自己调策略没有放之四海皆准的方案。2.4 稳定与并发框架选对一半另一半靠执行隔离AI Agent 怎么扛并发这是个近期高频问题。我要说点反常识的话Agent 的并发瓶颈往往不在模型 API而在你自身的编排层和工具层。模型 API 是外部依赖你控制不了它的并发上限但你自己的代码如果串行运行、状态混乱那么并发一上来系统先崩的是你自己。我的建议是Agent 实例本身要设计成无状态执行单元。也就是一个 Agent 工作流实例只处理一个具体任务它的输入是(用户ID, 会话ID, 请求内容)输出是(响应内容, 状态快照)。所有的会话状态、记忆都放外部存储。这样你可以像跑普通后端服务一样用水平扩容撑并发。由于 Agent 的每次决策可能会经过多轮模型调用和工具调用整体延迟会长达数秒甚至几十秒。这个时候如果用户用 HTTP 同步请求等结果连接会大量积压。我会把长耗时任务改成异步任务系统请求进来立刻返回一个任务 ID后台用任务队列比如 Celery 或者简单的 Redis Stream Worker执行执行完成后通过 WebSocket 或者轮询接口推送给用户。这里加一个任务超时熔断机制超过某个阈值就强制终止 Agent 的循环并把中间状态存下来方便排查。并发下的限流和降级也必须考虑。模型 API 有 RPM/TPM 限制你的 Agent 如果失控地循环调用模型很快就会把限额打满。因此我在编排层做了一层令牌桶限流按用户维度限制每分钟的模型调用次数。这样即使某个用户搞了一个复杂任务也不会拖垮整个系统的配额。3. 从零到一个能上线的 Agent实操走一遍3.1 先别急着写代码把状态机画出来很多人的 Agent 写成一坨串行代码其实就是先看你说的啥然后调个工具然后把结果拼回去。这在小 demo 里能跑但一旦业务复杂起来就乱了。我建议你基于 LangGraph 或者自研一个轻量的状态机先把 Agent 的流程节点和边定义清楚。我的做法是把 Agent 的完整流程拆成七个节点Entry入口、IntentRecognition意图识别、ContextAssemble上下文组装、ToolSelect工具选择、ParamValidate参数校验、Execute执行、Respond生成回复。然后定义节点间的转移条件。比如 IntentRecognition 判定为chat直接跳到 Respond判定为task才进入 ToolSelect。这些节点和转移条件就对应前面说的七个决策点。这样做的好处是每个节点都是独立的函数有明确的输入输出既方便单测又方便观测。一旦整个链路出了问题你能精确地说卡在工具选择而不是只能说Agent 有点笨。3.2 通用工作流意图识别 任务规划 执行循环我最近做的一个项目是用 FastAPI 搭后端配合 LangGraph 做编排。核心工作流处理逻辑是这样一图流我不用流程图工具了直接用文字描述给你入口接住用户消息之后第一步做意图识别。我在这里做了一个轻量级的分类提示词让模型把用户输入分成 chat / task / clarify 三类。分类结果直接决定后面走哪条路。如果是 chat不调用任何工具直接生成回复成本低、速度快如果是 task则进入任务规划。任务规划这一层我会让模型输出一个结构化的 JSON包含计划步骤列表。比如用户说帮我查一下这个商品的物流信息并退掉它模型会输出第一步 query_logistics第二步 cancel_order。这里要注意模型输出的计划只是草案最终是否执行每一步还是要经过工具选择和参数校验关口不能因为模型计划了就直接放行。执行循环就是典型 ReAct 模式但加了严格的护栏。循环的条件是任务未完成 且 未超过最大步数。每一步分为 think模型决定下一步、act执行工具、observe观察结果三段。我设置的最大步数是 10 步超过直接终止防止 Agent 疯跑。下一步再执行最终响应生成把多步得到的结果综合起来组织成给用户的回复。3.3 选用框架还是手写我的选型思路这个问题我最近常听人问AI Agent用 LangChain 好还是 LangGraph 好还是自己写我给一个真实的使用经验如果你只做一个工具的调用LangChain 的简化封装够用如果你的 Agent 涉及多条路径、多个条件分支、多轮工具调用循环直接上 LangGraph如果你对性能、稳定性和隐私要求极高比如业务核心场景我更倾向于用 Rust 或者原生 Python 手写一套轻量编排。Rust 写 Agent 这件事理论上很诱人性能好、并发强、类型安全但招聘成本和学习曲线也真实存在。我个人的看法是除非你的 Agent 要做极高并发且资源敏感的底层服务否则 Python 生态的 Agent 框架足够承载。毕竟工程成本最高的部分——提示词调优、工具协议、评估体系——跨语言都一样没必要在语言层面给自己加难度。对于想要最轻量实现的同学我自己梳理了一套最小代码模板用 FastAPI 做 HTTP 层用 LangGraph 做循环编排用语言模型 SDK 做模型调用用 Redis 做会话缓存用一个简单的函数注册表做工具管理。这条路线大概是两天可以跑通第一个版本一周内能整理出可上线雏形非常适合中小团队起步。3.4 一个最小实现FastAPI LangGraph 的骨架代码我提供一个高度精简、但结构完整的最小实现骨架方便你把上面说的这些抽象概念落到代码上。这是经过我删改后的示例生产环境建议在这个基础上扩展。from fastapi import FastAPI, HTTPException from pydantic import BaseModel, Field from typing import Literal app FastAPI() class UserRequest(BaseModel): session_id: str text: str class AgentResponse(BaseModel): session_id: str reply: str trace: list # 工具注册表 TOOLS {} def register_tool(name: str, description: str, schema: dict): def decorator(func): TOOLS[name] {func: func, description: description, schema: schema} return func return decorator register_tool( get_weather, 查询指定城市的当前天气当用户询问天气时使用, {type: object, properties: {city: {type: string}}, required: [city]} ) def get_weather(city: str): # 实际逻辑对接天气服务 return f{city}的天气是晴天 # 全局会话存储生产环境用 Redis 替换 SESSION_STORE {} # Agent 编排主流程 async def agent_pipeline(session_id: str, user_text: str): messages SESSION_STORE.get(session_id, []) messages.append({role: user, content: user_text}) steps [] finished False max_steps 5 while not finished and len(steps) max_steps: response await llm_call(messages, TOOLS) steps.append(response) tool_calls response.get(tool_calls) if not tool_calls: finished True messages.append({role: assistant, content: response[content]}) break for call in tool_calls: func TOOLS[call[name]][func] # 参数校验 result func(**call[arguments]) call_result f工具 {call[name]} 返回{result} messages.append({role: tool, content: call_result}) steps.append(call_result) SESSION_STORE[session_id] messages[-20:] return messages[-1][content], steps app.post(/api/agent, response_modelAgentResponse) async def run_agent(req: UserRequest): reply, trace await agent_pipeline(req.session_id, req.text) return AgentResponse(session_idreq.session_id, replyreply, tracetrace)这段代码里核心是while循环里对没有工具调用时的退出条件判断以及对工具调用结果的注入。我这里做了简化但在生产实现里工具调用的解析、参数校验、失败重试都必须在这个循环内完善。3.5 把决策点变成代码一个 500 行的工程实现思路如果你觉得框架太重也可以手写一个精简的编排器。我的思路是把七个决策点映射成七个函数每个函数都是一个中间件输入上下文对象输出决策结果和更新后的上下文。上下文对象Context包含session_id、原始输入、历史消息、候选工具、已执行步骤、最终结果。七个决策点函数依次挂在一条执行链上。链式调用的好处是哪个环节出问题一目了然更妙的是你在每个函数都可以埋点观测。我写过一版用 Rust 实现的高性能 Agent核心也是这七个决策点。Rust 的好处是工具调用的 JSON Schema 校验可以做到零拷贝、极致性能并发模型也比 Python 的 GIL 舒服太多。要说缺点就是迭代速度慢。所以如果项目处于探索期我劝你耐住性子先用 Python 快速验证业务和逻辑都稳定了再动迁移的念头。4. 常见问题与排查实录4.1 上下文越滚越脏模型把历史里的老错误当成了事实这是我一模一样踩过的坑。某个 Agent 在一次工具调用中返回了一个错误的中间结果当时 Agent 没有发现就把这个错误结果编进了回复。然后这个被污染的回复写进了短期记忆。下一轮用户查询的时候Agent 把上一轮的污染内容当成了已知事实继续进行错误推理于是错误被一层层放大。排查思路是Agent 每次执行完主动检查关键信息是否和工具返回一致不一致就丢弃模型编造的中间结论上下文写入历史前也建议做一个敏感度审计尤其是数字、日期、状态字段。上下文干净比模型聪明更重要。4.2 工具调用参数幻觉模型生成了根本不存在的订单号幻觉不仅出现在回复内容里也会出现在工具参数里。模型可能信誓旦旦地给你一个格式完全正确、但业务上根本不存在的订单号。如果你直接拿这个参数去查询返回为空还能接受如果你直接拿它去删除或修改那就闯大祸了。我的对策分三层。第一层是 Schema 强校验类型不对直接拦截第二层是业务前置校验查询类工具在上游必须先校验订单号是否存在第三层是危险操作熔断凡是删除、修改、转账、发消息类工具必须额外附一个确认机制。这个机制可以简单理解为Agent 先把结构化操作意图提交给一个操作确认面板用户确认后才真正执行。别嫌麻烦一次护栏的缺失可能让你付出运维事故的代价。4.3 死循环和重试风暴Agent 在无人知晓的地方打架Agent 自主循环时最典型的失控场景就是重试风暴。比如工具调用失败Agent 重试重试又失败Agent 换一种说法再调模型发现结果不对继续调另一个工具……最后模型 API 配额耗尽工具服务被打到限流。我给的参数建议是设置硬性最大步数根据任务复杂度我一般放 8~15 步每步执行前做一次相似动作检测如果模型连续两步调用了同一个工具且参数完全一样直接中断并要求模型换思路。还有一个好习惯是给每个步骤加一个“累计 Token 预算”当某次任务已经消耗了过多 Token 时强制终止宁可让用户重新问也不能让它一路烧钱。4.4 并发导致的会话串线把上下文放内存的惨痛教训这个问题现在越来越多人在问。起因很简单服务部署了多个副本或者一个进程内用 asyncio 同时处理多个用户请求如果你把用户上下文放在模块级全局变量里不同用户的对话就会互相串线。你可能会觉得这么低级的问题不会发生在我身上但异步协作场景下尤其在用了全局dict存 session 时真的有概率会把 A 用户的消息写入 B 用户的上下文。我当时的修复方案很直接所有会话状态统一存 Rediskey 是 session_idvalue 是序列化之后的消息列表每次读写都走统一的仓储接口再加上进程启动时的自检确认没有残留的进程内状态。修完之后再没出过串线。4.5 Token 超时与响应过长用户等不及系统也扛不住最后一个常见问题我把响应生成时间太长单拎出来讲。Agent 工作流如果经历多轮工具调用总时长会达到用户的忍耐极限。你要在准确和及时之间做一个商业决策。我常用的策略是先返回一个初步结果然后后台继续精修。如果用户问的是需要研究一下的复杂问题可以先告诉用户正在处理请稍候再用异步方式推送完整答案。另一个思路是控制模型生成回复的长度提示词里明确要求高效简洁禁止冗余这些细节能让平均响应时间下降 30% 以上。最后再说两句我做 Agent 工程这么长时间一个最深的体感是Agent 的技术点其实没什么秘密七要素、七个决策点这些概念很多文章都在讲但真正拉开差距的是你有没有把每个决策点都落到可观测、可控制、可回滚的工程机制里。模型会进步框架会迭代但这套把不确定性关进笼子的思路在哪个阶段都不会过时。近期我正在把当前这套 Agent 编排的核心模块抽成一套通用库计划支持 Python 和 Rust 两套实现把工具注册、决策链、记忆管理、观测埋点这些能力直接以声明式配置暴露给上层业务。如果你也在做同类探索希望这篇拆解能帮你少走几段弯路我们后续可以多交流。
阅读完成 · 觉得有帮助?