当ERP遇上DeepSeek:飞书机器人智能问答的落地实践

一、为什么要在ERP里塞一个大模型?

一、为什么要在ERP里塞一个大模型?

做销售生产ERP那会儿,系统功能越堆越多:录订单、查库存、审流程、看报表……每个操作都要翻菜单、点按钮。

运营的同事跟我吐槽:"我就想查个昨天的订单数,至于要点N次鼠标吗?"

这句话点醒了我。ERP功能再多,如果交互门槛高,用户就不爱用。

于是我决定搞一个飞书机器人,背后接上DeepSeek大模型——用户在飞书里用自然语言问一句,机器人听懂意图、识别参数、调接口、返回结果。

二、整体架构

飞书群聊 ──WebSocket──▶ Node.js Bot ◀──HTTP──▶ DeepSeek LLM
                              │
                              ▼
                       意图解析 + 函数调用
                              │
                              ▼
                    ┌─────────┴─────────┐
                    │                   │
              Spring Boot API      本地SQLite
              (订单/库存/审批)      (查询即读)

技术栈: - 飞书开放平台 + 长连接 WebSocket 接收消息 - Node.js Express 作为 Bot 服务(端口 3001) - DeepSeek Chat API 做意图解析和参数提取 - Spring Boot(端口 8080)提供业务 REST API - SQLite 存储结构化业务数据

三、核心思路:用 Function Calling 代替硬编码意图识别

一开始我想自己写意图分类——正则匹配、关键词词典,搞了一周发现:

  • "查昨天的订单" 和 "昨天卖了多少钱" 本质是同一个意图,但写法变一下就匹配不上
  • 用户说"帮我看看张三最近三天下了哪些单",我人麻了

后来换了一种思路:把所有 ERP 操作封装成函数描述,交给 DeepSeek 去判断该调哪个、参数是什么。

// 定义可用的 "工具" 列表
const tools = [
  {
    type: "function",
    function: {
      name: "query_orders",
      description: "查询订单列表,可按时间范围、客户名筛选",
      parameters: {
        type: "object",
        properties: {
          start_date: { type: "string", description: "开始日期,如 2026-07-01" },
          end_date:   { type: "string", description: "结束日期,如 2026-07-03" },
          customer:   { type: "string", description: "客户姓名,可选" },
          status:     { type: "string", enum: ["待审核", "已通过", "已发货"], description: "订单状态" },
        },
        required: ["start_date", "end_date"]
      }
    }
  },
  {
    type: "function",
    function: {
      name: "query_inventory",
      description: "查询商品库存信息",
      parameters: {
        type: "object",
        properties: {
          product_name: { type: "string", description: "商品名称,支持模糊匹配" },
        },
        required: ["product_name"]
      }
    }
  },
  {
    type: "function",
    function: {
      name: "approve_order",
      description: "审批订单",
      parameters: {
        type: "object",
        properties: {
          order_id: { type: "string", description: "订单编号" },
          action:   { type: "string", enum: ["通过", "驳回"], description: "审批动作" },
          comment:  { type: "string", description: "审批意见,可选" },
        },
        required: ["order_id", "action"]
      }
    }
  }
];

四、消息处理流程

收到飞书消息后,处理链路如下:

async function handleMessage(userText, userId) {
  // Step 1: 调用 DeepSeek,传入工具定义
  const llmResponse = await callDeepSeek([
    { role: "system", content: SYSTEM_PROMPT },
    { role: "user",   content: userText }
  ], tools);

  // Step 2: DeepSeek 返回 tool_calls,告诉我们要调哪个函数
  const toolCall = llmResponse.choices[0].message.tool_calls[0];
  const fnName = toolCall.function.name;
  const fnArgs = JSON.parse(toolCall.function.arguments);

  // Step 3: 执行对应函数,调用 Spring Boot API
  let result;
  switch (fnName) {
    case "query_orders":
      result = await springApi.getOrders(fnArgs);
      break;
    case "query_inventory":
      result = await springApi.getInventory(fnArgs);
      break;
    case "approve_order":
      result = await springApi.approveOrder(fnArgs);
      break;
    default:
      return "抱歉,这个操作我暂时还不支持";
  }

  // Step 4: 把原始结果再喂给 DeepSeek,让它用自然语言组织回复
  const finalResponse = await callDeepSeek([
    { role: "system", content: "你是ERP助手,用简洁的中文把查询结果告诉用户" },
    { role: "user",   content: `原始问题:${userText}
查询结果:${JSON.stringify(result)}` }
  ], []);

  return finalResponse.choices[0].message.content;
}

