知识与集成

企业级 AI Agent“并行函数调用与 DAG 编排”(LLMCompiler)架构实战:破解串行延迟瓶颈、动态依赖调度与高并发吞吐

针对企业在跨 ERP/CRM 多系统工具调用中 ReAct 串行阻塞导致端到端延迟爆炸瓶颈,深度拆解 LLMCompiler 架构三件套:任务规划器(Planner)、DAG 依赖调度单元(Task Fetching Unit)与动态汇聚器(Dynamic Joiner),结合变量占位符 ($1, $2) 与异步 Fan-out 并发调度,提供生产级 TypeScript 核心引擎实现。

百信 AI Agent发布于 约 8 分钟
企业级 AI Agent LLMCompiler 并行函数调用与 DAG 编排控制中心示意图
企业级 AI Agent LLMCompiler 并行函数调用与 DAG 编排控制中心示意图

在企业级复杂智能体系统的落地过程中,当业务场景要求智能体协同调用多个异构系统(如同时查询 ERP 物料库存、CRM 客户授信级别、WMS 仓储货位,并结合 OA 组织架构执行合规性审批)时,传统的串行试错架构(如经典的 ReAct 模式)暴露出致命的性能瓶颈:模型每一步只能生成一个工具调用,必须等待下游 API 返回观察结果后才能进行下一步思考。在典型的企业网络与多系统交互环境下,这种“模型思考-等待API-再思考-再等待”的线性串联方式,导致端到端响应耗时呈线性甚至几何级数激增,动辄高达 15~30 秒,严重违背企业交互系统的可用性底线。为了攻克多工具调用的高延迟瓶颈,LLMCompiler(并行函数调用与 DAG 依赖编排架构)应运而生。本文结合百信企业智能体平台的私有化生产实践,深度拆解由 Planner 进行单次全局依赖表达、由 Task Fetching Unit 进行动态 DAG 拓扑构建与异步 Fan-out 并发调度、由 Joiner 进行确定性汇聚与增量补齐的完整架构,为企业构建百毫秒级高并发智能体提供落地指南。

企业多工具调用的延迟困境:ReAct 串行阻塞与端到端响应爆炸

在企业日常运转中,高价值的业务任务往往具有高度的跨系统联动性。例如在“高净值客户紧急备件出库调度”场景中,智能体至少需要执行以下 5 项动作:

  1. 调用 CRM 接口校验客户信用评级与当前账期余额;
  2. 调用 ERP 接口拉取主备料号对应的实时可用库存(ATP, Available to Promise);
  3. 调用 WMS 接口查询华东与华南保税仓的实际货位物理距离与出库优先级;
  4. 依赖前述 3 项的数据,调用风险评估算法判定是否允许快速放行;
  5. 依赖风控判定,调用消息网关向值班调度总监发送带交互回执的通知。

1. 串行 ReAct 模式下的“延迟滚雪球”效应

在传统的 ReAct 循环中,由于大模型在第 1 轮仅能决定调用 get_crm_credit,系统发起网络 I/O;待 CRM 返回后,将结果追加到 Prompt 再次送入大模型,模型思考后决定调用 get_erp_inventory……以此类推。这种执行拓扑本质上是严格单线程阻塞的:

  • I/O 等待时间线性叠加:假设外部系统 API 平均耗时为 800ms,大模型每轮思考耗时 1200ms。5 个工具调用需要经历 5 轮大模型推理(共 6000ms)和 5 次外部系统往返(共 4000ms),总耗时至少突破 10000ms(10 秒);
  • 算力成本成倍放大:由于每一轮都要将前面的 Thought、Action 和巨大的 JSON Observation 原样重新塞入 Prompt,上下文 Token 长度呈等差数列增长,造成企业私有化算力卡上的 KV Cache 剧烈抖动与计算资源浪费;
  • 下游连接池饥饿:多智能体并发请求时,长耗时请求长时间霸占后端微服务连接通道,引发连接池耗尽与级联超时。
编排架构范式工具调用执行机制端到端延迟表现大模型调用次数 (N个独立工具)变量依赖处理能力
经典 ReAct (串行交替)单步思考,串行阻塞等待 Observation最差(所有 LLM 推理耗时与网络 I/O 耗时纯线性累加)N 次推理(上下文逐轮暴增)依赖后续上下文直接读取,缺乏形式化拓扑声明
原生 Parallel Tool Calling单步内同时吐出多个 tool_calls,平铺并发中等(仅适用于完全互不依赖的一阶并行)1~2 次推理无依赖传递能力(工具 B 无法在入参中引用工具 A 的输出)
LLMCompiler (DAG 并行编排)单次规划输出 DAG 图 + 异步调度并发 Fan-out极优(关键路径耗时,延迟降低 60%~75%)1 次规划 + 1 次聚合(固定 2 次推理)强类型变量占位符 ($1, $2) 与动态拓扑替换

