智能体面试准备(七十七):智能体分布式执行与任务编排工程——长任务分片、依赖调度与状态一致性

智能体面试准备(七十七):智能体分布式执行与任务编排工程------长任务分片、依赖调度与状态一致性

引言

本文是「智能体面试准备」系列第 77 篇,承接(七十六)长程记忆与经验自进化工程。前面讲了记忆怎么存、经验怎么沉淀,这一篇谈一个落地时极容易被低估的问题:当智能体要处理的任务从"单轮问答"变成"跑三天的数据 pipeline + 反复调用十几个工具",单进程串行执行根本扛不住,必须引入分布式执行与编排。

面试官判断一个候选人是否真正做过生产级 Agent,常问:"你的 Agent 怎么保证一个跑了 2 小时的任务在 worker 挂掉后不重头再来?多个子任务有依赖怎么调度?状态在哪、怎么恢复?"能把这三问讲清楚,基本就过关了。本篇给出架构图、对比表、代码片段与面试速答。

复制代码
智能体分布式执行总览
==============================================
                任务编排器 (Orchestrator)
                 │  解析 DAG / 依赖
     ┌───────────┼───────────────┐
     ▼           ▼               ▼
  子任务 A    子任务 B        子任务 C
 (无依赖)    (依赖A产出)     (依赖A,B)
     │           │               │
     ▼           ▼               ▼
 ┌─────────────────────────────────────┐
 │  Worker 池(多进程/多机/容器)       │
 │  每个 worker 拉取可执行节点           │
 └─────────────────────────────────────┘
                 │
                 ▼
 ┌─────────────────────────────────────┐
 │  状态存储 (DAG状态 + 检查点 + 锁)    │
 │  Redis / 数据库 / 对象存储            │
 └─────────────────────────────────────┘

一、为什么单进程不够

维度 单进程串行 分布式编排
长任务容错 挂了全重来 节点级重试,仅重跑失败子任务
吞吐 1 任务/时刻 多子任务并行
工具限流 难隔离 按工具维度限流队列
资源 单卡/单机 可跨机调度重工具
可观测 日志散落 统一 DAG 视图

当任务天然有依赖(A 出报告→B 审报告→C 发邮件),串行虽然简单但慢;当子任务可并行(同时爬 10 个站点),串行就是浪费。编排器的价值就是用 DAG 描述依赖、并行可并行、串行保依赖。

二、任务建模:DAG 与节点

把任务抽象成有向无环图,节点是"一个可独立执行的动作"(调一次工具/跑一段代码/生成一段文本),边是依赖。执行引擎只调度"入度已满足且未执行"的节点。

复制代码
from dataclasses import dataclass, field
from typing import List

@dataclass
class Node:
    id: str
    deps: List[str] = field(default_factory=list)
    status: str = "pending"   # pending/running/done/failed
    payload: dict = field(default_factory=dict)

@dataclass
class DAG:
    nodes: dict = field(default_factory=dict)

    def ready(self, nid):
        """入度(依赖)全部 done 才可执行"""
        return all(self.nodes[d].status == "done" for d in self.nodes[nid].deps)

    def descendants_runnable(self):
        return [nid for nid, n in self.nodes.items()
                if n.status == "pending" and self.ready(nid)]

三、调度:从串行到并行

最朴素的是拓扑排序后串行;生产级要并发拉起可并行节点,并控制并发度与每工具限流。

复制代码
import threading, time

class Scheduler:
    def __init__(self, dag, max_parallel=4):
        self.dag = dag
        self.max_parallel = max_parallel
        self.lock = threading.Lock()

    def _run_node(self, nid, exec_fn):
        with self.lock:
            self.dag.nodes[nid].status = "running"
        try:
            exec_fn(self.dag.nodes[nid].payload)
            with self.lock:
                self.dag.nodes[nid].status = "done"
        except Exception:
            with self.lock:
                self.dag.nodes[nid].status = "failed"
            raise

    def run(self, exec_fn):
        while True:
            with self.lock:
                pending = self.dag.descendants_runnable()
                running = [n for n in self.dag.nodes.values() if n.status=="running"]
            if not pending and not running:
                break
            for nid in pending[: self.max_parallel - len(running)]:
                threading.Thread(target=self._run_node, args=(nid, exec_fn)).start()
            time.sleep(0.2)

四、状态一致性与检查点

分布式执行最大的坑是"状态在哪、怎么恢复"。原则:执行逻辑无状态,状态外置。每个节点执行前写"开始"标记,成功后写"完成"+产物引用;worker 崩溃重启后,编排器扫描 DAG,已 done 的跳过、failed 的可重试、pending 的重新调度。

复制代码
import json, redis

r = redis.Redis()

def checkpoint(node_id, status, artifact=None):
    r.hset(f"agent:dag:{node_id}", mapping={
        "status": status,
        "artifact": json.dumps(artifact or {}),
        "ts": time.time(),
    })

