ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI Agent七要素与七个决策点:从零搭建最小Agent的工程实践指南

AI Agent七要素与七个决策点:从零搭建最小Agent的工程实践指南

1. 为什么“七要素”和“七个决策点”是理解 Agent 的最短路径

很多人第一次接触 AI Agent,脑子里冒出来的第一个画面是“会自己干活的聊天机器人”。这个理解不算错,但太浅了。真正动手搭过 Agent 的人都知道,一个能跑起来的 Agent 和一段普通的 LLM 调用之间,差的不是一点半点——差的是整套工程结构。你让模型回答一个问题,那是问答;你让模型自己决定要不要调工具、调哪个工具、调完之后结果对不对、不对要不要重试,这才叫 Agent。

我见过太多团队在这一步栽跟头:Demo 阶段用几十行代码拼出一个“能查天气、能算数”的玩具,一旦要接入真实业务、要处理多轮任务、要保证稳定性,整个系统就开始崩。问题不在于模型不够强,而在于他们脑子里没有一张清晰的结构图。七要素解决的是“一个 Agent 由哪些部件构成”,七个决策点解决的是“这些部件在运行时到底在做什么判断”。前者是静态解剖,后者是动态流程。把这两张图吃透,你再看 LangChain、Dify、CrewAI 这些框架,就不会被它们的 API 绕晕——因为你知道它们本质上都在实现同一套东西。

这篇文章面向的是已经写过 LLM 调用、想往 Agent 方向深入的人,也适合正在做 Agent 选型和架构设计的技术负责人。我会把七要素逐个拆开讲清楚它为什么存在、缺了会怎样,再把七个决策点按运行时顺序串起来,配上参数计算、代码骨架和踩坑经验。读完你应该能自己从零搭一个不依赖重框架的最小 Agent,并且知道每一步为什么这么设计。

先给一个总览,后面逐层展开。七要素是:模型(Model)、指令(Instruction)、工具(Tools)、记忆(Memory)、编排(Orchestration)、状态(State)、护栏(Guardrails)。七个决策点是:是否触发 Agent、选哪个模型、要不要调工具、调哪个工具、参数怎么填、结果是否可信、下一步走向哪里。这两组概念不是并列的,而是互相咬合的——每一个决策点背后,都依赖某几个要素的支撑。

提示:不要一上来就纠结框架选型。框架是七要素的封装,你连要素都没搞明白,选哪个框架都是碰运气。

2. 七要素拆解:每个部件缺了会出什么事

2.1 模型与指令:Agent 的“大脑”和“岗位说明书”

模型是 Agent 的推理核心,这一点没有争议。但很多人忽略的是,Agent 场景对模型的要求和普通对话场景完全不同。普通对话看重的是语言流畅度和知识广度,Agent 场景看重的是三件事:指令遵循能力、结构化输出能力、多步推理的稳定性。一个在聊天里表现惊艳的模型,放到 Agent 里可能因为不肯老老实实输出 JSON 而让整个流程瘫痪。

我实测下来的经验是,选模型时优先看它在 function calling 或 tool use 上的原生支持程度。原生支持工具调用的模型,输出的参数结构更规范,解析失败率能低一个数量级。如果模型不支持原生工具调用,你就得靠 prompt 硬约束它输出特定格式,然后自己写解析器——这条路能走,但容错成本高很多,后面第七节会详细讲怎么兜底。

指令(Instruction)常被当成一句 system prompt 随便写写,这是大坑。指令本质上是这个 Agent 的“岗位说明书”,它要回答:你是谁、你的目标是什么、你能用哪些工具、你不能做什么、遇到不确定时怎么办。我见过一个客服 Agent 因为指令里没写“无法确认订单号时不要编造”,结果它凭空捏造了一个订单状态,直接造成客诉。指令里必须显式列出禁止行为,因为模型在压力下(比如用户反复追问)会倾向于“给个答案”而不是“承认不知道”。

