☰
AI Agent工程实现:七要素与七个决策点,从能跑到可靠
2026/10/26 9:12:11 网站建设 项目流程

1. 从七个零件到七次抉择:我眼中的 AI Agent 工程实现

这两年“AI Agent”这个词被炒得火热,打开任何一个技术社区,满屏都是 agent 开发、agent 框架、agent 学习路线。但真正动手搭过几个能跑起来的 Agent 之后,我发现一个很尴尬的事实:大部分教程都在教你调 API,却没人讲清楚一个 Agent 从“能跑”到“可靠”之间,到底要跨过多少道坎。我自己是从去年开始系统性地折腾 Agent 的,从最简单的“LLM 加个循环”到后来带工具调用、带记忆、带编排的完整系统,踩过的坑能写满一个笔记本。这篇文章我想把 Agent 的工程实现拆成两个视角来讲:一个是静态的“七要素”——也就是一个 Agent 系统由哪七个核心零件组成;另一个是动态的“七个决策点”——也就是在运行时,工程上必须做出的七次关键抉择。这两个视角合在一起,基本上就能把 Agent 的工程实现讲透。不管你是刚听说 agent 是什么的新手,还是已经在用 LLM 框架搭 agent 项目的老手,我相信都能从中找到一些可以直接抄作业的东西。

先说说为什么我要用“七要素”和“七个决策点”这两个框架。市面上讲 Agent 架构的文章不少,但很多要么太学术,上来就是 POMDP、belief state 那一套,看得人云里雾里;要么太浅,就告诉你“Agent 等于 LLM 加工具加循环”,然后没了。我想要的是一种既能指导实际编码、又能帮助理解本质的拆解方式。七要素解决的是“一个 Agent 系统需要哪些组件”的问题,它偏向静态结构;七个决策点解决的是“这些组件在运行时怎么协同”的问题,它偏向动态流程。两者一静一动,正好覆盖了从设计到落地的完整链路。下面我就按这个思路,把每个部分都掰开揉碎讲一遍。

2. 七要素:一个 Agent 系统的静态解剖

2.1 要素一:LLM 作为推理内核,但不是全部

很多人一提到 Agent,第一反应就是“哦,就是 LLM 嘛”。这话对了一半。LLM 确实是 Agent 的推理内核,它负责理解意图、生成计划、决定下一步动作。但如果你把 Agent 等同于 LLM,那就会掉进一个很大的坑:你会以为换个更强的模型,Agent 就自动变好了。实际情况远不是这样。

我拿一个实际例子来说明。之前我做一个自动整理会议纪要的 Agent,最开始用的是某个中等规模的模型,效果一般,经常漏掉待办事项。我以为是模型不够强,换了一个更大的模型,结果待办事项是抓全了,但它开始自作主张地给每个待办事项编造负责人和截止日期。问题出在哪?出在我只换了 LLM,但没有调整工具调用的约束和输出格式的校验。LLM 在 Agent 里的角色更像是一个“大脑”,但大脑再聪明,如果没有合适的“手”和“眼睛”配合,照样干不好活。

所以在工程上,我把 LLM 这个要素进一步拆成三个可配置的维度:模型选型、推理模式、输出约束。模型选型不用多说,根据任务复杂度和成本预算来定。推理模式指的是用不用思维链、用不用自洽性采样、要不要做多步推理。输出约束则是通过 prompt 工程或者结构化输出(比如 JSON schema)来限制 LLM 的输出空间。这三个维度组合起来,才构成一个完整的“推理内核”要素。

提示:不要迷信“最强模型”。在很多 Agent 场景里,一个中等模型加上好的输出约束,效果比一个强模型加上松散的 prompt 要稳定得多。我实测下来,约束带来的收益往往比模型升级更大。

2.2 要素二:工具调用接口,Agent 的手和脚

工具调用是 Agent 区别于普通聊天机器人的核心能力。没有工具调用,Agent 就只能“说”,不能“做”。但工具调用的工程实现里,有很多细节是教程里不会讲的。

