大家好,我是你们的老朋友。
最近在社区里看到一个特别高频的问题:为什么 ChatGPT、Claude 这类 AI Agent 能一口气连续执行几十步操作,像是自己写代码、自己运行、自己改错,最后把任务完整交付?而我自己在本地用 LangChain 或 LlamaIndex 搭的 Agent,往往跑几步就断,不是上下文超限,就是工具调用报错,甚至模型生成的下一轮动作直接“失忆”。
这篇文章,我就想把这层窗户纸彻底捅破。
我们会从模型本身的能力边界讲起,再深入到 Agent 的运行时调度、架构设计,最后给你一个最小可运行的完整长任务示例,并附上高频故障排查清单。无论你是刚开始接触 AI Agent 开发,还是已经在生产环境里趟过坑,这篇文章都能帮你建立一张完整的“长任务架构地图”。
1. 先说清楚:Agent、模型、运行时到底分别是什么
很多人把 Agent 和“模型”混为一谈,觉得 Agent 厉害,就是因为背后的大模型聪明。实际上,模型只是 Agent 的“大脑”,而 Agent 是一个完整的“身体系统”。
1.1 三者各自的职责
先说模型,也就是我们常说的 LLM(大语言模型)。它负责“理解”和“生成”:给它一段 Prompt,它返回一段文本。它本身没有记忆,没有循环,没有工具,也没有执行能力,它只是一次次地做文字接龙。
然后是 Agent,它是构建在模型之上的智能体程序。它拥有:目标拆解能力、任务规划能力、工具调用能力、记忆管理能力,以及自我纠错能力。你可以把 Agent 理解为一个“会调用模型来做决策的自动化流程引擎”。
最后是运行时,这是容易被忽略却最关键的一层。运行时负责:调度 Agent 的执行循环、保存和恢复状态、管理上下文窗口、执行工具调用、处理错误和重试。没有运行时,模型再聪明也无法独立跑完一个几十步的任务。
打个比方:模型是发动机,Agent 是驾驶员,运行时是整辆车的底盘、油箱和控制系统。发动机马力再大,没有底盘和控制系统,它也无法完成一次长途旅行。
1.2 长任务的定义
什么是长任务?这里需要给出一个可量化的标准。
一个长任务通常具备以下特征:
- 执行步骤超过 10 步(调用工具、生成中间结果、修改计划都算一步)。
- 需要在执行过程中多次读取或写入外部状态(例如数据库、文件、API)。
- 模型单次推理无法覆盖全部上下文,必须依赖外部记忆进行截断、摘要或检索。
- 过程中存在不确定性,Agent 需要根据中间结果动态调整后续计划。
换句话说,短任务靠“提示词工程”就能实现,长任务必须靠“架构设计”才能稳定落地。
2. 长任务的核心挑战:为什么 Agent 跑不了几步就“断片”
在深入研究架构之前,我们必须先理解长任务到底难在哪里。这有助于理解后面每一层的设计动机。
2.1 上下文窗口的物理限制
大模型有一个固定的上下文窗口(Context Window),例如 32K、128K、200K。这个窗口包含:系统提示词、用户新输入、历史对话、工具返回结果。
以 128K 窗口为例,看起来很大,但如果一个工具返回了一大段 JSON 或日志,几轮对话就能把窗口撑爆。一旦超出窗口,会出现两种结果:一是直接报错“已达到输出 token 上限”,二是早期内容被无形截断(有些 API 会自动丢弃中间内容)。
这就带来一个致命问题:** Agent 执行到第 20 步时,可能已经忘记了第 1 步的任务目标和约束条件。**
2.2 工具调用链路断裂
真实世界的 Agent 不可能只靠模型自己的知识完成所有事。它需要调用搜索引擎、数据库、代码解释器、文件系统、第三方 API。
工具调用链路可能因为以下原因断裂:
- 参数格式错误:模型生成了一个不符合工具规范的结构化参数。
- 权限不足:Agent 尝试访问未授权的资源。
- 外部服务超时:API 响应 30 秒未返回,Agent 等待超时。
- 返回结果与预期不符:模型无法从工具返回的异常数据中恢复。
2.3 状态维护困难
短任务不需要状态管理,模型输入一轮输出一轮即可。但长任务要求 Agent 在执行过程中始终知道:当前处于哪个阶段,已经完成了哪些子任务,还剩下哪些子任务,哪些结果已经被验证过。
如果这些“状态”全部放到 Prompt 里,很快就会超出上下文窗口。如果放到外部存储里,则需要运行时具备读写和同步机制。
2.4 错误恢复能力缺失
一个稳定的长任务系统必须具备“容错机制”:步骤失败后,是重试?是更换工具?是修改 Prompt?还是直接跳过该步骤?
大多数 Agent 框架只提供了最简单的“异常抛出 → 流程终止”,结果就是用户常常看到这样的报错:
agent execution terminated due to error.然后整个任务前功尽弃,所有耗时全部浪费。
3. 模型层:为什么 LLM 本身具备支撑长任务的潜力
我们要理解长任务架构,必须先弄清楚模型层提供了哪些基础能力,让 Agent 有了连续运行的可能性。
3.1 Transformer 架构带来了什么
现在的 LLM 几乎都基于 Transformer 架构。这个架构的核心优势在于:它能对输入序列中的每个 Token 与其他所有 Token 之间建立注意力关系。
注意力机制意味着模型在生成下一步输出时,可以“回看”当前上下文中的关键信息。举个例子:如果你在 Prompt 中写了“第 1 步已完成,第 2 步的任务是 XX”,Transformer 可以在生成第 3 步时依然把注意力放在这句话上,从而保持行为一致性。
这也是为什么 Agent 可以不依赖外部记忆跑完一小段连续操作,因为模型本身具备一定的上下文跟随能力。
3.2 函数调用/工具调用能力
目前主流模型(如 GPT-4、Claude、Qwen 系列等)都支持结构化的函数调用(Function Calling / Tool Use)。也就是说,模型可以输出一个 JSON 格式的调用意图:
{ "name": "search_web", "arguments": { "query": "Python 列表去重方法" } }运行时解析这个 JSON,执行对应的函数,再把结果返回给模型。这个能力是 Agent 能“动手做事情”的基础。
如果没有函数调用能力,模型只能生成一段“假想代码”,无法真正与环境交互。
3.3 ReAct 模式:推理与行动交替
现代 Agent 的工作方式,绝大多数都遵循一种叫 ReAct 的模式:
- Thought(思考):模型分析当前状态,决定下一步做什么。
- Action(行动):模型输出一个工具调用意图。
- Observation(观察):运行时执行工具,返回结果。
- 循环:模型基于观察结果继续思考、行动。
这个循环就是 Agent 能连续执行多步的基础。每一步都会产生新的观察,驱动下一步的推理。模型层解决了“每步应该干什么”的问题,而运行时层解决的是“怎么把这一步步串起来不中断”。
3.4 输出 Token 上限的约束
这里必须说一个很容易踩的坑:即使模型的上下文窗口很大,单次推理的输出 Token 上限往往是单独限制的。
什么意思?假设上下文窗口是 128K,但一次输出最多只能生成 4096 个 Token。如果模型试图在一次回复中输出一份完整的代码、一份长文档、或者许多步工具调用,就会被截断。
很多 Agent 新手遇到这种问题,第一反应是“模型不行”,其实正确做法是:拆分任务,让模型每次只输出一步,而不是一次输出多步。
这也是长任务架构中一个非常核心的设计原则:小步快跑。
4. 运行时层:长任务连续执行的核心引擎
聊完模型,我们进入整篇文章的重头戏——运行时。
4.1 为什么模型能力再强也需要运行时
模型本身是一个“无状态函数”,你调用一次,它返回一次结果。它不会自己维护循环、不会自动重试、不会管理历史记录。
运行时把这些能力补上。可以说,没有运行时设计,模型就只能做单轮交互,无法完成长任务。
一个成熟的长任务运行时,至少包含以下模块:
循环调度器:决定 Agent 当前是否要继续执行、暂停或终止。
状态管理器:保存任务执行的中间状态,包括已完成步骤、当前步骤、剩余步骤、已验证结果。
上下文管理器:控制哪些历史信息保留在 Prompt 中,哪些被摘要,哪些被存入外部存储。
工具执行器:解析模型生成的工具调用请求,执行对应函数,并校验返回结果。
错误处理器:捕获工具抛出的异常,决定是重试、修改参数、换工具,还是终止任务。
我们可以用一个流程图来描述运行时主循环:
+----------------------+ | 任务初始化 | | 加载目标、历史、工具表 | +----------+-----------+ | v +----------------------+ | 调用模型生成下一步 | | (ReAct: 思考+行动) | +----------+-----------+ | v +----------------------+ | 解析模型输出 | | 是否为最终答案? | +----------+-----------+ |否 v +----------------------+ | 执行工具调用 | | 追加观察结果 | +----------+-----------+ | v +----------------------+ | 错误?-> 重试/替换 | | 成功 -> 继续循环 | +----------------------+4.2 上下文管理策略
上下文管理是运行时设计中最难、也最关键的部分。
假设你的 Agent 要执行 30 步任务,每一步的思考、工具调用和结果都放进 Prompt,那么基本上到第 5 步就会超限。运行时通常采用以下策略:
第一种是截断:丢弃最早的历史消息,只保留最近 N 轮对话。缺点是模型可能会丢失初始任务目标。
第二种是摘要:每执行几步,就让模型把之前的历史压缩成一小段摘要,替换掉完整历史。这是比较推荐的做法,代价是增加一次模型调用。
第三种是检索增强:将历史信息写入外部向量数据库,每次执行时只检索相关的记忆片段,再放入 Prompt。
实际上,生产中通常混合使用这三种策略:保留任务目标、压缩过程细节、按需检索长期记忆。
4.3 状态机与任务追踪
长任务系统在运行时内部,通常会构建一个状态机。每个任务可以处于以下状态:
| 状态 | 含义 | 后续动作 |
|---|---|---|
| pending | 等待执行 | 进入运行队列 |
| running | 正在执行 | 持续调度 |
| paused | 暂停(等待人工输入或外部条件) | 恢复或终止 |
| completed | 执行成功 | 输出最终结果 |
| failed | 执行失败 | 触发重试或告警 |
状态机的好处是:即使运行时崩溃,重启后仍然可以从持久化存储中读取任务状态,恢复到崩溃前的步骤,而不是从头开始。这种设计通常叫“断点续跑”。
很多开源 Agent 框架,如 LangGraph、AutoGen、CrewAI 的底层都实现了类似的状态管理机制。
5. 架构层:一套完整的长任务系统由哪些组件构成
当我们把视角拉高,从单个 Agent 上升到一个完整的系统,长任务架构需要覆盖的组件就更多了。
5.1 调度器
调度器是整个长任务系统的“指挥中心”。它负责接收用户请求、创建任务实例、分配运行时资源、监控任务状态。
在微服务架构下,调度器通常是独立部署的服务,通过消息队列下发任务指令,这样可以避免单个任务阻塞整个系统。常见的做法是:用户请求 → API 网关 → 消息队列 → 工作节点消费 → 执行 Agent 循环。
5.2 记忆模块
长任务必然需要持久化记忆。记忆模块分为短期记忆和长期记忆。
短期记忆保存在运行时内存中,通常是当前任务的上下文。
长期记忆保存在外部存储中,可以是 Redis、关系型数据库、向量数据库(如 Milvus、Qdrant、Chroma)等。长期记忆保存的是历史任务的总结、业务规则、用户偏好等跨任务复用信息。
5.3 工具注册与执行中心
每个 Agent 需要知道自己能调用哪些工具,以及每个工具的参数结构是什么。
工具执行中心通常维护一张工具注册表:
[ { "name": "search_web", "description": "搜索互联网信息,返回相关网页摘要", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "搜索关键词" } }, "required": ["query"] } }, { "name": "execute_code", "description": "在沙箱环境中运行 Python 代码", "parameters": { "type": "object", "properties": { "code": { "type": "string", "description": "要执行的 Python 代码" } }, "required": ["code"] } } ]这个工具注册表在每次调用模型时,会被转换为模型能识别的函数描述,随 Prompt 一起发送给模型。运行时还需要做一层“参数校验”,因为模型偶尔会生成非法参数。
5.4 观测与日志
长任务系统必须可观测。因为一个任务执行 20 步,中间任何一步出错,都需要有日志可以回溯。实践中,建议记录以下关键事件:
- 每一步的思考内容。
- 每一步的工具调用请求与原始返回。
- 上下文窗口的占用率变化。
- 每一步耗时与 Token 消耗。
- 错误信息与重试决策。
有了这些日志,排查“为什么 Agent 在 17 步突然做出了错误决策”这种问题时,才能有据可依。
5.5 并发与隔离
在生产环境,系统通常要同时处理多个用户的长任务请求。如果所有任务共用一个上下文池,任务之间会互相污染。
建议采用“任务级隔离”方案:每个任务拥有独立的运行时实例、独立的上下文窗口、独立的工具调用配额。在 Kubernetes 环境中,可以通过 Pod 级别的调度实现资源隔离。你也可以使用 Actor 模型,将每个长任务封装为一个 Actor,通过消息传递进行通信。
6. 完整实战:设计一个最小可运行的长任务 Agent
理论讲再多,不如动手写一个。下面我用 Python 实现一个极简但完整的长任务 Agent 示例。这个示例会模拟一个“先搜索信息 → 再写代码 → 再运行代码 → 最后总结”的四步任务,但代码结构上支持无限多步执行。
6.1 项目结构
agent-demo/ ├── agent.py # Agent 主程序 ├── tools.py # 工具注册与执行 ├── memory.py # 简单上下文管理 └── requirements.txt # 依赖6.2 依赖准备
openai>=1.0.0 python-dotenv>=1.0.0这个示例是基于“模型具备工具调用能力”这个前提来设计的。如果你的模型不支持结构化函数调用,可以退化为“让模型输出 JSON 文本”,原理相同。
6.3 工具层:tools.py
# 文件路径:agent-demo/tools.py import json # 一个极简的工具注册表,实际项目中应该支持动态注册 TOOL_REGISTRY = { "search_web": { "description": "搜索互联网信息,返回相关网页摘要。", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "搜索关键词" } }, "required": ["query"] }, "handler": lambda query: f"根据「{query}」找到 3 条结果:Python 列表去重可以使用 set(),保持顺序可以使用 dict.fromkeys()。" }, "execute_code": { "description": "在沙箱环境中运行 Python 代码,返回执行结果。", "parameters": { "type": "object", "properties": { "code": { "type": "string", "description": "要执行的 Python 代码" } }, "required": ["code"] }, "handler": lambda code: run_code_safely(code) } } def run_code_safely(code: str) -> str: """ 在实际生产中,这里应该使用 Docker 或子进程沙箱来隔离代码执行。 本示例只做演示,不真正执行任意代码。 """ # 为了安全,这里只允许执行 print 语句的代码 if "print(" not in code: return "代码中没有找到 print 语句,请修改代码后重试。" try: exec(code, {"__builtins__": {}}, {}) return "代码执行成功,结果已通过 print 输出。" except Exception as e: return f"代码执行失败: {str(e)}" def execute_tool(tool_name: str, arguments: dict) -> str: """执行指定的工具,并返回字符串结果。""" if tool_name not in TOOL_REGISTRY: return f"错误:未知工具 {tool_name}" try: handler = TOOL_REGISTRY[tool_name]["handler"] return handler(**arguments) except TypeError as e: return f"工具参数错误: {str(e)}" except Exception as e: return f"工具执行异常: {str(e)}" def get_tool_schemas() -> list: """返回供模型使用的工具描述列表。""" schemas = [] for name, meta in TOOL_REGISTRY.items(): schema = { "type": "function", "function": { "name": name, "description": meta["description"], "parameters": meta["parameters"] } } schemas.append(schema) return schemas6.4 记忆管理:memory.py
真实的长任务场景中,上下文管理器是非常复杂的,这里先实现一个最简单的版本:保留所有消息,直到超过阈值才触发截断策略。
# 文件路径:agent-demo/memory.py class SimpleMemory: """ 极简上下文管理器。 维护消息历史,并在超过最大长度时进行裁剪(此处只保留系统消息和最后两轮)。 """ def __init__(self, max_messages: int = 10): self.max_messages = max_messages self.messages = [] def add_message(self, role: str, content: str): self.messages.append({"role": role, "content": content}) self._trim() def get_messages(self) -> list: return self.messages def _trim(self): # 保留系统消息和最后 N 条消息 if len(self.messages) > self.max_messages: # 假设第一条是系统消息,保留它 system_msgs = [m for m in self.messages if m["role"] == "system"] recent_msgs = self.messages[-self.max_messages:] self.messages = system_msgs + recent_msgs6.5 Agent 主循环:agent.py
这是核心。在这个 Agent 里,主循环的逻辑是:
- 调用模型获取下一步动作。
- 模型可能返回两种结果:最终回答、工具调用。
- 如果是工具调用,执行工具,追加观察信息,再次调用模型。
- 直到模型返回最终回答,或者达到最大步数。
# 文件路径:agent-demo/agent.py import json import os from openai import OpenAI from tools import get_tool_schemas, execute_tool from memory import SimpleMemory class LongTaskAgent: def __init__(self, model: str = "gpt-4o-mini"): self.model = model self.client = OpenAI( api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL") # 兼容第三方网关 ) self.memory = SimpleMemory(max_messages=20) self.max_steps = 50 # 最多执行 50 步 def run(self, task: str) -> str: # 1. 初始化记忆 self.memory.add_message("system", "你是一个擅长拆解任务并执行工具的 AI 助手。" "你需要一步步完成任务,每轮最多调用一个工具。" "当所有子任务都执行完毕后,再给出最终总结。") self.memory.add_message("user", task) # 2. 循环执行 for step in range(1, self.max_steps + 1): print(f"\n===== Step {step} =====") # 调用模型 response = self.client.chat.completions.create( model=self.model, messages=self.memory.get_messages(), tools=get_tool_schemas(), tool_choice="auto" ) message = response.choices[0].message # 打印模型当前的思考内容(如果有) if message.content: print(f"[模型回复] {message.content[:200]}") # 3. 检查是否有工具调用 if message.tool_calls: for tool_call in message.tool_calls: fn_name = tool_call.function.name fn_args = json.loads(tool_call.function.arguments) print(f"[工具调用] {fn_name}({json.dumps(fn_args, ensure_ascii=False)})") # 执行工具 result = execute_tool(fn_name, fn_args) print(f"[工具结果] {result[:200]}") # 将模型请求和工具结果追加到 memory self.memory.add_message("assistant", message.content or "") self.memory.add_message("tool", result) continue # 4. 没有工具调用,说明模型给出了最终回答 if message.content: return message.content return "已达到最大执行步数,任务未能在限定步骤内完成。" if __name__ == "__main__": agent = LongTaskAgent() task = ( "我需要完成一个 Python 任务:\n" "1. 搜索 Python 列表去重的最佳方法;\n" "2. 根据搜索结果,写一段 Python 代码实现去重并保持顺序;\n" "3. 执行这段代码;\n" "4. 最后总结结果。" ) final_answer = agent.run(task) print("\n===== 最终回答 =====") print(final_answer)6.6 运行与预期结果
在项目目录下执行:
export OPENAI_API_KEY=你的_API_KEY python agent.py预期你会看到类似下面的输出过程:
===== Step 1 ===== [模型回复] 我需要先搜索 Python 列表去重的最佳方法。 [工具调用] search_web({"query": "Python 列表去重方法"}) [工具结果] 根据「Python 列表去重方法」找到 3 条结果:... ===== Step 2 ===== [模型回复] 根据搜索结果,我来写一段代码实现列表去重并保持顺序。 [工具调用] execute_code({"code": "..."}) [工具结果] 代码执行成功,结果已通过 print 输出。 ===== Step 3 ===== [模型回复] 代码已经成功执行。模型会在第 3 步左右返回最终总结。这个示例虽然只有 3 步左右,但它的架构完全支持扩展到几十步。
6.7 示例代码的注意点
这个代码是“最小演示版”,直接用于生产会存在几个问题,我在这里提前说明:
第一,没有实现断点续跑。运行时重启后,任务状态会丢失。生产环境建议把 memory 中的数据持久化到 Redis。
第二,上下文裁剪策略过于简单。真实场景需要摘要和检索增强,否则一旦工具返回大段文本,memory 很快就会爆炸。
第三,工具执行使用了 exec,非常不安全。生产环境必须使用 Docker 沙箱或子进程隔离。
7. 常见问题与排查思路
在实际开发和生产中,Agent 长任务失败的形态五花八门。这里整理几个高频问题及其排查方案,都是实战中非常典型的情况。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 任务执行到第 10 步后,Agent 开始重复做同一件事 | 上下文窗口丢失了早期“已完成”信息,模型误以为之前步骤没做过 | 将“已完成子任务列表”压缩后注入 Prompt,并不断更新 |
| 模型连续生成无效的工具调用参数 | 工具描述不够清晰,或模型本身函数调用能力较弱 | 优化工具名称和参数描述,增加示例,选择更擅长工具调用的模型 |
| 工具返回结果太长,下一轮 Prompt 超限 | 没有对工具返回做截断或摘要 | 日志类返回只保留尾部,数据类返回只保留结构摘要 |
| Agent 中途因异常退出,任务白跑 | 运行时缺少错误恢复机制 | 加入重试、退避、分支策略;至少实现“失败后重试一次” |
| 模型在长任务中输出被截断 | 单次输出 Token 上限不够 | 让模型分步输出,不要一次生成全部内容 |
| 多个任务并发运行时,上下文互相干扰 | 缺少任务级隔离 | 每个任务使用独立的 Agent 实例和独立的 memory |
7.1 模型“失忆”怎么排查
如果 Agent 在后期步数表现出“忘记目标”的迹象,第一步先看日志:在最近几轮的 Prompt 中,是否还包含系统提示词中的任务目标?如果上下文管理器把系统消息也截断了,那模型自然失去目标锚点。
解决方案是:把“任务目标”单独保留,不参与上下文裁剪。即使历史全部截断,任务目标也永远在 Prompt 的最前面。
# 示例:裁剪时始终保留任务目标 def build_prompt(system_prompt: str, task: str, history: list, max_len: int) -> list: messages = [{"role": "system", "content": system_prompt}] messages.append({"role": "user", "content": task}) # history 按最近优先保留 remaining_len = max_len - 2 messages.extend(history[-remaining_len:]) return messages7.2 工具调用循环死锁怎么处理
有时模型会反复调用同一个工具,拿到相同的结果,然后继续调用同一个工具,形成死循环。最常见的原因是:任务目标本身与工具能力不匹配。
排查时先问自己两个问题:模型能通过当前工具获取到解决问题的信息吗?模型输出的调用参数是否总是缺失关键字段?
如果是参数问题,可以通过“工具参数校验器”强校验,发现缺参时让系统返回一条明确错误信息,引导模型修正。如果工具能力本身不够,需要新增更合适的工具,而不是试图靠 Prompt 硬掰。
7.3 Token 被截断怎么处理
在开发 Agent 时,最常看到的报错之一就是:
已达到输出 token 上限,回答被截断,已有输出保留在对话中。这不是 API 故障,只是模型输出长度达到限制。解决手段有两类:
第一类,降低单次输出长度期望:把大任务拆成多步。第二类,改用输出上限更高的模型或配置更大的 max_tokens 参数(如果服务商支持)。
8. 最佳实践与工程建议
到此,我们已经从模型层到运行时层,再到系统架构层,完整拆解了 Agent 长任务的工作原理。下面我根据实际项目经验,给出几条高价值的工程建议。
8.1 尽量让 Agent “小步快跑”,不要追求一步到位
很多长任务失败的本质原因是“步子迈得太大”。模型试图在一次输出中完成思考、工具调用、总结等多件事,结果输出 Token 上限被撑爆。
建议做法:每轮只让模型做一件事,要么思考,要么调用一个工具。这样即使错误发生,影响面也是可控的。
8.2 为每一个长任务接入“任务看板”
生产级 Agent 系统需要随时能回答三件事:任务当前卡在哪个步骤?为什么卡住?下一步预计什么时候完成?
实现方式就是引入持久化任务状态表。推荐字段如下:
| 字段 | 说明 |
|---|---|
| task_id | 任务唯一标识 |
| current_step | 当前已执行步骤 |
| total_steps | 规划总步骤 |
| status | pending/running/paused/completed/failed |
| last_error | 最近一次错误信息 |
| created_at | 创建时间 |
| updated_at | 最后更新时间 |
有了这个表,调度器可以随时干预:人工暂停、跳过步骤、终止任务。
8.3 构建工具调用失败熔断机制
工具调用失败是长任务中最常见的故障源。建议在运行时加入“熔断器”模式:同一个工具连续失败 3 次,则标记为不可用,后续所有尝试直接短路,不再消耗模型调用额度。
class CircuitBreaker: def __init__(self, max_failures: int = 3): self.max_failures = max_failures self.failures = 0 self.is_open = False def record_failure(self): self.failures += 1 if self.failures >= self.max_failures: self.is_open = True def record_success(self): self.failures = 0 self.is_open = False def can_execute(self) -> bool: return not self.is_open8.4 日志里永远记录 Token 消耗
不要只记录任务结果,还要记录每一步的 Token 消耗、耗时和模型版本。这样不仅可以做成本分析,还能帮助判断“模型升级后是否影响任务质量”。
8.5 在生产环境使用任务队列,不要直接同步调用
当你的 Agent 需要跑几十步时,单次 HTTP 请求很难承载这么长的执行时间(网关通常有 30-60 秒超时)。正确做法是:API 只负责创建任务并返回 task_id,后台 Worker 异步执行任务,前端或客户端通过轮询或 WebSocket 获取任务进度。
8.6 上下文管理必须考虑成本
很多人忽略一点:Prompt 越长,模型调用费用越高。长任务如果上下文管理不当,成本会成倍增长。
实践建议是:能摘要就摘要,能截断就截断。每执行 5 步,用一次“压缩调用”把历史总结成一段 200 字以内的摘要,再继续后续步骤。这能显著降低长期运行的成本。
9. 总结与进阶方向
Agent 能连续跑几十步,不是某个单一组件的功劳,而是模型层、运行时层与系统架构层协同工作的结果。
模型层提供了“理解与生成”的基础能力,包括工具调用与推理循环。运行时层解决了上下文管理、状态维护、错误恢复与调度执行。系统架构层则保障了并发、隔离、可观测性与生产可用性。
如果你正打算深入 Agent 开发,建议按照以下路径持续学习:
- 先熟悉 ReAct 模式的完整流程,亲手实现一个 10 行左右的 Agent 循环。
- 再负责上记忆管理,理解上下文裁剪、摘要与向量检索的适用场景。
- 然后学习 LangGraph、AutoGen 这类成熟框架,看看它们的运行时是怎么设计的。
- 最后,把 Agent 放到真实业务场景中打磨,尤其是测试工具调用的边界情况与异常恢复能力。
如果你对长任务架构中某个环节特别感兴趣,欢迎在评论区留言,我可以在后续文章中展开写。觉得有帮助的朋友,也可以先收藏备用,后续调试 Agent 时再翻出来对照排查。