从 ERP 系统出发,我是如何设计一套 LLM 多 Agent 系统的

4 阅读7分钟

从 ERP 系统出发,我是如何设计一套 LLM 多 Agent 系统的

本文以一个真实的电商 ERP 项目为背景,分享从架构设计到代码落地的全过程。


背景:一个「人工操作泥潭」

做电商的团队每天都在处理这样的场景:

  1. 销售订单审核通过 → 运营手动查库存 → 叫仓库发货 → 库存不够再去催采购 → 采购找供应商…

这条链路涉及 4 个角色、5 个系统,全靠人在中间传话。一旦流量上来,就是噩梦。

我们的 ERP 系统本身已经有完整的服务层 API:仓库库存、物流渠道、采购申请、供应商管理,数据能力完全够用。缺的是一个能把它们串联起来、会自己做决策的「大脑」。

这就是为什么我开始研究多 Agent 系统。


一、为什么选择多 Agent,而不是单个 LLM?

一开始我也想直接让 GPT 接一个大 Prompt,把所有工具塞进去,让它自己搞定。很快遇到问题:

问题单 LLM + 大工具集多 Agent
工具太多,Prompt 爆炸容易每个 Agent 只关注自己领域
追踪问题困难黑盒每个 Agent 独立日志和步骤
并发执行串行多 Agent 可并行
职责边界模糊清晰,便于维护

核心思路:把复杂问题拆解给「专家团队」,而不是指望一个全能助手。


二、系统架构设计

整体分为四层:

┌─────────────────────────────────────────┐
│            入口层(HTTP / 事件)           │
│    自然语言对话 / 订单事件 / 低库存告警     │
└──────────────────┬──────────────────────┘
                   │
┌──────────────────▼──────────────────────┐
│         Orchestrator Agent(主控)        │
│   LLM 规划器  →  ReAct 循环  →  结果汇聚  │
└──────────────────┬──────────────────────┘
                   │ 工具调用
    ┌──────────────┼──────────────┐──────────────┐
    ▼              ▼              ▼              ▼
┌───────┐    ┌─────────┐    ┌────────┐    ┌─────────┐
│ 库存  │    │  物流   │    │  采购  │    │ 供应商  │
│ Agent │    │  Agent  │    │  Agent │    │  Agent  │
└───────┘    └─────────┘    └────────┘    └─────────┘
    │              │              │              │
┌───────────────────── 工具层(ERP API 映射)─────────────────────┐
│  WarehouseStockService  OutOrderService  PurchaseApplyService  │
│  LogisticsChannelService               SupplierService        │
└────────────────────────────────────────────────────────────────┘

关键设计决策

Orchestrator 不直接操作业务 — 它只负责「想清楚该做什么、叫谁做」,具体执行全部委托给专业 Agent。这样主控的 System Prompt 可以保持精简,LLM 的推理质量更高。

专业 Agent 工具集小且内聚 — 库存 Agent 只认识 get_sku_stockbatch_get_stock,不会看到物流工具。这极大降低了 LLM 工具选错的概率。

两种调用模式共存 — 每个专业 Agent 同时提供:

  • LLM 驱动模式:通过 execute() 接入 ReAct 循环,供自然语言场景使用
  • 快捷方法:如 queryAndDecide() / createApplyDirect(),确定性场景直接调用,不过 LLM,节省 Token

三、核心机制:ReAct 循环

ReAct = Reasoning(推理)+ Acting(行动),是目前主流 Agent 框架的基础范式。

工作流程

输入
  |
  ▼
[LLM Thought] ← 我现在知道什么?下一步该做什么?
  |
  ▼
[Tool Call] ← 调用工具(查库存、创建订单...)
  |
  ▼
[Observation] ← 工具返回结果
  |
  ▼
[重复] ← 直到 LLM 判断任务完成
  |
  ▼
[Final Answer] ← 汇总输出

代码实现(核心 50 行)

// src/core/llm-client.ts — 简化版
async runReActLoop(
  messages: ChatMessage[],
  tools: ToolDef[],
  onStep?: (step: AgentStep) => void,
): Promise<string> {
  const history = [...messages];
  let step = 0;

  while (step < this.maxSteps) {
    // 1. 让 LLM 决定下一步
    const response = await this.client.chat.completions.create({
      model: this.model,
      messages: history,
      tools: this.buildOpenAITools(tools),
      tool_choice: 'auto',
    });

    const msg = response.choices[0].message;
    history.push(msg);

    // 2. 没有工具调用 → 得到最终答案
    if (!msg.tool_calls?.length) {
      return msg.content ?? '';
    }

    // 3. 并行执行所有工具调用(一轮可能调多个)
    const results = await Promise.all(
      msg.tool_calls.map(tc => this.executeTool(tc, tools))
    );

    // 4. 把结果作为 Observation 追加进历史
    history.push(...results);
    step++;
  }
  return '已达到最大步数限制';
}

关键细节

  • 每轮 LLM 可能返回多个工具调用(如同时查 3 个 SKU 的库存),全部并行执行
  • 历史记录完整保留,LLM 始终能看到"它之前做了什么、结果是什么"
  • maxSteps 防止死循环,通过环境变量 MAX_AGENT_STEPS 配置

四、Agent 桥接层的设计