指令的写法有个实用技巧:把工具清单和调用规则写进指令,而不是只丢给工具定义。原因是模型在生成时,指令的权重通常高于工具描述。你可以在指令里写“当用户询问天气时,必须先调用 get_weather 工具,禁止凭记忆回答”,这样能显著降低它跳过工具直接瞎答的概率。

2.2 工具与记忆:Agent 的“手脚”和“笔记本”

工具(Tools)是 Agent 与外部世界交互的唯一通道。没有工具,Agent 就只是个会说话的模型;有了工具,它才能查数据库、发请求、写文件、调 API。工具的定义质量直接决定 Agent 的能力上限。一个工具定义包含三部分:名称、描述、参数 schema。名称要语义清晰,描述要说明“什么时候用”而不只是“这是什么”,参数 schema 要严格。

这里有个反直觉的点:工具不是越多越好。当工具数量超过十几个,模型的选择准确率会明显下降,因为它要在太多选项里做判断。我的做法是按业务域分组,每组不超过 8 个工具,需要跨组时用编排层做路由。另外,工具描述里一定要写清楚边界,比如“本工具只能查询最近 30 天的数据”,否则模型会在超范围时硬调,然后拿到空结果还继续编。

记忆(Memory)分短期和长期。短期记忆就是当前对话的上下文,长期记忆是跨会话持久化的信息。很多新手把两者混为一谈,结果要么上下文爆炸,要么该记住的用户偏好每次都忘。短期记忆的管理核心是裁剪策略:什么时候截断、截断时保留哪些、怎么压缩。长期记忆的核心是写入和检索:什么信息值得存、存成什么结构、下次怎么捞出来。

我踩过的一个坑是长期记忆无脑全存,结果检索时噪声太大,捞出来的十条里八条没用。后来改成只存“用户明确表达的偏好”和“任务关键结论”,检索命中率立刻上来了。记忆不是越多越好,是越准越好。

2.3 编排、状态与护栏:Agent 的“调度中心”“账本”和“刹车”

编排(Orchestration)决定 Agent 的执行流程:是单轮直出,还是多步循环;是线性执行,还是带分支和并行。最简单的编排就是一个 while 循环——模型输出、判断是否要调工具、调完把结果塞回上下文、再让模型输出,直到它给出最终答案或达到步数上限。复杂一点的编排会引入规划节点、反思节点、多 Agent 协作。

状态(State)是 Agent 运行时的“账本”,记录当前进行到哪一步、已经调过哪些工具、拿到了什么中间结果、还剩多少预算。状态管理做得好,Agent 就能断点续跑、能审计、能回放;做得差,就会出现重复调用、上下文错乱、无法定位问题。我强烈建议把状态显式建模成一个结构体,而不是散落在各个变量里。这样出问题时你能一眼看出是哪一步的状态不对。

护栏(Guardrails)是最容易被忽略、但出事时最要命的一环。它包含输入过滤、输出校验、工具调用权限、预算限制、敏感操作二次确认等。护栏的本质是假设模型会犯错,然后在关键节点上拦住它。比如工具调用前校验参数范围,工具返回后校验结果格式,最终输出前校验是否包含禁止内容。没有护栏的 Agent 上线,等于开车不装刹车。

要素核心作用缺失后的典型症状
模型推理与生成无法理解任务、输出格式混乱
指令定义角色与边界行为漂移、越权操作、编造答案
工具连接外部世界只能空谈、无法执行实际操作
记忆保持上下文与个性化多轮失忆、重复询问、体验割裂
编排控制执行流程流程死板、无法处理多步任务
状态记录运行时信息无法续跑、难以排查、重复执行
护栏约束与保护越界调用、预算失控、输出风险内容

3. 七个决策点:Agent 运行时到底在判断什么

3.1 触发决策与模型选择:不是所有请求都该进 Agent

第一个决策点是是否触发 Agent。这听起来像废话,但实际系统里非常关键。Agent 调用是有成本的——token 成本、延迟成本、还有出错风险。如果用户只是说了句“你好”,你没必要启动一个带工具调用的完整 Agent 流程。常见的做法是在 Agent 前面加一个轻量分类器或规则层,判断这个请求是简单问答、闲聊,还是需要多步执行的复杂任务。

