Skip to content

Agent 运行时与长任务面试题 ​

面向 AI 应用开发、平台工程和后端架构岗位。重点考察:如何把一个会调用模型和工具的 Agent,做成可恢复、可观测、可审计、能处理外部副作用的长任务系统。

答题主线 ​

想象一个报销 Agent:它已经向财务系统提交申请,网络却在响应前断了。运行时不能凭超时把状态写成失败并再次提交,而要保留幂等键,先查账,再决定成功、未知还是转人工。

遇到运行时或系统设计题,可以沿着下面的顺序回答:

text
任务边界与成功标准
  -> 状态机与持久化快照
  -> 队列调度与 Worker 租约
  -> 步骤执行、checkpoint 与事件
  -> 超时、重试、幂等与未知结果
  -> 取消、暂停、恢复与人工接管
  -> 审计、重放、指标和接口 schema

先区分三个概念:

  • 模型决策:模型可以提出下一步动作和参数,但不能直接改变任务状态或宣称副作用已经发生。
  • 运行时控制:服务端负责状态转移、预算、权限、超时、重试和终止条件。
  • 业务事实:外部系统的真实结果必须通过响应、查询、回调或对账确认,不能从一次网络异常中臆测。

一、任务边界与状态机 ​

Q1:什么时候应该把 Agent 请求设计成长任务? ​

考察点

是否能根据延迟、资源占用、外部等待和可靠性要求选择交互模型。

回答脉络

先定义同步请求的时间边界,再说明异步任务的用户体验和恢复能力,最后谈轮询、SSE 或 Webhook 只是通知方式。

详细参考答案

如果任务时长不可预测,或者包含批量处理、长时间检索、人工审批、外部异步回调、代码执行和多次模型调用,就应该建模为长任务。HTTP 请求只负责校验输入、创建任务并返回 task_id,不应让连接一直占住。任务本身由持久化状态和后台 Worker 驱动,客户端可以轮询状态,也可以通过 SSE 或 Webhook 接收进度。判断标准不能只看平均耗时,还要看 P95/P99、失败恢复和用户是否需要离开页面后继续执行。

常见追问及答案

  • 所有超过几秒的请求都异步吗? 不必。短且可取消、可重试的请求可以同步;应看尾延迟和副作用,而不是固定秒数。
  • SSE 能替代任务系统吗? 不能。SSE 只负责推送,连接断开后任务仍需依靠数据库、队列和 Worker 继续运行。

扣分点

  • 只说“用 Celery/Kafka”,没有任务持久化和恢复语义。
  • 把异步理解成一直保持 HTTP 长连接。
  • 只看平均延迟,不讨论尾延迟和断线。

Q2:请设计 Agent 长任务的状态机。 ​

考察点

是否能区分业务状态、执行状态和等待状态,并限制合法转移。

回答脉络

列出状态,再列出触发事件和非法转移,最后说明状态转移必须由服务端原子校验。

详细参考答案

我会使用有限状态机,而不是让模型填一个任意字符串。一个实用集合是:QUEUED、RUNNING、WAITING_EXTERNAL、WAITING_APPROVAL、PAUSED、SUCCEEDED、FAILED、CANCELLED、UNKNOWN。创建后进入 QUEUED,调度领取后进入 RUNNING;等待回调或轮询进入 WAITING_EXTERNAL,等待人工进入 WAITING_APPROVAL。可恢复的步骤失败可回到 QUEUED,不可恢复错误进入 FAILED;用户取消进入 CANCELLED。终态不能被普通请求改回运行中。每次转移都校验当前版本、操作者、原因和前置条件,并写入事件。

常见追问及答案

  • 为什么需要 UNKNOWN? 超时或连接断开不等于失败;尤其是外部写操作,必须先对账才能决定成功、失败或人工处理。
  • 暂停和等待审批有什么区别? 暂停是用户或策略主动冻结;审批是等待一个带身份和过期时间的决策凭证。

扣分点

  • 只有 running/success/fail 三个状态。
  • 允许模型直接输出 DONE 结束任务。
  • 没有终态、事件和非法转移处理。

Q3:怎样保证状态转移不会被并发请求覆盖? ​

考察点

是否理解乐观锁、幂等事件和状态转移原子性。

回答脉络

先讲数据库版本号,再讲重复事件和并发冲突,最后说明重试策略。

详细参考答案

任务表保存 version,更新时使用条件写:UPDATE tasks SET status=?, version=version+1 WHERE task_id=? AND version=?。受影响行数为零表示版本冲突,需要重新读取后根据当前状态重算,而不是强行覆盖。事件表以 event_id 建唯一约束,消费者重复收到同一事件时直接返回已经处理的结果。状态机函数应集中定义“当前状态 + 事件 -> 新状态”,数据库事务中同时更新快照和写入事件,确保两者不会只成功一半。

常见追问及答案

  • 悲观锁还是乐观锁? 任务转移通常短事务、冲突可重试,优先乐观锁;需要串行执行同一任务时可加短时行锁。
  • 事件先写还是快照先写? 在同一数据库事务内同时写;跨系统发布则用 Outbox,不能依赖两个独立请求的顺序。