这层是我觉得设计最精妙的部分,也是最容易忽视的。

问题:Orchestrator 调用的是 LLM 工具(ToolDef),但各专业 Agent 是 TypeScript 类,怎么连接?

解法src/tools/agent-tools.ts — 把每个专业 Agent 的能力包装成 Orchestrator 的工具函数。

// 库存查询工具(连接 Orchestrator -> InventoryAgent)
{
  name: 'query_inventory',
  description: '查询 SKU 库存,返回可用数量和充足/不足/缺货状态',
  parameters: { /* JSON Schema */ },
  execute: async (args) => {
    // 直接调用 InventoryAgent 的快捷方法(不走 LLM,确定性查询)
    const decisions = await Promise.all(
      skuCodes.map(sku => inventoryAgent.queryAndDecide(sku, requiredQtys?.[sku]))
    );
    return { decisions };
  }
}

这样 Orchestrator 的 LLM 看到的是高层语义工具("查库存"),而不是底层 API 调用,推理质量更好。


五、典型流程走读

以「订单审核通过,自动处理」为例,完整流程如下:

用户/系统: POST /api/agent/order/808645/process
    │
    ▼
OrchestratorAgent.processOrder(808645)
    │
    ▼ 构建 Prompt:
    "订单 808645 已审核,请检查库存,库存充足则发货,不足则补货..."
    │
    ▼ LLM 第 1 轮思考:
    Thought: 先查清楚订单里都有哪些 SKU,以及各 SKU 库存
    Action:  call query_inventory({ sku_codes: ["SKU-A", "SKU-B", "SKU-C"] })
    │
    ▼ query_inventory 执行(调用 InventoryAgent.queryAndDecide):
    ←  SKU-A: available=50, status=sufficient
    ←  SKU-B: available=2,  status=insufficient (需要 10)
    ←  SKU-C: available=0,  status=empty
    │
    ▼ LLM 第 2 轮思考:
    Thought: SKU-A 够用,需要发货
            SKU-B 不够,需要补货 8SKU-C 缺货,需要紧急补货并通知供应商
    Action: 并行调用 3 个工具:
            - coordinate_logistics (SKU-A → 创建出库单)
            - trigger_purchase     (SKU-B, deficit=8)
            - trigger_purchase     (SKU-C, deficit=10, urgent=true)
    │
    ▼ 执行结果:
    ←  出库单 #20260310-001 已创建,DHL 渠道
    ←  采购单 #PO-2026-088 已创建,供应商: 广州某某,预计 3 天到
    ←  采购单 #PO-2026-089 已创建(紧急),供应商: 深圳某某,预计 1 天到
    │
    ▼ LLM 第 3 轮思考:
    Thought: SKU-C 紧急缺货,需要同时通知供应商
    Action:  call notify_supplier({ supplier_id: xxx, is_urgent: true })
    │
    ▼ LLM 最终输出:
    "订单 808645 处理完成:
     - SKU-A: 出库单 #001 已创建,DHL,预计 3 天送达
     - SKU-B: 采购单 #088 已提交,3 天后到货,届时自动发货
     - SKU-C: 紧急采购 #089 已提交,1 天到货,供应商已收到催货通知"

六、技术架构总结

技术选型

模块选型理由
运行时Node.js 20 + TypeScript 5与前端团队技术栈一致,类型安全
LLM 接入openai SDK标准 Function Calling,支持 GPT/Gemini/Ollama 一键切换
HTTP 服务Express够用,无需引入复杂框架
日志pino结构化 JSON,生产友好
HTTP 客户端axios对接现有 ERP API

接入 LLM 只需改 .env

# OpenAI
OPENAI_API_KEY=sk-xxx
LLM_MODEL=gpt-4o-mini

# Google Gemini(直接兼容)
OPENAI_API_KEY=AIza-xxx
LLM_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai
LLM_MODEL=gemini-1.5-pro

# 本地 Ollama(零成本)
OPENAI_API_KEY=ollama
LLM_BASE_URL=http://localhost:11434/v1
LLM_MODEL=llama3

七、我学到的几个教训

1. System Prompt 要有决策规则,不能只描述角色

❌ 弱 Prompt:你是一个库存助手,帮用户查库存。

✅ 强 Prompt:库存充足(available >= required)时触发物流协调;不足时触发采购;为零时同时触发采购并通知供应商。

2. 工具描述比代码更重要

LLM 选工具靠的是 description 字段。描述越清晰、越有歧义消除,工具选错的概率越低。反复测试和优化工具描述能显著提升准确率。

3. 先做快捷方法,再做 LLM 版本

对于确定性流程(如"查库存并判断充足/不足"),先用纯代码实现 queryAndDecide(),稳定后再套 LLM。这样既能保证核心逻辑可测试,又给 LLM 提供了可靠的工具基础。

4. 并发工具调用能大幅提速

OpenAI Function Calling 一轮可以返回多个工具调用,用 Promise.all 并行执行,可以把多步串行操作压缩到 2-3 轮 LLM 调用。


结语

这套架构目前还在接入真实 ERP 接口的过程中。但框架本身已经跑通,LLM 的多轮推理逻辑也经过验证。

如果你的系统也有一堆已有 API、但缺乏跨模块自动协作能力,这个思路应该适用。