LLMCompiler 核心三件套:LLM Planner、Task Fetching Unit 与 Dynamic Joiner

LLMCompiler 借鉴了传统计算机科学中“编译器与指令流水线”的经典思想,将自然语言到异构工具执行的映射过程抽象为编译执行流水线。其系统架构由三大核心组件协同驱动:

1. 任务规划器(LLM Planner)

Planner 承担“编译前端”的职能。面对用户的多系统复杂查询,它并不逐个触发工具,而是单次生成一份包含依赖关系拓扑的完整执行指令清单(Execution Plan)。为了在单次生成中表达跨工具的数据传递,Planner 引入了形式化的“变量占位符(Variable Placeholder)”语法(如 $1、$2)。

2. 任务分发调度单元(Task Fetching Unit, TFU)

TFU 承担“乱序执行流水线调度器”的职能。它实时维护一张内存中的动态有向无环图(Dynamic DAG):

  • 无依赖任务秒级 Fan-out 并发:扫描所有入度为 0(In-degree = 0)的任务节点,无需等待任何中间结果,直接将其封装为异步协程并发派发至执行器网络;
  • 动态依赖解封与变量热替换:当下游 API 返回结果后,TFU 立即捕获响应,将结果反向注入到依赖该输出的后续待执行节点参数中,使该节点的入度减 1。一旦入度降为 0,该节点立即被自动激活并推入并发队列。

3. 结果汇聚与动态决策器(Dynamic Joiner)

Joiner 承担“流水线收尾与分支判断”的职能。当所有并行或依赖任务执行完毕(或达到设定终止条件)时,Joiner 会对全量收集到的数据进行一致性校验与语义分析:

  • 任务闭环生成:若收集到的信息已足以完整回答用户请求或达成业务操作,Joiner 直接组织最终输出,流程终止;
  • 动态增量重编排(Re-planning):若在并发执行过程中,某个核心接口返回了非预期错误(如“仓库临时盘点关闭,请改走备用中转仓”),Joiner 将带着当前已取得的事实成果,发起一次极小代价的局部增量二次编译,仅对缺失的分支生成补齐指令,杜绝推倒重来。

动态变量依赖与 DAG 拓扑解析:$1/$2 占位符、拓扑排序与 Fan-out 并发

理解 LLMCompiler 的核心钥匙,在于其如何让大模型输出严格的图依赖表达式,并被宿主运行环境无缝解析为运行时异步并发任务。

1. 依赖占位符的规范设计

在百信企业智能体平台的实践中,Planner 的输出遵循结构化指令流。例如针对客户风控与出库场景,Planner 在单次生成中输出如下紧凑指令:

1. get_crm_credit(customer_id="CUST-8801")
2. get_erp_inventory(sku_code="SKU-CHIP-99")
3. get_wms_storage(warehouse_id="WH-EAST-01")
4. evaluate_credit_risk(credit_data=$1, inventory_data=$2)
5. notify_dispatcher(risk_report=$4, warehouse_info=$3)

2. DAG 构建与拓扑并发图示

TFU 接收到上述指令后,在内存中构建对应的节点图拓扑:

  • 节点 1、节点 2、节点 3 的前置依赖列表均为空(Dependencies = []),属于互斥无关节点。调度器在零时刻瞬间以异步协程方式并发发起这 3 个 HTTP/gRPC 调用;
  • 节点 4 声明依赖 $1 和 $2。调度器将其挂起在监听表中;
  • 节点 5 声明依赖 $4 和 $3。调度器将其同样挂起。

当节点 1 和节点 2 的响应在 450ms 内先后返回时,TFU 将实际返回的 JSON 数据结构实体替换到节点 4 的入参中,节点 4 依赖清零,立即唤醒执行;而节点 3 的返回直接满足节点 5 的部分前置条件。整个任务图的端到端执行时间,完全取决于关键路径(Critical Path)的耗时,彻底摆脱了不相关分支的拖累。

生产级异常自愈与流式处理:局部超时、部分重试与增量补齐

在企业复杂 IT 环境下,网络抖动、偶发超时与下游微服务降级是常态。LLMCompiler 架构必须在工程层面具备强韧的健壮性:

1. 节点级隔离与有限指数退避重试