我做过一个项目,前期没做这层过滤,所有请求都走 Agent,结果简单问题的平均响应时间从 800ms 涨到 4 秒,成本翻了三倍。后来加了一个基于关键词和意图分类的前置判断,把大约 60% 的请求挡在 Agent 之外,直接走普通对话,整体指标立刻好转。这个决策点的价值就在于此:把 Agent 用在真正需要它的地方。

第二个决策点是选哪个模型。一个成熟的 Agent 系统往往不是单模型,而是模型组合。简单判断用小模型,复杂推理用大模型,工具参数生成用对结构化输出友好的模型。这个决策可以静态配置(按任务类型路由),也可以动态判断(先让小模型试,置信度低再升级)。动态升级的策略要小心,因为它会带来延迟抖动,用户体验上要能接受才行。

3.2 工具调用三连问:要不要调、调哪个、参数怎么填

第三个决策点是要不要调工具。模型需要判断:这个问题我能不能凭已有知识回答,还是必须借助外部信息。判断错误的两种表现是:该调不调(凭记忆瞎答,可能过时或错误)和不该调乱调(浪费资源,还可能引入噪声)。降低误判的关键在指令里写清楚工具的适用场景,以及“不确定时优先调用工具”的默认策略。

第四个决策点是调哪个工具。当工具数量多时,这是最容易出错的一步。模型要在所有工具里选出最匹配的那个。提升准确率的实用手段有三个:一是工具描述写清楚使用场景和反例;二是给工具分组,先选组再选工具;三是把常用工具排在前面,利用位置偏好。我实测过,光是优化工具描述这一项,选择准确率就能提升 20% 以上。

第五个决策点是参数怎么填。这是最琐碎也最容易翻车的一步。模型要根据用户输入和上下文,把参数填成工具要求的格式。常见问题包括:类型不对(该填数字填了字符串)、必填项缺失、枚举值超出范围、日期格式不匹配。护栏要在这里介入,做参数校验,不合法就返回错误让模型重填,而不是硬着头皮调用。

# 工具参数校验的简化骨架 def validate_tool_call(tool_schema, params): errors = [] for field in tool_schema["required"]: if field not in params: errors.append(f"缺少必填参数: {field}") for key, value in params.items(): expected = tool_schema["properties"].get(key, {}).get("type") if expected == "integer" and not isinstance(value, int): errors.append(f"参数 {key} 应为整数") if expected == "string" and not isinstance(value, str): errors.append(f"参数 {key} 应为字符串") return errors

3.3 结果可信度与下一步走向:Agent 的自我校验和循环控制

第六个决策点是结果是否可信。工具返回了结果,但这个结果能不能直接用?可能是空结果、可能是报错、可能是格式不对、可能是明显异常的值。Agent 需要判断:这个结果是否满足任务需求,不满足的话是重试、换工具,还是向用户澄清。这一步做不好,Agent 就会拿着错误结果继续往下走,最后输出一个看似合理实则错误的答案。

我的经验是给工具返回加一层“结果摘要”处理,把原始返回压缩成模型容易判断的形式,同时在指令里明确“如果工具返回为空或报错,不要编造,应告知用户或尝试其他方式”。这一步能挡掉大量幻觉。

第七个决策点是下一步走向哪里。这是循环机制的核心:继续调工具、生成最终答案、还是终止并报错。判断依据包括:任务是否完成、步数是否超限、预算是否耗尽、是否陷入循环。循环检测特别重要,我见过 Agent 因为工具一直返回相同结果而无限重试,最后烧掉大量 token。解决办法是记录最近几次的工具调用签名,如果重复出现就强制跳出。

决策点判断依据出错后果主要防护手段
是否触发请求复杂度、意图资源浪费或能力不足前置分类器
选哪个模型任务难度、成本预算质量或成本失衡静态路由+动态升级
要不要调工具知识时效性、确定性幻觉或资源浪费指令约束+默认策略
调哪个工具工具描述匹配度调错工具、任务失败描述优化+分组
参数怎么填schema 约束调用报错参数校验护栏
结果是否可信返回内容质量错误传播结果校验+摘要
下一步走向任务状态、预算死循环或提前终止循环检测+步数上限