扣分点

  • 只在应用内加锁,忽略多实例。
  • 读取后无条件更新。
  • 把“消息只投递一次”当成可靠前提。

二、队列与 Worker ​

Q4:如何设计任务队列和 Worker 的领取协议? ​

考察点

是否能处理并发领取、崩溃、可见性超时和租约。

回答脉络

讲任务入队、原子领取、租约字段、心跳续租和过期回收。

详细参考答案

任务创建事务写入任务表和 Outbox,发布器再把可调度消息投到队列。Worker 领取时用数据库原子更新或队列的 visibility timeout,把 QUEUED 任务改为 RUNNING,写入 worker_id、lease_token、lease_expires_at。后续保存进度、checkpoint 和结果时必须携带租约令牌;令牌失效的旧 Worker 即使恢复,也不能提交状态。Worker 定期心跳续租,崩溃后由回收器把过期任务重新入队或标记为 UNKNOWN。队列至少一次投递是常态,因此执行逻辑必须幂等。

常见追问及答案

  • 为什么还需要数据库状态? 队列适合唤醒和削峰,不能完整承载用户查询、审计、恢复和状态机语义。
  • 两个 Worker 同时执行怎么办? 通过原子领取和 fencing token;即使重复执行,也只能有持有效令牌的一方写入。

扣分点

  • 用“先查再改”领取任务。
  • 没有租约过期和僵尸 Worker 防护。
  • 默认队列 exactly-once。

Q5:一个长任务包含 20 个子任务,怎样做公平调度? ​

考察点

是否理解并发配额、租户隔离、背压和子任务粒度。

回答脉络

先拆分并持久化,再讲调度优先级、配额和资源预算,最后讲聚合进度。

详细参考答案

把父任务和子任务分开建模,每个子任务有输入快照、状态、重试次数和依赖。调度器按租户、队列和优先级实施配额,可采用加权公平队列,避免一个大任务占满所有 Worker。对子任务设置并发上限和单项预算,失败只重试受影响的项。父任务的状态由子任务聚合得出:全部成功才成功;允许部分成功时要明确成功阈值并保留失败清单。高峰时通过队列长度、Worker 利用率和外部限额做背压。

常见追问及答案

  • 是否一次把 20 个任务都投队列? 可以先持久化,但不一定全部立即唤醒;调度器按配额分批投递。
  • 父任务进度怎么算? 用已完成、失败、运行中和总数计算,并注明权重;不能把模型输出的百分比当事实。

扣分点

  • 一个 Worker 顺序跑完所有子任务,没有断点。
  • 不区分租户和优先级。
  • 失败一个子任务就丢失全局结果。

Q6:Worker 崩溃后,任务怎样自动恢复? ​

考察点

是否能区分可重试失败、租约过期和外部结果未知。

回答脉络

从心跳失效开始,说明回收、恢复点选择、重试上限和人工升级。

详细参考答案

回收器扫描 RUNNING 且 lease_expires_at < now() 的任务,先记录 lease_expired 事件,再根据最近 checkpoint 和当前步骤类型决定重新入队。如果步骤是纯计算或幂等读,可以从该步骤重试;如果正在执行外部写操作,不能直接重放,应先进入 UNKNOWN,调用查询接口或对账流程确认。重试次数、退避时间和错误分类存入数据库,超过上限进入 FAILED 或 WAITING_APPROVAL。恢复必须使用新的租约令牌,旧 Worker 的迟到写入全部拒绝。

常见追问及答案

  • 租约过期就一定是 Worker 死了吗? 不一定,可能是网络分区或长 GC;所以要用 fencing 且允许对账。
  • 任务恢复会不会丢进度? checkpoint 只在事务确认后成为恢复点,进度事件用于展示,不能代替恢复数据。

扣分点

  • 发现心跳断了就直接标失败。
  • 从头重跑整个长任务。
  • 没有对外部副作用设置不同策略。

三、进度事件与客户端体验 ​

Q7:如何设计进度事件,让客户端实时展示? ​

考察点

是否能设计可排序、可去重、可重放的事件流。

回答脉络

讲事件 schema、顺序、持久化、推送和断线补偿。

详细参考答案

事件至少包含 event_id、task_id、type、sequence、aggregate_version、occurred_at、producer、payload_schema_version 和脱敏后的 payload。例如 STEP_STARTED、STEP_PROGRESS、WAITING_APPROVAL、STEP_FAILED、TASK_SUCCEEDED。重要事件先持久化,再由 SSE/Webhook 推送;客户端带最后收到的序号重连,服务端从事件表补发。展示层允许事件延迟和乱序,任务详情接口始终返回当前权威快照。

常见追问及答案

  • 百分比由谁计算? 运行时根据已完成工作量或明确权重计算;模型只能提供阶段说明。
  • 事件表会无限增长吗? 热数据和归档分层,按任务或租户保留策略归档,但审计所需原始证据不能无规则删除。

扣分点

  • 只在内存里广播进度。
  • 没有序号、去重和断线重连。
  • 把日志文本直接当 API 契约。