由于每个任务节点在 TFU 中都是独立的协程实体,某个边缘节点的网络抖动(例如 WMS 仓储货位系统偶发 504 Gateway Timeout)不会导致整个执行拓扑直接阻塞崩溃。系统为每个节点配置独立的超时阈值(如 2000ms)和局部退避重试(Backoff Retry)策略,保护关键路径不受非核心毛刺影响。

2. 失败短路与优雅降级(Graceful Degradation)

当某个节点在达到最大重试次数后仍然失败,TFU 将该节点标记为 FAILED,并沿 DAG 依赖树向下游传播。如果下游节点声明该入参为非必须依赖(Optional),则由默认兜底策略替代;若为核心硬依赖,则直接将带有局部失败上下文的数据移交至 Joiner,由 Joiner 判定是否调用备用系统或向运维人员返回清晰的诊断信息。

企业级私有化落地 TypeScript 生产参考实现:并行函数执行器与依赖解析引擎

以下为百信在企业级智能体平台中采用的 LLMCompiler 核心调度引擎 TypeScript 生产级参考实现,展示了变量依赖拓扑解析、异步 Fan-out 并发调度与上下文热替换的完整实现闭环:

/**
 * 企业级 AI Agent LLMCompiler 调度引擎核心实现
 * 职责:解析依赖占位符、构建有向无环图、执行最大并发异步调度与变量热注入
 */

// 任务节点定义接口
export interface CompilerTask {
  id: number;                          // 任务数字标识(对应 $1, $2)
  toolName: string;                    // 工具函数名称
  rawArgs: Record<string, any>;        // 原始入参(可能包含 $n 依赖占位符)
  resolvedArgs: Record<string, any>;   // 占位符解析替换后的实际入参
  dependencies: number[];              // 前置依赖的任务 ID 列表
  status: 'PENDING' | 'RUNNING' | 'RESOLVED' | 'FAILED'; // 运行状态
  result?: any;                        // 执行输出结果
  error?: string;                      // 异常信息
}

// 工具执行函数签名
export type ToolHandler = (args: Record<string, any>) => Promise<any>;

export class LLMCompilerEngine {
  private tools: Map<string, ToolHandler> = new Map();

  /**
   * 注册企业业务系统工具
   */
  public registerTool(name: string, handler: ToolHandler): void {
    this.tools.set(name, handler);
  }

  /**
   * 核心调度执行入口:输入指令列表,构建 DAG 并行执行
   */
  public async executePlan(rawPlanLines: string[]): Promise<Map<number, any>> {
    const tasks = this.parsePlan(rawPlanLines);
    const results = new Map<number, any>();
    const runningPromises = new Map<number, Promise<void>>();

    // 持续循环调度,直到所有任务处于终态
    while (tasks.some(t => t.status === 'PENDING' || t.status === 'RUNNING')) {
      // 1. 扫描所有待运行且依赖已完全满足的就绪任务
      const readyTasks = tasks.filter(task =>
        task.status === 'PENDING' &&
        task.dependencies.every(depId => {
          const depTask = tasks.find(t => t.id === depId);
          return depTask && depTask.status === 'RESOLVED';
        })
      );

      // 2. 将所有就绪任务同时发起异步 Fan-out 并发执行
      for (const task of readyTasks) {
        task.status = 'RUNNING';

        // 动态替换变量占位符(将 $1, $2 替换为真实返回值)
        task.resolvedArgs = this.resolveVariables(task.rawArgs, results);

        const promise = (async () => {
          try {
            const handler = this.tools.get(task.toolName);
            if (!handler) {
              throw new Error(`未找到注册工具: ${task.toolName}`);
            }
            // 真实发起异步工具调用
            const output = await handler(task.resolvedArgs);
            task.result = output;
            task.status = 'RESOLVED';
            results.set(task.id, output);
          } catch (err: any) {
            task.status = 'FAILED';
            task.error = err?.message || '未知调用错误';
          }
        })();

        runningPromises.set(task.id, promise);
      }

      // 如果当前没有正在运行的异步任务,且存在仍未完成的任务,说明发生依赖死锁或依赖项执行失败
      if (runningPromises.size === 0) {
        const pendingBlocked = tasks.filter(t => t.status === 'PENDING');
        if (pendingBlocked.length > 0) {
          throw new Error('DAG 依赖拓扑无法满足:前置依赖可能失败或存在死锁循环');
        }
        break;
      }

      // 3. 等待至少任意一个异步任务完成,以触发下一轮状态评估与下游解锁
      const activePromises = Array.from(runningPromises.values());
      await Promise.race(activePromises);

      // 移除已结束的 Promise 句柄
      for (const [id] of runningPromises) {
        const task = tasks.find(t => t.id === id);
        if (task && (task.status === 'RESOLVED' || task.status === 'FAILED')) {
          runningPromises.delete(id);
        }
      }
    }

    return results;
  }