五、让 DeepSeek 理解业务上下文

纯靠 Function Calling 还不够——DeepSeek 不知道你的业务术语。

比如用户说"张三的待审单子",DeepSeek 需要知道: - "待审" → status = "待审核" - "单子" → 查订单表

我的做法:在 System Prompt 里注入业务知识。

const SYSTEM_PROMPT = `你是销售生产ERP系统的智能助手。系统中有以下业务概念:

【订单状态流转】
待审核 → 已通过 → 已发货 → 已完成
(驳回则回到待审核)

【商品名称别名】
- "爱普生打印机" = "EPSON L3251"
- "A4纸" = "得力A4复印纸 70g"

【时间表述理解】
- "昨天" = 当前日期前一天
- "本周" = 周一至今天
- "最近三天" = 当前日期往前推3天
- "上月" = 上个月1号至最后一天

当前日期:${new Date().toISOString().slice(0, 10)}

请根据用户输入判断意图,选择合适的函数并提取参数。`;

这样 DeepSeek 就能把"查查张三最近三天有没有待审的单子"正确解析为:

{
  "name": "query_orders",
  "arguments": {
    "start_date": "2026-07-01",
    "end_date": "2026-07-04",
    "customer": "张三",
    "status": "待审核"
  }
}

六、踩过的坑

坑1:时间解析容易出错

DeepSeek 偶尔会把"下周"理解成"下下周",特别是周一的时候。后来我在 System Prompt 里把日期写死(当前日期固定注入),准确率高了很多。

坑2:函数返回数据量太大

有一次用户问"今年卖了哪些货",结果返回 2000 多条记录,DeepSeek 的 context 直接飙到 20K token,回复也要好几秒。

解决方法:在业务层做数据聚合,只返回摘要。比如不返回逐条订单,返回"2026年共 312 笔订单,总金额 ¥847,200"。

坑3:飞书消息 5 秒超时

飞书机器人回复消息有 5 秒超时限制。LLM 调用 + 业务查询经常超过 5 秒。

解决方法:先回复"正在查询,请稍候...",然后异步处理,结果通过飞书"回复消息"接口发回去。用户体感不卡。

// 先快速回复
await feishuApi.replyMessage(msgId, "正在查询,请稍候...");

// 异步处理
setTimeout(async () => {
  const answer = await handleMessage(userText, userId);
  await feishuApi.replyMessage(msgId, answer);
}, 100);

七、效果与感受

上线跑了一个月,最大的变化是:运营和销售开始主动在飞书群里问数据了。

以前他们要看报表得打开网页、登录系统、选筛选条件——三步走,很多人嫌麻烦。

现在飞书里打一行字:"昨天卖了多少",秒出结果。

几点体会:

  1. Function Calling 比写死意图识别好太多——维护成本几乎是零,新增功能只需要加一个函数描述
  2. System Prompt 就是你的"业务词典"——花时间写好它,比调模型参数管用
  3. 异步回复是刚需——别指望 LLM 能在 5 秒内完成全链路
  4. 数据要提前聚合——不要直接把几千行数据扔给 LLM,它不是你家的数据仓库

这套方案代码量不大、改造成本低,适合中小团队的 ERP / 内部工具快速接入 AI 能力。如果你也在搞类似的东西,欢迎交流。

posted @ 2026-07-04 15:24  C(5,3)  阅读(7)  评论(0)    收藏  举报