Q8:如何处理进度事件重复、乱序和迟到? ​

考察点

是否理解事件时间、序列号和状态版本不是同一概念。

回答脉络

先定义幂等和排序规则,再说明客户端容错和服务端校验。

详细参考答案

事件使用全局唯一 event_id 和同一任务内单调递增的 sequence。消费者以事件 ID 去重,客户端按序号合并;迟到事件不能回滚已确认的终态,只能作为历史记录显示。服务端事件处理根据 aggregate_version 做条件更新,版本不匹配时重新读取任务并判断事件是否仍合法。事件的 occurred_at 用于审计,不能拿来替代排序序号,因为时钟可能漂移。

常见追问及答案

  • 序号由多个 Worker 生成会冲突吗? 在聚合根事务中分配,或由事件存储提供单调序列,不能靠本地时间。
  • 客户端错过事件怎么办? 重连时带 cursor,从事件存储补发;若已归档则返回当前快照和“历史不可用”的明确标识。

扣分点

  • 只按时间戳排序。
  • 事件重复就重复累加进度。
  • 允许旧事件覆盖新终态。

四、取消、暂停与恢复 ​

Q9:如何实现取消任务? ​

考察点

是否理解协作式取消、不可中断步骤和外部副作用。

回答脉络

先记录取消意图,再让执行器观察 token,最后说明确认取消与补偿。

详细参考答案

取消接口不直接杀进程,而是在事务中写入 cancel_requested_at、操作者和原因,并发布唤醒事件。Worker 在步骤边界、工具调用前后和循环预算检查取消标记;支持取消的代码执行通过 cancellation token 中断。不可中断的外部调用只能等待返回或进入 UNKNOWN,不能把客户端的“取消成功”误报成副作用已撤回。所有子任务传播取消,最终只有在没有不可逆动作继续执行后才转为 CANCELLED;必要时创建补偿任务。

常见追问及答案

  • 用户点取消后立即返回什么? 返回“取消请求已受理”和当前状态;只有状态机确认后才返回已取消。
  • 强杀 Worker 可以吗? 只适合隔离失控资源,不能作为业务取消语义,否则会遗留未知外部结果。

扣分点

  • 只发一个进程 kill。
  • 取消后仍允许旧 Worker 写成功结果。
  • 没讨论已发出的外部请求。

Q10:暂停、恢复和从 checkpoint 重试有什么区别? ​

考察点

是否能区分用户控制、故障恢复和新的执行分支。

回答脉络

分别定义语义、状态、权限和数据来源。

详细参考答案

暂停是对尚未结束的任务设置停止调度或协作式停止标志,保留当前版本和租约释放后的恢复意图;恢复是从同一任务的最新有效 checkpoint 继续。checkpoint 重试通常是失败后选择某个历史步骤开新执行分支,可能替换输入或策略,不应覆盖原始执行记录。恢复需要重新校验配置版本、权限、预算和外部状态;如果原任务已发生不可逆副作用,则只能从对账后的安全边界继续。接口应记录操作者、理由和所选 checkpoint。

常见追问及答案

  • 暂停能在任意指令中生效吗? 只能保证在安全检查点或可中断边界生效;要向用户说明延迟。
  • 恢复是否沿用旧租约? 不沿用,必须获取新租约和新 fencing token。

扣分点

  • 把暂停等同于删除任务。
  • 没有 checkpoint 选择和配置版本校验。
  • 恢复时无条件从第一步重跑。

五、Checkpoint 与 Durable Execution ​

Q11:一个 Agent checkpoint 应该保存什么? ​

考察点

是否知道恢复需要确定性输入、上下文和副作用边界。

回答脉络

按任务、步骤、模型上下文、工具结果和运行时元数据分类。

详细参考答案

checkpoint 至少保存:任务和执行分支 ID、状态机版本、工作流或配置版本、当前步骤、已确认的结构化输入输出、模型请求摘要和模型版本、工具调用及响应引用、重试计数、预算消耗、下一步候选、随机性配置、依赖的知识库版本,以及恢复所需的临时文件地址。大对象放对象存储,checkpoint 只保存内容哈希和不可变引用。敏感内容加密和字段级脱敏。只有事务提交后的 checkpoint 才能成为恢复点。

常见追问及答案

  • 要保存完整 prompt 吗? 对可审计任务通常要保存版本化模板和经过策略处理的实际输入,但应按敏感等级加密、脱敏和限留。
  • 保存模型输出就够了吗? 不够,还要保存工具结果、配置、权限上下文和状态版本,否则无法解释或复现。

扣分点

  • 只保存一段聊天记录。
  • 保存可变 URL,不保存内容版本或哈希。
  • checkpoint 写入没有原子性。

Q12:什么是 durable execution?为什么不能只靠重试? ​

考察点

是否理解“进程存活”和“执行事实持久化”的区别。

回答脉络

解释持久化工作流、确定性恢复、外部调用边界和代价。

详细参考答案