4. 从零搭一个最小 Agent:不依赖重框架的实操路径

4.1 环境准备与核心循环骨架

理解了七要素和七个决策点,接下来动手搭一个最小可用 Agent。我建议第一版不要用 LangChain 这类重框架,因为框架会帮你隐藏太多细节,你反而学不到东西。用原生 API 加一个 while 循环,把七个决策点显式写出来,跑通之后再考虑要不要上框架。

环境上你只需要一个能调用的模型 API 和一个 HTTP 客户端。核心循环的逻辑是:把系统指令、对话历史、工具定义一起发给模型;模型返回后,检查它是否要求调用工具;如果要求,执行工具并把结果追加到历史;如果不要求,输出最终答案并结束。这个循环里,七个决策点分别落在不同位置——触发判断在循环外,模型选择在请求构造时,工具相关决策在解析模型输出时,结果校验在工具执行后,走向判断在循环末尾。

def run_agent(user_input, max_steps=8): messages = [{"role": "system", "content": SYSTEM_PROMPT}] messages.append({"role": "user", "content": user_input}) state = {"step": 0, "tool_calls": [], "done": False} while state["step"] < max_steps and not state["done"]: response = call_model(messages, tools=TOOL_SCHEMAS) if response.has_tool_call: call = response.tool_call errors = validate_tool_call(get_schema(call.name), call.args) if errors: messages.append({"role": "tool", "content": f"参数错误: {errors}"}) state["step"] += 1 continue result = execute_tool(call.name, call.args) state["tool_calls"].append(call.name) messages.append({"role": "tool", "content": summarize(result)}) else: state["done"] = True return response.content state["step"] += 1 return "任务未在限定步数内完成"

这段骨架虽然简单,但已经把七要素里的模型、指令、工具、状态、编排、护栏都体现出来了。记忆在这里是 messages 列表,护栏是参数校验和步数上限。你可以先跑通它,再逐个增强。

4.2 工具定义与参数校验的落地细节

工具定义是这一步的重头戏。我建议用 JSON Schema 描述参数,因为主流模型对它的理解最到位。每个工具的描述里,除了功能说明,还要写清楚“什么时候不该用”。比如一个查询订单的工具,描述里要写“仅用于查询已支付订单,未支付订单请用另一个工具”。这种负向说明能显著减少误调。

参数校验要分两层:第一层是结构校验,检查必填项、类型、枚举范围;第二层是业务校验,检查参数在业务上是否合理,比如日期不能是未来、金额不能为负。结构校验可以自动生成,业务校验需要手写。两层都过了才执行工具,任何一层不过都把错误信息返回给模型让它重填。这里要注意,错误信息要写得让模型能理解并修正,而不是抛一个技术堆栈。

注意:工具执行一定要设超时。我遇到过外部 API 卡住导致整个 Agent 挂起的情况,加了超时和重试之后才稳定。超时后返回的错误信息也要友好,让模型知道是超时而不是参数错误。

4.3 记忆裁剪与上下文预算控制

上下文窗口是有限资源,Agent 跑多步之后 messages 会越来越长,必须裁剪。裁剪策略我常用的是“保留系统指令 + 最近 N 轮完整对话 + 更早对话的摘要”。摘要可以用模型生成,也可以规则提取关键信息。关键是别把工具调用的中间结果全留着,那些往往很长且用完就没用了,可以只保留摘要。

预算控制要算清楚:每步请求的 token 数 = 系统指令 + 历史 + 工具定义 + 当前输入。工具定义如果很多,本身就很占 token。我的做法是动态加载工具定义,只把当前任务可能用到的工具放进请求,而不是全量塞进去。这样既省 token,又能提升工具选择准确率,一举两得。

5. 那些文档不会告诉你的踩坑实录

5.1 工具描述写得太“技术”,模型反而不会用

