ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI Agent 工程实战:七要素与七个决策点全解析

AI Agent 工程实战:七要素与七个决策点全解析 1. 为什么“七要素”和“七个决策点”才是 Agent 工程的骨架1.1 从“会聊天的 LLM”到“能办事的 Agent”中间隔着一整套工程很多人第一次接触 AI Agent脑子里浮现的画面是“一个更聪明的聊天机器人”。真上手做项目就会发现聊天和办事是两码事。LLM 本身只是一个无状态的文本生成器你给它一段上下文它吐出下一段文本。它不会主动记住上一轮干了什么不会自己去调接口更不会在失败之后换个思路重试。而 Agent 要解决的核心问题恰恰是让这个“只会说话”的模型变成一个能感知环境、做决策、执行动作、并根据结果调整策略的闭环系统。我习惯把 Agent 拆成七个要素来看模型Model、指令Instruction、工具Tools、记忆Memory、规划Planning、执行循环Loop、状态与观测State Observation。这七个要素不是学术分类而是你搭任何一个 Agent 时都绕不开的“零件清单”。少一个系统要么跑不起来要么跑起来不可靠。举个最直观的例子。你让 Agent 去“查一下今天北京的天气如果下雨就提醒我带伞”。模型负责理解意图指令告诉它“你有查天气的工具输出要结构化”工具是那个真正去调天气 API 的函数记忆让它记得“用户在北京”规划让它决定先查天气、再判断、再决定要不要提醒执行循环负责把“查—判断—提醒”串起来状态与观测则记录每一步的输入输出方便你排查它为什么没提醒。你看缺了任何一个这个任务都完不成。1.2 七个决策点Agent 工程里真正让你纠结的地方如果说七要素是“零件”那七个决策点就是“装配时的选择”。我在实际搭 Agent 的过程中反复卡壳的地方基本都集中在这七个决策上模型选型用哪个 LLM大而强还是小而快要不要本地部署指令设计System Prompt 怎么写要不要用结构化输出约束工具粒度工具拆多细一个工具干一件事还是一个工具干一类事记忆策略短期记忆放上下文长期记忆存哪里怎么检索规划方式让模型一次性出计划还是边做边想ReAct 风格循环控制循环几次停怎么判断“任务完成”怎么防止死循环容错与观测工具报错怎么办怎么记录轨迹、怎么回放这七个决策点之所以关键是因为它们直接决定了 Agent 的可靠性、成本和可维护性。我见过太多 Demo 跑得飞起、一上生产就崩的 Agent问题几乎都出在这七个点上没想清楚。下面我就按这个骨架把每个决策点的工程实现掰开揉碎讲一遍。提示七要素是“系统由什么组成”七个决策点是“每个组成部分你怎么选”。前者帮你查漏后者帮你做取舍。两者配合使用基本能覆盖 Agent 开发 90% 的设计问题。2. 七要素逐个拆解每个零件到底怎么落地2.1 模型与指令Agent 的“大脑”和“行为准则”模型选型是第一个决策点也是最容易被低估的。很多人一上来就选最强的模型结果 token 成本爆炸、延迟高到用户跑光。我的经验是按任务复杂度分层选型。简单意图识别、参数抽取用便宜的小模型就够复杂规划、多步推理再上大模型。现在很多框架支持“路由”机制先让小模型判断任务类型再决定用哪个模型处理这个思路在成本敏感的场景里非常实用。关于token这是新手最容易懵的概念。你可以把 token 理解成模型眼里的“字”。中文里一个汉字大约对应 1 到 2 个 token英文一个单词大约 1 到 1.3 个 token。为什么要在意它因为模型的上下文窗口是有限的而且你按 token 付费。一个 Agent 每轮循环都要把历史对话、工具定义、系统指令全部塞进上下文循环几轮下来 token 消耗是线性甚至指数增长的。我踩过的坑就是一个查数据的 Agent因为把完整的历史记录每轮都带上跑到第五轮就超了上下文窗口直接报错。后来改成“只保留最近 N 轮 关键信息摘要”问题立刻解决。指令设计的核心是“把模型当成一个刚入职、能力很强但完全不懂你业务的员工”。你不能只说“帮我处理订单”而要告诉它你的角色是什么、有哪些工具可用、输出格式是什么、遇到不确定的情况怎么办。我强烈建议用结构化输出比如 JSON Schema来约束模型这样下游代码解析起来才稳。纯自然语言输出看着灵活实际解析时全是坑。{ role: order_assistant, tools: [query_order, refund_order, send_notification], output_format: { action: string, params: object, reasoning: string }, constraints: [ 退款金额超过 500 元时必须先请求人工确认, 无法确定用户意图时返回 actionclarify ] }上面这种指令结构比一大段散文式的 Prompt 可靠得多。原因很简单模型在生成时有了明确的“填空模板”自由度被约束在合理范围内幻觉和格式错误都会大幅减少。2.2 工具调用Agent 的“手脚”也是最容易出事的地方工具调用Tool Calling是 Agent 区别于聊天机器人的分水岭。模型本身不能查数据库、不能发邮件、不能操作文件它只能“请求”调用某个工具真正执行的是你的代码。这个机制听起来简单工程上却有一堆细节。第一个细节是工具的描述。模型是根据你给的函数名、参数说明、功能描述来决定调不调、怎么调的。描述写得含糊模型就会乱调。我见过一个案例工具叫search描述只写了“搜索”结果模型在需要查订单时也去调它。后来改成search_product_by_keyword描述写清楚“根据关键词搜索商品返回商品列表不用于查询订单”误调率立刻下降。第二个细节是工具的粒度。工具拆得太细模型要调很多次才能完成一件事循环次数暴涨拆得太粗一个工具内部逻辑复杂出错难定位。我的经验法则是一个工具对应一个原子业务动作。比如“查订单”和“退款”是两个工具而不是一个“处理订单”的大工具。这样模型规划起来清晰你也好做权限控制和错误处理。第三个细节是参数校验。模型生成的参数不一定合法可能少字段、类型错、甚至编造不存在的值。所以工具执行前一定要做校验校验失败要把清晰的错误信息返回给模型让它自己修正。这就是所谓的“自主容错”——不是靠人兜底而是让 Agent 在循环里自我纠正。def refund_order(order_id: str, amount: float) - dict: # 参数校验 if not order_id or not isinstance(order_id, str): return {error: order_id 必须是非空字符串} if amount 0: return {error: 退款金额必须大于 0} # 业务校验 order db.get_order(order_id) if not order: return {error: f订单 {order_id} 不存在} if amount order.paid_amount: return {error: 退款金额不能超过实付金额} # 执行 result payment.refund(order_id, amount) return {success: True, refund_id: result.id}注意上面每个错误分支都返回了人类可读的错误信息。这不是给人看的是给模型看的。模型拿到“退款金额不能超过实付金额”这句话下一轮就知道该调整参数了。如果你只返回一个{error: true}模型就懵了只能瞎猜。2.3 记忆与状态让 Agent 不再“七秒记忆”记忆Memory分两层短期记忆和长期记忆。短期记忆就是当前对话的上下文放在 prompt 里长期记忆是跨会话、跨任务的知识需要存到外部存储向量库、数据库、文件里用的时候检索出来。短期记忆的管理核心是上下文预算。你不能把所有历史都塞进去得做取舍。我的做法是保留最近 3 到 5 轮完整对话更早的内容做摘要压缩。摘要不是随便压而是保留“用户目标、已完成的步骤、关键结论”这三类信息。这样即使对话很长模型也不会丢失主线。长期记忆的坑更多。最常见的是检索不准用户问“我上次说的那个方案”向量检索可能召回一堆不相关的历史。解决办法是给记忆打标签时间、主题、任务类型检索时先按标签过滤再语义匹配。另外长期记忆要定期清理和更新否则会积累大量过时信息反而干扰模型判断。状态与观测是很多人忽略的一环。Agent 每执行一步都应该记录当前轮次、输入、模型输出、调用的工具、工具返回、耗时、token 消耗。这些数据在排查问题时价值极高。我遇到过一个“Agent 偶尔不回复”的问题查了观测日志才发现是某次工具调用超时后循环没有正确处理异常直接静默退出了。没有日志这种问题能查一整天。2.4 规划与循环Agent 的“思考节奏”规划Planning有两种主流风格。一种是先规划后执行让模型一次性输出完整步骤列表然后逐步执行。这种方式适合流程固定的任务比如“生成报告”这种步骤明确的场景。另一种是边做边想也就是 ReAct 风格模型每一步都先“思考”再“行动”根据上一步结果决定下一步。这种方式灵活适合探索性任务但 token 消耗更高也更容易跑偏。我的建议是混合使用先让模型出一个粗粒度的计划3 到 5 步执行过程中允许它根据实际情况调整。这样既有全局方向又保留了灵活性。执行循环Loop是 Agent 的引擎。一个典型的循环是这样的把当前状态历史 工具定义 指令发给模型模型输出要么是“调用工具”要么是“给出最终答案”如果是调用工具执行工具把结果加入状态回到第 1 步如果是最终答案结束循环这个循环必须有终止条件否则就是死循环。终止条件通常有三个模型给出最终答案、达到最大轮次、检测到重复动作。最大轮次我一般设 10 到 15 轮具体看任务复杂度。重复动作检测是防止模型“卡住”——比如连续三次调用同一个工具、传同样的参数那就该强制中断了。注意循环控制里最危险的是“看起来在干活实际在原地打转”。模型可能因为工具一直返回错误反复重试同一个动作。所以除了轮次限制一定要加“相同动作重复检测”连续两次相同调用就该介入。3. 七个决策点的工程取舍我踩过的坑和最终方案3.1 决策点一模型选型——别迷信“最强”要算“性价比”模型选型我走过两个极端。一开始全用最强模型效果确实好但一个中等复杂度的 Agent 跑一次任务要花掉几毛钱日活一上来成本根本扛不住。后来全换成小模型成本降了但复杂任务的成功率掉得厉害用户投诉不断。最终的方案是分层路由。具体做法在 Agent 入口加一个轻量分类器可以用小模型也可以用规则判断任务复杂度。简单任务单工具调用、参数抽取走小模型复杂任务多步规划、需要推理走大模型。实测下来成本能降 60% 到 70%而整体成功率只掉几个百分点。任务类型推荐模型档位典型场景单次成本参考意图识别、参数抽取小模型分类、格式化极低单工具调用中小模型查数据、发通知低多步规划、复杂推理大模型报告生成、问题诊断中高高风险决策大模型 人工确认退款、删除操作高选型时还要考虑延迟。用户能接受的响应时间通常在 3 到 5 秒内。如果一个任务要循环 8 轮每轮模型调用 1 秒那就是 8 秒用户早跑了。所以复杂任务要么做流式输出让用户看到进度要么做异步处理先返回“处理中”完成后通知。3.2 决策点二指令设计——结构化是王道指令设计我最大的教训是不要指望模型“理解你的意图”要把它当成一个需要精确指令的执行器。早期我写的 Prompt 很“人性化”比如“请你帮我看看这个订单能不能退”结果模型时而调工具、时而直接回答、时而反问行为完全不可预测。后来我全面转向结构化指令 结构化输出。System Prompt 里明确写清楚角色、可用工具、输出格式、约束条件、异常处理方式。输出强制用 JSON字段固定。这样模型的行为空间被大幅压缩稳定性提升非常明显。还有一个技巧是给例子Few-shot。在指令里放两三个“输入-输出”示例模型的表现会稳定很多。例子要覆盖正常情况和边界情况比如“参数缺失时怎么返回”“工具报错时怎么处理”。这比写一堆规则描述有效得多。3.3 决策点三工具粒度——原子化但别太碎工具粒度这个决策点我的经验是按业务动作原子化。什么叫原子化就是这个工具只干一件事干完就有明确结果。比如“查订单状态”是一个工具“发起退款”是另一个工具而不是一个“处理订单问题”的大工具。但也不能太碎。我见过有人把“查订单”拆成“查订单基本信息”“查订单物流”“查订单支付记录”三个工具结果模型每次都要调三次循环次数翻倍token 成本也上去了。合理的粒度是一个工具对应一个用户能理解的业务动作。用户说“查订单”你就给他一个查订单的工具内部把该查的都查了。工具设计还有一个关键是幂等性。尤其是写操作退款、发消息、改状态一定要支持重复调用不出错。因为 Agent 循环里可能因为超时重试同一个工具被调两次。如果退款工具不幂等用户就被退两次钱这是生产事故。3.4 决策点四记忆策略——短期靠压缩长期靠标签记忆策略我最终定下来的方案是短期记忆滑动窗口 摘要长期记忆标签化检索。短期记忆保留最近 5 轮完整对话更早的每 5 轮做一次摘要摘要保留“目标、进度、结论”。这样即使对话 50 轮上下文里也只有“5 轮完整 若干摘要”token 可控。长期记忆每条记忆打三个标签——时间、主题、任务类型。检索时先用标签过滤比如只查“最近一个月 退款相关”再做语义匹配。这样召回准确率比纯向量检索高很多。另外长期记忆要设过期策略超过一定时间的低价值记忆自动清理避免干扰。3.5 决策点五规划方式——粗计划 动态调整规划方式我试过纯 ReAct 和纯先规划最后选了混合。具体是第一轮让模型输出一个粗粒度计划3 到 5 步后续每轮允许它根据执行结果调整计划。这样既有方向感又不会因为计划太死而卡住。实现上我在状态里维护一个plan字段模型每轮可以更新它。如果模型发现原计划走不通可以修改plan并说明原因。这个“计划变更记录”在排查问题时特别有用你能清楚看到 Agent 为什么改变了策略。3.6 决策点六循环控制——三道保险防死循环循环控制我加了三道保险最大轮次限制默认 12 轮超过就强制结束并返回当前最佳结果。重复动作检测连续两次相同工具 相同参数判定为卡住中断并报错。无进展检测如果连续三轮状态没有实质变化没有新信息、没有新工具调用判定为无进展中断。这三道保险配合使用基本能杜绝死循环。实测下来正常任务平均 3 到 5 轮完成复杂任务 8 到 10 轮极少触发上限。3.7 决策点七容错与观测——让 Agent 自己爬起来容错的核心思路是把错误信息喂回给模型让它自己修正。工具报错不要直接抛异常终止而是把错误信息作为工具返回结果让模型在下一轮决定怎么办。这就是“自主容错”的工程实现。但有些错误模型修不了比如“数据库连接失败”“权限不足”。这类错误要分类处理可恢复错误参数错、格式错喂回模型不可恢复错误系统故障、权限问题直接终止并告警。观测方面我建议至少记录这些字段trace_id、轮次、模型输入摘要、模型输出、工具调用、工具返回、耗时、token 消耗。有了这些任何问题都能回放定位。我现在的习惯是每个 Agent 任务都生成一个 trace出问题直接查 trace比看日志快十倍。错误类型处理方式是否喂回模型参数缺失/格式错返回错误描述是业务规则不满足返回规则说明是工具超时重试一次仍失败则终止否权限不足终止并告警否系统故障终止并告警否4. 从零搭一个 Agent完整实操流程与关键代码4.1 环境准备与依赖选型搭 Agent 不一定需要重型框架。我的建议是先用最小依赖跑通核心循环再按需引入框架。很多框架封装太厚出问题你根本不知道是哪一层的事。基础依赖就三样一个 LLM 调用库、一个 HTTP 客户端调工具用、一个日志库。Python 生态里openai或各家模型的 SDK 负责模型调用httpx负责工具请求logging或structlog负责观测。就这些足够跑通一个完整的 Agent。如果你要用框架选型时看三点是否支持自定义工具、是否暴露循环控制、是否有观测能力。不满足这三点的框架用起来会很憋屈。pip install openai httpx structlog4.2 核心循环的实现下面是一个最小可用的 Agent 循环实现。我把它拆成几个函数方便你理解和修改。import json from openai import OpenAI client OpenAI() def run_agent(user_input, tools, max_turns12): state { messages: [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_input} ], turn: 0, last_action: None, repeat_count: 0 } while state[turn] max_turns: state[turn] 1 # 1. 调用模型 response client.chat.completions.create( modelyour-model, messagesstate[messages], toolstools, tool_choiceauto ) msg response.choices[0].message # 2. 判断是否要调工具 if not msg.tool_calls: # 模型给出最终答案 return msg.content # 3. 执行工具 for tool_call in msg.tool_calls: action_key f{tool_call.function.name}:{tool_call.function.arguments} # 重复动作检测 if action_key state[last_action]: state[repeat_count] 1 if state[repeat_count] 2: return 检测到重复动作任务中断 else: state[repeat_count] 0 state[last_action] action_key # 执行 result execute_tool( tool_call.function.name, json.loads(tool_call.function.arguments) ) # 4. 把结果加入状态 state[messages].append(msg) state[messages].append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse) }) return 达到最大轮次任务未完成这段代码虽然短但包含了循环控制、重复检测、工具执行、状态更新这几个核心环节。你可以直接拿去改。4.3 工具注册与执行工具的定义要遵循模型的 function calling 格式。关键是描述要精确参数要写清楚类型和含义。TOOLS [ { type: function, function: { name: query_order, description: 根据订单号查询订单详情返回订单状态、金额、物流信息。不用于退款操作。, parameters: { type: object, properties: { order_id: { type: string, description: 订单号格式为 ORD 开头加 12 位数字 } }, required: [order_id] } } } ] def execute_tool(name, args): if name query_order: return query_order_impl(args[order_id]) return {error: f未知工具: {name}}注意description里我特意写了“不用于退款操作”。这是为了防止模型在需要退款时误调查询工具。这种“负向说明”在实际项目里非常有用。4.4 观测埋点观测不用搞得很复杂关键是每轮都记录。我通常在循环里加一个log_turn函数把关键信息写进结构化日志。import structlog log structlog.get_logger() def log_turn(trace_id, turn, model_output, tool_calls, tool_results, elapsed): log.info( agent_turn, trace_idtrace_id, turnturn, outputmodel_output[:200], # 截断避免日志过大 tools[tc.function.name for tc in tool_calls] if tool_calls else [], results[str(r)[:200] for r in tool_results], elapsed_mselapsed )有了这些日志你可以随时回放任何一个任务的执行过程。我排查过一个“Agent 偶尔返回空”的问题就是靠 trace 发现某轮模型返回了空 content 且没有 tool_calls循环直接把它当成最终答案返回了。后来加了个判断如果 content 为空且无工具调用视为异常重试一次。5. 常见问题与排查技巧实录5.1 模型不调工具直接瞎编答案这是最常见的问题。原因通常是工具描述不够清晰或者指令里没强调“必须用工具获取信息”。解决办法在 System Prompt 里明确写“涉及订单、物流、退款等事实性问题必须先调用工具获取数据禁止凭记忆回答”。另外工具描述里把“什么时候该用”写清楚。还有一个隐藏原因是模型能力不足。小模型在工具调用上的表现确实不如大模型。如果指令优化后还是不行考虑换模型档位。5.2 工具调用参数错误参数错误分两类格式错和语义错。格式错比如该传字符串传了数字靠 JSON Schema 约束基本能解决。语义错比如订单号编造比较麻烦需要在工具里做校验把错误信息返回给模型让它重新生成。我的经验是在工具返回的错误信息里给出正确示例效果更好。比如“订单号格式错误正确格式如 ORD202401011234”模型看到示例后修正的概率大幅提升。5.3 循环停不下来前面说的三道保险轮次限制、重复检测、无进展检测基本能解决。但还有一种情况是模型在“思考”和“行动”之间反复横跳调一个工具觉得不对又调另一个来回几次。这种情况通常是任务本身有歧义模型不确定该怎么做。解决办法是在指令里加“如果信息不足直接向用户提问不要反复尝试”。5.4 token 消耗过快token 消耗快通常有三个原因历史记录太长、工具定义太多、循环轮次太多。对应的优化历史做摘要压缩、工具按需加载不要一次全塞进去、循环加轮次上限。另外工具返回结果也要精简不要把整个数据库记录都返回给模型只返回必要字段。问题现象可能原因排查方向解决手段不调工具瞎编描述不清/指令缺失检查工具描述和 System Prompt加“必须用工具”约束参数错误Schema 不严/模型能力看错误日志加校验 错误示例死循环无终止条件看轮次和动作记录三道保险token 暴涨历史/工具/轮次过多看 token 统计压缩 按需加载返回空模型异常输出看 trace空输出视为异常重试5.5 一个容易被忽略的坑工具返回结果太大我踩过一个坑一个查询工具返回了完整的订单对象包含几十个字段其中还有嵌套的物流轨迹数组。结果每轮循环 token 消耗巨大而且模型被无关信息干扰判断变慢。后来改成只返回模型需要的字段状态、金额、预计送达时间token 直接降了 70%模型判断也更准了。这个经验的核心是工具返回给模型的数据要像给人看简报一样精简。模型不需要原始数据它需要的是决策所需的关键信息。6. 一些关于 Agent 学习路线和架构选择的个人看法6.1 学习路线先跑通循环再谈架构很多人一上来就研究各种 Agent 架构、框架对比结果连一个完整的循环都没跑通过。我的建议是先手写一个最小 Agent一个模型调用、一个工具、一个循环。跑通之后你自然就理解七要素和七个决策点分别在哪儿了。然后再去看框架你会发现框架帮你解决的正是你刚踩过的那些坑理解会深很多。学习顺序我推荐LLM 基础调用 → 工具调用 → 循环控制 → 记忆管理 → 规划策略 → 容错观测。每一步都动手写代码不要只看文章。6.2 架构选择没有银弹只有取舍Agent 架构没有“最好”的只有“最适合当前场景”的。简单任务用简单架构别过度设计。我见过有人给一个“查天气”的 Agent 上了多 Agent 协作架构纯属杀鸡用牛刀维护成本还高。判断标准很简单如果单 Agent 几个工具能解决就别上多 Agent。多 Agent 适合任务可以清晰拆分、且子任务之间需要独立上下文的场景。否则多 Agent 带来的通信开销和调试难度往往超过它带来的收益。6.3 关于安全工具权限要最小化Agent 能调工具就意味着它能产生实际影响。所以工具权限必须最小化能查的别给写权限能改一条的别给批量权限高风险操作必须加人工确认。我见过一个 Agent 因为工具权限过大在测试环境误删了数据。生产环境如果这样就是事故。具体做法给工具分等级只读、写入、高危高危工具调用前强制走确认流程。确认可以是人工也可以是二次模型校验。总之不能让 Agent 无约束地执行高危操作。6.4 最后分享一个实用技巧用“回放”来调试Agent 调试最有效的方法不是看日志而是回放。把一次失败任务的完整 trace 拿出来逐步看模型每轮的输入输出你很快就能定位是哪一步出了问题。我现在的习惯是每个 Agent 任务都存 trace出问题直接回放比反复加日志高效得多。回放还能帮你发现“隐性 bug”。比如模型某轮其实已经拿到了正确信息但下一轮因为上下文组织问题又丢了。这种问题不看回放根本发现不了。这个内容后续还可以这样扩展把七个决策点做成一个配置化的 Agent 模板每个决策点对应一组可选策略这样搭新 Agent 时直接选配置就行不用每次从头写。我现在正在往这个方向整理等成型了再单独写一篇。
返回列表