durable execution 指任务即使进程、机器、部署或网络发生故障,也能依据持久化的工作流历史和 checkpoint 继续执行,而不是依赖某个内存中的调用栈。运行时把每个可恢复步骤、输入输出、状态转移和副作用确认写入持久存储;恢复时从最后一个已提交边界继续。模型本身有随机性,因此要记录请求配置和结果,不能期待重新调用得到同样输出。对外部调用要通过幂等键、查询或 Outbox 等协议封装。重试只是失败处理的一部分,不能解决状态丢失和未知结果。

常见追问及答案

  • durable execution 等于 event sourcing 吗? 不等于。它可以使用事件溯源,也可以用快照加事件日志;关键是恢复所需事实持久化。
  • 每个 token 都 checkpoint 吗? 通常不需要;按步骤、批次或安全边界保存,在恢复成本和写放大之间取平衡。

扣分点

  • 把 Redis 临时缓存当持久化事实。
  • 重启后只重新调用模型。
  • 没有版本、输入和副作用边界。

Q13:Worker 在外部调用后宕机,重试会不会重复扣款? ​

考察点

是否能处理响应丢失导致的“未知结果”。

回答脉络

先承认不能从超时推断失败,再讲幂等键、查询对账和人工升级。

详细参考答案

请求发出后 Worker 宕机,系统可能不知道扣款是否成功,不能简单重试。首次调用必须使用由任务和业务动作确定的幂等键,服务端保存请求哈希、供应商请求 ID 和状态。恢复时先按幂等键查询供应商,或者通过账单、订单和支付流水对账;确认未执行才允许重新提交,确认成功则补写本地结果,仍无法确认就进入 UNKNOWN 并人工处理。业务数据库中不能把“HTTP 超时”直接映射为“支付失败”。

常见追问及答案

  • 供应商不支持幂等键怎么办? 使用本地动作表加对账查询,尽量改为预授权/确认两阶段;无法证明安全时禁止自动重试。
  • 幂等键可以每次重试都变吗? 对同一个业务动作不应变,否则供应商会认为是不同请求。

扣分点

  • 以指数退避包裹所有 POST。
  • 把超时当失败。
  • 没有业务动作 ID 或对账路径。

六、超时、租约与心跳 ​

Q14:任务超时、步骤超时、工具超时应如何区分? ​

考察点

是否能进行分层预算和安全收口。

回答脉络

给出三层定义,再说明剩余时间传递、超时分类和未知结果。

详细参考答案

任务超时是整个用户目标的截止时间;步骤超时是某个可恢复单元的最大执行时间;工具超时是单次网络调用的等待上限。父级预算应向子级传递 deadline,不能每层都重新计时导致总时长失控。工具超时后先分类:只读请求通常可重试,写请求先查询或对账。步骤超时可释放租约并重入队,但要保留超时证据;任务超时通常停止新动作,进入 FAILED、CANCELLED 或 UNKNOWN,取决于最后一个副作用边界。

常见追问及答案

  • 为什么不只设一个 30 分钟超时? 一个总超时无法限制单个卡死步骤,也无法给不同工具设置合理 SLA。
  • 超时后能立即释放资源吗? 可终止的计算可以释放;不可中断的外部动作必须保留对账状态。

扣分点

  • 只设置网关超时。
  • 超时重试不考虑任务剩余预算。
  • 任何超时都改成 FAILED。

Q15:心跳和租约应该怎样设计? ​

考察点

是否理解活性信号、进度事实和 fencing 的关系。

回答脉络

讲租约期限、续租条件、心跳内容、抖动容忍和失联回收。

详细参考答案

Worker 领取任务时获得有期限的租约和单调递增的 fencing token。心跳携带任务 ID、token、当前步骤、最后 checkpoint、资源使用和预计下一次边界;服务端只接受 token 未过期且版本匹配的续租。租约应明显大于正常心跳间隔,并留出网络抖动余量;续租失败时 Worker 停止产生副作用并自我隔离。回收器发现过期租约后重新调度纯计算任务,对外部动作则先对账。心跳证明 Worker 还在,不证明任务业务结果正确。

常见追问及答案

  • 为什么需要 fencing token? 旧 Worker 可能网络恢复后继续写,token 能让存储层拒绝它。
  • 心跳频率越高越好吗? 不是,要平衡检测延迟、数据库负载和网络抖动,可使用批量或自适应间隔。

扣分点

  • 只用 updated_at 判断活跃。
  • 没有令牌校验。
  • 心跳正常就认为任务没有卡死。

七、重试与幂等 ​

Q16:如何设计 Agent 长任务的重试策略? ​

考察点

是否能按错误类型、动作类型和预算决定重试。

回答脉络

先分类错误,再给退避和上限,最后讲观测与人工升级。

详细参考答案

先把错误分为参数或权限错误、限流、临时网络错误、服务端 5xx、模型内容失败、资源耗尽和外部结果未知。参数、权限和 schema 错误通常不重试;限流采用带抖动的指数退避并遵守 Retry-After;临时失败在任务 deadline 内有限次数重试。模型输出校验失败可以按更低温度或修复提示重试,但要限制轮数。每次重试记录原因、延迟、输入哈希和成本,达到上限进入失败或人工队列。写操作额外要求幂等或先对账。