  /**
   * 递归解析对象结构中的变量占位符(如 "$1", "$2")
   */
  private resolveVariables(args: Record<string, any>, results: Map<number, any>): Record<string, any> {
    const resolved: Record<string, any> = {};

    for (const [key, value] of Object.entries(args)) {
      if (typeof value === 'string' && value.startsWith('$')) {
        const depId = parseInt(value.slice(1), 10);
        if (!isNaN(depId) && results.has(depId)) {
          resolved[key] = results.get(depId);
        } else {
          resolved[key] = value;
        }
      } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
        resolved[key] = this.resolveVariables(value, results);
      } else {
        resolved[key] = value;
      }
    }

    return resolved;
  }

  /**
   * 形式化解析指令行文本为结构化 CompilerTask 列表
   */
  private parsePlan(lines: string[]): CompilerTask[] {
    const tasks: CompilerTask[] = [];

    for (const line of lines) {
      const trimmed = line.trim();
      if (!trimmed) continue;

      // 匹配行模式:1. tool_name(param1="...", param2=$1)
      const match = trimmed.match(/^(\d+)\.\s*([a-zA-Z0-9_]+)\((.*)\)$/);
      if (!match) continue;

      const id = parseInt(match[1], 10);
      const toolName = match[2];
      const paramsRaw = match[3];

      // 简单参数解析器(实际生产采用严谨的 JSON AST 解析)
      const rawArgs: Record<string, any> = {};
      const dependencies: number[] = [];

      if (paramsRaw.trim()) {
        const paramPairs = paramsRaw.split(',').map(p => p.trim());
        for (const pair of paramPairs) {
          const [pKey, pVal] = pair.split('=').map(s => s.trim());
          if (pVal.startsWith('$')) {
            const depId = parseInt(pVal.slice(1), 10);
            if (!isNaN(depId)) {
              dependencies.push(depId);
            }
            rawArgs[pKey] = pVal;
          } else {
            rawArgs[pKey] = pVal.replace(/^["']|["']$/g, '');
          }
        }
      }

      tasks.push({
        id,
        toolName,
        rawArgs,
        resolvedArgs: {},
        dependencies,
        status: 'PENDING'
      });
    }

    return tasks;
  }
}

架构选型决策矩阵与性能压测对比:ReAct、Plan-and-Execute 与 LLMCompiler

在企业级私有化 AI Agent 系统选型中,架构师必须清晰权衡三种主流任务编排模式的核心适用边界:

1. 三大编排范式的量化选型维度

  • ReAct(串行探索型):适合决策路径不确定、下一步完全取决于前一步细节反馈的未知探索场景(如复杂数据分析写 SQL 过程中的自修正)。缺点是延迟高、Token 浪费大;
  • Plan-and-Execute(宏观解耦型):适合超长流程(10~30 步)、存在复杂分支逻辑、中间需要长时间异步等待或人工审批的企业工作流。通过大步骤隔离保证模型不迷航;
  • LLMCompiler(极限并行编译型):适合工具数量多、工具间依赖明确、对响应耗时有极致严苛要求(如面向前台坐席、移动端交互或高频实时业务系统)的生产场景。通过 DAG 并行化将延迟压缩至理论极限。

2. 生产环境基准压测对比(5 个工具联动场景)

在百信内部企业私有化测试基准(配备 4 张高算力本地推理卡,对接真实企业 ERP/CRM 测试集群)中,执行包含 5 个异构工具的业务综合编排任务,测试结果如下:

编排模式平均端到端延迟 (P95)首 Token 产生延迟 (TTFT)单次请求 Token 消耗总量私有化单节点最大并发承载
经典串行 ReAct12.8 秒1.2 秒4,850 Tokens8 QPS
Plan-and-Execute (顺序执行)8.4 秒2.1 秒2,600 Tokens15 QPS
LLMCompiler 并行编排3.6 秒(降低 71.8%)1.8 秒1,420 Tokens(节省 70.7%)36 QPS(提升 4.5 倍)

通过将编译器架构思想引入大模型工具调度层,企业智能体平台得以在复杂业务与严苛时延之间找到最佳平衡点,真正让多系统联动的企业级 Agent 具备了支撑关键业务实时交互的工业级生产力。