def recover(dag):
    """worker 重启后据此恢复,避免重复执行已完成的节点"""
    for nid, n in dag.nodes.items():
        s = r.hget(f"agent:dag:{nid}", "status")
        if s:
            n.status = s.decode()

关键设计:幂等执行。同一节点可能因重试被执行两次,执行函数必须保证重复执行不产生副作用(用 artifact 引用去重、用唯一 key 写库)。这是工程里最常被忽略却最致命的一点。

五、失败处理与重试策略

失败类型 处理 注意
工具超时 指数退避重试 设最大次数,避免死循环
限流 429 入队等待 + 退避 全局令牌桶
产物缺失 依赖节点失败→本节点标 failed 上游失败不盲目重试
worker 崩溃 节点回 pending 重调度 依赖幂等
逻辑错误 标记 failed,人工/LLM 复盘 别无限重试
复制代码
import random

def with_retry(fn, max_retry=3, base=1.0):
    for i in range(max_retry):
        try:
            return fn()
        except TransientError as e:
            if i == max_retry - 1:
                raise
            time.sleep(base * (2 ** i) + random.random())

六、与 LLM 编排的关系

注意区分两层:LLM 负责"决策下一步做什么"(ReAct / 规划),编排器负责"把决策变成可靠执行"。理想架构是 LLM 产出计划(DAG 草案),编排器落地执行与容错,LLM 只在节点失败或需动态分支时再介入。这样既利用 LLM 的灵活性,又用确定性的调度保证可靠性。

面试速答

问:Agent 长任务怎么保证 worker 挂了不重头再来?

答:状态外置 + 节点级检查点。把 DAG 状态和每个节点的完成标记/产物引用存在 Redis/DB,worker 崩溃重启后扫描 DAG,done 跳过、failed 重试、pending 重调度,只重跑失败部分。

问:多个子任务有依赖怎么调度?

答:用 DAG 建模,节点是独立动作、边是依赖。调度器只拉"入度全 done"的节点并行执行,依赖未满足的等待,天然支持部分并行部分串行。

问:重试会不会导致重复副作用?

答:会,所以执行函数必须幂等------用 artifact 唯一 key 去重、写库用 upsert、工具调用带幂等令牌。这是生产级 Agent 的硬要求。

问:LLM 和编排器怎么分工?

答:LLM 负责决策(规划/下一步),编排器负责可靠执行(调度/容错/重试/状态)。LLM 产计划,编排器落地,失败或需动态分支时 LLM 再介入。

高频追问清单

  1. DAG 出现环(LLM 规划产出循环依赖)怎么检测与打破?
  2. 跨机执行时状态存储选 Redis 还是数据库?一致性与性能如何权衡?
  3. 节点产物很大(如大文件)怎么存?checkpoint 只存引用还是存内容?
  4. 怎样防止"脑裂"------两个 worker 同时执行同一节点?
  5. 动态新增子任务(执行中发现还要做 X)怎么并入正在跑的 DAG?
  6. 每工具维度的限流队列怎么设计?令牌桶还是漏桶?
  7. 长任务 Human-in-the-loop 审批节点怎么嵌进 DAG?
  8. 如何对分布式 Agent 做端到端可观测(trace 串联所有节点)?
  9. 重试次数用尽仍失败的节点,怎么优雅降级而非整任务失败?
  10. 多 Agent 协作时,任务编排和 Agent 间通信怎么解耦?
相关推荐
thesky1234562 小时前
27届大模型面试准备(七十七):多模态大模型预训练工程——数据配比、多阶段训练与模态对齐
大模型·面试准备
长谷深风1112 小时前
AI记忆会过期,会冲突,更需要治理
人工智能·ai·大模型·prompt·memory·aiagent
thesky1234562 小时前
27届大模型面试准备(七十八):大模型推理线上问题定位与根因分析工程——TTFT/TBT 抖动、OOM、吞吐跌零排查手册
大模型·面试准备
ReleaseU4 小时前
数据库 MCP:让 Agent 自己查数据、写报表
人工智能·大模型
夏文强5 小时前
DeepSeek Harness 可观测性:用 OpenTelemetry 把会话遥测出去
人工智能·开源·大模型·agent·deepseek
夏文强6 小时前
DeepSeek Harness SDK 集成:把 Agent 嵌进你的应用
人工智能·开源·大模型·agent·deepseek
thesky12345615 小时前
27届大模型面试准备(七十一):多租户 GPU 虚拟化与推理隔离工程——MIG、MPS 与噪声邻居治理
大模型·mig·多租户·mps·gpu虚拟化·显存隔离·噪声邻居
虎虎(_ _)。゜zzZ1 天前
Qdrant向量数据库工程实战
数据库·人工智能·大模型·向量数据库·rag·qdrant
deepseek231 天前
GPT-6 Astra 发布拆解:Computer Use 产品化时刻、CoT 监控失守与 AGI 契约的终结
大模型·openai·ai agent