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

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

引言

本文是「智能体面试准备」系列第 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 间通信怎么解耦?
相关推荐
程序猿编码4 天前
告别改源码适配模型:纯 C++ 可配置 LLM 推理引擎,全格式全结构兼容
c++·大模型·llm·推理引擎
蔡俊锋5 天前
华为昇腾 960 超节点发布:4096 卡、5500 个光引擎替 4.8 万光模块——国产算力拐点到了吗?
大模型·模型部署·ai架构·国产算力·华为昇腾
O。O蛋黄酥啊5 天前
Claude Code 记忆机制全拆解:Auto Memory 与 CLAUDE.md 双轨解析
大模型·agent·memory·claude·codex·记忆
BlackStar_L5 天前
第二章 上下文工程
大模型·llm·agent
User_芊芊君子5 天前
让 Claude Code 换上国产大脑:蓝耘元生代上 GLM-5.2 / DeepSeek / Qwen 模型横评实测
人工智能·ai·大模型
寻道码路5 天前
大模型工程化实战(十二):RAG 数据工程底座——采集到入库流水线(离线+在线双链路)
大模型·知识库·rag·ai工程化·llmops`·数据工程底座·文档采集
Web3&Basketball5 天前
CRM Agent 后训练实战:3 倍更少错误
python·架构·大模型·agent·推理
蚁小二官方5 天前
AI 安全治理框架更新|大模型训练数据合规,企业实操落地思路
大模型·数据合规·ai安全
AI模力圈5 天前
On-Policy Distillation:原理、变体与工程实现解析
大模型·强化学习·知识蒸馏
长谷深风1115 天前
根因定位:三步隔离法破解AIBadcase
人工智能·ai·大模型·retrieval·ai智能体·aiagent·aibadcase