1. 为什么任务规划是AI Agent面试的分水岭面试过几十个做Agent方向的候选人之后,我发现一个很明显的规律:聊ReAct、聊Function Calling、聊Prompt Engineering,大部分人都能说上几句,但一旦把话题切到“任务规划、状态管理、长任务恢复”,能讲清楚的人瞬间少了一大半。这不是偶然的,因为前面那些是“能跑起来”的层面,后面这些是“能跑得住”的层面。一个Demo级别的Agent和一个生产级别的Agent,差距几乎全在这三个词上。所谓任务规划,说白了就是Agent拿到一个复杂目标之后,怎么把它拆成可执行的步骤序列。你让Agent“帮我做一份竞品分析报告”,它不能直接输出一份报告,它得先知道要去搜集哪些信息、按什么维度对比、先做哪一步后做哪一步、哪些步骤可以并行、哪些必须串行。这个拆解过程就是任务规划。状态则是Agent在执行这些步骤时,需要记住的上下文——当前执行到哪一步了、上一步的输出是什么、哪些中间结果需要保留、哪些可以丢弃。而长任务恢复解决的是一个更现实的问题:一个任务可能跑几十分钟甚至几个小时,中间可能因为网络抖动、模型超时、工具调用失败、进程重启等各种原因中断,Agent能不能从中断的地方接着跑,而不是从头再来。这三个东西为什么在面试里是分水岭?因为它们直接对应了生产环境中最痛的几个点。我见过太多团队,Demo演示的时候行云流水,一上生产就各种翻车:任务拆解到一半模型开始“幻觉”,把不存在的步骤编进去;执行了二十步之后上下文爆了,前面的关键信息全丢了;任务跑到第37步进程挂了,重启之后Agent一脸茫然地从第一步重新开始,前面半小时的API调用费用全白花。面试官问这些问题,不是想考你背没背过某个框架的API,而是想看你对这些真实痛点有没有系统性的思考。这篇文章我会把这三个主题拆开揉碎,从面试考察的角度、实际实现的方案选型、到具体的代码结构和参数设计,全部讲一遍。适合正在准备Agent方向面试的同学,也适合已经在做Agent项目但被长任务稳定性折磨的开发者。我不会只讲概念,每个部分都会给出可落地的方案和我在实际项目中踩过的坑。2. 任务规划的核心思路与方案选型2.1 从“一步到位”到“分而治之”的认知转变很多人刚开始做Agent的时候,思路是“给模型一个超级详细的Prompt,让它一次性输出完整的执行计划”。这个思路在任务简单的时候能用,比如“帮我查一下明天天气然后发邮件通知团队”,模型直接输出一个三步计划就完事了。但任务一复杂,比如“分析我们产品过去三个月的用户反馈,找出Top 5的痛点,针对每个痛点生成一份改进方案,并按照优先级排序”,你让模型一次性输出完整计划,它大概率会给你一个看起来很合理但实际上没法执行的计划——步骤粒度不均匀、依赖关系混乱、有些步骤需要的信息前面根本没产生。这里的关键认知转变是:任务规划不是一个静态的一次性动作,而是一个动态的、可以多轮迭代的过程。业界目前主流的方案大致可以分成三类,每类都有它的适用场景和取舍。第一类是一次性规划(Plan-and-Execute)。Agent先让模型生成一个完整的步骤列表,然后按顺序逐步执行,每执行完一步把结果喂回给模型,让模型决定下一步怎么做或者是否要调整计划。这种方案实现简单,适合步骤数在10步以内、依赖关系清晰的任务。缺点是如果第一步就规划错了,后面全错,而且模型在规划时看不到实际执行结果,容易产生不切实际的步骤。第二类是增量式规划(ReAct风格的迭代)。Agent不预先制定完整计划,而是每一步都根据当前状态和已有结果,让模型决定下一步做什么。这种方案灵活性强,能根据实际情况动态调整,但缺点是容易“迷失”——执行了十几步之后模型可能忘了最初的目标是什么,或者陷入循环。而且每一步都要调用模型,Token消耗大,延迟高。第三类是分层规划(Hierarchical Planning)。把任务分成高层计划和低层执行两个层级。高层负责把大目标拆成几个粗粒度的阶段,每个阶段再动态展开成细粒度的步骤。这种方案兼顾了全局视野和局部灵活性,但实现复杂度最高。我个人的经验是,大部分生产场景用“一次性规划+执行中动态调整”的混合方案就够了,纯分层规划在步骤数超过50的超级长任务里才真正体现出优势。2.2 规划粒度的取舍:为什么你的Agent总是“想太多”或“想太少”规划粒度是很多人忽略但极其关键的一个参数。粒度太粗,比如“第一步:搜集信息,第二步:分析,第三步:输出报告”,模型在执行时根本不知道具体要做什么,每一步都变成了一次新的开放式提问,等于没规划。粒度太细,比如把“打开浏览器、输入网址、等待加载、点击搜索框、输入关键词……”全部列出来,计划本身就有几十步,模型在执行时很容易在某个细节步骤上卡住,而且Token消耗巨大。我的经验法则是:每个步骤应该是一个“语义上完整、可独立验证”的动作。什么叫语义上完整?就是这个步骤做完之后,你能明确地说“这一步完成了,结果是XXX”。什么叫可独立验证?就是这一步的成功或失败有明确的判断标准,而不是“大概做了一些事情”。比如“搜集三家竞品的定价信息”就是一个合格的步骤粒度,“打开网页”就太细,“做竞品分析”就太粗。在实际操作中,我会在规划Prompt里加一个约束:要求模型输出的每个步骤都包含“动作描述+预期输出+成功判断标准”三个要素。这样即使用户不检查计划,Agent在执行时也有明确的靶子。下面是一个规划Prompt的简化示例:PLANNING_PROMPT = """ 你是一个任务规划器。请将用户的目标拆解为可执行的步骤序列。 要求: 1. 每个步骤必须包含:动作描述、预期输出、成功判断标准 2. 步骤数量控制在5-15步之间 3. 标注步骤之间的依赖关系(哪些步骤必须串行,哪些可以并行) 4. 如果某一步需要前序步骤的输出作为输入,请明确标注 用户目标:{goal} 请以JSON格式输出计划: { "steps": [ { "id": 1, "action": "动作描述", "expected_output": "预期输出", "success_criteria": "成功判断标准", "depends_on": [], "can_parallel": false } ] } """这个Prompt的关键在于强制模型输出结构化的计划,而不是一段自然语言描述。结构化输出的好处是后续执行器可以程序化地解析依赖关系、判断并行可能性、追踪每步状态。很多团队在这一步偷懒,让模型输出一段文字计划,结果执行器根本没法可靠地解析,最后还是要靠模型自己“理解”计划,等于把规划的责任又推回给了模型。2.3 规划中的依赖管理与并行执行依赖管理是任务规划里最容易被低估的部分。一个稍微复杂点的任务,步骤之间不可能全是线性关系。比如“分析用户反馈并生成改进方案”这个任务,搜集反馈数据、搜集竞品数据、搜集内部数据这三个步骤之间没有依赖,可以并行;但“分析反馈”必须等“搜集反馈数据”完成;“生成改进方案”必须等“分析反馈”和“竞品分析”都完成。如果Agent不管理依赖关系,要么全部串行执行(浪费时间),要么乱序执行(拿到空数据就开始分析)。我在实际项目中见过最离谱的情况是,Agent在数据还没搜集完的时候就开始“分析”,然后模型基于空上下文硬编了一段分析结果出来,整个任务的结果完全不可信。实现依赖管理的基本思路是维护一个有向无环图(DAG),每个步骤是一个节点,依赖关系是边。执行器每次找出所有“依赖已满足且尚未执行”的节点,并行执行它们。下面是一个简化的DAG执行器逻辑:class TaskDAG: def __init__(self, steps): self.steps = {s["id"]: s for s in steps} self.completed = set() self.running = set() self.failed = set() def get_ready_steps(self): ready = [] for sid, step in self.steps.items(): if sid in self.completed or sid in self.running or sid in self.failed: continue deps = set(step.get("depends_on", [])) if deps.issubset(self.completed): ready.append(sid) return ready def mark_completed(self, step_id, result): self.completed.add(step_id) self.steps[step_id]["result"] = result def mark_failed(self, step_id, error): self.failed.add(step_id) self.steps[step_id]["error"] = error这个结构看起来简单,但实际用起来有几个坑。第一个坑是循环依赖:模型规划的时候可能产生A依赖B、B依赖A的情况,执行器会死锁。所以规划完成后必须做一次环检测,发现环就打回去让模型重新规划。第二个坑是隐式依赖:模型可能没有显式标注依赖,但实际执行时某一步需要前序步骤的输出。我的做法是在每个步骤的Prompt里注入所有已完成步骤的结果摘要,让模型自己判断需不需要用到,而不是完全依赖显式的依赖声明。实操心得:依赖关系不要完全信任模型的标注。我会在规划后加一个校验步骤,用另一个模型调用检查“这个计划里有没有遗漏的依赖关系”,把两个结果合并后再执行。这个额外的调用成本很低,但能避免大量执行时的诡异问题。3. 状态管理的核心机制与实操细节3.1 Agent状态到底包含哪些东西很多人对“状态”的理解停留在“对话历史”层面,觉得把之前的消息都塞进上下文就是状态管理了。这在短任务里勉强能用,但长任务里完全不够。一个生产级Agent的状态至少包含以下几层:第一层是任务级状态,包括当前任务的目标、整体计划、每个步骤的执行状态(未开始/进行中/已完成/失败)、每个步骤的输出结果。这一层是任务恢复的基础,必须持久化。第二层是执行级状态,包括当前正在执行的步骤的中间状态,比如工具调用的参数、API返回的原始数据、模型推理的中间结果。这一层决定了任务能否从中断点精确恢复。第三层是上下文级状态,包括对话历史、模型已经“知道”的信息、当前上下文窗口里有哪些内容。这一层决定了Agent的“记忆”是否连贯。第四层是环境级状态,包括外部系统的状态,比如文件系统里写了哪些文件、数据库里插入了哪些记录、外部API的调用配额还剩多少。这一层最容易被忽略,但恢复时如果环境状态和Agent记忆不一致,会产生非常诡异的问题。我见过一个典型的翻车案例:Agent执行到第15步时往数据库里插入了一批数据,然后进程挂了。重启后Agent从第15步重新执行,又插入了一遍同样的数据,导致数据重复。这就是环境级状态没有和任务级状态同步的问题。解决方案要么是让每个步骤的操作幂等(比如用upsert代替insert),要么是在状态里记录“第15步已产生副作用”,恢复时跳过。3.2 状态持久化的时机与策略状态持久化不是越频繁越好,也不是越少越好,关键是在“恢复精度”和“性能开销”之间找平衡。我的经验是采用检查点(Checkpoint)机制:不是每一步都持久化完整状态,而是在关键节点做检查点。什么算关键节点?我的判断标准是:如果一个步骤失败后重做的成本很高,或者一个步骤产生了不可逆的副作用,就在这个步骤完成后立即做检查点。比如调用了一个昂贵的API、写入了一批数据、生成了一个重要的中间结果,这些步骤完成后必须持久化。而一些纯计算、可快速重做的步骤,可以攒几个一起持久化。持久化的内容也有讲究。完整持久化所有上下文会导致存储膨胀,而且恢复时加载大量无关历史反而会干扰模型。我的做法是分层持久化:任务级状态和执行级状态完整持久化,上下文级状态只持久化“摘要+关键引用”。具体来说,每个步骤完成后,让模型生成一个该步骤结果的摘要(100字以内),持久化这个摘要而不是完整输出。恢复时把摘要注入上下文,如果某个步骤需要完整输出,再从持久化存储里按需加载。class StateManager: def __init__(self, storage_backend): self.storage = storage_backend self.task_state = {} self.step_summaries = {} def checkpoint(self, task_id, step_id, step_result, is_critical=False): summary = self._summarize(step_result) self.step_summaries[step_id] = summary self.task_state["current_step"] = step_id self.task_state["completed_steps"].append(step_id) if is_critical: self.storage.save_full(task_id, { "task_state": self.task_state, "step_summaries": self.step_summaries, "full_result": step_result }) else: self.storage.save_incremental(task_id, { "step_id": step_id, "summary": summary }) def restore(self, task_id): state = self.storage.load(task_id) self.task_state = state["task_state"] self.step_summaries = state["step_summaries"] return self.task_state["current_step"]这个设计的核心思想是:恢复时不需要完整重放历史,只需要知道“执行到哪了”和“之前每步大概产出了什么”。具体的完整结果在需要时再按需加载。这样既控制了存储成本,又保证了恢复的精度。3.3 上下文窗口管理与信息压缩长任务绕不开的一个硬约束是上下文窗口。一个跑了30步的任务,如果每步的输出都塞进上下文,很容易就爆了。而且即使没爆,上下文太长也会导致模型注意力分散,关键信息被淹没。上下文管理的核心策略是分层压缩。我会把上下文分成三个区域:固定区(任务目标、整体计划、关键约束)、滚动区(最近N步的详细输出)、摘要区(更早步骤的压缩摘要)。固定区永远保留,滚动区保留最近5-10步的完整内容,摘要区用模型生成的摘要替代原始内容。摘要的生成时机很重要。我试过两种方案:一种是在步骤完成时立即生成摘要,另一种是攒一批再生成。立即生成的好处是信息新鲜、摘要质量高,坏处是每步都要多一次模型调用。攒批生成的好处是省调用,坏处是摘要时模型已经“忘了”细节,摘要质量下降。实测下来,对于关键步骤立即生成摘要,对于普通步骤攒3-5步批量生成,是一个比较好的平衡。还有一个技巧是用结构化摘要代替自然语言摘要。自然语言摘要容易丢失关键信息,而且不同步骤的摘要格式不统一,后续模型读取时理解成本高。我会要求模型按固定格式输出摘要:步骤ID: 15 动作: 调用竞品定价API 关键结果: 获取到3家竞品定价,A产品$29/月,B产品$49/月,C产品$19/月 数据引用: raw_data/step_15_pricing.json 状态: 成功这种结构化摘要的好处是,后续模型读取时能快速定位关键信息,而且程序化处理也方便。比如恢复时可以直接从摘要里提取“数据引用”字段,按需加载原始数据。注意事项:上下文压缩是有损的,一定要保留“数据引用”这种指针信息。我踩过的坑是早期只保留自然语言摘要,结果恢复时发现某个步骤的原始数据找不到了,只能重新执行,浪费了大量时间。4. 长任务恢复的完整实现方案4.1 中断检测与恢复触发机制长任务恢复的第一步是检测到中断发生了。中断的来源有很多种:进程崩溃、网络超时、模型API限流、工具调用失败、人工暂停。不同来源的中断,恢复策略不一样。进程崩溃和网络超时属于“意外中断”,恢复时应该从最后一个检查点继续。模型API限流属于“可重试中断”,应该等待一段时间后重试当前步骤,而不是跳过。工具调用失败属于“步骤级失败”,需要判断是重试、跳过还是终止整个任务。人工暂停属于“主动中断”,恢复时应该从暂停点继续,但可能需要重新确认一些状态。实现上,我会给每个步骤的执行包一层“执行器”,执行器负责捕获异常、判断异常类型、决定恢复策略:class StepExecutor: def __init__(self, state_manager, max_retries=3): self.state_manager = state_manager self.max_retries = max_retries def execute(self, step, context): retries = 0 while retries < self.max_retries: try: result = self._do_execute(step, context) self.state_manager.checkpoint( step["task_id"], step["id"], result, is_critical=step.get("is_critical", False) ) return result except RateLimitError: time.sleep(2 ** retries * 10) retries += 1 except ToolCallError as e: if step.get("allow_skip", False): return {"status": "skipped", "reason": str(e)} retries += 1 except Exception as e: self.state_manager.mark_failed(step["id"], str(e)) raise raise MaxRetriesExceeded(f"Step {step['id']} failed after {self.max_retries} retries")这个执行器的关键设计是区分异常类型。不是所有异常都应该重试,也不是所有异常都应该终止。限流异常用指数退避重试,工具调用异常根据步骤配置决定是否跳过,未知异常直接标记失败并向上抛。这样恢复逻辑才能精确处理不同情况。4.2 从检查点恢复的完整流程恢复流程的核心是重建执行上下文。具体来说,需要做以下几件事:第一,加载任务级状态,确定当前执行到哪一步、哪些步骤已完成、哪些步骤失败。第二,加载已完成步骤的摘要,重建上下文。第三,检查环境级状态,确认外部系统的状态和Agent记忆一致。第四,从当前步骤继续执行。这里最复杂的是第三步。环境级状态的检查需要针对具体任务设计。比如如果任务涉及文件操作,恢复时要检查文件系统里实际有哪些文件;如果涉及数据库操作,要检查数据库里的实际记录。我通常会在任务定义里声明“环境依赖”,恢复时按声明逐项检查。def restore_and_resume(task_id): state = state_manager.restore(task_id) current_step = state["current_step"] # 重建上下文 context = build_context( goal=state["goal"], plan=state["plan"], completed_summaries=state["step_summaries"] ) # 检查环境状态 env_issues = check_environment(state["env_dependencies"]) if env_issues: context = inject_env_warnings(context, env_issues) # 从当前步骤继续 remaining_steps = get_remaining_steps(state["plan"], current_step) for step in remaining_steps: executor.execute(step, context) context = update_context(context, step) return finalize_task(state)这个流程看起来简单,但实际实现时有几个细节要注意。第一个细节是“当前步骤”的确定:如果中断发生在步骤执行过程中,当前步骤是“未完成”状态,恢复时应该重新执行这一步。如果中断发生在步骤完成后、检查点写入前,那这一步实际上已经完成了但状态里没记录,恢复时会重复执行。所以检查点写入必须尽量靠近步骤完成,减少这个窗口期。第二个细节是上下文的“新鲜度”:恢复时重建的上下文是基于摘要的,可能丢失了一些细节。如果当前步骤对上下文细节要求很高,可能需要从持久化存储里加载更多原始数据。我的做法是在步骤定义里标注“上下文需求级别”,高需求的步骤恢复时加载完整历史,低需求的步骤只用摘要。第三个细节是幂等性保证:恢复时重新执行的步骤可能产生重复副作用。对于写操作,尽量用幂等的方式实现(upsert、去重写入等)。对于无法幂等的操作,在状态里记录“副作用已产生”的标记,恢复时检查标记决定是否跳过。4.3 恢复后的状态一致性校验恢复之后、继续执行之前,做一次状态一致性校验是非常有必要的。我见过太多因为状态不一致导致的诡异问题:Agent以为某个文件已经生成了,实际上没有;Agent以为某个API已经调用过了,实际上调用失败了但状态记录成了成功。一致性校验的核心是对比Agent记忆和实际环境。具体做法是:从状态里提取所有“应该存在”的产物(文件、数据记录、API调用结果),逐一验证它们是否真实存在且符合预期。如果发现不一致,有两种处理策略:一种是修正状态(以实际环境为准),另一种是重新执行产生该产物的步骤。def validate_consistency(state): issues = [] for step_id, summary in state["step_summaries"].items(): if summary.get("status") != "success": continue artifacts = summary.get("artifacts", []) for artifact in artifacts: if not verify_artifact(artifact): issues.append({ "step_id": step_id, "artifact": artifact, "issue": "artifact_missing_or_invalid" }) return issues校验发现的问题需要分类处理。如果是“产物缺失”,重新执行对应步骤。如果是“产物存在但内容不符”,需要判断是重新执行还是修正状态。如果是“产物存在但状态记录为失败”,修正状态即可。这个分类逻辑需要根据具体任务定制,没有通用方案。实操心得:一致性校验的成本不低,不需要每次恢复都做全量校验。我的做法是只校验“关键产物”(标记了is_critical的步骤的产物),普通产物在用到的时候再校验。这样既保证了关键路径的可靠性,又控制了恢复时间。5. 常见问题与排查技巧实录5.1 任务规划阶段的典型问题问题一:模型规划出无法执行的步骤。比如让Agent“分析用户情感倾向”,但前面根本没有搜集用户数据的步骤。这种问题的根源是模型在规划时没有考虑“信息可得性”。解决方案是在规划Prompt里加一条约束:“每个步骤的输入必须来自用户提供的初始信息或前序步骤的输出,不能假设存在未提供的信息。”问题二:规划步骤数量失控。模型有时候会把一个简单任务拆成三四十步,每步都很细。这会导致执行时间长、Token消耗大、出错概率高。解决方案是在Prompt里明确步骤数量范围(比如5-15步),并且要求“每个步骤应该是一个语义完整的动作,而不是一个原子操作”。问题三:依赖关系标注错误。模型可能漏标依赖,或者标错依赖方向。解决方案是规划后加一个校验调用,专门检查依赖关系的合理性。校验Prompt可以这样写:“以下是一个任务计划,请检查步骤之间的依赖关系是否完整且正确。特别注意:如果步骤B需要步骤A的输出作为输入,但B没有标注依赖A,这是一个错误。”问题四:规划粒度不均匀。有的步骤很粗(“做竞品分析”),有的步骤很细(“打开浏览器”)。这种不均匀会导致执行时有的步骤很快完成,有的步骤卡很久。解决方案是在Prompt里要求“所有步骤的预期执行时间应该在同一量级”,并且给出正反示例。5.2 状态管理阶段的典型问题问题一:状态膨胀导致存储成本失控。每个步骤都持久化完整输出,跑几十步之后存储占用巨大。解决方案是分层持久化,只对关键步骤存完整输出,普通步骤存摘要。问题二:恢复时上下文重建不完整。恢复后Agent“忘了”之前的一些关键信息,导致重复提问或做出错误决策。解决方案是恢复时不仅加载摘要,还要加载“关键决策记录”——记录之前做过哪些重要判断、基于什么信息做的。问题三:状态更新和实际执行不同步。状态记录某步成功了,实际执行失败了;或者反过来。解决方案是状态更新必须在执行结果确认之后进行,不能在执行前“预写”状态。问题四:并发步骤的状态冲突。多个步骤并行执行时,同时更新状态可能导致冲突。解决方案是状态更新加锁,或者用乐观并发控制(版本号机制)。5.3 长任务恢复阶段的典型问题问题一:恢复后重复执行已完成的步骤。根源是检查点写入不及时。解决方案是缩小检查点窗口,关键步骤完成后立即写入。问题二:恢复后环境状态不一致。Agent以为文件存在,实际不存在。解决方案是恢复时做一致性校验,以实际环境为准修正状态。问题三:恢复后陷入无限重试。某个步骤一直失败,恢复后重试还是失败,循环往复。解决方案是设置最大重试次数,超过后标记任务失败并通知人工介入。问题四:恢复后上下文过长导致模型性能下降。恢复时加载了太多历史摘要,上下文又爆了。解决方案是恢复时只加载“当前步骤需要的最小上下文”,其他历史按需加载。下面是一个常见问题的速查表,方便在实际排查时快速定位:问题现象可能原因排查方向解决方案任务执行到一半卡住某步骤死循环或等待超时检查当前步骤的执行日志设置步骤级超时,超时后标记失败恢复后重复执行检查点未及时写入检查检查点写入时机关键步骤完成后立即写入恢复后结果不一致环境状态与记忆不一致对比状态记录和实际环境恢复时做一致性校验上下文爆掉历史信息未压缩检查上下文管理策略启用分层压缩,摘要替代原文规划步骤无法执行模型假设了不存在的信息检查规划Prompt的约束加“信息可得性”约束依赖关系错误模型漏标或错标依赖检查依赖图是否有环规划后加依赖校验调用5.4 我踩过的几个印象深刻的坑第一个坑是**“成功”状态误报**。早期实现时,工具调用返回了HTTP 200就标记步骤成功,但实际上API返回的body里可能是错误信息。后来改成必须校验返回内容的业务字段,确认业务成功才算成功。这个坑让我意识到,状态标记不能只看“调用有没有报错”,要看“业务有没有达成”。第二个坑是恢复时的“幽灵依赖”。某个步骤在规划时没有标注依赖,但实际执行时用到了前序步骤的输出。恢复时因为前序步骤的摘要里没有这个信息,导致当前步骤执行失败。后来我在每个步骤的Prompt里都注入了所有已完成步骤的摘要,让模型自己判断需要哪些信息,而不是完全依赖显式依赖声明。第三个坑是检查点写入的原子性问题。状态写入过程中进程崩溃,导致状态文件损坏,恢复时读不出来。后来改成先写临时文件再原子重命名,保证状态文件要么是完整的旧版本,要么是完整的新版本,不会出现半写状态。第四个坑是恢复后的“上下文污染”。恢复时加载了之前失败步骤的错误信息,导致模型在后续步骤里一直“记得”那个错误,行为变得保守或异常。后来在恢复时会把失败步骤的错误信息标记为“已处理”,并在上下文里明确说明“该错误已修复,不影响后续执行”。这些坑的共同特点是:它们不会在Demo阶段暴露,只有在长任务、真实环境、异常情况下才会出现。这也是为什么面试官喜欢问这些问题的原因——能讲清楚这些细节的人,大概率是真的在生产环境里跑过Agent的。6. 面试中的高频追问与应对思路6.1 关于任务规划的追问面试官常问的一个问题是:“如果模型规划出来的计划执行到一半发现不可行,怎么办?”这个问题考察的是你对“动态重规划”的理解。我的回答思路是:首先,在执行过程中设置“计划校验点”,每执行完几个步骤就让模型评估“当前计划是否仍然合理”;其次,如果发现不可行,触发重规划,但重规划不是从头再来,而是基于已完成步骤的结果重新规划剩余步骤;最后,重规划时要保留已完成步骤的成果,避免浪费。另一个高频问题是:“怎么保证规划的步骤是可执行的?”这个问题可以从三个层面回答:Prompt层面加约束(信息可得性、步骤粒度、数量范围),校验层面加检查(依赖关系、环检测、可行性评估),执行层面加兜底(步骤失败后的重试、跳过、降级策略)。6.2 关于状态管理的追问“状态存在哪里?”这个问题看似简单,但能区分出有没有实战经验。我的回答会分场景:小规模用内存+定期快照,中规模用Redis或类似的内存数据库,大规模用关系型数据库或对象存储。关键不是用什么存储,而是“存什么”和“什么时候存”。我会强调分层持久化和检查点机制。“上下文窗口不够用怎么办?”这个问题考察上下文管理能力。我的回答框架是:分层压缩(固定区+滚动区+摘要区)、结构化摘要(代替自然语言摘要)、按需加载(用指针引用代替完整内容)、关键信息优先(任务目标和约束永远保留)。6.3 关于长任务恢复的追问“怎么判断一个任务需要恢复?”这个问题考察中断检测机制。我的回答是:通过心跳机制检测进程存活,通过步骤超时检测执行卡住,通过异常捕获检测执行失败。不同中断类型对应不同恢复策略。“恢复后怎么保证不重复执行?”这个问题考察幂等性设计。我的回答分两层:对于可幂等的操作(读操作、upsert写操作),直接重新执行;对于不可幂等的操作,在状态里记录副作用标记,恢复时检查标记决定是否跳过。同时,检查点写入要尽量靠近步骤完成,缩小重复执行的窗口。“恢复后状态不一致怎么处理?”这个问题考察一致性校验能力。我的回答是:恢复时做一致性校验,对比Agent记忆和实际环境;发现不一致时,以实际环境为准修正状态,或者重新执行产生不一致的步骤;关键产物必须校验,普通产物按需校验。6.4 面试中的加分项与减分项加分项包括:能说出具体的检查点策略和参数(比如“关键步骤完成后立即写入,普通步骤攒3-5步批量写入”);能区分不同中断类型的恢复策略;能讲出自己踩过的具体坑和解决方案;能给出可落地的代码结构或伪代码。减分项包括:只讲概念不讲实现;把所有状态都塞进上下文;恢复时从头开始执行;不考虑幂等性;不知道依赖管理;说不出具体的参数和阈值。我在实际面试别人的时候,最喜欢问的一个问题是:“如果你的Agent执行一个任务跑了两个小时,中间进程重启了三次,你怎么保证最终结果是正确的?”这个问题几乎覆盖了任务规划、状态管理、长任务恢复的所有核心点。能系统性地回答这个问题的人,基本可以判定为有实战经验。这个方向后续还可以往“多Agent协作下的状态同步”和“跨会话的长期记忆管理”两个方向扩展,前者解决多个Agent并行执行时的状态一致性问题,后者解决Agent在多次会话之间如何保持记忆连贯的问题。这两个方向目前在生产环境中的需求越来越大,也是面试中越来越常见的话题。
1. 为什么任务规划是AI Agent面试的分水岭面试过几十个做Agent方向的候选人之后,我发现一个很明显的规律:聊ReAct、聊Function Calling、聊Prompt Engineering,大部分人都能说上几句,但一旦把话题切到“任务规划、状态管理、长任务恢复”,能讲清楚的人瞬间少了一大半。这不是偶然的,因为前面那些是“能跑起来”的层面,后面这些是“能跑得住”的层面。一个Demo级别的Agent和一个生产级别的Agent,差距几乎全在这三个词上。所谓任务规划,说白了就是Agent拿到一个复杂目标之后,怎么把它拆成可执行的步骤序列。你让Agent“帮我做一份竞品分析报告”,它不能直接输出一份报告,它得先知道要去搜集哪些信息、按什么维度对比、先做哪一步后做哪一步、哪些步骤可以并行、哪些必须串行。这个拆解过程就是任务规划。状态则是Agent在执行这些步骤时,需要记住的上下文——当前执行到哪一步了、上一步的输出是什么、哪些中间结果需要保留、哪些可以丢弃。而长任务恢复解决的是一个更现实的问题:一个任务可能跑几十分钟甚至几个小时,中间可能因为网络抖动、模型超时、工具调用失败、进程重启等各种原因中断,Agent能不能从中断的地方接着跑,而不是从头再来。这三个东西为什么在面试里是分水岭?因为它们直接对应了生产环境中最痛的几个点。我见过太多团队,Demo演示的时候行云流水,一上生产就各种翻车:任务拆解到一半模型开始“幻觉”,把不存在的步骤编进去;执行了二十步之后上下文爆了,前面的关键信息全丢了;任务跑到第37步进程挂了,重启之后Agent一脸茫然地从第一步重新开始,前面半小时的API调用费用全白花。面试官问这些问题,不是想考你背没背过某个框架的API,而是想看你对这些真实痛点有没有系统性的思考。这篇文章我会把这三个主题拆开揉碎,从面试考察的角度、实际实现的方案选型、到具体的代码结构和参数设计,全部讲一遍。适合正在准备Agent方向面试的同学,也适合已经在做Agent项目但被长任务稳定性折磨的开发者。我不会只讲概念,每个部分都会给出可落地的方案和我在实际项目中踩过的坑。2. 任务规划的核心思路与方案选型2.1 从“一步到位”到“分而治之”的认知转变很多人刚开始做Agent的时候,思路是“给模型一个超级详细的Prompt,让它一次性输出完整的执行计划”。这个思路在任务简单的时候能用,比如“帮我查一下明天天气然后发邮件通知团队”,模型直接输出一个三步计划就完事了。但任务一复杂,比如“分析我们产品过去三个月的用户反馈,找出Top 5的痛点,针对每个痛点生成一份改进方案,并按照优先级排序”,你让模型一次性输出完整计划,它大概率会给你一个看起来很合理但实际上没法执行的计划——步骤粒度不均匀、依赖关系混乱、有些步骤需要的信息前面根本没产生。这里的关键认知转变是:任务规划不是一个静态的一次性动作,而是一个动态的、可以多轮迭代的过程。业界目前主流的方案大致可以分成三类,每类都有它的适用场景和取舍。第一类是一次性规划(Plan-and-Execute)。Agent先让模型生成一个完整的步骤列表,然后按顺序逐步执行,每执行完一步把结果喂回给模型,让模型决定下一步怎么做或者是否要调整计划。这种方案实现简单,适合步骤数在10步以内、依赖关系清晰的任务。缺点是如果第一步就规划错了,后面全错,而且模型在规划时看不到实际执行结果,容易产生不切实际的步骤。第二类是增量式规划(ReAct风格的迭代)。Agent不预先制定完整计划,而是每一步都根据当前状态和已有结果,让模型决定下一步做什么。这种方案灵活性强,能根据实际情况动态调整,但缺点是容易“迷失”——执行了十几步之后模型可能忘了最初的目标是什么,或者陷入循环。而且每一步都要调用模型,Token消耗大,延迟高。第三类是分层规划(Hierarchical Planning)。把任务分成高层计划和低层执行两个层级。高层负责把大目标拆成几个粗粒度的阶段,每个阶段再动态展开成细粒度的步骤。这种方案兼顾了全局视野和局部灵活性,但实现复杂度最高。我个人的经验是,大部分生产场景用“一次性规划+执行中动态调整”的混合方案就够了,纯分层规划在步骤数超过50的超级长任务里才真正体现出优势。2.2 规划粒度的取舍:为什么你的Agent总是“想太多”或“想太少”规划粒度是很多人忽略但极其关键的一个参数。粒度太粗,比如“第一步:搜集信息,第二步:分析,第三步:输出报告”,模型在执行时根本不知道具体要做什么,每一步都变成了一次新的开放式提问,等于没规划。粒度太细,比如把“打开浏览器、输入网址、等待加载、点击搜索框、输入关键词……”全部列出来,计划本身就有几十步,模型在执行时很容易在某个细节步骤上卡住,而且Token消耗巨大。我的经验法则是:每个步骤应该是一个“语义上完整、可独立验证”的动作。什么叫语义上完整?就是这个步骤做完之后,你能明确地说“这一步完成了,结果是XXX”。什么叫可独立验证?就是这一步的成功或失败有明确的判断标准,而不是“大概做了一些事情”。比如“搜集三家竞品的定价信息”就是一个合格的步骤粒度,“打开网页”就太细,“做竞品分析”就太粗。在实际操作中,我会在规划Prompt里加一个约束:要求模型输出的每个步骤都包含“动作描述+预期输出+成功判断标准”三个要素。这样即使用户不检查计划,Agent在执行时也有明确的靶子。下面是一个规划Prompt的简化示例:PLANNING_PROMPT = """ 你是一个任务规划器。请将用户的目标拆解为可执行的步骤序列。 要求: 1. 每个步骤必须包含:动作描述、预期输出、成功判断标准 2. 步骤数量控制在5-15步之间 3. 标注步骤之间的依赖关系(哪些步骤必须串行,哪些可以并行) 4. 如果某一步需要前序步骤的输出作为输入,请明确标注 用户目标:{goal} 请以JSON格式输出计划: { "steps": [ { "id": 1, "action": "动作描述", "expected_output": "预期输出", "success_criteria": "成功判断标准", "depends_on": [], "can_parallel": false } ] } """这个Prompt的关键在于强制模型输出结构化的计划,而不是一段自然语言描述。结构化输出的好处是后续执行器可以程序化地解析依赖关系、判断并行可能性、追踪每步状态。很多团队在这一步偷懒,让模型输出一段文字计划,结果执行器根本没法可靠地解析,最后还是要靠模型自己“理解”计划,等于把规划的责任又推回给了模型。2.3 规划中的依赖管理与并行执行依赖管理是任务规划里最容易被低估的部分。一个稍微复杂点的任务,步骤之间不可能全是线性关系。比如“分析用户反馈并生成改进方案”这个任务,搜集反馈数据、搜集竞品数据、搜集内部数据这三个步骤之间没有依赖,可以并行;但“分析反馈”必须等“搜集反馈数据”完成;“生成改进方案”必须等“分析反馈”和“竞品分析”都完成。如果Agent不管理依赖关系,要么全部串行执行(浪费时间),要么乱序执行(拿到空数据就开始分析)。我在实际项目中见过最离谱的情况是,Agent在数据还没搜集完的时候就开始“分析”,然后模型基于空上下文硬编了一段分析结果出来,整个任务的结果完全不可信。实现依赖管理的基本思路是维护一个有向无环图(DAG),每个步骤是一个节点,依赖关系是边。执行器每次找出所有“依赖已满足且尚未执行”的节点,并行执行它们。下面是一个简化的DAG执行器逻辑:class TaskDAG: def __init__(self, steps): self.steps = {s["id"]: s for s in steps} self.completed = set() self.running = set() self.failed = set() def get_ready_steps(self): ready = [] for sid, step in self.steps.items(): if sid in self.completed or sid in self.running or sid in self.failed: continue deps = set(step.get("depends_on", [])) if deps.issubset(self.completed): ready.append(sid) return ready def mark_completed(self, step_id, result): self.completed.add(step_id) self.steps[step_id]["result"] = result def mark_failed(self, step_id, error): self.failed.add(step_id) self.steps[step_id]["error"] = error这个结构看起来简单,但实际用起来有几个坑。第一个坑是循环依赖:模型规划的时候可能产生A依赖B、B依赖A的情况,执行器会死锁。所以规划完成后必须做一次环检测,发现环就打回去让模型重新规划。第二个坑是隐式依赖:模型可能没有显式标注依赖,但实际执行时某一步需要前序步骤的输出。我的做法是在每个步骤的Prompt里注入所有已完成步骤的结果摘要,让模型自己判断需不需要用到,而不是完全依赖显式的依赖声明。实操心得:依赖关系不要完全信任模型的标注。我会在规划后加一个校验步骤,用另一个模型调用检查“这个计划里有没有遗漏的依赖关系”,把两个结果合并后再执行。这个额外的调用成本很低,但能避免大量执行时的诡异问题。3. 状态管理的核心机制与实操细节3.1 Agent状态到底包含哪些东西很多人对“状态”的理解停留在“对话历史”层面,觉得把之前的消息都塞进上下文就是状态管理了。这在短任务里勉强能用,但长任务里完全不够。一个生产级Agent的状态至少包含以下几层:第一层是任务级状态,包括当前任务的目标、整体计划、每个步骤的执行状态(未开始/进行中/已完成/失败)、每个步骤的输出结果。这一层是任务恢复的基础,必须持久化。第二层是执行级状态,包括当前正在执行的步骤的中间状态,比如工具调用的参数、API返回的原始数据、模型推理的中间结果。这一层决定了任务能否从中断点精确恢复。第三层是上下文级状态,包括对话历史、模型已经“知道”的信息、当前上下文窗口里有哪些内容。这一层决定了Agent的“记忆”是否连贯。第四层是环境级状态,包括外部系统的状态,比如文件系统里写了哪些文件、数据库里插入了哪些记录、外部API的调用配额还剩多少。这一层最容易被忽略,但恢复时如果环境状态和Agent记忆不一致,会产生非常诡异的问题。我见过一个典型的翻车案例:Agent执行到第15步时往数据库里插入了一批数据,然后进程挂了。重启后Agent从第15步重新执行,又插入了一遍同样的数据,导致数据重复。这就是环境级状态没有和任务级状态同步的问题。解决方案要么是让每个步骤的操作幂等(比如用upsert代替insert),要么是在状态里记录“第15步已产生副作用”,恢复时跳过。3.2 状态持久化的时机与策略状态持久化不是越频繁越好,也不是越少越好,关键是在“恢复精度”和“性能开销”之间找平衡。我的经验是采用检查点(Checkpoint)机制:不是每一步都持久化完整状态,而是在关键节点做检查点。什么算关键节点?我的判断标准是:如果一个步骤失败后重做的成本很高,或者一个步骤产生了不可逆的副作用,就在这个步骤完成后立即做检查点。比如调用了一个昂贵的API、写入了一批数据、生成了一个重要的中间结果,这些步骤完成后必须持久化。而一些纯计算、可快速重做的步骤,可以攒几个一起持久化。持久化的内容也有讲究。完整持久化所有上下文会导致存储膨胀,而且恢复时加载大量无关历史反而会干扰模型。我的做法是分层持久化:任务级状态和执行级状态完整持久化,上下文级状态只持久化“摘要+关键引用”。具体来说,每个步骤完成后,让模型生成一个该步骤结果的摘要(100字以内),持久化这个摘要而不是完整输出。恢复时把摘要注入上下文,如果某个步骤需要完整输出,再从持久化存储里按需加载。class StateManager: def __init__(self, storage_backend): self.storage = storage_backend self.task_state = {} self.step_summaries = {} def checkpoint(self, task_id, step_id, step_result, is_critical=False): summary = self._summarize(step_result) self.step_summaries[step_id] = summary self.task_state["current_step"] = step_id self.task_state["completed_steps"].append(step_id) if is_critical: self.storage.save_full(task_id, { "task_state": self.task_state, "step_summaries": self.step_summaries, "full_result": step_result }) else: self.storage.save_incremental(task_id, { "step_id": step_id, "summary": summary }) def restore(self, task_id): state = self.storage.load(task_id) self.task_state = state["task_state"] self.step_summaries = state["step_summaries"] return self.task_state["current_step"]这个设计的核心思想是:恢复时不需要完整重放历史,只需要知道“执行到哪了”和“之前每步大概产出了什么”。具体的完整结果在需要时再按需加载。这样既控制了存储成本,又保证了恢复的精度。3.3 上下文窗口管理与信息压缩长任务绕不开的一个硬约束是上下文窗口。一个跑了30步的任务,如果每步的输出都塞进上下文,很容易就爆了。而且即使没爆,上下文太长也会导致模型注意力分散,关键信息被淹没。上下文管理的核心策略是分层压缩。我会把上下文分成三个区域:固定区(任务目标、整体计划、关键约束)、滚动区(最近N步的详细输出)、摘要区(更早步骤的压缩摘要)。固定区永远保留,滚动区保留最近5-10步的完整内容,摘要区用模型生成的摘要替代原始内容。摘要的生成时机很重要。我试过两种方案:一种是在步骤完成时立即生成摘要,另一种是攒一批再生成。立即生成的好处是信息新鲜、摘要质量高,坏处是每步都要多一次模型调用。攒批生成的好处是省调用,坏处是摘要时模型已经“忘了”细节,摘要质量下降。实测下来,对于关键步骤立即生成摘要,对于普通步骤攒3-5步批量生成,是一个比较好的平衡。还有一个技巧是用结构化摘要代替自然语言摘要。自然语言摘要容易丢失关键信息,而且不同步骤的摘要格式不统一,后续模型读取时理解成本高。我会要求模型按固定格式输出摘要:步骤ID: 15 动作: 调用竞品定价API 关键结果: 获取到3家竞品定价,A产品$29/月,B产品$49/月,C产品$19/月 数据引用: raw_data/step_15_pricing.json 状态: 成功这种结构化摘要的好处是,后续模型读取时能快速定位关键信息,而且程序化处理也方便。比如恢复时可以直接从摘要里提取“数据引用”字段,按需加载原始数据。注意事项:上下文压缩是有损的,一定要保留“数据引用”这种指针信息。我踩过的坑是早期只保留自然语言摘要,结果恢复时发现某个步骤的原始数据找不到了,只能重新执行,浪费了大量时间。4. 长任务恢复的完整实现方案4.1 中断检测与恢复触发机制长任务恢复的第一步是检测到中断发生了。中断的来源有很多种:进程崩溃、网络超时、模型API限流、工具调用失败、人工暂停。不同来源的中断,恢复策略不一样。进程崩溃和网络超时属于“意外中断”,恢复时应该从最后一个检查点继续。模型API限流属于“可重试中断”,应该等待一段时间后重试当前步骤,而不是跳过。工具调用失败属于“步骤级失败”,需要判断是重试、跳过还是终止整个任务。人工暂停属于“主动中断”,恢复时应该从暂停点继续,但可能需要重新确认一些状态。实现上,我会给每个步骤的执行包一层“执行器”,执行器负责捕获异常、判断异常类型、决定恢复策略:class StepExecutor: def __init__(self, state_manager, max_retries=3): self.state_manager = state_manager self.max_retries = max_retries def execute(self, step, context): retries = 0 while retries < self.max_retries: try: result = self._do_execute(step, context) self.state_manager.checkpoint( step["task_id"], step["id"], result, is_critical=step.get("is_critical", False) ) return result except RateLimitError: time.sleep(2 ** retries * 10) retries += 1 except ToolCallError as e: if step.get("allow_skip", False): return {"status": "skipped", "reason": str(e)} retries += 1 except Exception as e: self.state_manager.mark_failed(step["id"], str(e)) raise raise MaxRetriesExceeded(f"Step {step['id']} failed after {self.max_retries} retries")这个执行器的关键设计是区分异常类型。不是所有异常都应该重试,也不是所有异常都应该终止。限流异常用指数退避重试,工具调用异常根据步骤配置决定是否跳过,未知异常直接标记失败并向上抛。这样恢复逻辑才能精确处理不同情况。4.2 从检查点恢复的完整流程恢复流程的核心是重建执行上下文。具体来说,需要做以下几件事:第一,加载任务级状态,确定当前执行到哪一步、哪些步骤已完成、哪些步骤失败。第二,加载已完成步骤的摘要,重建上下文。第三,检查环境级状态,确认外部系统的状态和Agent记忆一致。第四,从当前步骤继续执行。这里最复杂的是第三步。环境级状态的检查需要针对具体任务设计。比如如果任务涉及文件操作,恢复时要检查文件系统里实际有哪些文件;如果涉及数据库操作,要检查数据库里的实际记录。我通常会在任务定义里声明“环境依赖”,恢复时按声明逐项检查。def restore_and_resume(task_id): state = state_manager.restore(task_id) current_step = state["current_step"] # 重建上下文 context = build_context( goal=state["goal"], plan=state["plan"], completed_summaries=state["step_summaries"] ) # 检查环境状态 env_issues = check_environment(state["env_dependencies"]) if env_issues: context = inject_env_warnings(context, env_issues) # 从当前步骤继续 remaining_steps = get_remaining_steps(state["plan"], current_step) for step in remaining_steps: executor.execute(step, context) context = update_context(context, step) return finalize_task(state)这个流程看起来简单,但实际实现时有几个细节要注意。第一个细节是“当前步骤”的确定:如果中断发生在步骤执行过程中,当前步骤是“未完成”状态,恢复时应该重新执行这一步。如果中断发生在步骤完成后、检查点写入前,那这一步实际上已经完成了但状态里没记录,恢复时会重复执行。所以检查点写入必须尽量靠近步骤完成,减少这个窗口期。第二个细节是上下文的“新鲜度”:恢复时重建的上下文是基于摘要的,可能丢失了一些细节。如果当前步骤对上下文细节要求很高,可能需要从持久化存储里加载更多原始数据。我的做法是在步骤定义里标注“上下文需求级别”,高需求的步骤恢复时加载完整历史,低需求的步骤只用摘要。第三个细节是幂等性保证:恢复时重新执行的步骤可能产生重复副作用。对于写操作,尽量用幂等的方式实现(upsert、去重写入等)。对于无法幂等的操作,在状态里记录“副作用已产生”的标记,恢复时检查标记决定是否跳过。4.3 恢复后的状态一致性校验恢复之后、继续执行之前,做一次状态一致性校验是非常有必要的。我见过太多因为状态不一致导致的诡异问题:Agent以为某个文件已经生成了,实际上没有;Agent以为某个API已经调用过了,实际上调用失败了但状态记录成了成功。一致性校验的核心是对比Agent记忆和实际环境。具体做法是:从状态里提取所有“应该存在”的产物(文件、数据记录、API调用结果),逐一验证它们是否真实存在且符合预期。如果发现不一致,有两种处理策略:一种是修正状态(以实际环境为准),另一种是重新执行产生该产物的步骤。def validate_consistency(state): issues = [] for step_id, summary in state["step_summaries"].items(): if summary.get("status") != "success": continue artifacts = summary.get("artifacts", []) for artifact in artifacts: if not verify_artifact(artifact): issues.append({ "step_id": step_id, "artifact": artifact, "issue": "artifact_missing_or_invalid" }) return issues校验发现的问题需要分类处理。如果是“产物缺失”,重新执行对应步骤。如果是“产物存在但内容不符”,需要判断是重新执行还是修正状态。如果是“产物存在但状态记录为失败”,修正状态即可。这个分类逻辑需要根据具体任务定制,没有通用方案。实操心得:一致性校验的成本不低,不需要每次恢复都做全量校验。我的做法是只校验“关键产物”(标记了is_critical的步骤的产物),普通产物在用到的时候再校验。这样既保证了关键路径的可靠性,又控制了恢复时间。5. 常见问题与排查技巧实录5.1 任务规划阶段的典型问题问题一:模型规划出无法执行的步骤。比如让Agent“分析用户情感倾向”,但前面根本没有搜集用户数据的步骤。这种问题的根源是模型在规划时没有考虑“信息可得性”。解决方案是在规划Prompt里加一条约束:“每个步骤的输入必须来自用户提供的初始信息或前序步骤的输出,不能假设存在未提供的信息。”问题二:规划步骤数量失控。模型有时候会把一个简单任务拆成三四十步,每步都很细。这会导致执行时间长、Token消耗大、出错概率高。解决方案是在Prompt里明确步骤数量范围(比如5-15步),并且要求“每个步骤应该是一个语义完整的动作,而不是一个原子操作”。问题三:依赖关系标注错误。模型可能漏标依赖,或者标错依赖方向。解决方案是规划后加一个校验调用,专门检查依赖关系的合理性。校验Prompt可以这样写:“以下是一个任务计划,请检查步骤之间的依赖关系是否完整且正确。特别注意:如果步骤B需要步骤A的输出作为输入,但B没有标注依赖A,这是一个错误。”问题四:规划粒度不均匀。有的步骤很粗(“做竞品分析”),有的步骤很细(“打开浏览器”)。这种不均匀会导致执行时有的步骤很快完成,有的步骤卡很久。解决方案是在Prompt里要求“所有步骤的预期执行时间应该在同一量级”,并且给出正反示例。5.2 状态管理阶段的典型问题问题一:状态膨胀导致存储成本失控。每个步骤都持久化完整输出,跑几十步之后存储占用巨大。解决方案是分层持久化,只对关键步骤存完整输出,普通步骤存摘要。问题二:恢复时上下文重建不完整。恢复后Agent“忘了”之前的一些关键信息,导致重复提问或做出错误决策。解决方案是恢复时不仅加载摘要,还要加载“关键决策记录”——记录之前做过哪些重要判断、基于什么信息做的。问题三:状态更新和实际执行不同步。状态记录某步成功了,实际执行失败了;或者反过来。解决方案是状态更新必须在执行结果确认之后进行,不能在执行前“预写”状态。问题四:并发步骤的状态冲突。多个步骤并行执行时,同时更新状态可能导致冲突。解决方案是状态更新加锁,或者用乐观并发控制(版本号机制)。5.3 长任务恢复阶段的典型问题问题一:恢复后重复执行已完成的步骤。根源是检查点写入不及时。解决方案是缩小检查点窗口,关键步骤完成后立即写入。问题二:恢复后环境状态不一致。Agent以为文件存在,实际不存在。解决方案是恢复时做一致性校验,以实际环境为准修正状态。问题三:恢复后陷入无限重试。某个步骤一直失败,恢复后重试还是失败,循环往复。解决方案是设置最大重试次数,超过后标记任务失败并通知人工介入。问题四:恢复后上下文过长导致模型性能下降。恢复时加载了太多历史摘要,上下文又爆了。解决方案是恢复时只加载“当前步骤需要的最小上下文”,其他历史按需加载。下面是一个常见问题的速查表,方便在实际排查时快速定位:问题现象可能原因排查方向解决方案任务执行到一半卡住某步骤死循环或等待超时检查当前步骤的执行日志设置步骤级超时,超时后标记失败恢复后重复执行检查点未及时写入检查检查点写入时机关键步骤完成后立即写入恢复后结果不一致环境状态与记忆不一致对比状态记录和实际环境恢复时做一致性校验上下文爆掉历史信息未压缩检查上下文管理策略启用分层压缩,摘要替代原文规划步骤无法执行模型假设了不存在的信息检查规划Prompt的约束加“信息可得性”约束依赖关系错误模型漏标或错标依赖检查依赖图是否有环规划后加依赖校验调用5.4 我踩过的几个印象深刻的坑第一个坑是**“成功”状态误报**。早期实现时,工具调用返回了HTTP 200就标记步骤成功,但实际上API返回的body里可能是错误信息。后来改成必须校验返回内容的业务字段,确认业务成功才算成功。这个坑让我意识到,状态标记不能只看“调用有没有报错”,要看“业务有没有达成”。第二个坑是恢复时的“幽灵依赖”。某个步骤在规划时没有标注依赖,但实际执行时用到了前序步骤的输出。恢复时因为前序步骤的摘要里没有这个信息,导致当前步骤执行失败。后来我在每个步骤的Prompt里都注入了所有已完成步骤的摘要,让模型自己判断需要哪些信息,而不是完全依赖显式依赖声明。第三个坑是检查点写入的原子性问题。状态写入过程中进程崩溃,导致状态文件损坏,恢复时读不出来。后来改成先写临时文件再原子重命名,保证状态文件要么是完整的旧版本,要么是完整的新版本,不会出现半写状态。第四个坑是恢复后的“上下文污染”。恢复时加载了之前失败步骤的错误信息,导致模型在后续步骤里一直“记得”那个错误,行为变得保守或异常。后来在恢复时会把失败步骤的错误信息标记为“已处理”,并在上下文里明确说明“该错误已修复,不影响后续执行”。这些坑的共同特点是:它们不会在Demo阶段暴露,只有在长任务、真实环境、异常情况下才会出现。这也是为什么面试官喜欢问这些问题的原因——能讲清楚这些细节的人,大概率是真的在生产环境里跑过Agent的。6. 面试中的高频追问与应对思路6.1 关于任务规划的追问面试官常问的一个问题是:“如果模型规划出来的计划执行到一半发现不可行,怎么办?”这个问题考察的是你对“动态重规划”的理解。我的回答思路是:首先,在执行过程中设置“计划校验点”,每执行完几个步骤就让模型评估“当前计划是否仍然合理”;其次,如果发现不可行,触发重规划,但重规划不是从头再来,而是基于已完成步骤的结果重新规划剩余步骤;最后,重规划时要保留已完成步骤的成果,避免浪费。另一个高频问题是:“怎么保证规划的步骤是可执行的?”这个问题可以从三个层面回答:Prompt层面加约束(信息可得性、步骤粒度、数量范围),校验层面加检查(依赖关系、环检测、可行性评估),执行层面加兜底(步骤失败后的重试、跳过、降级策略)。6.2 关于状态管理的追问“状态存在哪里?”这个问题看似简单,但能区分出有没有实战经验。我的回答会分场景:小规模用内存+定期快照,中规模用Redis或类似的内存数据库,大规模用关系型数据库或对象存储。关键不是用什么存储,而是“存什么”和“什么时候存”。我会强调分层持久化和检查点机制。“上下文窗口不够用怎么办?”这个问题考察上下文管理能力。我的回答框架是:分层压缩(固定区+滚动区+摘要区)、结构化摘要(代替自然语言摘要)、按需加载(用指针引用代替完整内容)、关键信息优先(任务目标和约束永远保留)。6.3 关于长任务恢复的追问“怎么判断一个任务需要恢复?”这个问题考察中断检测机制。我的回答是:通过心跳机制检测进程存活,通过步骤超时检测执行卡住,通过异常捕获检测执行失败。不同中断类型对应不同恢复策略。“恢复后怎么保证不重复执行?”这个问题考察幂等性设计。我的回答分两层:对于可幂等的操作(读操作、upsert写操作),直接重新执行;对于不可幂等的操作,在状态里记录副作用标记,恢复时检查标记决定是否跳过。同时,检查点写入要尽量靠近步骤完成,缩小重复执行的窗口。“恢复后状态不一致怎么处理?”这个问题考察一致性校验能力。我的回答是:恢复时做一致性校验,对比Agent记忆和实际环境;发现不一致时,以实际环境为准修正状态,或者重新执行产生不一致的步骤;关键产物必须校验,普通产物按需校验。6.4 面试中的加分项与减分项加分项包括:能说出具体的检查点策略和参数(比如“关键步骤完成后立即写入,普通步骤攒3-5步批量写入”);能区分不同中断类型的恢复策略;能讲出自己踩过的具体坑和解决方案;能给出可落地的代码结构或伪代码。减分项包括:只讲概念不讲实现;把所有状态都塞进上下文;恢复时从头开始执行;不考虑幂等性;不知道依赖管理;说不出具体的参数和阈值。我在实际面试别人的时候,最喜欢问的一个问题是:“如果你的Agent执行一个任务跑了两个小时,中间进程重启了三次,你怎么保证最终结果是正确的?”这个问题几乎覆盖了任务规划、状态管理、长任务恢复的所有核心点。能系统性地回答这个问题的人,基本可以判定为有实战经验。这个方向后续还可以往“多Agent协作下的状态同步”和“跨会话的长期记忆管理”两个方向扩展,前者解决多个Agent并行执行时的状态一致性问题,后者解决Agent在多次会话之间如何保持记忆连贯的问题。这两个方向目前在生产环境中的需求越来越大,也是面试中越来越常见的话题。