2026/10/7 18:36:41

AI Agent工程实现精要:七要素与七个关键决策点

AI Agent工程实现精要:七要素与七个关键决策点 先交代一下背景。AI Agent 这个名词最近火得不行几乎每场技术分享都会有人问“你们到底怎么落地 Agent 的”。问多了我发现一个现象很多人把 Agent 等同于“用大模型调工具”搭个 demo 能跑就以为完事了结果一放到真实业务里要么并发一高就超时要么回答开始胡说八道要么上下文一长直接崩。这背后的差距不在某个单一模型够不够强而在工程实现上有没有想清楚两个层面的东西Agent 的组成要素以及做选择时的决策点。我自己的体会是搞懂这两块比追某一家新框架重要得多。不管你是用 LangGraph、MetaGPT、Rust 还是 Spring AI 去写底层要解决的问题都差不多。这篇文章我就按“七要素 七个决策点”这个框架结合我自己做过的几个项目和踩过的坑尽量把 Agent 工程实现里那些绕不开的事讲清楚。适合正在搭建 Agent 的人看也适合刚入门想搞懂“Agent 到底在工程上多麻烦”的朋友。1. Agent 的七要素拆开看每个都不神秘先说七要素。我见过很多文章把 Agent 定义成“LLM 工具”这个说法没错但太粗了。真正工程化的时候你至少需要拆成七个可设计、可度量、可迭代的组件模型Model、指令Instruction、规划Planning、记忆Memory、工具Tools、执行Execution、反馈Feedback。这七个不是并列的“零件”而是一条链路上的不同环节少了任何一个都会在某个场景里出问题。1.1 为什么是这七个而不是三个或十个一开始我也试图用“模型、工具、记忆”三件套去解释后来发现根本解释不了“为什么有时候 Agent 会钻牛角尖”或者“为什么换一个任务就拉胯”。三件套是静态视角而 Agent 的运行是一个动态循环它每执行一步都要重新读一遍当前状态然后决定下一步做什么。这个循环里模型负责推理指令负责划定行为边界规划负责拆解路径记忆负责保留上下文工具负责与外部世界交互执行负责真实调用动作反馈负责告诉模型“你这一步到底行不行”。缺了反馈Agent 做错了也只能硬着头皮往下走缺了记忆多轮任务一长就是失忆现场。你可以把 Agent 想象成一个踩点上班的实习生模型是大脑和嘴能思考能说话指令是公司规章制度规划是他出门前看地图的步骤记忆是他口袋里的小本子记着刚才走到哪了工具是手机里的打车软件和门禁卡执行是他真正迈开腿走路反馈则是他到了路口发现走错方向立刻调整路线。任何一个环节短路上班迟到都是迟早的事。1.2 各要素的责任边界别指望大模型替你兜底这是我最想强调的一点。很多人觉得自己用了个很大的模型所以指令写得随意一点没关系规划不用太细记忆直接用历史消息拼进去就行。这种思路在 demo 里能糊弄过去可一旦任务量大、请求并发高模型推理速度会变慢上下文会被撑爆错误会累积。工程上正确的做法是把这个七要素的责任边界划清楚。模型只负责“推理和生成”不负责记住所有历史指令负责定义“不能做什么”和“以什么风格输出”不负责教模型每一步怎么做规划负责把大目标拆成可执行的小步骤但要给模型留出动态调整的空间记忆负责存储和检索短期记忆用窗口长期记忆用向量库工具负责封装外部能力每个工具要有清晰的“能力描述”和“参数格式”执行负责调用工具、处理返回值、捕获异常它应该像中间件一样稳定反馈负责把外部世界的结果“翻译”回模型能理解的语言包括成功、失败、超时、权限不足等各种情况。我在做一个自动化运维 Agent 的时候最初让模型直接通过工具去调服务器的重启接口结果模型在一条指令里同时尝试了三个重启动作如果没有执行层做熔断线上就出事故了。后来我在执行层加了规则同一类工具在 30 秒内最多调用一次超出则等待确认。这就是把执行从模型里独立出来的价值。1.3 七要素之间的典型协作时序用一个最常见的“让 Agent 帮我查数据并生成报告”的案例感受一下协作流程用户输入目标“查一下上周所有接口的异常率并生成一份改进建议。”指令层先把目标规范成内部任务描述附加安全约束不要访问生产库不要修改数据。规划层将任务拆成“查询数据”“统计异常率”“分析可能原因”“生成报告”四个子任务并按依赖关系排序。记忆层从短期记忆里恢复刚才对话里提到的“上周”是指哪几天从长期记忆里检索之前类似的报告模板。模型根据子任务描述选择工具调用 get_metrics(接口名, 时间范围)并生成合法的 JSON 参数。执行层校验参数、调用工具、拿到返回结果把结果包装成“结果片段”写回记忆。反馈层检查结果片段是否有异常比如接口超时、数据为空如果异常触发重试或重新规划如果正常将结果交给模型生成报告。最终输出前模型整体检查一遍报告确认没有遗漏关键指标再返回给用户。这整个过程看起来不复杂但每一步都有至少一个工程上的坑。下面我从“决策点”的角度讲哪些地方最容易让人纠结。2. 七个决策点选型之前先问自己这七组问题这是文章的核心。七要素是“有什么”七个决策点是“怎么选”。我总结了七个岔路口新手最容易在这里走弯路。2.1 决策点一任务拆多大粒度拆错了就是灾难规划的第一个决策点是任务的粒度。你让模型把整个任务一口气做完还是拆成几个子任务分别做拆得太粗模型容易遗漏细节上下文也容易超过窗口拆得太细子任务之间流转开销大模型被规则束缚得住反而没有灵活性。我自己的建议是“两步走”拆法先让模型对目标任务做一次轻量分析输出候选的子任务列表每个子任务必须对应一个明确的产出物然后由程序侧对这些子任务做合法性校验比如是否重复、是否依赖序正确再交给执行循环。合法校验这一步一定要放在程序里不能全交给模型。因为模型生成的子任务列表可能高达五六项但其中一半根本没法调用工具硬跑就是浪费 token。举个例子我们做合同审查 Agent 时用户上传一份合同目标子任务可能是“抽取关键条款”“核对赔偿金额”“与公司政策对比”“输出风险提示”。其中“核对赔偿金额”这个子任务需要一个专门的工具如果模型没有找到这个工具它能蒙混过关说“我无法核对”而不是明确告诉用户“缺少核对工具”。所以我们在规划层做了工具能力映射每个子任务都要对应一个存在的工具配对不上就直接报错。这件事看起来是“限制”其实是对模型的一种保护。2.2 决策点二选哪个模型上下文窗口是不是越大越好模型选择这个决策点我经常被问“是不是直接用最大上下文的旗舰模型就完了”。还真不是。上下文窗口大只代表你“能装”多少 token不代表模型在长上下文里注意力还能保持稳定。我实测过同一个问题在不同上下文长度下的表现3k token 内正确率 95%到了 50k token 可能降 10 个百分点。所以关键不是追求窗口大而是控制“有效输入”。工程上比较稳的做法是分层默认任务用中档模型速度快成本低需要复杂推理的时候再把输入提取成一个精炼的“思考包”发给旗舰模型。所谓思考包就是把检索出来的关键信息、本轮任务目标、历史决策结果压缩成几百字的摘要而不是把三万字日志全丢进去。还有一个细节模型的 temperature 设置。Agent 规划阶段我建议温度调到 0.2 以下保证决策稳定而生成最终回答的阶段可以适当调高到 0.7 左右让输出更自然。同一个模型在不同的循环阶段用不同的采样参数这是我早期项目里完全没注意到的后来发现对结果稳定性影响非常大。2.3 决策点三用 ReAct 还是 Plan-and-Execute别被概念绕晕规划策略是另一个经典决策点。ReAct 是“边思考边行动”每一步都基于事实进行推理然后调用工具然后把观察到的结果融入思考。它的优点是非常灵活特别适合工具结果不可预料的场景缺点是多轮迭代可能很慢token 消耗大。Plan-and-Execute 则是先制定一个全局计划然后按计划执行中途不重新规划速度快但遇到计划外情况容易翻车。其实这两种不是对立的我见过的最稳的架构是“两层混合”先用 Plan-and-Execute 做一次粗粒度规划得到子任务列表每个子任务内部执行时再用 ReAct 做细粒度行动。外层保证方向内层保证应变。你自己写代码的时候不需要发明新架构直接用 LangGraph 这类图编排框架把规划和执行定义成不同类型的节点在节点里分别设定 prompt 和工具权限就行。这里我要多啰嗦一句无论选哪种策略都要给 Agent 设定“最大步数”。比如最多执行 15 次工具调用超过就停止并输出部分结果。别问为什么等你遇到一个任务里模型反复调用同一个工具超过几十次的场景就明白了。没有最大步数限制就是给失控开了一扇门。2.4 决策点四记忆怎么设计别一股脑都塞进上下文记忆是七要素里最容易出问题的因为它的方案太多了message history 直接拼接、滑动窗口、向量入库、摘要记忆、实体记忆……我自己的经验是“混合记忆架构”。短期记忆用滑动窗口保留最近 N 轮的对话原始内容。N 一般取 58 轮超过就把更早的内容压缩成摘要。压缩摘要这个动作可以单独丢给一个小模型去做也可以用简单的规则概括。不要直接删否则用户中途问“你刚才说那个数据是多少”就容易答不上来。长期记忆用向量数据库我常用 Qdrant 或 Milvus存语义记忆检索时用相关性阈值过滤比如 score 0.7 就不启用那段记忆。这个阈值很关键设得太低会引入噪声设得太高又会漏掉关键信息。你可以拿几个真实查询去离线测一测选一个平衡点。一定要记住记忆写回的时机很重要。我做过一个客服 Agent一开始是每轮对话都重新向量化所有历史记录结果用户每发一句话检索耗时从 300ms 涨到 1.2s。后来改成“只有任务状态发生变化时才写长期记忆”耗时又降回去了。记忆不是越快越好而是越少越好——这句话你体会一下。2.5 决策点五工具层怎么设计参数定义比实现更重要工具层是 Agent 是否“能干活”的关键。很多人觉得工具层就是给模型提供几个函数其实工程上对工具的定义要求非常高。每一个工具都要有清晰的名称、描述、参数列表、返回值结构以及错误抛出方式。我在团队的规范里明确要求工具描述必须写清“什么时候用、什么时候不用、输出格式是什么、会被谁调用”。比如一个查询天气的工具描述不能只写“查询天气”要写“当用户询问某个城市未来3天的天气时使用。参数 city 必须使用城市标准名称。返回值为 JSON包含温度、降水概率、风力等级。” 这样模型才知道该传什么参数以及结果怎么解读。参数格式我建议统一用 JSON Schema 做强约束并在工具调用前做参数校验。模型经常会把枚举值写错比如工具要求 status 是active或inactive模型可能传enabled这时候与其让工具去兼容不如直接让执行层把校验失败反馈给模型让它重新生成参数。这个“重试 反馈”闭环是工具调用的稳定基石。另外工具注册的顺序也有影响。模型在选择工具时往往会偏向排在最前面的几个。如果你有一堆工具可以把最常用、最核心的放到前面不常用的放后面。这个细节是我在一个几十个工具的项目里发现的后来调整了工具列表顺序决策准确率明显提升。2.6 决策点六并发和状态管理不用 Rust 也能扛住并发搜索热词里“ai agent 怎么扛并发”是个高频问题。很多人的认知是“并发 高并发编程”要用 Rust 或异步框架。其实 Agent 场景下的并发主要不是计算密集并发而是 IO 密集的并发——你在等待模型响应、等待工具响应、等待数据库响应。所以方案关键是“异步化 连接池 限流”。我用 Python 的 FastAPI 直接写 Agent 服务配合 LangGraph 跑编排一样能扛住每秒几十个会话请求。只要做到三件事Agent 循环的每个 IO 操作都用 async/await不要用同步 requests外部 API 请求用连接池比如 OpenAI 的 AsyncClient 自带头连接池每个用户会话有独立的运行实例状态用 Redis 保存避免全局共享。还要为非阻塞留足余地不要在一个 HTTP 请求里等整个 Agent 跑完再返回。要么用流式返回SSE 实时吐字要么用任务队列比如 Celery把 Agent 运行放到后台前端轮询结果。这两点的选择直接影响用户体验。我的建议是对交互型应用一定要上流式因为 Agent 多轮工具调用可能耗时十几秒用户看着转圈会崩溃对批量任务型应用用队列更稳定。我顺便提一下“ai agent token 是什么意思”这个热搜词。简单说token 是 Agent 计费和上下文长度的最小单位。你写 Prompt、指令、记忆、工具描述、模型输出全都要消耗 token。工程上一定要有 token 预算控制比如设计一个“当前回合最多消耗 50k token超出就压缩记忆”的机制否则成本会迅速失控。2.7 决策点七可观测性和质量闭环别等到用户投诉才发现问题最后一个决策点也是 Agent 工程和普通后端工程最大的不同你无法预料 Agent 每一步会做什么。这就要求必须有可观测性。我每次做 Agent 项目第一件事不是写业务代码而是先把日志打全。每个节点的输入输出、每次工具调用的参数和结果、每个模型的响应时间、token 消耗全部落库。把这些数据放进一个看板里你能很快发现“是不是规划层经常拆出无效子任务”“是不是某个工具频繁报错”。质量闭环还意味着要有评测集和回归测试。拿一组典型任务在每次改动后自动跑一遍看看 Agent 的输出有没有变差。我们内部叫“黄金测试集”大概 50 条业务问题一条一条跑然后人工看命中率。没有这个机制你根本不知道调了一次 prompt 之后整体效果是提升了还是劣化了。3. 从零搭一个最小可运行的 Agent 工程讲了这么多决策给一个可参考的骨架。我用的是 Python FastAPI LangGraph这是目前最主流也最容易上手的组合。如果你想用 Rust 或者 Spring AI 也没问题架构思路是一致的只是框架选择不同。3.1 核心依赖和项目结构fastapi0.115.0 uvicorn[standard] langgraph0.2.0 langchain-openai0.2.0 pydantic2.8.0 redis5.0.0 qdrant-client1.10.0目录结构就四块agent/放核心逻辑tools/放工具注册memory/放记忆封装api/放 FastAPI 路由。agent-project/ ├── agent/ │ ├── graph.py │ ├── planner.py │ ├── executor.py │ └── feedback.py ├── tools/ │ ├── registry.py │ └── weather.py ├── memory/ │ ├── short_term.py │ └── vector_memory.py ├── api/ │ └── chat.py └── main.py3.2 定义状态图和节点LangGraph 的核心思路是先定义状态再定义节点和边。状态就是整个 Agent 循环中传递的数据对象。from typing import TypedDict, List class AgentState(TypedDict): messages: List[dict] # 对话历史 task_plan: List[str] # 子任务列表 current_tool_calls: List[dict] tool_results: List[dict] step_count: int final_answer: str节点我一般定义四个plan、act、observe、finish。plan负责拆解任务act负责选择并调用工具observe接收工具结果并更新状态finish生成最终答案。用 LangGraph 把它们连成有条件跳转的图。from langgraph.graph import StateGraph, END def plan_node(state: AgentState) - AgentState: # 调用模型的 planning 能力生成 task_plan # 这里省略具体的 prompt 组装 return {task_plan: [subtask1, subtask2]} def act_node(state: AgentState) - AgentState: # 根据当前子任务从工具注册表里选择工具生成参数 # 返回 current_tool_calls return {current_tool_calls: [...]} def observe_node(state: AgentState) - AgentState: # 执行工具调用将结果写入 tool_results并检查是否需重试 # 若执行失败且步数未超限可以回到 act_node return {tool_results: [...]} def finish_node(state: AgentState) - AgentState: # 汇总所有结果生成 final_answer return {final_answer: ...} graph StateGraph(AgentState) graph.add_node(plan, plan_node) graph.add_node(act, act_node) graph.add_node(observe, observe_node) graph.add_node(finish, finish_node) graph.set_entry_point(plan) graph.add_edge(plan, act) graph.add_edge(act, observe) graph.add_conditional_edges(observe, lambda state: finish if state[step_count] 10 else act) graph.add_edge(finish, END)上面这个图只展示了最简流程。实际项目里observe到act之间还会插入一个“重试判断”如果工具返回错误则把错误信息塞回current_tool_calls让模型重新生成参数最多重试两次。3.3 工具注册与调用的封装工具层我用一个装饰器做注册表让工具开发者只写函数本体由框架自动生成 JSON Schema。import json from typing import Callable, Dict, Any TOOL_REGISTRY: Dict[str, dict] {} def register_tool(name: str, description: str, parameters: dict): def decorator(func: Callable): TOOL_REGISTRY[name] { name: name, description: description, parameters: parameters, func: func, } return func return decorator register_tool( get_weather, 查询指定城市未来3天的天气参数city必须是城市标准名称, { type: object, properties: { city: {type: string, description: 城市标准名称如北京、上海} }, required: [city] } ) def get_weather(city: str) - dict: # 这里替换为真实的天气 API 调用 return {city: city, forecast: 晴, temperature: 25}执行层调用工具时先用 pydantic 校验模型生成的参数校验不过直接返回错误反馈给模型。注意工具函数外层最好包一层超时控制比如用asyncio.wait_for防止一个工具卡死拖垮整个 Agent。async def call_tool(name: str, args: dict, timeout: float 10.0): tool TOOL_REGISTRY.get(name) if not tool: return {error: ftool {name} not found} try: result await asyncio.wait_for(tool[func](**args), timeouttimeout) return {result: result} except Exception as e: return {error: str(e), retryable: True}3.4 并发场景下的 FastAPI 接口接口层提供两个端点一个用于同步等待完整结果适合内部调用一个用 SSE 流式返回适合对话产品。from fastapi import FastAPI from fastapi.responses import StreamingResponse import asyncio app FastAPI() app.post(/agent/chat) async def chat(request: dict): # 从 Redis 按 session_id 恢复历史状态 # 跑 graph中间将结果写入 Redis result await run_agent(request[session_id], request[message]) return {answer: result[final_answer]} app.post(/agent/chat/stream) async def chat_stream(request: dict): async def event_generator(): async for chunk in run_agent_stream(request[session_id], request[message]): yield fdata: {chunk}\n\n return StreamingResponse(event_generator(), media_typetext/event-stream)并发控制方面我建议对每个 session 加一个简单的异步锁避免同一会话的多个请求同时触发 Agent 循环导致状态错乱。对于全局并发的上限可以在 Uvicorn 层设置--limit-concurrency或者用 Redis 做分布式限流。这里最容易踩的坑是把 Agent 循环设计成共享状态对象然后并发请求互踩。一定要做到“每个会话独立实例状态不落内存全走 Redis”。4. 常见问题与排查技巧实录这块是我经验最多的部分几乎每个 Agent 项目都会碰到下面这几个问题。4.1 高频问题速查表问题现象可能原因排查思路与解决Agent 多次重复调用同一个工具规划节点没看到上次调用的结果检查observe节点是否把工具结果写回了messages确保模型能看到上一次的观察结果生成 JSON 参数经常非法工具参数描述不清晰用 JSON Schema 强约束并在执行层增加json.loads异常捕获失败后把原始字符串反馈给模型重试上下文一长回答开始跑题短期记忆窗口太长噪声太多缩短滑动窗口到 5-8 轮把早期内容压缩为摘要或加向量检索长期记忆工具调用耗时超过 30 秒外部 API 没有设置超时每个工具调用外层包asyncio.wait_for设置超时并让工具尽快失败不要无限等待并发一高数据库连接被打满每次请求新建数据库连接改用连接池对 Redis 使用单例连接避免每请求重连模型总是不按指令输出指令里界定不清在系统指令中增加“禁止做什么”列表并使用 few-shot 示例固定输出格式Agent 死循环不断规划没有设置最大步数在状态图中加入 step_count 计数超过阈值强制进入结束节点并输出部分结果4.2 几个实战避坑心得第一个一定要区分“模型幻觉”和“工具故障”。有时候 Agent 告诉你查不到数据我第一反应是工具接口挂了查日志发现工具确实返回了 200数据是空的。后来发现是模型在规划阶段就把查询时间范围搞错了。这种情况靠调整工具描述不够还得在规划层把时间参数拆出来单独做校验规则。规则引擎不是模型的敌人。第二个日志里一定要记录 token 消耗并按会话累计。我之前上线过一个小助手用户聊了 20 多轮每轮都带全量记忆结果一个上午话费飙升。后来加了 token 预算在上下文超过 80% 时强制压缩历史。我的经验是token 预算的触发线设在模型实际最大上下文的 70%因为有些模型会在接近满员时表现明显变差而不是真到 100% 才崩。第三个不要迷信“更大模型更聪明”。有一次我在业务里把默认模型从一个小小的 model 换成了超大参数模型结果测试集上的准确率不升反降。原因是超大模型对用户提问的“发散理解”更强生成预设格式时容易画蛇添足。后来我把超大模型只用在规划节点而执行节点保留原来的中小模型效果立刻回来了。这说明模型选择要和具体任务复杂度挂钩不要一刀切。第四个SSE 流式输出要注意编码和缓冲。在 FastAPI 里用 StreamingResponse 时如果不把headers里的Cache-Control设置为no-cache前端可能等全部结束后才拿到内容。另外流式返回时保持长连接的负载均衡器超时要调大否则中途断开。我用内网部署时愣是因为 Nginx 默认 60 秒超时导致 Agent 长任务流式输出被切断后来把 proxy_read_timeout 调到 300 秒才解决。第五个关于“agent 学习路线”与其收集一堆框架不如先写一个最小循环从单工具到多工具从无记忆到记忆检索一点一点加复杂度。你每加一个组件都要回到七要素里看看它属于哪个环节再到七个决策点里找到对应的注意点。这样你的知识是成体系的而不是碎片化的。5. 做完一个 Agent 后我通常会做的三件事严格来说这也算收尾。每次 Agent 开发告一段落我做的第一件事是拿黄金测试集重新跑一遍回归第二件事是去日志里随机抽 20 个成功会话和 20 个失败会话人工分析失败原因把高频失败模式写进反馈层第三件事是检查工具权限——凡是危险动作比如删除、覆盖、发送消息都要求二次确认这一步是底线。我自己在设计 Agent 的时候越来越觉得工程实现的关键不是“让它更智能”而是“让它更可控”。七要素里每个要素都可以变成控制点七个决策点其实就是七个“在哪里踩刹车、在哪里踩油门”的选择。把这些问题想清楚了后面调 prompt、换模型、加工具都是水到渠成的事。如果你现在正准备从零搭建自己的 Agent我的建议是别急着抄网上那个高大上的架构图先拿纸把你自己的任务填进七要素里再逐个做决策。哪怕只是做一个“自动回复”的小助手走完这遍流程你也会比我当初一遍遍试错快很多。