首先是工具的粒度问题。工具太粗,比如一个“处理文件”的工具,LLM 很难准确调用,因为它不知道这个工具到底能处理什么格式、返回什么结果。工具太细,比如“读取文件第一行”“读取文件第二行”分开,那工具数量会爆炸,LLM 的选择困难症就犯了。我的经验是,一个工具只做一件事,但这件事要有明确的输入输出契约。比如“读取文件”就是一个好工具,“处理文件”就是一个坏工具。

其次是工具的描述。很多人写工具描述就一句话“读取文件”,然后抱怨 LLM 不会用。工具描述其实是给 LLM 看的“说明书”,你得写清楚:这个工具干什么、什么时候用、输入参数是什么格式、返回什么、有什么限制。我通常会写三段式描述:第一段一句话概括功能,第二段详细说明参数和返回值,第三段给一两个调用示例。这样 LLM 的调用准确率会明显提升。

最后是工具的错误处理。工具调用失败是常态,网络超时、文件不存在、权限不足,各种情况都有。如果工具直接抛异常,整个 Agent 循环就断了。我的做法是让工具永远返回一个结构化的结果,包含success、data、error三个字段,这样 LLM 可以根据错误信息决定是重试、换工具还是放弃。

2.3 要素三:记忆系统,不只是存聊天记录

Agent 的记忆系统经常被简化成“把历史对话塞进 context”。这在简单场景下能用,但一旦任务变长、变复杂,就会出问题。Context 窗口是有限的,你不可能把所有历史都塞进去。而且很多历史信息是冗余的,塞进去反而干扰 LLM 的判断。

我把 Agent 的记忆分成三层:短期记忆、长期记忆、工作记忆。短期记忆就是最近几轮对话,直接放在 context 里。长期记忆是跨会话的持久化信息,比如用户的偏好、之前任务的结论,通常存在向量数据库或者关系数据库里,需要的时候检索出来。工作记忆是当前任务的中间状态,比如已经完成了哪些步骤、当前在哪个环节,这个可以用一个结构化的状态对象来维护。

这三层记忆的读写时机很关键。短期记忆每轮都更新,长期记忆在任务结束时写入、在任务开始时检索,工作记忆在每个决策点更新。我见过很多 Agent 项目只做了短期记忆,结果就是 Agent 聊着聊着就“失忆”了,或者把之前已经确认过的信息又问一遍。加上长期记忆和工作记忆之后,体验会好很多。

2.4 要素四:规划与分解模块,把大任务拆小

规划模块是 Agent 的“项目经理”。用户给一个模糊的大目标,比如“帮我调研一下 AI Agent 的主流架构”,规划模块要把它拆成可执行的子任务:先搜索资料,再筛选重点,然后整理成文,最后检查完整性。

规划的实现方式有好几种。最简单的是让 LLM 一次性生成一个任务列表,然后按顺序执行。这种方式适合步骤明确、依赖关系简单的任务。复杂一点的是动态规划,每执行完一步,根据结果决定下一步做什么。这种方式更灵活,但也更容易跑偏。我通常的做法是混合式:先让 LLM 生成一个粗粒度的计划,然后在执行过程中允许动态调整。

规划模块的一个常见坑是“过度规划”。LLM 有时候会生成一个十几步的计划,其中很多步骤是冗余的或者不可执行的。我的经验是,计划不要超过七步,超过七步就说明任务拆得太细了,应该合并。另外,每个步骤都要有明确的完成标准,否则 Agent 不知道自己什么时候该进入下一步。

2.5 要素五:执行引擎,循环机制的核心

执行引擎是 Agent 的“心脏”,它负责驱动整个循环:调用 LLM 推理、解析输出、执行工具、更新状态、判断是否继续。这个循环机制看起来简单,但工程上有几个关键决策。

第一个决策是循环的终止条件。常见的有:LLM 输出最终答案、达到最大步数、超时、遇到不可恢复的错误。我一般会同时设置最大步数和超时,防止 Agent 陷入死循环。最大步数根据任务复杂度来定,简单任务五到十步,复杂任务可以到二十步。

