
上周部署的一个Agent任务跑了一个多小时从市场资料搜集、竞品对比、初稿撰写到最后的报告输出中间调了十几次工具、发生了两次重试、触发了一次上下文压缩。它在最后五分钟通过校验的时候我在监控面板上看着它中间三次进入待定状态心里一直在打鼓。这个经历让我重新想明白一件事当AI Agent从单次回答走向长任务执行工程工作到底发生在哪里这个问题答案比大多数人想象的更系统。它不只是一个Prompt写得好不好的问题也不只是把模型调用包一层的问题。它涉及状态、编排、记忆、并发、可观测性——是一整套后端系统的复杂度只是这些复杂度以Agent的名义被重新组合了一遍。本文写给正在把AI Agent应用从Demo推向生产的工程师也写给准备搭建Agent平台的产品和技术负责人。我会从工程落地的角度拆解长任务Agent的关键设计取舍结合我实际操作过的框架和踩过的坑把工程到底发生在哪这件事讲透。1. 从单轮问答走到长任务三个被打破的工程假设1.1 单轮问答的工程本质单轮问答比如帮我解释一下什么是RAG本质上就是一个无状态函数调用输入prompt输出completion。工程上你只需要关心三件事把Prompt组织好、调用模型API、解析响应。这跟一次普通HTTP请求没有本质区别。很多团队在最开始做Agent Demo时也是这么干的FastAPI起一个路由里面调模型把结果返回给前端。这个阶段根本不需要Agent工程——你处理的问题跟任何一个LLM应用都一模一样。有意思的是几乎所有Agent踩坑的开端都是因为我觉得它应该能处理更长的任务这句话。单轮问答里工程角色的比重小到什么程度呢你甚至可以不开日志中间件不设超时策略不做状态管理因为一次调用最多几秒钟出错了重来一次就行。可一旦任务开始持续几分钟、几十分钟这些因为简单而可以省略的东西全部会变成必须正面处理的工程问题。1.2 长任务打破的三个假设第一个被打破的假设时间尺度。单轮问答是秒级的长任务是分钟级甚至小时级的。秒级任务里HTTP超时、连接池耗尽、浏览器页面刷新、云函数执行时长上限这些问题都不存在分钟级任务里它们全部变成需要认真对待的硬约束。你的API网关默认超时可能只有30秒前端轮询可能隔一分钟就断后台Worker的可用区还可能因为发布被重启。第二个被打破的假设状态连续性。单轮问答不需要关心上一步是什么长任务却要求第20步始终知道第3步的结果。可LLM本身是无状态的——每一次调用都是独立请求不保留任何历史。这意味着记忆必须由外部系统承担。谁来写、谁来读、怎么更新在老后端眼里就是一个典型的多进程、多实例状态管理问题只是换了一层AI的外衣。第三个被打破的假设容错门槛。单轮问答失败了大不了重问一次成本也就一次模型调用。但长任务如果做到第15步挂了从头再来意味着前面几十次模型调用和工具调用全部作废。所以你必须设计checkpoint、恢复、重试策略平台的可靠性能力将成为核心竞争力而不是锦上添花。1.3 思维方式的转变从调用链到任务系统单轮问答的工程是调用链工程请求进来、响应出去很简单。长任务的工程是任务系统工程任务需要实例化、状态需要持久化、执行需要调度。说白了你不再是在写一个函数而是在写一个后台任务系统。这个转变是整个Agent工程的分水岭。很多Agent项目做不大不是模型能力不够而是工程上根本没为任务要跑很久、状态要一直存在做好准备。项目死在Demo阶段原因往往不是模型不够聪明而是产品上线后那个任务跑到第10分钟开始出现各种基础设施层面的怪问题。理解了这一点接下来所有讨论就顺了。长任务Agent的工程工作主要发生在五个地方状态管理、任务编排、上下文与记忆、异步与可靠性、可观测性。下面逐一展开。2. 状态管理长任务最容易翻车的地方2.1 状态里到底存什么长任务执行过程中需要维护的状态远比想象中多我习惯分类来看对话上下文用户与Agent的历史消息、Agent每一步的推理过程。任务进度当前执行到哪个节点、哪些步骤已完成、哪些还在pending。工具中间态某个API返回的原始数据、临时文件、中间计算结果。外部句柄调用第三方服务时创建的会话ID、临时Token、上传任务ID。这些看似零散的东西本质上就是任务能恢复和继续的依据。如果没有它们任务中断后只能从头再来。很多人在设计Agent时只关注Prompt怎么写完全忽略状态模型结果就是项目一跑起来各个节点之间信息传递靠拼字符串混乱到没法维护。2.2 状态放在哪里存储选型对比按工程复杂度从低到高我梳理了四个主流选择它们之间的取舍很清晰存储方案适用场景主要风险进程内存本地验证、单机调试进程一重启就丢失多副本无法共享Redis短期热状态、分布式锁、任务队列大JSON塞进去会拖垮性能和带宽关系数据库任务元数据、审计日志、精确查询嵌套结构的灵活状态建模费劲事件溯源强审计合规、全量回放理解和实现成本高非必要不引入我比较推荐的核心组合是任务的基础元数据和关键状态字段放关系库热点数据比如当前节点缓存放Redis事件日志单独落一份存储。不要试图用一个存储解决所有问题。Redis不是万能缓存关系库也不是JSON桶每种存储都有它擅长的场景。2.3 Checkpoint断点续跑的关键设计Checkpoint本质上是周期性把任务状态序列化。设计要点在于打点的时机。我的做法是在两类节点打checkpoint一类是每个工具调用的前后因为工具调用通常有副作用是出错概率最高的点另一类是每次LLM调用之前因为模型调用贵、慢、还可能重复执行。在这两类节点保存完整状态恢复时从最近的checkpoint重放能最大限度减少浪费。还有一个细节很容易被忽略checkpoint恢复之后LLM的决策结果可能和崩溃之前不一样。模型本身是不确定的你用同样的prompt再调一次出来的结果可能不同。所以我会额外记录一份决策日志把当时的prompt、模型名、参数、输出全部钉在任务记录里。这样即使重放后结果变了你也有据可查能判断是模型漂移还是状态损坏。2.4 状态设计的三条排坑经验第一状态一定要版本化。长任务跑很久你很可能中途改状态结构。用JSONB字段加版本号解析时按版本做兼容迁移比把schema规范化到死要可靠得多。第二明确状态的所有权。并行分支、父子任务之间谁写哪个key要提前约定不然就会面对大量同一个状态被两个节点同时改写的数据竞争问题。第三只持久化必要内容。很多初写Agent的人喜欢把LLM的完整输出原封不动塞进状态任务跑几十步后状态体积爆炸。我的习惯是状态里存结构化摘要原始完整输出放到对象存储或日志里按需取用。3. 任务编排为什么线性Prompt链在长任务里撑不住3.1 线性链的死穴条件、循环与并行一开始大家都习惯把任务拆成先做A再做B再做C的线性链。这个思路在两三步的小任务里完全够用。但真实的长任务充满控制流条件分支搜索结果不充分要不要换数据源再查一次。循环写完代码跑测试测试失败要改代码再测这个动作往往要重复好几轮。并行同时查三份资料再汇总比串行省一半时间。补救分支某个工具调用失败是等重试还是换一条路径。线性链没有能力表达这些控制流。你当然可以让LLM看着前面的历史自己决定但这等于把所有工程风险都押在概率上。哪怕模型每一步有99%的概率做对几十步跑下来整体成功率也会以指数级速度衰减。这不是你的模型写得不够好这是概率的基本规律。3.2 从DAG到状态机图编排的核心思想现在行业里主流的做法——不管是LangGraph、Temporal还是自研引擎——本质都是把任务建模成一张有向图节点是执行单元LLM调用、工具调用、逻辑判断边是依赖关系。执行引擎沿着图走每到一个节点就执行对应函数根据返回结果决定下一步去哪。与线性链最大的不同是控制流被显式声明了。这意味着两件事取消和重试可以精确到节点级别执行过程可以在任意节点停下、查看、恢复。这两点在单轮问答时代是不可想象的。我看LangGraph的StateGraph时最大的感受是它本质上就是一个有状态的状态机state在图里流转节点函数读取并更新state。理解这个本质之后用什么框架都不慌了甚至自己写一个也不难。3.3 节点状态机每个节点都不是一锤子买卖工程上一个容易忽略的细节是每个节点在执行过程中本身也是有状态的。我习惯给节点定义几个状态pending还没轮到、running正在执行、success正常完成、failed执行失败、retrying失败后等待重试、canceled被取消。节点状态的转移逻辑是核心工程点。LLM类节点失败多数情况直接重试即可工具类节点失败要看是网络层失败还是业务层失败——网络层可以重试业务层比如权限错误重试一万次也没用直接failed并走fallback路径。这是我强烈建议自己手写一遍状态机的原因你能在真实失败中体会到该在哪个位置做判断。3.4 并行分支快了三倍也难了三倍并行是长任务提速最直接的手段比如同时搜多个数据源。但它引入的麻烦是状态合并——多个分支都往同一个state里写数据不控制就会互相覆盖。我的经验是每个并行分支只操作自己名字空间下的state key例如research_source_a、research_source_b等所有分支结束后由归约节点把各自的输出合并进主状态。合并规则要提前定义比如追加到列表、取并集、还是覆盖。不要指望模型去理解怎么合并这是纯工程问题就该用代码保证。4. 上下文与记忆长任务执行的分寸拿捏4.1 上下文窗口物理边界还是工程边界128k的上下文看起来很大但长任务跑十几个步骤之后把每轮对白、工具返回、中间结果都堆进去也撑不了多久。而且更长上下文带来两个实际问题token成本按量涨、模型的有效注意力会下降——业内常说的lost in the middle问题。所以工程目标不是尽量塞进去而是刚好够用地把最相关的信息放进prompt。这个思路会引导出一套记忆的分层体系。4.2 记忆分层工作记忆、情景记忆、语义记忆我把长任务涉及的记忆分成三层工作记忆当前步骤需要的那一小段信息。比如我正在生成第三章节的初稿类似函数栈上的局部变量。情景记忆整个任务到目前为止发生了什么以摘要或关键决策日志保存类似会话历史。语义记忆长期稳定的知识比如用户偏好、领域知识、历史项目资料类似知识库。每次调用LLM前我会从三层分别取样拼装出一个上下文包。这个拼装过程就是长任务Agent工程里最费心思的部分——既不能把模型饿死也不能让它被无关信息淹没。4.3 摘要、向量检索与结构化记忆的组合三种常用手段各有分工滚动摘要Summarization每执行N步就把历史压缩成一段摘要旧对话内容不再进prompt。这是成本最低、最常用的手段。向量检索Vector Retrieval把已完成步骤的结果向量化后续需要细节时按语义检索top-k片段放回上下文适合过去某个具体数字或结论这种场景。结构化记忆Structured Memory把任务进度、决策点、工具输出整理成固定字段直接放在state里供代码读取也拼一小段关键字段进prompt。我的推荐组合是summary字段加少量检索片段再加上当前步骤所需的结构化字段。每次拼装前先按token预算估算。举个例子假设当前预算上限是8000 tokensummary占2000检索片段占1000工具上下文占500留给模型思考的也就5000多。这个预算就是每次调用前要心里有数的数。4.4 记忆污染长任务最容易犯的错这是我在生产环境观察到的最普遍问题Agent把过时的、不相关的信息一直留在上下文里导致后续决策被污染。一个经典场景用户中途改了需求Agent把旧需求的分析结论继续带进新步骤越跑越偏。解决办法是在每个关键节点做一个信息清理动作——标记哪些信息已过期、哪些是临时中间量、哪些需要归档。类似你在写代码时管理变量生命周期而不是把所有变量统统放在全局作用域里。做过大型后端系统的人对这个模式应该很熟悉它只是换了个名字出现在Agent里。5. 到这一步才算生产异步、并发与可观测性5.1 超过1分钟的任务先解决等待问题从单轮问答到长任务第一个物理障碍是等待。同步HTTP请求最多撑几十秒浏览器用户等不了云函数也有执行时长上限。所以生产环境的长任务基本上必然走向异步任务架构用户提交任务后立即拿到task_id后台用Worker进程慢慢跑前端轮询或通过SSE推送进度。实际流程就是用户POST /tasks主进程把任务写入队列立即返回task_idWorker从队列拉任务创建任务状态开始执行图前端通过GET /tasks/{id}轮询进度。整个过程中没有任何环节需要保持HTTP长连接。我通常用FastAPI接一个任务队列Redis Stream、Celery或云平台的任务服务Worker里做长任务执行。这个架构思路跟传统的异步任务系统没有区别。不少Agent项目迟迟上不了生产卡的就是这一步——大家都在研究怎么设计Prompt没人处理任务提交后谁去执行它这个最基础的问题。5.2 并发控制多用户、多任务与LLM限流并发有两层含义一是平台上有多个用户同时提交任务二是单个任务内部有多个并行分支。两层都要管。用户隔离是第一原则。任务是最基本的隔离单元状态表、日志表、缓存key都必须带task_id和user_id前缀不能混用。其次是任务级限流LLM服务商有QPS限制和每秒token限制并发任务太多会触发429。需要在任务队列之上再加一层信号量或令牌桶控制同时进行中的LLM调用总数。关于并发参数的调整我自己走过的路径是先用压测脚本测出单任务内的并发上限和单次LLM调用的平均耗时再反推平台同时执行的合理任务数和单任务并行度。比如你的LLM QPS上限是10单任务串行执行大概需要20次调用任务提交高峰是每秒2个那你需要把同时执行的任务数限制在20个以内否则必触发限流。这个数字不要拍脑袋定一定要有压测数据撑着。5.3 重试与幂等不确定性是怎么被兜住的LLM调用天然不稳定可能超时、可能限流、可能返回JSON解析不了、可能输出一段莫名其妙的文本。所以重试是必须的但必须讲究策略。超时和限流用指数退避加随机抖动重试通常两三轮就能过去。格式错误可以重试一次但重试时最好在prompt里附上上次解析失败请严格按模板输出这类提示。重头戏是幂等有副作用的工具调用绝对不能盲目重试。发消息、扣库存、写数据库这类操作一旦重复执行会造成严重事故。正确做法是为每个外部操作生成一个幂等键idempotency key在任务状态里记录已成功执行的操作列表重试前先检查这个操作是否已经成功过。这是我见过最多的事故源头为了对抗不确定性做了重试结果把不确定性变成了重复执行。工程上的原则应当是尽量让失败可恢复但永远不要让同一操作被执行两次。5.4 可观测性长任务没有Trace就是摸黑开车单轮问答看日志就够了长任务必须上Tracing。我的埋点方案是这样的trace_id直接等于task_id把整条任务链路串起来每个LLM调用是一个span记录模型名、输入输出token数、温度、耗时、成本估算每个工具调用是一个span记录请求参数、响应状态码、耗时、重试次数每个节点状态转移都打结构化日志便于复盘这个节点为什么会走到failed分支。配合结构化日志每条日志都带task_id可以一键grep和节点状态面板任何一次运行都能被完整回放。我在生产环境排查超时问题、理解模型异常行为时几乎没有一次不是靠这套Trace定位到具体节点和具体参数的。没有Trace的长任务Agent出了问题只能靠猜根本没法救。6. 从Demo到生产我的工程检查清单6.1 先用最小引擎跑通逻辑再看框架如果你正在研究LangGraph、CrewAI或者准备自研引擎我建议先自己写一遍最小执行循环def run_workflow(graph, state): node graph.entry while node ! END: # 执行当前节点函数传入共享状态 result graph.nodes[node](state) # 根据节点返回决定下一个节点 node result.get(next, END) return state这段不到十行的小循环能帮你彻底理解状态流转节点函数拿到state返回增量更新引擎根据返回值决定走哪条边。等你理解了本质再去看任何框架的文档都会轻松很多因为你已经知道它在底层干什么了。6.2 我至今还记着的三个事故事故一上下文塞爆。早期版本把每轮完整记录都往state里堆任务跑到第30步左右就开始变慢、变贵最后模型输出质量急剧下降。后来改成summary加检索的组合问题解决。事故二重试导致重复发消息。某个Agent在调用通知服务时超时重试逻辑直接把同一个通知发了两遍用户收到了两条一模一样的消息。从那以后所有带副作用的调用都加了幂等检查和人工确认环节。事故三checkpoint存了不完整状态。某次改状态结构时漏了迁移逻辑恢复任务后节点函数读取到空字段任务在恢复后三分钟内崩溃。从那以后每次变更状态结构都会同时更新checkpoint的版本号与迁移脚本并且把恢复路径放进自动化回归测试。这三个事故本质上都不是模型不够聪明而是工程系统没兜住。Agent应用上线后出问题90%以上是这类工程问题不是模型推理错误。6.3 一条从简单到复杂的落地路径我建议按四个阶段演进每个阶段都要有明确的验收标准阶段一单机验证。脚本里写死状态、顺序跑完整个图验证业务逻辑成立。验收点业务流程跑通结果符合预期。阶段二状态外置。把状态迁到Redis和数据库实现checkpoint与断点恢复。验收点中途杀掉进程重启后能续跑。阶段三异步与并发。引入任务队列和Worker实现任务提交、进度查询、并发控制与限流。验收点多用户同时提交平台稳定不崩。阶段四生产加固。补可观测性、审计日志、卡控节点比如涉及外部写操作前必须人工确认、权限隔离。验收点线上运行一周无未兜住的事故。多数团队死在第二阶段和第三阶段之间。很多人觉得Demo已经验证过了生产环境自然而然也能跑——这是最大的幻觉。长任务Agent的生产化本质上就是把这四个阶段全部补满的过程。我把一个检索报告Agent从Demo推进到生产的过程中改了三个版本最深的感觉是Agent的智能在模型里但Agent的可靠在工程里。模型的推理能力决定任务的天花板而状态、编排、记忆、并发、可观测性这些工程细节决定了你实际能飞多高。希望这篇能把工程工作到底发生在哪里这个问题说透给大家在架构选型和排障时一点实在的参考。