常见追问及答案

  • 指数退避的上限是什么? 受任务 deadline、供应商限额和业务 SLA 共同限制,不能无限等待。
  • 重试会不会放大故障? 会,所以要加全局并发上限、熔断、抖动和按租户配额。

扣分点

  • 所有异常统一重试三次。
  • 没有退避、抖动和总预算。
  • 只统计最终失败,不记录重试成本。

Q17:幂等键怎样设计才可靠? ​

考察点

是否理解业务语义、请求哈希和结果复用。

回答脉络

说明粒度、生命周期、冲突处理和供应商透传。

详细参考答案

幂等键代表一次业务动作,而不是一次网络尝试。例如 task_id + step_id + action_type,需要区分不同版本或用户明确发起的新动作。动作表以幂等键建唯一约束,同时保存规范化请求哈希、执行状态、外部请求 ID 和最终结果。相同键且参数哈希不同应报冲突,防止误复用;相同键重试则返回原结果或当前处理中状态。键的保留期必须覆盖业务重试、对账和供应商窗口,不能只存在内存缓存里。

常见追问及答案

  • 只用用户的 Idempotency-Key 可以吗? 可以作为入口键,但服务端要绑定租户、接口和请求哈希,不能跨业务复用。
  • 读请求也需要幂等键吗? 读通常天然幂等,但分页快照、计费检索或昂贵任务仍可能需要请求去重。

扣分点

  • 用随机 UUID 作为每次重试的键。
  • 只在 API 网关缓存结果。
  • 不校验相同键的请求内容。

八、外部副作用、Outbox、Saga ​

Q18:如何把“工具调用成功”和“业务动作成功”区分开? ​

考察点

是否会定义响应 schema、确认来源和未知状态。

回答脉络

区分受理、完成、失败和未知四种语义,并说明查询接口。

详细参考答案

工具 HTTP 返回 200 只说明某一层收到请求,不一定说明业务动作完成。工具输出应结构化为 accepted、completed、failed 或 unknown,包含业务动作 ID、外部请求 ID、状态、可查询时间和证据引用。运行时只有拿到业务服务的明确完成状态,或通过查询/回调确认,才能写入成功;连接超时、代理断开和供应商无响应应写成未知。模型最终回答要根据权威状态生成,不能把工具原始文本原样当事实。

常见追问及答案

  • 供应商只返回 202 怎么办? 进入等待外部状态,依靠回调或轮询查询最终结果。
  • 工具返回“success”字符串够吗? 不够;应有稳定的版本化输出 schema 和业务对象 ID。

扣分点

  • 以 HTTP 200 直接判业务成功。
  • 把自然语言工具结果当协议。
  • 没有查询或回调路径。

Q19:为什么需要 Outbox?它解决不了什么? ​

考察点

是否理解本地事务与消息发布的一致性边界。

回答脉络

先讲双写问题,再讲 Outbox 流程和重复消费限制。

详细参考答案

任务状态更新和“需要唤醒 Worker”的消息如果分两次写,可能出现数据库成功但消息丢失,或消息先发而事务回滚。Outbox 把业务状态、事件和待发布消息写在同一个本地事务中,发布器扫描未发送记录投递到队列,成功后标记发送时间;发布器崩溃可重复投递,因此消费者必须幂等。Outbox 解决的是本地数据库到消息系统的可靠发布,不自动保证消息消费、外部 API 和数据库之间的全局 exactly-once,也不能替代幂等键和对账。

常见追问及答案

  • Outbox 表很大怎么办? 按状态和时间索引,批量发布、归档已发送记录,必要时按租户分片。
  • 消息发布成功但标记失败怎么办? 会重复发布,消费者用 event_id 去重,这是设计的一部分。

扣分点

  • 认为 Outbox 能保证所有系统原子提交。
  • 没有发布重试和去重。
  • 直接在事务里同步调用队列或外部 API。

Q20:什么时候用 Saga 和补偿事务? ​

考察点

是否知道长事务不能依赖分布式锁和全局回滚。

回答脉络

定义步骤、补偿动作、正向/反向状态和不可补偿情况。

详细参考答案

跨多个服务且每一步都可能产生独立副作用时,可以用 Saga 把流程拆成可持久化步骤,并为已完成步骤定义业务补偿。例如创建订单、预留库存、支付、发通知,失败后按逆序释放库存或撤销预授权。补偿不是数据库回滚,可能失败、延迟或只能部分恢复,所以要有补偿重试、人工接管和对账。先判断动作是否可逆、补偿窗口和业务接受度;对不可逆的发送、转账或发布,通常要加审批、状态查询和人工处理,而不是承诺自动回滚。

常见追问及答案

  • Saga 一定逆序补偿吗? 常见是逆序,但要按业务依赖和实际补偿语义设计。
  • 补偿也失败怎么办? 持久化补偿状态,继续有限重试,告警并转人工;不能静默吞掉。