第二个决策是循环的节奏控制。是每一步都调用 LLM,还是某些步骤可以批量执行?我的做法是,涉及决策的步骤必须调用 LLM,纯执行类的步骤(比如连续读取多个文件)可以批量做。这样既保证了灵活性,又控制了成本。

第三个决策是异常处理。循环中任何一步都可能失败,LLM 调用超时、工具返回错误、输出格式不对。我的做法是给每一步都包一层重试逻辑,重试次数用尽后,把错误信息作为观察结果反馈给 LLM,让它决定下一步怎么办。这样 Agent 就有了一定的“自主容错”能力。

2.6 要素六:状态管理,让 Agent 知道自己在哪

状态管理是很多 Agent 项目容易忽略的部分。没有状态管理,Agent 就不知道自己执行到哪一步了、哪些信息已经收集了、哪些还没做。状态管理做得好,Agent 的行为就会很连贯;做得不好,Agent 就会反复问同样的问题、重复执行同样的步骤。

我用的是一个结构化的状态对象,包含几个关键字段:task(当前任务描述)、plan(计划步骤列表)、current_step(当前步骤索引)、observations(已收集的观察结果)、artifacts(已生成的产物)。这个状态对象在每个循环迭代中更新,并且会作为 context 的一部分传给 LLM。

状态管理的一个难点是状态的序列化和反序列化。如果 Agent 需要支持中断恢复,状态就必须能持久化。我一般用 JSON 来序列化状态,存到数据库或者文件里。这样即使服务重启,Agent 也能从上次中断的地方继续。

2.7 要素七:安全与护栏,别让 Agent 闯祸

安全与护栏是 Agent 从“玩具”走向“工具”的关键。一个没有护栏的 Agent,可能会删除重要文件、发送不当消息、泄露敏感信息。护栏要做在多个层面。

输入层面,要对用户输入做过滤和校验,防止 prompt 注入。输出层面,要对 LLM 的输出做检查,防止生成不当内容。工具调用层面,要对敏感操作做权限控制,比如删除文件、发送邮件这类操作,要么需要二次确认,要么直接禁止。循环层面,要设置资源上限,防止 Agent 消耗过多 token 或调用过多工具。

我自己的做法是维护一个“危险操作清单”,清单上的工具调用必须经过人工确认或者额外的权限校验。这个清单不是固定的,根据具体应用场景来定。比如一个只读的调研 Agent,所有写操作都应该被禁止;一个自动发消息的 Agent,发送操作就需要确认。

3. 七个决策点:Agent 运行时的动态抉择

3.1 决策点一:何时调用 LLM,何时走规则

Agent 循环里最频繁的决策就是“这一步要不要调用 LLM”。全部走 LLM,成本高、延迟大;全部走规则,灵活性差、覆盖不了复杂情况。我的经验是,能用规则的地方就用规则,规则覆盖不了的地方才用 LLM。

具体来说,工具调用的参数解析可以用规则,比如从 LLM 输出里提取 JSON 就是纯规则。工具的选择可以用 LLM,因为需要理解语义。结果的判断可以用规则加 LLM 混合,比如先检查返回码,返回码正常再让 LLM 判断内容是否满足要求。这样搭配下来,成本和效果能取得一个不错的平衡。

3.2 决策点二:工具调用的粒度与编排

前面讲工具要素的时候提到了粒度问题,这里从决策角度再展开一下。在运行时,Agent 面临的一个实际决策是:多个工具调用是串行还是并行?串行简单但慢,并行快但复杂。

我的判断标准是看工具之间有没有依赖。没有依赖的工具可以并行,比如同时搜索三个不同的关键词。有依赖的工具必须串行,比如先读取文件再修改文件。并行调用的时候要注意结果合并的顺序问题,我一般会给每个并行调用分配一个 ID,结果回来之后按 ID 合并。

