首页 / 资讯中心 / 文章详情

长任务AI Agent工程实践:从认知架构到系统护栏的关键设计

长任务AI Agent工程实践:从认知架构到系统护栏的关键设计 ★ FEATURED ARTICLE
一次回答是一道题一个长任务是一场施工。这句话是我做了几个 Agent 项目之后最想跟同行分享的体会。模型单次回答的时候工程只需要管住“提示词 返回解析”可一旦要求它连续执行二三十步、跨系统调用工具、在没人盯着的环境下把一件完整的事做成难度曲线几乎是陡增的。我自己跟过的几个落地项目全部卡在了同一个问题上模型本身还够用系统先崩了。这篇文章用实战经验拆一个具体问题——当 AI Agent 从单次回答走向长任务执行工程工作到底发生在哪里适合正在做 Agent 落地、或者打算从零搭一套长任务系统的同学参考也适合那些“模型能帮我干活”的乐观派冷静一下。1. 认知层长任务工程的起点1.1 从“生成答案”到“执行计划”Agent 的思维模式变了单次回答和长任务执行对模型的要求完全不是一个量级。单次回答是“生成”给一个输入模型根据训练数据和上下文生成一段文字工程只需要处理输入输出格式。长任务执行是“决策 行动 复盘”的闭环模型要理解目标拆解成步骤一步步调用工具观察执行结果发现异常还要自己修正路线。跟开车一样单次回答是告诉你“前方第三个路口右转”长任务是让你自己看着导航开完全程中途还得躲坑、绕路、加油。这个区别决定了架构选型。目前主流的长任务 Agent 架构有两种ReAct 模式和 Plan-and-Execute 模式。ReAct 是“想一步做一步”模型每轮先思考下一步该干什么然后执行再根据执行结果继续思考。好处是灵活能应对动态变化坏处是每步都走大模型token 消耗和延迟都很高而且很容易跑偏。Plan-and-Execute 是先让模型生成一份完整计划再按计划逐步执行执行过程中可做局部修正。好处是稳定、可控、成本低坏处是遇到计划外的情况反应慢。我的经验是纯 ReAct 只适合短链路3~5 步任务长任务一定要做“计划 执行”的分离。在实际工程里我会让一个“规划器”模块负责目标拆解输出结构化任务树再让“执行器”模块单独跑每一步每步结束后把结果回填给规划器做增量调整。这本质上就是从“让模型自由发挥”走向“把模型塞进流程里”工程工作从这一步就开始发生了。1.2 上下文与记忆的工程化是长任务的第一道坎模型上下文窗口再大也架不住长任务反复消耗。我自己实测过一个 8 步左右的工具调用任务中间会穿插工具返回结果、模型思考、外部报错信息原始上下文容易膨胀到 3 万 token 以上。如果任务到 20 步不控制上下文后半段模型基本上就在“失忆”状态下工作——它忘记了最初的任务约束忘记了自己已经做过什么甚至会把改造过的数据当原始数据再算一遍。解决方案是给 Agent 做记忆分层。我一般把记忆拆成三层短期记忆当前步骤的输入输出用完就丢、工作记忆任务阶段、已完成清单、关键状态值常驻、长期记忆用户偏好、历史任务结果、领域知识可离线存储。工作记忆是这个分层里最容易被忽略也最关键的。我会强制要求每一步执行完后把“当前状态”写成一个结构化的 JSON 对象保存到数据库或 Redis而不是让模型靠上下文猜。这里有一个非常典型的翻车案例早期我让 Agent 做跨 30 步的数据处理任务没有做工作记忆只靠上下文堆叠。跑到第 15 步的时候模型开始把前面几步处理过的中间文件名当原始文件直接导致后续所有步骤处理错对象。后来我把任务状态显式持久化每一步都从状态对象里读“当前文件名”问题立刻消失了。记住一句话长任务里模型的大脑中不该存状态状态应该放在系统里模型只是状态的读写者。2. 执行层Agent 的“手”比“脑”更考验工程2.1 工具调用不只是拼接接口而是定义契约长任务 Agent 跟外界交互靠的不是模型直接回答而是工具调用。很多人以为工具调用就是给模型配一堆 API让它调就行。真做了才发现工具调用本质上是“模型和系统之间的一场契约谈判”。模型产生的参数要能被系统严格校验系统返回的结果要能被模型稳定理解这中间任何一处模糊都会在长任务里被放大成灾难。我在项目里给每个工具设计一个严格的 schema输入参数必须标注类型、必填项、取值范围返回结果必须结构化错误码必须统一。比如一个查询订单的工具返回结构我会定义为{ code: 0, data: { order_id: ..., status: ..., items: [...] } }。code 为 0 表示成功非 0 表示各种失败类型比如参数错误、订单不存在、服务超时。模型看到错误码就能决定是换个参数重试、还是放弃当前步骤、还是上报异常。契约设计里有三个坑是新手最容易踩的。第一个坑是返回结果太自由让模型自己从自由文本里提取关键信息长任务下错误率奇高一定要结构化返回。第二个坑是参数名用模型不认识的缩写模型对cust_id的理解远不如customer_id工具 schema 的命名要贴近自然语言。第三个坑是没有给模型“不知道”的出口有些查询结果模型无法从工具返回里判断对错工具体系里要设计“信息不足”这一类状态允许模型主动询问人而不是硬猜。2.2 执行环境的隔离与副作用控制长任务执行最危险的是外部副作用。模型每走一步可能真的在改数据库、发消息、转钱、创建云资源。一次生成错了可以重新生成一次外部动作错了代价是真实的。这也是工程工作密度最高的地方。我自己的准则是凡是涉及写操作的工具执行前必须经过“二次确认 幂等保护”。幂等保护是后端工程的常识但放到 Agent 场景就变成硬约束同一个步骤如果因为网络问题执行了两遍系统绝对不能产生两条相同的通知、两笔相同的扣款。做法是在任务启动时生成一个全局 run_id每一步工具调用带一个 step_token执行端用这个 token 做去重重复请求直接返回第一次的结果。环境隔离同样重要。一般情况下不会让 Agent 直接操作生产数据库而是给它一个受限的执行环境子进程沙箱、docker 容器、或者至少一个只读副本。权限上遵循最小化原则——工具需要什么权限就给什么权限。我在一个项目里见过 Agent 因为工具权限过大在排查问题时顺手把整个测试环境的配置表清了原因是“我当时觉得这是多余数据”。没人故意犯错但长任务里模型对上下文的“误判”是会真实触发行为的。你需要在工程上设置一道物理边界而不是指望模型每次都“理性”。另外超时控制一定要加到每一个工具调用上。模型生成可以等几秒外部 API 可不行。我给每个工具设了独立的超时上限——查询类 10 秒、写操作 15 秒、文件处理 30 秒超过上限直接标记该步骤失败进入重试或人工兜底流程绝不让 Agent 在单步上无限等待。3. 可靠层长任务能不能交付取决于失败怎么处理3.1 状态持久化与断点续跑是长任务的地基长任务执行中失败是常态而不是异常。模型会收到错误返回工具会超时外部系统会临时不可用甚至 Agent 进程本身都可能被重启。工程上要解决的核心问题不是“怎么避免失败”而是“失败后怎么恢复”。我的做法是把任务生命周期做成显式状态机。一个客服工单 Agent 的任务状态可以定义为NEW - CLASSIFYING - QUERYING - RESPONDING - REVIEWING - CLOSED。每个状态对应一组可执行的动作Agent 只能从当前状态转移到合法的下一个状态。状态信息实时写入数据库每次执行一步就更新一步。这样即使进程中途崩了重启后从数据库拿回任务状态就能从断点继续而不是从头再来。还有一个很关键的工程决策重试不能一视同仁。要把失败分成临时错误和永久错误。临时错误网络超时、服务 5xx、限流可以重试且有退避策略比如第一次等 2 秒第二次等 10 秒最多重试 3 次。永久错误参数非法、数据不存在、业务规则不满足说明 Agent 的理解或动作本身就错了重试一万次也没用应该直接触发人工介入、或者让 Agent 重新规划方案。我当时给重试逻辑分类的时候第一版就是不加区分地重试 5 次结果参数错误被重试了 5 次白白烧掉几万 token还把用户重复打扰了 5 遍。后来允许 Agent 在遇到永久错误时“重新解释工具返回并修正下一步计划”成功率立刻提了上来。3.2 可观测性长任务必须“看得见”普通接口你打印几行日志就能排查问题长任务 Agent 不行。一次任务涉及十几步、多次工具调用、多轮模型生成任何一步出错你都需要知道“是哪一步出的错、模型当时看到了什么、它为什么决定这么做”。没有可观测性做支撑长任务 Agent 就是一个黑盒子出了事故只能靠猜。我在项目里给每个任务下发一个 trace_id从任务开始贯穿到每一步日志、每一次工具调用、每一轮模型生成。日志不是简单的task started/task completed而是每一步都记录输入快照、模型输出、工具返回截断到可承受的长度、耗时、tokens 消耗、重试次数。这里有一个细节记录模型“当时看到的输入”比记录输出更重要。有一次 Agent 出现幻觉回复内容里提到一个不存在的订单号。我排查时发现工具返回里根本没有这个订单号模型是自己“脑补”出来的。如果日志只存工具返回的最终结果这个幻觉的根源永远查不到。指标也不能少。我会在监控面板上盯五个数字任务平均步数、单步平均耗时、工具调用失败率、任务整体成功率、总 token 消耗。前两个帮你发现 Agent 是不是在“兜圈子”中间两个帮你判断系统健不健康最后一个直接关联成本。每一步都要考虑“值不值”的问题。4. 控制层给 Agent 配好缰绳才算负责任4.1 反馈回路与人工审批节点长任务不等于全程无人值守。真实世界里关键的对外动作必须有人的确认。我在设计长任务系统时会给 Agent 预设几个“checkpoint”给外部客户发消息前、执行扣费或退款前、删除数据前、发布内容前。在这些节点上Agent 把已经做完的事情和即将执行的动作整理成摘要推送给相关人审批审批通过才继续拒绝则回到规划阶段重新调整方案。有人担心这种人工介入是不是拖慢了效率。我的看法是长任务的效率是“不返工”换来的。一个需要人工确认的节点虽然多花 30 秒却避免了模型犯一个可能引发严重问题的错误。类比一下银行境外转账不走人工复核你也不放心。Agent 也一样——权限越大审批越重。反馈回路不只是“人工审”也包括“自动校验”。模型判断结果对错的能力其实不稳定我用两层校验先跑规则引擎检查硬性条件订单号存在、金额在允许范围、必填字段非空再跑一个轻量模型或规则分类器做语义抽查。LLM 当 judge 很好用但局限也很明显——模型会觉得“看起来合理”就放行。硬性规则层是地基模型判断是补充不能反过来。4.2 预算限额与行为护栏长任务的控制底线模型再强也不能让它毫无约束地跑。预算和护栏是每个长任务 Agent 的保命装置。先说预算。很多新手会问 agent token 是什么意思简单说就是模型计费的基本单位一个汉字大约对应 1~2 个 token英文一个词约 1~2 个 token。长任务烧 token 是肉眼可见的一个 20 步的任务光模型生成和工具返回就轻松消耗 5 万 token成本在几十元以上。如果 Agent 陷入循环这个数字会指数级上涨。我在工程上会设置三层预算单步 token 上限、单任务 token 总上限、单账户/单项目日消耗上限。超过阈值就降级——停止模型生成、转入人工处理、或者切换到更小的模型。再说两个行为护栏。第一个是步骤上限。给每个任务设定“最多执行 N 步”的硬限制通常是 15~30 步超过就不再让模型自动决策强制转人工。这个设计是为了防死循环。我遇到过一次真实循环Agent 在“查询订单 - 找不到 - 换个方式查 - 还是找不到 - 再换个方式查”的循环里跑了 20 多轮直到触发步骤上限才停下来。第二个是工具白名单。在长任务中模型能调用的工具集合应该是提前规划好的与场景严格匹配。不是每个 Agent 都能随便调用所有工具白名单本身就是一种权限管理。护栏设计的核心是让 Agent 在“自由行动”和“安全边界”之间保持平衡。这个平衡点每个业务不同但原则一致宁可让 Agent 在边界内憋屈一点也不要让它越过边界闯祸。5. 实操实录一个客服工单 Agent 的工程落地5.1 场景设定与整体架构上面这些环节串起来看会更有体感。我用最近做的一个项目来拆解自动处理客服工单的 Agent。需求是用户提交工单后Agent 自动完成分类、查订单、查退款政策、生成回复、更新工单状态、通知客户全程无人协管只有退款动作需要人工审批。整个任务链路大概 6~8 步属于典型的长任务范畴。整体架构我用的是“入口服务 队列 Worker 外部工具集”。入口服务接收工单创建事件丢进 Redis 队列Worker 进程从队列里拉取任务启动 Agent 循环Agent 循环内部拆成规划器、执行器、校验器三个模块。工单状态存 PostgreSQL每一步的关键数据都落库保证可恢复。外部工具统一走一层工具网关限制访问范围和权限。选这个架构是因为它简单、可控、每一层都能单独排查问题。5.2 核心模块与关键配置要点先看任务状态对象这是整个长任务的“中枢记忆”我用这样的结构保存# 任务状态对象简化版 { task_id: T20240612_001, trace_id: 8f3a9d2c..., status: QUERYING, # NEW/CLASSIFYING/QUERYING/RESPONDING/REVIEWING/CLOSED steps_done: [classify, query_order], completed_step_count: 2, context: { order_id: ORD-789012, customer_id: CUST-5566, ticket_content: ..., refund_policy_hit: True } }注意steps_done是累积列表context是当前任务的关键信息摘要这两个字段让 Agent 在任何时候都能恢复“自己做到哪了”和“关键信息是什么”。每次执行一步就把状态写回数据库。这是断点续跑的物理基础。Agent 主循环非常朴素脚本就长这样while not state.is_terminal(): # 1. 规划器根据当前状态决定下一步 next_action planner.decide(state.summarize()) # 2. 工具网关执行带幂等 token result tool_gateway.execute(next_action, step_tokengen_token()) # 3. 校验器检查工具返回是否合法 valid, error validator.check(result, next_action) if not valid: state.record_failure(error) if should_escalate(error): # 永久错误转人工 state.status REVIEWING notify_human(state) continue # 4. 回填状态进入下一步 state.apply(next_action, result) persist(state)planner.decide才是大模型真正参与的地方而且我传给它的不是全量历史是状态摘要 当前目标。这样 token 消耗和模型“迷惑”的程度都会低很多。工具 schema 严格化也很关键。比如退款查询工具我会写成这样{ name: query_refund_policy, description: 根据订单和退款类型查询适用的退款规定, parameters: { order_id: {type: string, required: true}, refund_type: {type: string, enum: [full, partial, damage], required: true} }, returns: { eligible: {type: boolean}, max_refund_amount: {type: number}, notes: {type: string} } }所有的返回都结构化模型不需要从文本里“猜”退款规定系统也不会被模型的表达带偏。5.3 真实踩坑与排查记录这个项目让我踩了不少坑挑三个有代表性的分享。第一个坑是重复通知。前期没有加 step_token 幂等保护一次网络抖动导致“通知客户”这个动作被执行了两次客户收到了两条一模一样的短信。排查时发现工具网关收到了重复请求返回了第一次的成功结果但外部通知服务已经被真实调用了两次。后来我在工具网关层做了基于 step_token 的去重缓存重复请求一律返回“已执行”才彻底解决。写操作必须幂等这是长任务系统里血量最高的一条教训。第二个坑是模型在工具返回为空时硬编数据。有一个工单的关联订单不存在工具返回data: null结果模型在生成回复时臆造了一个错误的订单号并且编了一句“您的订单 ORD-999 正在处理中”。排查过程我就是靠日志里“模型当时的输入”定位的它的输入工具返回只有 null却生成了具体订单号。最后我给所有工具返回加上“空值语义”说明并在校验器里加了“回复中出现的订单号必须存在于已查询结果中”这样的规则才堵住漏洞。模型会脑补工程必须帮它挡住脑补。第三个坑是状态不落库导致长任务漂移。有一版我把状态存在内存里进程不重启也没事可一旦 Worker 滚动更新或者 OOM所有进行中的任务直接丢失。后来我把所有状态改成了每次执行后写库还做了一个“任务恢复”脚本启动时扫描状态为 “PROCESSING” 且超过 10 分钟未更新的任务统一恢复到 REVIEWING 状态由人工确认。这样即使系统崩了业务也不会出现悬空状态。最后再分享一点体会我现在做 Agent 项目的选择标准很简单一个任务能不能用长任务 Agent 来做先看它失败后的代价有多高。代价低、容错空间大就大胆交给 Agent代价高、牵扯真实资源工程上就必须把审批、幂等、可观测、状态恢复全做扎实。AI Agent 从单次回答走向长任务执行真正的工作量落在了认知层的规划、执行层的契约、可靠层的恢复、控制层的护栏这几个地方。这也是我不断对同行强调的观点长任务系统的天花板其实是工程团队对不确定性的控制能力。Agent 把不确定性带进了系统而工程就是把这些不确定性重新约束回可控的轨道上。
阅读完成 · 觉得有帮助?
咨询建站