我早期写工具描述,习惯写成“输入参数为 user_id,返回用户信息”这种技术文档风格。结果模型经常在该调用的时候不调用。后来改成“当用户询问账户相关信息(如余额、等级、注册时间)时使用本工具,参数 user_id 可从对话中获取”,调用率立刻上来了。工具描述是写给模型看的,不是写给同事看的,要用自然语言说明使用场景,而不是罗列技术细节。

还有一个细节:工具名称不要用缩写。qry_usr_bal这种名字模型很难理解,query_user_balance就好很多。名称本身就是语义信号,别浪费它。

5.2 循环失控:Agent 反复调同一个工具的排查过程

有一次上线后监控报警,某个会话的 token 消耗异常高。我拉日志一看,Agent 在反复调用同一个查询工具,每次参数都一样,结果也一样,但它就是不停。排查下来发现两个问题:一是工具返回空结果时,指令里没写清楚该怎么办,模型就默认重试;二是没有循环检测,系统不知道自己在做重复动作。

修复方案分三步:第一,在指令里明确“如果工具返回空结果,最多重试一次,仍为空则告知用户”;第二,在状态里记录最近三次工具调用签名,重复就强制终止;第三,给单次会话设 token 预算上限,超了就中断并提示。这三招下去,循环失控再没出现过。这个坑的教训是:永远假设模型会陷入循环,提前设好刹车。

5.3 参数幻觉:模型编造了一个不存在的枚举值

另一个印象深刻的坑是参数幻觉。一个工具的状态参数只接受pending、paid、cancelled三个值,模型却填了个processing。它不是在乱填,而是根据语义“合理推测”了一个值。问题在于工具端没做枚举校验,直接拿这个值去查数据库,返回空结果,模型又基于空结果编了个答案给用户。

修复很直接:在参数校验层加枚举检查,不合法就返回“状态参数只能是 pending/paid/cancelled 之一”让模型重填。同时把枚举值写进工具描述里。这件事让我意识到,护栏不是可选项,是必选项。模型的“合理推测”在开放场景是优点,在需要精确匹配的场景就是灾难。

5.4 多轮对话里记忆污染导致的答非所问

还有个隐蔽的坑是记忆污染。用户先问了一个订单,Agent 查了订单 A;接着用户问“那它的物流呢”,Agent 却去查了订单 B 的物流。原因是历史里订单 A 和订单 B 的信息混在一起,模型没分清“它”指代谁。解决办法是在记忆里给关键实体打标签,比如把订单号显式记录在状态里,并在指令里要求“指代不明时先向用户确认”。另外,工具返回结果里要带上实体标识,方便模型对齐。

这些坑的共同点是:它们都不会在 Demo 阶段暴露,只有真实流量、多轮交互、边界输入才会触发。所以我的建议是,Agent 上线前一定要做压力测试和对抗测试,专门喂一些模糊、矛盾、超范围的输入,看它怎么反应。

6. 框架选型:LangChain、Dify、CrewAI 到底该怎么选

6.1 先想清楚你要的是“库”还是“平台”

框架选型的第一步不是比较功能,而是想清楚你的使用形态。LangChain 更像一个库,给你积木让你自己拼,灵活但学习曲线陡;Dify 更像一个平台,可视化编排,上手快但定制受限;CrewAI 专注多 Agent 协作,适合角色分工明确的场景。选错了形态,后面怎么用都别扭。

我的判断标准是:如果你需要深度定制执行流程、要嵌入现有系统、团队有工程能力,选 LangChain 这类库;如果你想快速验证、业务人员也能参与编排、不需要太底层的控制,选 Dify 这类平台;如果你的任务天然可以拆成多个角色协作(比如一个写一个审),可以看 CrewAI。没有最好的框架,只有最匹配的形态。

6.2 用七要素当尺子去量每个框架

不管你选哪个,都可以用七要素去评估它。模型支持哪些、指令怎么管理、工具怎么注册、记忆怎么持久化、编排支持哪些模式、状态是否可观测、护栏有没有内置。拿这把尺子一量,每个框架的强弱项就清楚了。比如有的框架工具生态丰富但状态管理弱,有的编排灵活但护栏要自己加。