还有一个决策是工具调用的重试策略。不是所有失败都值得重试。网络超时可以重试,参数错误重试也没用,应该把错误信息反馈给 LLM 让它修正参数。我的做法是给每个工具定义一个retryable属性,只有标记为可重试的错误才走重试逻辑。

3.3 决策点三:记忆的读写时机与内容筛选

记忆系统的决策点在于“什么时候读、什么时候写、读什么、写什么”。读的时机一般是任务开始时和每个决策点。写的时机一般是任务结束时和关键信息产生时。

内容筛选是个技术活。不是所有信息都值得写入长期记忆。我的筛选标准是:跨会话有用的信息才写入长期记忆。比如用户的偏好、项目的背景、之前任务的结论。一次性的中间结果不写入长期记忆,只放在工作记忆里。

检索的时候也有讲究。我一般用向量检索加关键词检索的混合方式,向量检索负责语义相似,关键词检索负责精确匹配。检索结果不要全部塞进 context,按相关度排序取前几条就行。

3.4 决策点四:规划的执行与动态调整

规划在执行过程中经常需要调整。用户的需求可能变化,执行的结果可能不符合预期,外部环境可能改变。Agent 需要有能力在运行时修改计划。

我的做法是设置一个“重新规划”的触发条件。当连续两步没有进展、或者工具返回了预期之外的结果、或者用户输入了新的指令时,触发重新规划。重新规划不是从头再来,而是基于当前状态和已有观察,生成一个新的计划。

重新规划的一个坑是“规划震荡”,就是 Agent 反复修改计划但始终不执行。为了避免这个问题,我会限制重新规划的次数的上限,比如最多三次。超过三次就强制按当前计划执行,或者向用户求助。

3.5 决策点五:错误处理与自主容错

错误处理是 Agent 可靠性的关键。我前面提到过,Agent 的自主容错能力来自于“把错误当作观察结果反馈给 LLM”。但这里有个决策:哪些错误反馈给 LLM,哪些直接终止?

我的分类是:可恢复错误反馈给 LLM,不可恢复错误直接终止。可恢复错误包括工具调用失败、输出格式不对、信息不足。不可恢复错误包括权限不足、资源耗尽、安全违规。对于可恢复错误,LLM 可以根据错误信息决定重试、换工具还是调整计划。对于不可恢复错误,直接终止并报告给用户。

还有一个决策是错误的重试次数。我一般设置每个步骤最多重试两次,整个任务最多重试五次。超过上限就终止,避免无限循环。

3.6 决策点六:输出格式与结构化约束

LLM 的输出格式直接决定了 Agent 循环能不能顺利解析。如果 LLM 输出的是自由文本,解析起来就很麻烦;如果输出的是结构化格式,解析就简单很多。

我的做法是强制 LLM 输出 JSON,并且用 JSON schema 做校验。JSON 里包含几个固定字段:thought(推理过程)、action(下一步动作)、action_input(动作参数)。这样解析逻辑就很统一,不管 LLM 输出什么内容,我都能提取出这三个字段。

结构化输出的一个坑是 LLM 有时候会输出不合法的 JSON,比如多了一个逗号、少了一个引号。我的做法是在解析失败时,用规则做一次修复,修复不了再让 LLM 重新输出。重新输出的 prompt 里会带上解析错误的信息,帮助 LLM 修正。

3.7 决策点七:终止条件与结果交付

终止条件的决策看似简单,其实有很多细节。什么时候算任务完成?是 LLM 说完成了就算完成,还是需要额外的校验?

我的做法是双重校验:LLM 输出最终答案后,再用一个轻量的校验逻辑检查答案是否满足任务要求。比如任务要求“列出三个要点”,校验逻辑就检查答案里有没有三个要点。校验通过才终止,不通过就把校验结果反馈给 LLM 让它补充。

结果交付的格式也要提前定义好。是纯文本、Markdown、还是 JSON?我一般根据下游消费方来定。如果是给人看的,用 Markdown;如果是给程序消费的,用 JSON。交付之前还要做一次清理,去掉中间过程的冗余信息,只保留最终结果。