扣分点

  • 把 Saga 说成分布式事务回滚。
  • 每个动作都没有补偿定义。
  • 忽略补偿本身可能产生副作用。

九、人工接管、重放与审计 ​

Q21:哪些情况必须人工接管?交接信息应包含什么? ​

考察点

是否能识别不可自动判定和高风险动作。

回答脉络

列出触发条件,再设计人工队列、锁定和交接包。

详细参考答案

外部写操作结果未知、重复风险无法证明安全、金额或权限超策略、连续重试耗尽、模型输出无法通过校验、来源冲突、敏感数据泄露疑虑以及补偿失败,都应进入人工队列。交接包包含任务目标、租户和操作者、配置版本、状态时间线、最近 checkpoint、工具请求与响应摘要、外部业务 ID、已确认副作用、待决策选项、风险和推荐动作。人工决策必须带身份、理由、过期时间和一次性 approval ID;执行前再次校验任务版本和业务状态,避免旧审批放行新动作。

常见追问及答案

  • 人工可以直接改数据库状态吗? 不应绕过状态机;应使用带权限和审计的管理接口。
  • 如何防止审批后参数被改? 审批凭证绑定规范化请求哈希、资源、金额、版本和有效期。

扣分点

  • 只说“失败后通知管理员”。
  • 人工页面没有上下文和动作影响范围。
  • 审批不绑定具体参数。

Q22:重放和重试有什么区别?如何安全重放? ​

考察点

是否理解诊断性重放和真实副作用的隔离。

回答脉络

先定义两者,再说明快照、版本、沙箱和脱敏。

详细参考答案

重试是继续原执行,目标是完成业务任务;重放是依据历史输入和事件,在隔离环境复现流程,用于排障、评估或审计。安全重放默认不触达真实写工具,外部调用使用录制的响应或模拟器;如果必须真实验证,要单独审批、使用新的演练租户和明确动作 ID。重放记录原任务 ID、分支 ID、代码/配置/模型版本、输入哈希和操作者,并与生产事件链区分。由于模型非确定,要固定温度、种子(若支持)和工具响应,同时把差异标记为诊断结果。

常见追问及答案

  • 为什么不能直接从第一步重试? 可能重复外部副作用,也会丢失当时的权限和输入上下文。
  • 重放结果不同说明谁错? 先比较版本、工具响应和随机性;重放用于发现差异,不自动改写原始事实。

扣分点

  • 重放直接调用生产写接口。
  • 没有分支 ID 和版本记录。
  • 把重放输出覆盖原任务结果。

Q23:长任务审计日志要记录什么? ​

考察点

是否做到可追责、可回放、可脱敏和防篡改。

回答脉络

按身份、决策、工具、状态、数据和安全事件分类,再说明保留策略。

详细参考答案

审计记录应串联 trace_id、task_id、step_id、event_id、租户、操作者、配置和模型版本、状态转移、输入输出摘要、工具名称和版本、资源范围、授权与审批凭证、幂等键、外部业务 ID、时间、延迟、错误和补偿动作。原始敏感数据与普通观测分离,日志脱敏、加密、访问控制并设置保留期限。关键事件追加写入或使用哈希链/不可篡改存储,支持按任务重建时间线,但不把模型自由文本当唯一证据。

常见追问及答案

  • 是否记录完整 prompt 和返回内容? 按合规和故障排查需要分级保存,敏感字段可加密或哈希,至少保存版本、摘要和引用。
  • 日志和审计是一回事吗? 日志偏运行排错,审计强调主体、授权、动作和不可抵赖,存储和权限要求更严格。

扣分点

  • 只记录最终回答。
  • 把银行卡、密钥等敏感信息原样写日志。
  • 没有操作者、配置版本或外部业务 ID。

十、接口 Schema 与数据模型 ​

Q24:请设计长任务 API 的核心接口和 schema。 ​

考察点

是否能把异步语义、幂等、版本和错误表达清楚。

回答脉络

从创建、查询、事件、控制和人工决策五类接口展开。

详细参考答案

核心接口可以是:

http
POST /v1/tasks
Idempotency-Key: create-report-user-42-20260902

{
  "type": "research_report",
  "input": {"topic": "供应链风险"},
  "config_version": "agent-report:v7",
  "deadline_at": "2026-09-02T12:00:00Z"
}

201 Created
{
  "task_id": "task_01H...",
  "status": "QUEUED",
  "version": 1,
  "poll_url": "/v1/tasks/task_01H..."
}

GET /v1/tasks/{id} 返回权威快照、进度摘要、当前步骤、错误、结果引用和 ETag;GET /v1/tasks/{id}/events?after=... 支持补发;POST /cancel、POST /pause、POST /resume 使用版本条件和幂等控制。所有响应带 schema_version,错误区分参数、权限、冲突、未知和暂时不可用,不能只返回一段字符串。

常见追问及答案

  • 创建接口返回 200 还是 202? 若任务已受理但未完成,用 202 更准确;若资源已创建并返回任务,201 也可以,关键是契约说明。
  • 如何防止旧客户端覆盖新控制请求? 使用 If-Match/版本号和服务端状态机校验。

