网上讲 AI Agent 的文章实在太多,但绝大多数停在概念层:画一个“感知-规划-记忆-行动”的圈,配一句“让大模型自己决策”,然后就没了。真正动手做的人会发现,从一句“我要做个 Agent”到能稳定上线的系统,中间隔着一整条工程鸿沟。这篇文章想做的,是把 Agent 的工程实现拆成两件事来看:第一件事是解剖它的七要素,搞清楚一个 Agent 到底由哪些部件组成;第二件事是做七个关键决策,搞清楚工程落地时每个环节该怎么选、怎么取舍。我自己从零搭过好几个 Agent 项目,从 LangGraph 状态图到 FastAPI 并发改造都踩过不少坑,下面这些内容都是实操验证过的思路,适合刚接触 Agent 开发、或者已经在做但经常被各种诡异问题卡住的人参考。
1. 七要素拆解:先看清一个 Agent 的骨架
很多初学者上来就写一个agent = create_agent(model, tools),用的时候还行,一上复杂业务就崩。原因很简单:你根本没把一个 Agent 当作一个系统来设计。七要素是我自己做项目时习惯用的拆法,你可以把它理解成 Agent 的解剖学骨架——目标、规划、记忆、工具、上下文状态、执行、反思与评估。缺任何一个要素,短期能跑,长期必炸。
1.1 第一个要素:目标——但目标不等于系统提示词
目标和系统提示词是两回事。系统提示词是设定人设和规则,目标是这次任务要达成的具体结果。很多工程新手把目标写死在 prompt 里,这是最偷懒也最危险的做法。实际业务里目标往往是动态的:用户第一句话“帮我查一下上个月所有订单的异常退款”,这个目标必须被结构化地存下来,供后续每一步规划、执行、反思去对照。
我在项目里会把目标单独设计成状态里的一个字段,比如goal或者task_list。它由“用户输入解析模块”生成,而不是靠用户在对话里反复描述。这个字段有两个作用:第一,规划节点每次循环都要拿它做参照,避免 Agent 跑偏;第二,日志和评估也以它为基准,出了问题能明确说“是理解错了目标,还是执行错了步骤”。类比一下,Agent 就像一个小型项目组,目标是项目经理手里的需求说明书,开发不能自己发明需求。
1.2 第二个要素:规划——把目标翻译成动作序列
规划是 Agent 的“任务拆解器”。大模型本身有推理能力,但推理和规划不一样:规划要求输出结构化、可执行、能追踪的动作序列。工程上我很少让模型直接输出“下一步动作”就算了,我要求它输出一份 JSON 形式的计划:包含task_id、action_name、params、expected_result、status。这样每个动作都能被记录、重放、中断和恢复。
这里有一个很多人忽略的点:规划是有成本的。每做一次规划就要调一次模型,就要花钱、花时间。所以不要天真地认为“每个任务都先让模型规划十步”是聪明的做法。我通常加一个触发条件:简单任务直接执行,复杂任务才进入规划分支。用 LangGraph 的 conditional edge 就能实现这个逻辑,后面实操部分我会给代码。
1.3 第三个要素:记忆——短期像寄存器,长期像硬盘
Agent 没有记忆,就像一个人失忆了做事——每次都重新理解上下文,效率极低,而且会互相矛盾。记忆必须分层。
短期记忆就是当前任务上下文,它活在模型 token 窗口里,由对话历史和当前状态组成。长期记忆是跨会话的知识,包括用户偏好、历史决策、业务规则,一般存放在向量数据库或普通数据库里,需要时再检索进来。这里要特别注意:短期记忆不是越长越好,窗口塞满之后,模型效果断崖式下降,成本也直线上升。
“token”这个词在 Agent 开发里出现频率极高,它就是大模型计费和计算的基本单位。1 个 token 大约对应 0.7 个英文单词,中文一个汉字大概占 1 到 2 个 token。你发一段话给模型,模型读多少 token、生成多少 token,都是按这个计费的。所以记忆管理的本质,就是 token 预算管理——哪些历史必须留,哪些可以压缩成摘要,哪些可以丢掉。这个问题我在第 4 章会展开讲。
1.4 第四个要素:工具——Agent 和真实世界的唯一握手渠道
LLM 本身不会执行任何真实操作,它只能输出“我要调用某个工具、传这些参数”,真正的执行是代码做的。所以工具层是 Agent 和外界握手的唯一渠道。工具设计得好不好,直接决定 Agent 能干多少活。我给工具分了三个类别:读类(查询、检索)、写类(创建、更新)、计算类(数学、代码执行)。这三个类别对安全要求完全不一样,写类和计算类需要额外审批。
工具的关键不是代码,而是描述和参数 Schema。模型是通过工具的名字和描述来决定调用哪个工具的。描述写得太模糊,它就会在几个相近工具之间乱猜;参数约束不严格,传进来的东西就乱七八糟。我见过最离谱的一次,模型调用一个“发送邮件”的工具,把收件人地址填成了正文,就是因为参数描述里没有写清楚格式。这些细节,第 2 章决策点四里我会详细讲。
1.5 第五个要素:上下文状态——所有节点共享的“白板”
在 LangGraph 这类编排框架里,状态就是一张共享白板,每个节点都可以读它、改它,改完传给下一个节点。状态设计是整个 Agent 架构里最容易被低估的一环。没有状态管理,你会陷入两种灾难:一种是所有节点都直接操作原始对话记录,逻辑耦合到改一处崩三处;另一种是状态里什么都在塞,最后 token 爆炸。
我自己的经验是状态要“瘦”。状态里只放四类东西:目标与计划、对话或消息历史(精简版)、中间工具执行结果、最终输出。不要把外部系统里的业务数据一股脑塞进状态,需要时再通过查询拿回来。本质上,状态是给模型看的临时工作区,不是你的数据库。
1.6 第六个要素:执行——最容易在 Demo 里被美化的环节
执行模块是真正调用外部服务、执行动作的地方。很多人 Demo 里写的执行就是requests.post(...),看起来简单,但真实场景里它有超时、限流、状态码异常、幂等、重试、审计日志等问题。一次外部 API 失败,该不该让模型重试?重试几次?重试会不会造成重复扣款这类副作用?这些都必须事先定义好。
我习惯给执行节点加两层保障:第一层是超时和重试机制,超过指定时间自动终止,避免任务挂死;第二层是敏感操作前置审批,比如发送消息、转账、删除数据这类动作,先返回一个需要确认的状态给用户。这个设计可能让交互多一步,但能省掉大量生产事故。
1.7 第七个要素:反思与评估——自我纠错的闭环
七要素里最容易被砍掉的就是反思与评估。很多人的 Agent 跑完就结束了,错了就错了。真正能用的 Agent 必须有反思机制:执行完一个动作后,让模型判断结果是否符合预期,不符合就回到规划节点重新修正,形成一个闭环。注意,这个自我反思过程也是要花钱的,所以要给反思设置上限,不让它无限循环下去。
工程上的评估和模型层的反思还不一样。我建议搞一个黄金测试集,比如几条典型业务场景,每次改动后跑一遍,看 Agent 输出是否符合预期。这一步的作用非常大,否则你就是盲改。没有评估集的 Agent 项目,和没有单测的后端项目一样,走不远的。
2. 七个决策点:工程实现就是一连串选择
七要素解决的是“ Agent 由什么组成”的问题,但每个要素落到工程实现,都逃不过选择和取舍。我总结了七个决策点,覆盖从模型选型到部署上线的关键环节。这七个决策你绕不开,早做比晚做好。
2.1 决策点一:模型选型,先看函数调用和 JSON 稳定性
模型选择不是看哪个跑分高、哪个热度大,而是看三个工程指标:函数调用(Function Calling)能力、结构化输出(JSON 输出)稳定性、成本与延迟。很多模型在聊天体验上不错,但一遇到函数调用就开始乱传参数,一要求 JSON 输出就开始加前后缀解释文字,这在 Agent 工程里是致命的。因为你后面所有规划、工具调用都要依赖结构化输出。
我自己的经验是,拿同一个工具集和同样的测试用例,让几个候选模型各跑 50 轮,统计函数调用成功率和 JSON 解析成功率,再对比价格和响应速度。跑分你能靠榜单看个大概,但函数调用稳定性你必须自己测。这个测试集以后还能复用,作为产品评估的重要依据。
2.2 决策点二:编排框架,选 LangGraph 还是自研状态机
现在主流的 Agent 编排方案大概分成三类:通用编排框架(LangGraph、CrewAI)、语言生态方案(Spring AI、Rust 生态里的一些 Agent 库)、完全自研状态机。我的建议是:中小型项目直接用 LangGraph,它能覆盖循环、分支、条件跳转、人工审批这些核心能力,不用重复造轮子;Java 团队落地可以看 Spring AI,它把模型调用和 Spring 生态集成得比较顺;如果业务极其特殊,比如对延迟和资源消耗有极致要求,再考虑自研。
自研状态机并不是看起来那么自由。你要处理模型重入、状态持久化、节点恢复、超时终止、人工介入挂起等等,工作量远超预期。用框架的意义不在于省代码,而在于框架把常见的 Agent 运行模式已经验证过了,你踩到的坑大概率别人也踩过,社区有答案。以下是几个方案的直观对比:
| 方案 | 适用场景 | 优点 | 主要代价 |
|---|---|---|---|
| LangGraph + LangChain | 大多数业务型 Agent | 状态图清晰、生态成熟、支持人工审批 | 学习曲线陡,链的抽象较重 |
| CrewAI | 多角色协作场景 | 角色化分工直观、上手快 | 复杂流程控制弱,偏高层封装 |
| Spring AI | Java 团队、企业应用 | 集成 Spring 生态、运维友好 | 略封闭,定制复杂流程要自己写 |
| Rust 自研 | 极端性能/资源受限场景 | 资源占用低、延迟可控 | 开发周期长,生态待完善 |
2.3 决策点三:上下文与记忆的分层策略
上下文管理只有一句话:窗口是有限的,记忆是有成本的。决策点三就是决定你的 Agent 怎么在有限窗口里保留最关键的信息。分层策略一般是这样:对话历史近几轮完整保留,更早的内容压缩成摘要,跨会话的偏好与事实性信息存向量库,按需检索。
举个例子,一个客服 Agent,用户昨天问过退货政策,今天再来问订单状态。如果你在每次请求里都拼上昨天全部对话,既浪费 token 又干扰模型判断。正确做法是:从长期记忆里提取“用户退货了解过,可能有退货需求”这个事实,再结合当前问题做回答。这里记忆写入也要设计:不是每句话都值得记,用一次独立的模型调用去判断“这段话里有没有值得长期保留的信息”。这个机制叫记忆写入策略,控制好了能节省大量成本。
2.4 决策点四:工具接口设计,决定 Agent 的能力边界
工具接口设计是七个决策点里和模型效果最直接相关的一个。核心原则是:工具的粒度要适中,描述要精确,参数约束要严格。工具粒度太粗,比如一个execute_sql走天下,模型很容易传入非法 SQL 造成事故;粒度太细,比如把get_user_name和get_user_email拆成两个工具,模型选择起来也会混乱,反而降低准确率。
我在实践中的做法是:按业务能力聚合工具。比如把用户查询相关的操作合并成一个“用户查询工具”,参数里通过不同的query_type区分是查姓名、查余额还是查订单。另外,工具数量超过十几个的时候,要给工具分组。像 LangChain 的 tool 装饰器支持 metadata,可以把工具标记为“外部系统 A”“外部系统 B”,提示词里也能按组引导模型优先选择。工具输出也要有统一封装,返回结构化数据,而不是让模型去解析一堆原始文本。
2.5 决策点五:并发与扩展,第一个真实的性能坎
“AI Agent 怎么扛并发”是最近被问得最多的问题。这里我先说一个容易误解的点:Agent 的并发瓶颈通常不在模型推理本身,而在状态隔离和外部依赖。每个用户的 Agent 运行都有一份独立的会话状态,如果所有请求共享一个全局状态,数据互相覆盖是必然的。所以第一原则是:状态按会话隔离,绝不允许跨会话复用可变数据。
第二原则是:模型调用属于 IO 密集型操作,等待响应期间线程不能被阻塞。如果你用 FastAPI 这类异步框架,那接口层、执行层、外部调用层都要做成异步。如果用的是阻塞的 requests 库去调用模型 API,并发一上来线程池直接被占满。我给自己定了一个指标:单实例能稳定扛住几十个并发会话,才算过了第一道生产门槛。具体的改造方式,第 3 章会给出代码。
2.6 决策点六:权限与安全,别让 Agent 拥有“管理员权限”
很多人做 Agent 时只考虑功能,不考虑权限边界。一个能调用删除接口、能发消息、能转账的 Agent,一旦在工具选择上出现偏差,后果是非常严重的。我给 Agent 的安全策略定了几个等级:只读工具可以自动执行;写操作必须经过用户确认;影响资金或核心数据的操作,额外加一道人工审批,或者直接在工具层禁止。
在技术实现上,要给每个工具标注权限等级,执行节点在调用前检查权限。此外,工具返回给模型的内容也要脱敏,不能让模型把用户的手机号、身份证号转发到别处。安全策略不是产品上线以后再加的,而应该从第一天就进框架。这个决策上你可能要多花一点开发时间,但省下的是未来可能发生的重大事故。
2.7 决策点七:可观测性与评估,没有度量就没有优化
最后一个决策点几乎被所有初学者忽略,却是生产环境最重要的。Agent 不像传统接口,输入输出不是简单的“请求-响应”,中间会经历规划、多次工具调用、可能的失败重试。出问题时,如果你只有一句“用户反馈不对”,没有 trace,你根本不知道是模型规划错了、工具执行失败了还是状态被污染了。
可观测性至少包含三块:请求链路追踪(每个会话的每一步执行记录)、成本监控(token 消耗按会话和用户维度汇总)、质量评估(黄金测试集回归)。我见过太多团队在 Agent 上线后“靠爱运行”,出了问题只能猜,这是最危险的。
3. 实操:基于 FastAPI + LangChain + LangGraph 的一个可运行 Agent
前面讲了要素和决策,现在进入实操。下面这个示例是一个最小但完整的 Agent 服务:接收用户请求,由模型决定是否查询外部数据,最终返回结构化答案。技术栈用 FastAPI + LangChain + LangGraph,这是我目前最推荐的中小项目组合。
3.1 为什么我选择这个组合
FastAPI 负责 HTTP 层和并发模型,它原生支持 async,处理 IO 密集型请求很顺手。LangGraph 负责 Agent 的编排,它的核心抽象是状态图(StateGraph),你定义节点和边,LangGraph 负责调度。LangChain 在这里只负责模型封装、Prompt 模板和工具装饰器,重点用它做模型调用和工具注册,不用它做复杂链。
像 Spring AI 在 Java 团队里体验也很顺,Rust 也有新兴的 Agent 框架,但如果你追求最快速度验证业务逻辑,FastAPI + LangChain + LangGraph 这个组合是我个人建议的起点。它的社区资料多,踩坑经验容易搜到,这对工程化非常重要。
3.2 状态定义:瘦状态是后面所有优化的前提
先定义状态结构,这是 LangGraph 里所有节点的共享对象。状态用 TypedDict 定义,谨慎添加字段,因为字段越多,模型读写的 token 就越多。
from typing import TypedDict, List, Optional class AgentState(TypedDict): goal: str # 本次任务目标 messages: List[dict] # 对话历史(精简) plan: Optional[dict] # 当前计划,JSON 结构 tool_results: List[dict] # 工具执行结果集 final_answer: Optional[str] # 最终输出这个状态里没有塞用户原始大段文本,只保留解析后的目标、必要的历史和工具结果。无论 Agent 循环多少轮,状态都不会无限膨胀。这个设计后面会直接决定你能扛多少并发,因为环境变量小、序列化快、内存占用低。
3.3 节点编写:让模型决策,让代码执行
LangGraph 的节点就是一个接收状态并返回更新后状态的函数。我们要两个核心节点:一个是“模型推理节点”,负责理解目标并决定是否调用工具;另一个是“工具执行节点”,负责真实执行模型选中的工具。
from langchain_openai import ChatOpenAI from langchain_core.tools import tool from langgraph.graph import StateGraph, END llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) @tool def query_order_status(order_id: str) -> dict: """查询订单当前状态,返回订单状态与预计送达时间。 Args: order_id: 订单号,格式为 12 位数字字符串。 """ # 这里实际会去查业务系统,示例直接返回 return {"order_id": order_id, "status": "shipped"} tools = [query_order_status] llm_with_tools = llm.bind_tools(tools) def agent_node(state: AgentState) -> dict: response = llm_with_tools.invoke(state["messages"]) if response.tool_calls: return {"plan": response.tool_calls[0]} return {"final_answer": response.content} def tool_node(state: AgentState) -> dict: # 严格按 plan 中指定的工具与参数执行 tool_call = state["plan"] result = query_order_status.invoke(tool_call["args"]) return {"tool_results": [result], "messages": state["messages"] + [ {"role": "tool", "content": str(result)} ]}这段代码里最值得注意的点是bind_tools。模型一旦识别到需要查询,就会输出一个标准化的工具调用对象,而不是自己拼一段文本。工具执行节点拿到这个结构化的调用参数,直接反射调用对应函数,整个过程不需要模型直接接触真实 API。这样做的好处是,模型永远不会直接控制外部系统,它只负责决策,执行权在代码手里。
3.4 把 Agent 包成 HTTP 接口
只跑 CLI 的 Agent 没有生产价值,必须通过接口暴露。下面用 FastAPI 包一层,用session_id做会话隔离。
from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class ChatRequest(BaseModel): session_id: str user_input: str @app.post("/agent/run") async def run_agent(req: ChatRequest): # 生产环境下这里要从数据库/Redis 恢复历史状态 init_state: AgentState = { "goal": req.user_input, "messages": [{"role": "user", "content": req.user_input}], "plan": None, "tool_results": [], "final_answer": None, } graph = build_agent_graph().compile() result = await graph.ainvoke(init_state) return {"answer": result["final_answer"]}接口返回的就是最终字符串,调用方不需要关心 Agent 内部做了几次规划、调了几次工具。这个封装方式让前后端解耦,后续无论把 Agent 换成什么框架,接口协议都不用变。如果业务里有流式输出的需求,比如让用户看到逐字输出,那就得把响应改成 WebSocket 或 SSE,LangGraph 也支持异步流式调用。
3.5 并发改造:从同步到异步
上面这个版本其实还不算真正扛并发。有几个隐性坑需要用代码堵上:第一,工具执行里的 HTTP 依赖必须用异步库,否则还是会阻塞线程;第二,模型调用本身要并发限制,否则上游 API 限流会直接报错;第三,每个session_id的状态要独立存储。
import asyncio from langgraph.checkpoint.memory import MemorySaver semaphore = asyncio.Semaphore(10) # 限制同时进行的 Agent 任务数 async def run_with_limit(state: AgentState): async with semaphore: graph = build_agent_graph().compile(checkpointer=MemorySaver()) return await graph.ainvoke(state)这个等待队列的好处是,即使高峰期同时来 50 个请求,真正同时打到模型 API 的也只有 10 个。模型 API 都有速率限制,超过限额会返回 429 错误。用信号量做限流,是 Agent 服务扛并发的最简单有效手段之一。更完善的方案是把任务丢进消息队列,用 worker 消费,但那是后话了。
4. 常见问题与排查技巧实录
这一部分是我从多个项目里总结的真实踩坑记录。每一条都有对应的排查思路和解法,你完全可以直接拿来用。
4.1 上下文和 Token 飞速增长
典型现象是任务跑到中段,每轮响应速度越来越慢,成本肉眼可见上涨,有些会话甚至直接把模型最大 token 窗口塞爆。排查的时候,先看日志里的消息历史长度,你会发现大量工具返回的原始文本被原样塞进历史,比如一次数据库查询返回几百行记录,全进了上下文。
我的解法有两条:一是工具返回给模型的内容必须先做精简,只保留关键字段和必要的摘要;二是历史消息定期压缩,超过一定轮数后把前面内容用一个小模型生成摘要,替代原始文本。压缩摘要这件事看起来多花一次调用,实际上非常划算,因为长上下文的输入成本和延迟都是超线性上涨的。
4.2 模型输出经常不是可用的 JSON
规划节点要求模型输出结构化 JSON,但模型偶尔会在 JSON 前后加“好的,这是计划如下”之类的废话,甚至直接把字段名改了。这是所有做 Agent 的人都逃不过的问题。先在解析层做容错,用正则把 JSON 部分截出来;再不行就用模型的 JSON Mode,强制约束输出格式;最后还有一层,解析失败就带着错误信息让模型重新生成一次。
不要指望模型百分百按格式输出。工程上必须做校验和重试的闭环,并在日志里记录失败次数。如果一个模型的失败率长期超过 5%,它就说明不适合做这个任务,该换模型了。
4.3 工具调用提示“没有可用工具”或老选错工具
出现这种情况,九成问题出在工具描述上,而不是模型能力。我见过一个案例:一个“查询库存”的工具描述里写的是“查询商品库存数量,返回剩余库存”,结果模型拿它去回答“商品价格”的提问。原因是描述里没有说明它“不负责价格”。工具描述的第一原则是:写清楚这个工具能做什么、不能做什么、参数是什么格式。
另一个常见问题是工具数量一多,模型选择准确率就下降。我的经验是超过 15 个工具后开始分组,并且在系统提示词里写一个“工具选择指南”,比如“涉及用户资金操作,优先选择账户服务组工具”。这本质上是给模型降低选择范围,和人在面对复杂选项时需要引导是一个道理。
4.4 并发一上来,任务超时和内存飙升
如果 Agent 服务做了异步改造和信号量限流还是超时,问题多半出在外部依赖上。比如你调用的模型 API 本身响应就很慢,或者工具执行里嵌了一个慢速数据库查询。我的排查路线是:先看可观测性面板里哪一步耗时占比最高,如果是工具执行,那就得给每一步工具调用加上超时时间并优化外部依赖;如果是模型推理,那就检查所用模型的档位是不是太小了。
内存飙升则是状态没做好隔离的典型信号。如果有人把全局状态和会话状态混用,或者长期会话不清理缓存,内存迟早出问题。我的建议是给每个会话设置生命周期:超过多久不活跃就清理状态,下次用户再来重新初始化。
这里把常见问题整理成速查表:
| 现象 | 常见原因 | 优先排查点 | 解决方向 |
|---|---|---|---|
| 响应越来越慢 | 上下文膨胀 | 检查历史消息长度 | 摘要压缩、工具输出精简 |
| JSON 解析失败 | 模型输出夹杂文本 | 检查原始输出 | 容错解析、JSON Mode、重试 |
| 工具选择错误 | 工具描述不清 | 检查工具描述和参数 | 重写描述、工具分组 |
| 并发时大量 429 | 上游模型限流 | 看日志中的 429 数量 | 信号量限流、消息队列削峰 |
| 内存持续上涨 | 会话状态未回收 | 检查会话缓存 | 状态生命周期管理 |
5. 生产环境里,几个容易忽略的细节
如果你已经能把第 3 章的 Demo 跑起来,并且解决了第 4 章的问题,那恭喜你,Agent 的基本盘已经立住了。但要真正上线,还有几个我从实际运营里学到的细节,它们不会写在任何框架文档里。
5.1 可观测性链路的三个关键点
第一,每次模型调用的 token 消耗要按会话、按用户记下来。第二,工具调用的入参和出参必须完整记日志,出问题才能复盘。第三,要记录“模型决策轨迹”,也就是每一步状态转换。这三块数据都有了,运营 Agent 就不再是玄学。阿里云那份 Agent 白皮书里也强调了可观测性和评估体系要前置,我深有同感,它是后期所有优化的地基。
5.2 Token 成本,是 Agent 运营里最大的隐性支出
传统接口的运维成本是 CPU、内存、带宽,Agent 运营成本里很大一块是 token 费用。而且 token 消耗会随着用户量线性增长。我见过有的团队上线第一个月成本远超预期,因为他们对每个请求都传了大量历史上下文。要控成本,就得做三件事:缓存模型输出(命中缓存直接返回)、精简上下文、使用便宜的模型做摘要等次要任务。成本控制不是产品做大以后才考虑的事,而是从架构设计第一天就要设定的指标。
5.3 灰度发布与回滚策略
Agent 这类系统的升级和传统接口不一样,因为你不能确定新模型、新提示词会产生什么影响。我建议做 Agent 版本灰度:同一时间跑新旧两版,对比同一批请求的完成率和用户满意度,没问题再全量放量。回滚也要单独准备,Agent 框架一般支持状态版本管理,关键是把状态结构兼容性做好,否则新版本一上,旧会话状态直接读不了,用户就掉线了。
5.4 我的个人体会:Agent 工程化,本质是在管理不确定性
折腾了几个 Agent 项目之后,我最大的体会是:不要试图把 Agent 做成一个“全知全能的神”,也不要指望模型推理能解决所有边界问题。真正稳定的系统,靠的是把每个环节的不确定性尽量控制在局部:规划可能错,那就用校验兜底;工具可能失败,那就用重试兜底;模型可能乱输出,那就用结构化约束兜底。
你如果把七要素当成一个检查清单,把七个决策点当成一轮架构评审,你会发现 Agent 并没有传闻里那么玄乎。它就是一个有循环、有条件跳转、有外部依赖的分布式系统,只是它的“业务逻辑”一部分由大模型动态生成,而不是全部由程序员静态写死。所以做好状态隔离、工具边界和可观测性,你就已经超过大半数的 Agent 项目了。每次迭代改动之后,拿你的黄金测试集跑一遍,看着成本曲线和成功率的数字,让每一次优化都有据可循,这是 Agent 工程化最踏实的路。