4. 工程实现中的关键细节与实操要点

4.1 循环机制的具体实现

Agent 的循环机制用伪代码表示大概是这样:

def agent_loop(task, max_steps=10, timeout=300): state = init_state(task) start_time = time.time() for step in range(max_steps): if time.time() - start_time > timeout: return build_timeout_result(state) prompt = build_prompt(state) llm_output = call_llm(prompt) parsed = parse_output(llm_output) if parsed.action == "final_answer": if validate_answer(parsed.action_input, task): return build_result(parsed.action_input) else: state.observations.append("答案校验不通过,请补充") continue tool_result = execute_tool(parsed.action, parsed.action_input) state.observations.append(tool_result) state.current_step += 1 return build_max_steps_result(state)

这段代码看起来简单,但每个函数里面都有很多细节。比如build_prompt要把任务描述、计划、历史观察、当前状态都拼进去,还要控制总长度不超过 context 窗口。parse_output要做 JSON 解析和容错。execute_tool要做参数校验、权限检查、错误捕获。

我实测下来,这个循环在大多数任务上都能跑通。关键是要把每个环节的边界情况处理好,比如 LLM 输出为空、工具返回超时、状态更新失败等等。

4.2 工具调用的参数校验与错误恢复

工具调用的参数校验经常被忽略。LLM 生成的参数不一定符合工具的要求,比如该传整数的传了字符串,该传数组的传了单个值。如果不做校验,工具执行时就会报错。

我的做法是在工具定义里加上参数 schema,用 JSON schema 来描述每个参数的类型、格式、取值范围。调用工具之前先用 schema 校验一遍,不通过就把校验错误反馈给 LLM 让它重新生成参数。

错误恢复的策略我分三级:第一级是参数修正,LLM 根据错误信息重新生成参数;第二级是工具替换,如果某个工具反复失败,尝试用功能相似的工具替代;第三级是计划调整,如果工具替换也不行,就重新规划这一步。

4.3 记忆系统的存储与检索方案

记忆系统的存储我一般用两种方案:轻量级用 SQLite,重量级用 PostgreSQL 加 pgvector。SQLite 适合单机部署、数据量小的场景,pgvector 适合需要向量检索、数据量大的场景。

检索的时候,我一般用混合检索:先用关键词检索召回一批候选,再用向量检索排序。关键词检索用全文索引,向量检索用 embedding 相似度。两路结果合并后取 top-k。k 一般取 3 到 5,太多会干扰 LLM,太少可能漏掉关键信息。

写入的时候要注意去重。同样的信息不要重复写入,否则检索结果里会出现大量重复内容。我的做法是写入前先做一次相似度检查,相似度超过阈值的就不写入。

4.4 安全护栏的落地方式

安全护栏的落地我分三层:输入过滤、输出检查、操作审批。

输入过滤主要是防 prompt 注入。我会检查用户输入里有没有试图覆盖系统指令的内容,比如“忽略之前的指令”这类。检测到就拒绝或者转义。

输出检查主要是防不当内容。我会用一个轻量的分类模型或者规则引擎检查 LLM 的输出,发现不当内容就拦截并重新生成。

操作审批主要是针对敏感工具。我会维护一个敏感操作清单,清单上的操作需要二次确认。确认方式可以是人工确认,也可以是额外的权限校验。

5. 常见问题与排查技巧实录

5.1 Agent 陷入死循环怎么办

死循环是 Agent 最常见的问题之一。表现是 Agent 反复执行同样的步骤,或者反复调用同样的工具。原因通常有几个:终止条件没设好、状态没有正确更新、LLM 的输出格式不稳定。

排查的时候先看状态有没有更新。如果状态没更新,Agent 就不知道自己已经执行过某一步,自然会重复。再看终止条件,最大步数和超时都要设。最后看 LLM 输出,如果输出格式不稳定,解析逻辑就会出错,导致循环卡住。

解决死循环的根本方法是加“进展检测”。每一步执行后,检查状态有没有实质性变化。如果没有变化,就触发重新规划或者终止。