框架形态编排能力状态可观测护栏内置适合场景
LangChain库强,可自定义需自行实现少,需自建深度定制、嵌入现有系统
Dify平台中,可视化较好部分内置快速验证、业务参与
CrewAI库多 Agent 协作中等少角色分工明确的任务

6.3 我的实际组合:轻框架 + 自建核心循环

折腾一圈之后,我现在的默认方案是:核心循环自己写,只借用框架里的工具定义规范和部分工具实现。这样既保留了控制力,又不用重复造轮子。对于复杂编排,我会引入轻量的状态机库,而不是全套 Agent 框架。这个组合的好处是,出问题时我能精确知道每一行在干什么,而不是在框架的黑盒里猜。

如果你团队刚开始做 Agent,我的建议是先用原生 API 跑通最小循环,把七要素和七个决策点都亲手实现一遍,再决定要不要上框架。这个学习成本是值得的,因为它让你在后续选型和排错时有了判断力。

7. 让 Agent 稳定跑在生产环境的几条硬规矩

7.1 可观测性:没有日志的 Agent 等于裸奔

生产环境的 Agent 必须可观测。每一步的输入、输出、工具调用、参数、结果、耗时、token 消耗都要记下来。我习惯给每个会话一个 trace_id,所有日志按 trace 串起来,出问题能完整回放。没有这层,你连“它为什么这么答”都说不清,更别提优化了。

日志之外,还要有指标监控:平均步数、工具调用成功率、参数校验失败率、循环终止率、单会话 token 消耗分布。这些指标能帮你在用户投诉之前发现问题。比如参数校验失败率突然上升,往往意味着模型更新了或者工具描述改了,需要及时排查。

7.2 预算与降级:给 Agent 装上“油表”和“备胎”

预算控制是生产环境的生命线。每个会话、每个用户、每天都要有 token 和调用次数上限。超限时的降级策略也要提前设计:是直接返回“当前繁忙请稍后再试”,还是降级到无工具的普通对话,还是转人工。我一般用分级降级:轻度超限降级到小模型,重度超限直接终止并提示。

降级还要考虑工具不可用的情况。外部 API 挂了,Agent 不能跟着挂。我的做法是给每个工具配一个降级返回,比如查询失败时返回“暂时无法获取,请稍后重试”,让模型基于这个信息给用户一个体面的回复,而不是抛异常。

7.3 安全护栏:输入、工具、输出三道闸

安全护栏要覆盖三个位置。输入侧过滤明显恶意的请求,比如试图让 Agent 执行危险操作的指令;工具侧做权限校验,敏感工具(如写操作、支付)要二次确认或人工审批;输出侧校验是否包含不该出现的内容。这三道闸缺一不可。

工具侧的权限尤其重要。读操作和写操作要分开,写操作要有更严格的校验和确认。我见过 Agent 因为指令理解偏差,把“查询订单”理解成“修改订单”的案例。护栏在这里的作用就是:即使模型判断错了,系统也不会真的执行危险操作。

注意:护栏的校验逻辑要独立于模型,不能靠模型自己判断“这个操作安不安全”。模型是被约束的对象,不是约束的执行者。

8. 我对 Agent 工程实现的一点个人体会

折腾 Agent 这段时间,最大的体会是:Agent 的难点从来不在模型,而在工程。模型能力每年都在涨,但工程上的坑——循环控制、状态管理、参数校验、预算控制、可观测性——这些不会因为模型变强就自动消失。反而模型越强,能做的事越多,工程上的约束就越重要。

七要素和七个决策点这套框架,我用了很久,最大的价值是它让我在设计和排错时有了统一的语言。团队讨论时,说“第三个决策点出问题了”比说“它好像调错工具了”精确得多。如果你也在做 Agent,建议把这套框架内化成自己的思维习惯,比记住任何框架的 API 都管用。

最后分享一个小技巧:每次 Agent 出问题,别急着改 prompt,先去看状态和日志,定位是哪个决策点错了。大部分问题不在模型,而在你给它的结构。结构对了,模型自然就稳了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进