扣分点

  • API 同步等待模型完成。
  • 没有幂等键、版本和错误 schema。
  • 让客户端直接写状态字段。

Q25:任务、步骤、事件和动作表如何建模? ​

考察点

是否能把快照、历史、执行租约和外部副作用分离。

回答脉络

先给核心字段,再说明索引、一致性和大对象存储。

详细参考答案

可以设计四类核心表:

表关键字段作用
taskstask_id、tenant_id、status、version、config_version、deadline、result_ref当前任务快照
stepsstep_id、task_id、kind、status、attempt、checkpoint_ref、lease_token可恢复步骤和执行尝试
task_eventsevent_id、task_id、sequence、type、payload、schema_version历史时间线和通知游标
external_actionsaction_id、idempotency_key、request_hash、provider_id、status、result_ref外部副作用和对账

另设 outbox 保存待发布事件。任务按租户和状态索引,事件按 task_id, sequence 索引,动作键唯一。正文、附件和大模型上下文放加密对象存储,表里保存不可变引用和哈希。快照更新与领域事件在同一事务中完成,跨系统发布走 Outbox。

常见追问及答案

  • 为什么不把所有内容塞进 tasks JSON? 会导致并发更新、索引、审计和局部重试困难;结构化字段与不可变引用更适合长期运行。
  • 事件能完全替代 tasks 快照吗? 小规模可事件溯源,但查询和恢复通常需要快照或物化视图。

扣分点

  • 一个表存任务、日志、工具结果和附件。
  • 没有租户字段和唯一约束。
  • 没有外部动作表,无法对账。

十一、综合系统设计 ​

Q26:设计一个支持人工审批和外部异步调用的 Agent Runtime。 ​

考察点

是否能把状态、队列、工具、审批、事件和数据存储串成闭环。

回答脉络

先澄清规模与 SLA,再给模块和主流程,最后讲故障、观测和安全。

详细参考答案

入口层负责认证、租户限流、幂等创建和输入 schema;任务服务持有状态机、任务快照和事件;调度器从 Outbox 投递可执行步骤,Worker 领取带租约的工作;编排器驱动模型循环,但模型只能返回结构化的下一步意图。工具网关负责 schema、权限、预算、幂等和超时;外部动作写入动作表,异步结果通过回调或轮询回到状态机。审批服务发放绑定参数哈希的凭证。事件存储驱动 SSE、Webhook 和审计,checkpoint 存对象存储或专用表,指标系统记录任务成功率、步骤延迟、重试、未知结果和成本。

主流程是:创建 QUEUED 任务 -> Worker 执行规划和步骤 -> 每个步骤校验输出并 checkpoint -> 高风险动作进入 WAITING_APPROVAL -> 审批后再次校验版本并执行 -> 外部 202 进入 WAITING_EXTERNAL -> 回调/查询确认 -> 汇总结果并进入终态。故障时依靠租约回收、幂等动作、对账、补偿和人工队列收口。

常见追问及答案

  • 如何水平扩展? 任务表按租户或哈希分片,队列按能力和优先级分区,Worker 无状态化,checkpoint 和事件使用可扩展存储。
  • 最先做哪些指标? 任务端到端成功率、P95 时延、各状态停留时间、工具错误率、重试次数、未知副作用数、队列等待和每任务成本。

扣分点

  • 只有“API + LLM + 队列”的框图。
  • 没有人工审批和外部结果确认。
  • 没有租约、checkpoint、幂等和审计。

Q27:如何保证长任务系统的安全与多租户隔离? ​

考察点

是否把 Agent 运行时当成受控执行平台,而不是普通聊天接口。

回答脉络

从身份、对象级授权、工具隔离、数据和审计五层说明。

详细参考答案

每个任务绑定租户、用户、Agent 配置版本和授权上下文,所有任务、步骤、事件、checkpoint、工具资源查询都做服务端对象级授权,不能信任模型生成的 tenant_id 或用户 ID。工具按风险分级,读、草稿、外部写和高危执行分开;高风险动作需要确认或审批,执行时使用最小权限短期令牌。不同租户的队列配额、对象存储前缀、向量检索过滤和缓存键都隔离。提示注入内容视为数据,不能获得系统权限。日志和 checkpoint 加密、脱敏、审计,代码执行使用沙箱、资源上限和默认禁网。

常见追问及答案

  • 模型能否决定需要什么权限? 只能提出意图;权限由服务端策略根据真实身份和资源计算。
  • 缓存如何避免跨租户泄露? 缓存键包含租户、主体、资源范围和配置版本,敏感结果默认不做共享缓存。

扣分点

  • 只依赖 prompt 约束越权。
  • 工具共享超级账号。
  • 忽略缓存、向量库和日志中的跨租户泄露。

Q28:长任务运行时如何控制成本、延迟和质量? ​

考察点

是否能把预算变成运行时约束,并用数据形成质量闭环。

回答脉络

先讲预算模型,再讲分层策略和降级,最后讲评估指标。

详细参考答案

