
AI Agent 这个词在过去一年里被反复提及但真正动手搭过一套能跑起来的 Agent 系统的人都知道从知道它是什么到让它稳定干活之间隔着一整套工程决策。我前后参与过几个 Agent 项目从最初用现成框架拼装到后来自己拆解循环逻辑重写调度层踩过的坑基本覆盖了工具调用失败、上下文爆炸、循环停不下来、并发下状态错乱这几大类。这篇内容不打算复述概念定义而是把 Agent 拆成七个构成要素再顺着工程实现的时间线梳理七个必须做决策的关键点每个决策点都讲清楚为什么这样选以及选错了会怎样。适合已经了解 LLM 基础、准备动手搭建 Agent 的开发者也适合正在评估现有 Agent 框架是否够用的技术负责人。1. 先把 Agent 拆成七个要素不然后面全是糊涂账很多人一上来就找框架、抄 Demo结果代码写到一半发现不知道该往哪个方向扩展。根本原因是没有先把 Agent 的构成拆清楚。我习惯把它拆成七个要素目标Goal、感知Perception、规划Planning、记忆Memory、工具Tools、执行Execution、反思Reflection。这七个要素不是学术分类而是工程上必须分别处理的模块边界。1.1 目标与感知Agent 的输入到底是什么目标决定了 Agent 存在的意义但工程上更关键的是目标如何被表达。自然语言目标比如帮我整理这份周报和结构化目标比如{action: summarize, input: weekly_report.md, format: markdown}在实现上完全是两回事。前者需要 LLM 做意图解析后者可以直接路由。我的经验是能用结构化目标就别用自然语言因为自然语言目标在后续每一步都要重新解释误差会累积。感知则是 Agent 获取外部信息的通道。它不只是读用户输入还包括读取文件、调用 API 返回结果、监听事件等。感知层的设计要点是统一输入格式。我见过一个项目用户输入走一条链路工具返回走另一条链路结果 LLM 在拼接上下文时格式混乱经常把工具返回的 JSON 当成用户说的话。后来统一成{role, content, source}三元组才解决。1.2 规划与记忆最容易被低估的两个模块规划是 Agent 把大目标拆成小步骤的能力。简单场景可以用 ReAct 模式推理-行动交替复杂场景需要显式的任务分解树。这里有个常见误区以为规划是一次性的。实际上好的 Agent 会在每一步执行后重新评估剩余计划因为工具返回的结果可能改变后续路径。记忆分短期和长期。短期记忆就是当前对话的上下文窗口长期记忆则是跨会话的持久化存储。工程上最容易出问题的是短期记忆的裁剪策略。上下文窗口有限不可能把所有历史都塞进去。我试过三种策略滑动窗口保留最近 N 轮、摘要压缩把旧对话总结成一段话、关键信息提取只保留实体和决策。实测下来摘要压缩 关键信息提取组合效果最稳纯滑动窗口在长任务里会丢失早期关键约束。1.3 工具、执行与反思让 Agent 真正下地干活工具是 Agent 与外部世界交互的手段。每个工具需要定义三样东西名称、参数 schema、返回值格式。参数 schema 必须严格否则 LLM 生成的调用参数经常缺字段或类型错误。我现在的做法是给每个工具写 JSON Schema并在系统提示里明确列出所有工具及其参数要求。执行层负责实际调用工具并处理返回。这里的关键是错误处理。工具调用失败是常态不是异常。网络超时、参数错误、权限不足都会发生。执行层必须能区分可重试错误和不可重试错误并把错误信息以 LLM 能理解的方式反馈回去让它决定下一步。反思是 Agent 检查自己输出质量的能力。最简单的反思是让 LLM 对自己的回答打分复杂一点的是让它对比目标和实际结果找出差距。反思不是每步都要做那样 token 消耗太大。我的做法是在关键节点触发反思比如任务完成时、连续两次工具调用失败时、或者输出长度超过阈值时。2. 决策点一循环机制用固定轮次还是动态终止Agent 的核心是一个循环感知 → 规划 → 执行 → 反思 → 再感知。但这个循环什么时候停是第一个必须做的工程决策。2.1 固定轮次的问题要么不够要么浪费最简单的做法是设一个最大轮次比如 10 轮跑完就停。这种做法在 Demo 里没问题但生产环境会出两种事故。一种是任务没完成就停了比如需要调用 12 次工具的任务在第 10 轮被截断用户拿到半成品。另一种是任务早就完成了还在空转LLM 在最后几轮反复确认我已经完成了白白消耗 token。我实测过一个客服场景的 Agent固定 8 轮的情况下简单问题平均 2 轮就解决但复杂问题有 30% 概率被截断。后来改成动态终止截断率降到 3% 以下。2.2 动态终止的三种信号动态终止需要定义明确的终止条件。我通常用三种信号组合判断显式完成信号LLM 在输出中明确表示任务完成比如返回{status: done, result: ...}。这需要提示词里约定好格式。无进展检测连续两轮的工具调用和输出高度相似说明 Agent 卡住了。可以用文本相似度或工具调用参数比对来判断。预算耗尽token 消耗或时间超过预设上限强制终止并返回当前最佳结果。这三种信号里无进展检测最容易被忽略但最重要。我见过 Agent 因为一个 API 一直返回空结果反复重试了 20 多次把 token 预算全烧光了。后来加了无进展检测连续两次相同工具调用且返回为空就直接终止并报错。2.3 终止后的收尾逻辑终止不等于结束。Agent 停下来之后需要有一个收尾逻辑整理当前状态、生成最终输出、记录日志。这里有个细节如果是因为预算耗尽终止的最终输出应该明确告诉用户任务未完全完成以下是已完成部分而不是假装任务完成了。我在项目里会维护一个completion_status字段取值包括completed、partial、failed随最终结果一起返回。3. 决策点二工具调用是并行还是串行工具调用的编排方式直接影响 Agent 的响应速度和可靠性。这个决策没有标准答案取决于工具之间是否有依赖关系。3.1 串行调用的确定性与代价串行调用是最安全的做法一个工具执行完拿到结果再决定下一个调用。它的优点是状态清晰、错误容易定位。如果第三个工具失败了你知道前两个的结果是可靠的。缺点是慢。假设每个工具调用平均耗时 2 秒5 个工具就是 10 秒用户等待体验很差。我在早期项目里一律用串行后来发现有些场景完全可以并行。比如查天气和查日程这两个工具互不依赖串行执行纯属浪费。3.2 并行调用的依赖分析并行调用的前提是工具之间没有数据依赖。判断方法很简单看工具 B 的输入参数是否包含工具 A 的输出。如果不包含就可以并行。实现上可以把无依赖的工具调用打包成一个批次同时发起等所有结果返回后再统一交给 LLM 处理。但并行会带来一个新问题结果顺序不确定。LLM 收到的工具返回顺序可能和调用顺序不一致。解决办法是在每个返回结果里带上tool_call_id让 LLM 能对应上。OpenAI 的 function calling 和 Anthropic 的 tool use 都支持这个机制用的时候别漏了。3.3 混合编排分组并行 组间串行实际项目里最常见的是混合模式。我会先把所有工具按依赖关系分组组内并行组间串行。比如一个数据分析 Agent第一组是读取数据文件和读取数据字典可并行第二组是数据清洗依赖第一组结果第三组是生成图表和生成摘要可并行。这种编排方式需要提前知道工具依赖图。如果依赖关系是动态的取决于 LLM 的规划结果那就只能串行或者让 LLM 显式输出依赖关系再编排。后者实现复杂度高我一般只在工具数量少且依赖固定的场景用。4. 决策点三记忆存储用上下文窗口还是外部存储记忆是 Agent 工程里最容易被简化处理的部分。很多人直接把所有历史塞进上下文窗口直到塞不下为止。这种做法在短会话里能用但一旦会话变长或需要跨会话记忆就必须引入外部存储。4.1 上下文窗口的硬限制与软限制上下文窗口有硬限制模型支持的最大 token 数和软限制实际可用 token 数。软限制比硬限制小因为要预留空间给系统提示、工具定义和输出。以 128K 窗口的模型为例系统提示和工具定义可能占 5K输出预留 4K实际可用于历史对话的只有 119K 左右。但即使没到硬限制上下文过长也会导致性能下降。我实测过当上下文超过 60K token 时LLM 对早期信息的召回率明显下降经常忘记用户最开始说的约束条件。所以我的经验是上下文使用率超过 70% 就该触发记忆压缩。4.2 外部记忆的三种形态外部记忆我通常分三种形态存储记忆类型存储内容存储方式检索方式事实记忆用户偏好、实体信息键值存储精确匹配经验记忆历史任务的成功/失败模式向量数据库语义相似度会话记忆对话摘要、关键决策文档存储时间 关键词事实记忆用键值存储就够了比如用户说我偏好简洁的回答存成{user_id: {preference: concise}}。经验记忆需要向量化因为要按语义检索相似的历史任务。会话记忆则是折中方案存摘要和关键节点用于跨会话恢复上下文。4.3 记忆写入的时机与去重记忆不是越多越好。我见过一个 Agent 把每轮对话都写入长期记忆结果检索时返回一堆重复内容。正确的做法是在关键节点写入任务完成时、用户明确表达偏好时、出现重要决策时。去重也很关键。写入前先检索是否已有相似记忆如果有就更新而不是新增。我用的是向量相似度阈值 0.85超过就认为是同一条记忆执行更新操作。这个阈值可以调调低了会合并不同记忆调高了会产生重复。5. 决策点四错误处理是重试还是降级工具调用失败、LLM 输出格式错误、外部服务不可用这些在 Agent 运行中是常态。错误处理策略直接决定了 Agent 的可靠性。5.1 可重试错误与不可重试错误的区分不是所有错误都值得重试。我通常按错误类型分类可重试网络超时、限流429、临时服务不可用503。这类错误重试大概率能成功。不可重试参数错误400、权限不足403、资源不存在404。重试多少次都一样。不确定500 错误、返回格式异常。这类需要看具体情况我一般重试一次还失败就降级。区分方法是在工具封装层捕获异常根据 HTTP 状态码或错误类型打标签。这个标签会随错误信息一起返回给 LLM让它决定是重试还是换方案。5.2 重试策略指数退避 上限可重试错误用指数退避重试。第一次等 1 秒第二次 2 秒第三次 4 秒最多重试 3 次。这个策略能有效应对限流和临时故障。但要注意重试上限我见过 Agent 因为一个一直超时的 API 重试了十几次把整个任务卡死。上限设 3 次是经验值超过就降级。5.3 降级方案的设计降级是指主方案失败后用备选方案继续。比如主搜索工具不可用降级到缓存搜索主模型调用失败降级到小模型。降级方案需要提前设计好不能等出错了再想。我在项目里会为每个关键工具准备降级方案并在系统提示里告诉 LLM如果工具 A 失败可以尝试工具 B。这样 LLM 在收到错误信息后能自主选择降级路径而不是直接报错终止。6. 决策点五并发场景下状态如何隔离Agent 扛并发是生产环境的硬要求。单用户单会话的 Agent 好做多用户多会话并发时就容易出现状态串扰。6.1 会话隔离的基本要求每个会话必须有独立的上下文、独立的记忆空间、独立的工具调用状态。实现上我会给每个会话分配一个session_id所有状态存储都以session_id为前缀。上下文用内存字典存键是session_id长期记忆用数据库存查询时带上session_id过滤。这里有个容易忽略的点工具调用的中间状态也要隔离。比如一个工具调用发起了异步任务返回了一个task_id这个task_id必须和session_id绑定否则另一个会话可能误取到不属于它的任务结果。6.2 共享资源的并发控制有些资源是跨会话共享的比如工具定义、模型连接池、缓存。这些资源需要并发控制。模型连接池用信号量限制并发数避免打爆下游 API。缓存用读写锁读多写少的场景用RWMutex性能更好。我踩过一个坑工具定义缓存在全局变量里多个会话同时读写导致数据竞争。后来改成启动时加载一次运行期只读才解决。能只读的就别写这是并发场景下的黄金法则。6.3 限流与排队并发量超过系统承载能力时需要限流。我通常用令牌桶算法给每个用户分配一个桶桶空了就排队或拒绝。排队适合异步任务拒绝适合实时交互。限流阈值根据下游 API 的承载能力和自身资源确定我一般从保守值开始比如每秒 10 个请求压测后再调整。7. 决策点六提示词是硬编码还是动态组装提示词是 Agent 的操作系统它的组织方式直接影响可维护性和灵活性。7.1 硬编码提示词的问题把提示词写死在代码里初期开发快但后期改起来痛苦。改一个措辞要重新部署不同场景要用不同提示词就得写一堆 if-else。我早期项目就是这么干的后来提示词文件膨胀到 2000 多行维护成本极高。7.2 动态组装的模块化设计后来我改成模块化组装把提示词拆成基础指令、角色设定、工具说明、约束条件、输出格式几个模块运行时按需拼接。基础指令和输出格式基本固定角色设定和约束条件根据场景切换工具说明根据当前可用工具动态生成。这种设计的好处是改一处不影响其他。比如要调整输出格式只改输出格式模块不用动其他部分。工具增减时工具说明模块自动更新不会出现提示词里写了工具但实际没注册的情况。7.3 提示词版本管理提示词也是代码需要版本管理。我会把提示词模块存成独立文件用 Git 管理每次修改记录变更原因。线上出问题时能快速回滚到上一个版本。另外提示词变更后要做回归测试确保核心场景的表现没有退化。我一般维护一个包含 20-30 个典型场景的测试集每次改提示词都跑一遍。8. 决策点七可观测性做到什么程度Agent 是个黑盒出了问题如果不做可观测性根本不知道哪一步错了。可观测性包括日志、指标、追踪三个层面。8.1 日志记录什么、怎么记Agent 的日志要记录每轮循环的输入输出、工具调用参数和结果、LLM 的原始响应、错误信息。这些信息量大不能全量存需要分级。我通常分三级DEBUG存完整上下文INFO存关键决策点ERROR存异常。生产环境默认INFO排查问题时临时开DEBUG。日志格式用结构化 JSON方便后续检索和分析。每条日志带上session_id、turn_number、timestamp这样能还原完整执行链路。8.2 指标关注哪些数字Agent 的关键指标包括任务完成率、平均轮次、平均 token 消耗、工具调用成功率、错误率、响应延迟。这些指标能反映 Agent 的健康状况。比如工具调用成功率突然下降可能是某个下游 API 出问题了平均轮次上升可能是提示词退化或任务变复杂了。我用 Prometheus 收集指标Grafana 做看板。每个指标按session_id和tool_name打标签方便下钻分析。8.3 追踪还原完整执行链路追踪是把一次任务的所有步骤串起来。我用 OpenTelemetry 做分布式追踪每个 Agent 循环是一个 span工具调用是子 span。这样能看到每一步的耗时和依赖关系定位性能瓶颈。追踪数据采样存储不用全量。我一般采样 10%出问题时能通过session_id捞到完整链路就行。9. 七个决策点的联动关系与实操建议这七个决策点不是孤立的它们之间有联动。比如循环机制的选择影响记忆策略动态终止需要更精细的记忆管理工具调用编排影响错误处理并行调用的错误处理比串行复杂并发控制影响可观测性并发场景下日志和追踪需要更强的隔离。9.1 从最小可用版本开始迭代我的建议是不要一开始就追求七个决策点都做到最优。先做一个最小可用版本固定轮次循环、串行工具调用、上下文窗口记忆、简单重试、单会话、硬编码提示词、基础日志。跑通之后再根据实际遇到的问题逐个优化。我见过太多项目在初期就过度设计结果复杂度失控连基本功能都跑不稳。Agent 工程的本质是在不确定性中找确定性先让系统跑起来再逐步收敛。9.2 每个决策点都要有回退方案生产环境的 Agent 必须能回退。循环机制要能切回固定轮次工具调用要能切回串行记忆策略要能切回纯上下文。这些回退开关用配置控制出问题时能快速切换不用改代码重新部署。9.3 实测数据比理论推导可靠Agent 工程里很多决策没有标准答案只能靠实测。我建议每个决策点都做 A/B 测试用真实任务跑数据。比如循环机制固定 10 轮和动态终止各跑 100 个任务对比完成率和 token 消耗数据会告诉你哪个更好。我在一个项目里测过并行 vs 串行工具调用理论上并行快但实测发现并行场景下 LLM 处理多结果时更容易出错最终完成率反而低了 5%。所以理论最优不等于实际最优一定要用数据说话。9.4 常见问题速查问题现象可能原因排查方向Agent 循环停不下来无进展检测缺失或阈值太松检查终止条件加相似度检测工具调用参数错误参数 schema 不严格检查 JSON Schema加参数校验上下文爆炸记忆压缩未触发检查 token 计数调低压缩阈值并发下状态串扰session_id 未贯穿全链路检查所有状态存储是否带 session_id响应越来越慢记忆检索效率低检查向量索引加缓存输出格式不稳定提示词约束不够加输出格式示例用结构化输出这套东西我在几个项目里反复打磨过每次都会发现新的坑。Agent 工程没有银弹七个决策点每个都要根据具体场景权衡。我的经验是先把循环和工具调用做稳再优化记忆和并发最后打磨提示词和可观测性。顺序反了后面会反复返工。