5.2 工具调用准确率低怎么提升

工具调用准确率低通常有三个原因:工具描述不清楚、工具数量太多、LLM 能力不够。

提升的方法按优先级排序:第一,优化工具描述,写清楚功能、参数、返回值、示例;第二,合并相似工具,减少工具数量;第三,如果前两步都做了还不行,再考虑换更强的模型。

我实测下来,优化工具描述带来的提升最明显。一个写得很好的工具描述,能让准确率从百分之六十提升到百分之九十以上。

5.3 Context 窗口不够用怎么优化

Context 窗口不够用是长任务 Agent 的常见问题。优化方法有几个:压缩历史观察、按需检索记忆、分层管理 context。

压缩历史观察就是把冗长的工具返回结果做摘要,只保留关键信息。按需检索记忆就是不要把所有记忆都塞进 context,只检索当前步骤需要的。分层管理 context 就是把 context 分成固定部分和动态部分,固定部分放系统指令和工具定义,动态部分放当前状态和最近观察。

我的经验是,经过这三层优化,同样的任务需要的 context 能减少一半以上。

5.4 Agent 输出不稳定的排查思路

输出不稳定表现为同样的输入有时候结果好有时候结果差。原因可能是 LLM 的随机性、prompt 的歧义、状态的不一致。

排查的时候先把 temperature 调到零,排除随机性。如果还不行,检查 prompt 有没有歧义,把模糊的表述改明确。最后检查状态管理,确保每次运行的初始状态是一致的。

如果这些都做了还不稳定,那可能是任务本身太复杂,需要拆成更小的子任务。

常见问题可能原因排查方法解决思路
死循环终止条件缺失、状态未更新检查状态变化、检查终止条件加进展检测、设最大步数
工具调用不准描述不清、工具太多检查工具描述、统计工具数量优化描述、合并工具
Context 不够历史太长、记忆太多统计 token 消耗压缩观察、按需检索
输出不稳定随机性、prompt 歧义调 temperature、检查 prompt明确 prompt、固定状态

5.5 实操避坑清单

最后分享几个我踩过的坑,都是文档里不会写的:

  • 不要在 prompt 里放太多工具定义,超过十个工具 LLM 的选择准确率会明显下降。如果工具确实多,先做一轮工具筛选,只把相关的工具放进 prompt。
  • 工具返回结果不要直接塞进 context,先做一次摘要。原始结果可能很长,摘要后能省很多 token。
  • 状态对象不要太大,只存必要信息。状态太大不仅占 context,还容易在序列化时出错。
  • 重试逻辑要加退避,不要立即重试。立即重试容易触发限流,退避重试成功率高很多。
  • 测试的时候要覆盖边界情况,比如空输入、超长输入、工具全部失败。这些情况在真实环境里都会遇到。

6. 从能跑到可靠:我的 Agent 工程化体会

搭一个能跑的 Agent 不难,难的是让它可靠。我自己的体会是,可靠性来自于对细节的把控。七要素和七个决策点这两个框架,本质上就是帮我把细节梳理清楚。七要素让我知道系统里有哪些零件,七个决策点让我知道运行时要做哪些选择。每个零件、每个选择都有坑,把这些坑填上,Agent 的可靠性就上来了。

还有一个体会是,不要追求一步到位。我一开始就想搭一个全能的 Agent,结果什么都做不好。后来改成先做一个最小可用的版本,只保留最核心的要素和决策点,跑通之后再逐步加记忆、加规划、加护栏。这样迭代下来,每一步都有反馈,改起来也有的放矢。

最后说一个我觉得很重要的点:Agent 的工程实现里,LLM 只是其中一环,而且往往不是最难的那一环。难的是工具的设计、状态的维护、错误的处理、安全的保障。这些“脏活累活”才是决定 Agent 能不能真正用起来的关键。如果你正在搭 Agent,不妨把更多精力放在这些地方,而不是一味地追新模型、新框架。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询