任务创建时设置总 token、模型调用次数、工具次数、并发、时间和金额预算;每步原子扣减预算,剩余预算传递给子步骤,超过阈值停止、降级或请求人工。只读检索可缓存,但缓存键需包含权限和知识库版本;简单分类使用小模型,复杂规划再升级;限流时优先级队列保护关键租户。质量上同时评估最终结果、工具选择、引用完整性、安全违规、重试和成本,按配置版本做回归。不能为了降低成本无限减少验证步骤,尤其是高风险副作用。

常见追问及答案

  • 模型调用次数怎么限制? 在运行时计数并持久化,达到上限进入明确的预算失败,而不是继续循环。
  • 降级会影响审计吗? 降级后的模型、策略和原因必须写入事件,结果仍要经过同样的权限和输出校验。

扣分点

  • 只在 prompt 里说“节省 token”。
  • 只看 token 成本,不看重试和外部 API 成本。
  • 无预算终止条件和质量指标。

十二、场景追问 ​

Q29:一个任务卡在 99%,你如何排查? ​

考察点

是否能从用户现象定位到状态、子任务、租约、事件和外部依赖。

回答脉络

先看权威快照,再看事件和 trace,最后核对租约、依赖和进度计算。

详细参考答案

先根据 task_id 查询任务快照、版本、当前步骤、截止时间和最后心跳;再查看事件序列和 trace,确认 99% 是哪个子任务计算出来的。检查该子任务是否仍有租约、是否在等待外部回调、是否重复重试、是否被限流或因进度权重错误没有完成收口。若外部写结果未知,先走动作查询而不是重试。修复后通过状态机或补偿接口推进,不能直接把百分比改成 100%。最后补充指标和告警,避免“长时间无事件但心跳正常”的假活跃。

常见追问及答案

  • 只有心跳没有新事件说明什么? 可能是 Worker 卡在不可观测的调用,需按步骤超时、工具 trace 和资源监控判断。
  • 能不能手动改进度? 只能修正计算或补写事实事件,不能用显示字段掩盖未完成步骤。

扣分点

  • 只重启 Worker。
  • 只看应用日志,不查任务和外部动作。
  • 直接把状态改为成功。

Q30:如何做长任务运行时的测试? ​

考察点

是否覆盖时间、并发、故障和副作用,而不只测正常路径。

回答脉络

从单元、契约、集成、故障注入和性质测试分层回答。

详细参考答案

状态机单测覆盖每个合法和非法转移、终态不可回退、版本冲突和重复事件;schema 契约测试覆盖模型输出、工具响应、回调和 API 版本兼容。集成测试验证数据库事务、Outbox、队列、Worker 租约和 SSE 补发。故障注入测试包括 Worker 宕机、网络分区、消息重复/乱序、数据库重启、模型超时、供应商 202、响应丢失和补偿失败。对外部写动作使用沙箱供应商,断言同一幂等键不会产生重复业务动作。再做压力、租户隔离、预算耗尽和安全回归测试。

常见追问及答案

  • 如何验证不会重复扣款? 用可查询的模拟支付服务和唯一动作表,随机在请求前后杀死 Worker,最后核对供应商动作数为一。
  • 复杂页面测试要不要全自动? 先测服务端契约和事件流,页面只覆盖关键用户路径;长时间压力测试单独运行并限制资源。

扣分点

  • 只有 happy path 单测。
  • 用 sleep 模拟所有故障。
  • 没有断言外部副作用次数和最终一致性。

Q31:设计完成后,如何判断这个 Runtime 真的可上线? ​

考察点

是否有明确的上线门槛、回滚和运营闭环。

回答脉络

按可靠性、安全、可观测性、成本和运维准备度给验收标准。

详细参考答案

上线前至少要求:状态机和 schema 有版本且契约测试通过;任务在 Worker、数据库和网络故障后能从有效 checkpoint 恢复;所有外部写动作有幂等键、查询/对账或人工收口;取消、暂停、审批和补偿有明确终态;事件可去重、补发,审计能重建谁在何时做了什么;租户、权限、敏感数据和沙箱边界经过测试;有队列积压、租约过期、未知动作、成本异常和错误率告警。灰度时按配置版本逐步放量,保留禁用高风险工具和停止调度的开关,回滚只回滚配置或新执行分支,不篡改已经发生的业务事实。

常见追问及答案

  • 最危险的上线缺口是什么? 无法确认外部副作用却能自动重试,或旧 Worker 没有 fencing 仍可写入。
  • 出现事故先做什么? 停止高风险新动作、保留证据、查询外部事实、隔离受影响任务,再按补偿和人工流程处理。

扣分点

  • 只看成功率和接口 200。
  • 没有灰度、熔断和应急开关。
  • 用数据库改状态代替事故处理和审计。

结语 ​

长任务 Agent 的核心不是把模型循环拉长,而是把不确定的模型决策放进确定的工程边界:状态可验证,步骤可恢复,消息可重复,副作用可确认,人工可接管,事件可重放,接口可演进。面试中能把这条链路讲完整,通常比背某个框架的 API 更能体现真实的系统设计能力。

持续学习,持续实践。