基于 LangChain 的商城智能客服落地实践
项目背景:api-nest(NestJS 智能服务)为商城小程序提供 AI 智能客服能力
技术栈:NestJS 11 + TypeScript · LangChain 1.x · TypeORM · MySQL(与 ThinkPHP 8 商城业务共享库)
涉及代码:src/config/llm.config.ts·src/modules/ai_chat/·src/modules/ai_mall/
状态:一期已完成开发并实测验证通过
一、技术框架简略描述
1.1 LangChain 是什么
LangChain 是一个面向大语言模型(LLM)应用开发的框架,项目使用的 1.x 版本由四层抽象构成:
| 抽象层 | 核心概念 | 解决的问题 |
|---|---|---|
| 模型层 | BaseChatModel及各厂商实现(ChatOpenAI/ChatDeepSeek/ChatOllama) | 屏蔽不同大模型平台的 SDK 差异,统一调用接口 |
| 消息层 | BaseMessage/ 多模态 content blocks | 统一 system / user / assistant 消息与图文混合内容的表示 |
| 工具层 | tool()+ zod schema(StructuredTool) | 把任意函数封装成模型可调用的工具,参数强类型约束 |
| 智能体层 | createAgent()预置智能体 | 内建「模型思考 → 调用工具 → 回喂结果 → 继续思考」的 ReAct 循环 |
与之同生态的还有LangGraph(以状态图组织多节点、带循环与人工干预的复杂编排)和LangSmith(链路追踪与评估)。三者是递进关系:单智能体场景用 LangChain 即可,编排复杂度上升再引入 LangGraph。
1.2 与其他路线的定位差异
「LLM + 工具调用」并非只有一种做法,各路线定位不同:
| 路线 | 能力边界 | 适用场景 |
|---|---|---|
| 裸调 OpenAI 兼容 SDK | 单次调用与 function calling 可用,但 Agent 循环、多平台适配、消息归一化均需手写 | 一次性脚本、极简调用 |
| LlamaIndex | 以 RAG 数据检索为强项,Agent 编排非其主场 | 文档知识库问答 |
| LangGraph | 状态图编排,支持循环、分支、持久化检查点 | 多步骤工作流、多智能体协作 |
| LangChain | 覆盖「模型抽象 → 工具协议 → 智能体」全链路,与 LangGraph/LangSmith 同栈演进 | 单智能体 + 结构化工具调用的生产应用 |
本项目的形态是单智能体 + 少量结构化查询工具,正落在 LangChain 的甜区:用最小的框架成本获得完整的 Agent 能力,同时保持未来向 LangGraph 升级的路径(工具与模型可直接作为图节点复用)。
二、项目需求(应用场景)
2.1 业务痛点
商城小程序的客服场景存在四个结构性问题:
| 痛点 | 现状 | 业务影响 |
|---|---|---|
| 商品咨询依赖人工 | 价格、库存、规格等问题等待客服回复 | 响应慢,夜间与高峰期咨询流失 |
| 搜索体验单一 | 仅支持关键词精确匹配,口语化表达搜不到 | 潜在成单机会流失 |
| 商品信息分散 | 名称、价格、库存、SKU 规格、分类分布在不同页面 | 用户需多次跳转才能了解一个商品 |
| 图片咨询无法处理 | 用户截图/拍照咨询时无法识别商品 | 纯人工处理,成本高 |
2.2 核心诉求
- 7×24 小时即时应答:商品咨询秒级响应,无需人工值守;
- 数据必须精确:价格、库存、销量等数字 100% 来自数据库实时查询,杜绝大模型幻觉;
- 多轮对话连贯:支持「第一个多少钱?」「它的库存呢」这类上下文追问;
- 图文双通道:文字咨询走商品问答,图片咨询走视觉理解,覆盖小程序主要咨询形态;
- 架构可演进:后续接入订单查询、售后政策、RAG 知识库时不改主链路。
2.3 技术路线决策
针对「让 AI 掌握真实商品数据」,候选路线有三条:关键词规则机器人(维护成本高、覆盖面窄)、RAG 向量知识库(文档有同步周期,价格库存等数字易过期失真)、Tool Calling 实时查库。由于商品咨询是强结构化、强实时性场景,最终选定 Tool Calling 路线:智能服务与商城系统共享同一 MySQL 数据库,AI 在对话中实时查询,零同步延迟;RAG 保留为二期能力,与 Tool Calling 形成结构化 + 非结构化互补。
三、实际落地
3.1 总体架构
┌─────────────────────────────────────────────────────────────┐ │ 商城小程序(微信) │ │ 图文聊天界面 · 消息会话 │ └───────────────────────┬─────────────────────────────────────┘ │ HTTPS ▼ ┌─────────────────────────────────────────────────────────────┐ │ api-nest 智能服务(NestJS) │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ ai_chat 智能客服模块 │ │ │ │ · 双通道路由(文本→Agent / 图片→视觉模型) │ │ │ │ · 多轮历史管理 · 客服人设 System Prompt │ │ │ └──────────────┬──────────────────────┬──────────────────┘ │ │ │ 纯文本 │ 含图片 │ │ ▼ ▼ │ │ ┌──────────────────────────┐ ┌──────────────────────┐ │ │ │ LangChain Agent │ │ 视觉大模型 │ │ │ │ createAgent(model, │ │ 图文理解直接作答 │ │ │ │ tools, systemPrompt) │ └──────────────────────┘ │ │ └──────────────┬───────────┘ │ │ │ Tool Calling │ │ ▼ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ ai_mall 商品知识库模块(只读) │ │ │ │ 4 个查询工具:模糊搜索 / 商品详情 / 分类清单 / 分类浏览 │ │ │ └──────────────┬────────────────────────────────────────┘ │ └─────────────────┼─────────────────────────────────────────────┘ │ TypeORM 只读查询 ▼ ┌─────────────────────────────────────────────────────────────┐ │ MySQL 共享数据库 tb_mall(与 TP8 同库) │ │ product / app_category / sku_stock 等商城表 │ └─────────────────────────────────────────────────────────────┘3.2 多平台模型工厂
src/config/llm.config.ts把「选哪个大模型平台」收敛为一个环境变量LLM_PLATFORM,业务代码只面对createChatModel():
// dashscope(默认):qwen-plus 文本 / qwen3-vl-plus 视觉// deepseek:deepseek-chat(纯文本)// ollama:本地 qwen2.5 / qwen3-vl,满足数据不出内网场景constllm=createChatModel({vision:true})// 视觉任务constagent=createAgent({model:createChatModel(),tools,systemPrompt})BaseChatModel统一抽象使平台切换零业务代码改动:密钥缺失时给出可读报错,视觉标志位自动映射到平台的视觉模型。
3.3 商品知识库工具集
src/modules/ai_mall/ai_mall.tools.ts用tool()+ zod schema 定义 4 个只读查询工具,工具描述面向模型编写(说明用途与调用时机),模型据此自主决策:
| 工具 | 能力 | 典型触发语 |
|---|---|---|
search_products | 关键词模糊搜索在售商品(名称 + 关键词双字段) | 「有没有保温杯」 |
get_product_detail | 按商品编号查价格、库存、SKU 规格、详情摘要 | 「第一个多少钱」 |
list_categories | 列出全部分类 | 「你们都卖什么品类」 |
list_products_by_category | 按分类浏览在售商品 | 「手机分类下有什么」 |
constsearchProducts=tool(async({keyword})=>JSON.stringify(awaitmallService.searchProducts(keyword)),{name:'search_products',description:'按关键词模糊搜索商城在售商品...',schema:z.object({keyword:z.string().describe('搜索关键词')}),},)设计要点:
- 参数强类型约束:zod schema 自动转为各模型的 JSON Schema,模型提取参数错误在协议层即被拦截,打不到 SQL;
- 返回明确语义:空结果返回「商品不存在或已下架」等可读信息,避免模型误读空数组;
- 查询语义对齐商城业务:统一过滤
deleted = 0且仅在售,排序规则(置顶 > 排序值 > id 倒序)对齐 TP8 现有 v1 接口——AI 看到的商品世界与用户端完全一致; - 只读实体隔离:mall 模块独立定义只读 Entity,不含成本价等敏感字段,AI 链路天然无法触及经营敏感数据。
工具集通过 NestJS 依赖注入装配(ai_mall.module.ts的MALL_TOOLS令牌 +useFactory),测试中可整体 mock,不依赖真实模型与数据库。
3.4 智能客服 Agent
src/modules/ai_chat/ai_chat.service.ts的chat()方法是链路核心,双通道路由 + Agent 构造:
// 纯文本路径:智能体 + 4 个商品工具 + 客服人设constagent=createAgent({model:createChatModel(),tools:this.mallTools,systemPrompt,})constresult=awaitagent.invoke({messages:[...historyMessages,{role:'user',content:text}],})returnresult.messages.at(-1)?.content三条关键设计:
- 双通道解耦:携带图片时直接调用视觉模型
llm.invoke(多模态消息:image_url+text),不挂工具——规避视觉模型 Tool Calling 不稳定的问题;纯文本才走 Agent; - 历史消息安全过滤:仅接收 user/assistant 角色消息,过滤工具中间态消息(tool 消息需
tool_call_id,前端无法提供),防止协议层注入; - 场景上下文注入:
systemContext可选拼接到 system prompt(如「用户当前咨询的商品 X」),让 AI 默认按当前商品回答,避免重复追问。
3.5 防幻觉约束(客服人设 Prompt)
智能体内置六条强制规则,是数据可信的核心保障:
| 规则 | 内容 |
|---|---|
| 先查后答 | 任何商品相关问题必须先调用工具查真实数据,严禁编造 |
| 查无如实 | 查不到时如实告知,并建议更换关键词 |
| 编号锚定 | 列举商品必须附上来自工具返回的真实编号 |
| ID 溯源 | 详情查询的 ID 只能来自本轮或历史对话中工具的真实返回 |
| 表达规范 | 中文作答、简洁清晰、多商品列表化 |
| 边界友好 | 商品无关问题礼貌自然回应,不生硬拒绝 |
其中「编号锚定 + ID 溯源」解决了一个实测中暴露的真问题:多轮追问时(「第一个多少钱」),模型会编造商品 ID。通过强制要求列举商品时附带工具真实返回的编号,「编号」成为多轮对话中的数据锚点,模型只能引用真实存在过的 ID,从机制上根除编造。
3.6 一次完整对话的内部流程
以「有没有保温杯?第一个多少钱?」为例:
POST /api/chat/message { message, history } │ ▼ ai_chat.chat() 判定纯文本路径 │ ▼ createAgent({ model, tools: MALL_TOOLS, systemPrompt }) │ 4 个 zod schema 已转为模型的 JSON Schema ▼ agent.invoke({ messages: [...history, user] }) │ ├─► 第 1 轮:模型输出 tool_call │ → search_products("保温杯") → TypeORM 查询 tb_mall │ → JSON 结果回喂(role: tool 消息由框架自动拼装) │ → 模型组织回答,按约束附上真实商品编号 │ └─► 第 2 轮(追问):模型从历史提取真实编号 → get_product_detail(12) → 返回价格/库存/SKU → 最终回答 │ ▼ result.messages.at(-1) 取终态文本返回小程序框架自动完成了 tool_call 解析、tool_call_id配对、role: "tool"消息拼装、循环终止判断;业务代码只写「人设 + 工具集 + 取终态」。
四、技术成果与总结
4.1 已交付成果
| 成果 | 说明 |
|---|---|
| 图文对话接口 | POST /api/chat/message,支持文本/图片/多轮历史/场景上下文 |
| 商品知识库模块 | 4 个只读查询工具,直连共享 MySQL,查询语义对齐商城 v1 接口 |
| LangChain Agent 客服 | 三行构造代码获得完整工具调用回路,零协议工程代码 |
| 多平台模型工厂 | LLM_PLATFORM环境变量一键切换 dashscope / deepseek / ollama |
| 防幻觉机制 | 六条人设规则 + zod 参数约束 + 编号锚定,多轮 ID 编造问题修复并回归验证 |
| 画布文本节点接口 | POST /api/chat/text(不挂工具的纯文本生成,走 deepseek 平台,带余额计费校验) |
4.2 技术收益
- 协议代码归零:Agent 循环、工具协议、消息序列化全部由框架承担,
ai_chat+ai_mall两个模块的 LangChain 相关胶水代码约 30 行; - 数据可信:回答中每个数字来自当次 SQL 查询,配合 pino 结构化日志,「AI 查了什么」可回放审计;
- 零厂商锁定:换模型平台不改业务代码;本地 Ollama 部署满足数据不出内网的合规场景;
- 写入风险为零:AI 链路没有任何写库路径,从架构上杜绝误改商品/订单数据。
4.3 演进路线
| 阶段 | 能力 | 框架侧准备 |
|---|---|---|
| 二期 | RAG 知识问答(退换货政策、使用指南) | ai_mbti模块已用@langchain/ollamaEmbeddings 跑通向量检索,与 Tool Calling 互补即可上线 |
| 二期 | 小程序正式接入 | 会话持久化 + SSE 流式输出(BaseChatModel.stream()原生支持) |
| 三期 | 订单/售后工具扩展 | 新增tool()定义并注册进MALL_TOOLS,主链路零改动 |
| 远期 | 多步骤编排(如画布整图生成) | 现有 Tool/Model 可直接作为 LangGraph 节点原材料,同栈升级 |
4.4 总结
本项目验证了一个务实结论:在「单智能体 + 结构化工具调用」这个复杂度甜区上,LangChain 用模型抽象、工具协议、预置 Agent 三块积木,把 LLM 应用的协议工程成本压到接近于零,使团队精力全部投入在业务工具设计与防幻觉规则上。技术选型上没有追求「最强大的框架」,而是选择了「抽象层级刚好匹配当前复杂度、且保留同栈演进路径」的方案——工具 zod 化、模型切换配置化、Agent 循环框架化,这三条原则也是后续扩展(RAG、订单工具、流式输出)可以复用的既定范式。当编排需求真正跨过「多节点 + 循环 + 人工干预」的门槛时,再引入 LangGraph,届时今天的每一个 Tool 都是图节点的原材料。