一、背景:领导一句话,我开始了「AI+电商」的不归路

“小X,你看现在 ChatGPT 这么火,咱们商城也搞个 AI 客服吧,用户问订单、查物流,让AI直接回答,省得招那么多客服。”

领导说得轻巧。我当时心里想的是:不就是调个 API 嘛,Python 几行代码的事。

但真正动手才发现——这是在微服务架构里给已有电商系统装一个 AI 大脑,坑多到我想骂人。

当时我们用的技术栈是 SpringCloud Alibaba + Nacos + Feign + Redis,数据库 MySQL,服务拆分得很细:mall-ordermall-productmall-usermall-seckill……各自独立部署。

而我要做的是:新起一个 mall-ai 模块,让它能看懂用户的咨询,能查订单数据,还得记住上下文。听起来是不是很简单?

结果第一个版本上线就崩了 —— AI 回答完全不沾边,用户说"帮我看看昨天那个红色衣服的订单",AI 回了一句"我是一个二次元少年,今年18岁"。

嗯???

二、核心难题:让AI「说人话」+「干实事」为什么这么难?

我的需求其实很明确:

  1. AI 要能理解电商业务场景(查订单、查物流、处理售后)
  2. AI 要能调用真实订单数据(不能瞎编)
  3. AI 要记住上下文(用户连续问,不能失忆)
  4. 所有功能跑在微服务架构里,不能把其他模块拖垮

听起来就是个 Prompt + 几个 API 调用的事对吧?

但真正上手后你会发现,最大的坑不在于 AI 本身,而在于「AI 怎么跟你的微服务体系打通」。我拆出四个血泪坑,一个一个说。

三、坑一:AI 满嘴跑火车,问订单它编数据

问题现象

用户问:“我的订单 ORDER20240512001 发货了吗?”

AI 答:“亲,您的订单已经发货啦,预计3天后到达哦~”

但实际数据库里这个订单明明还在"待支付"状态。

根因分析

大模型的本质是生成式的,它不是数据库,它不知道真实数据是什么。你光靠 Prompt 说"你要查询真实数据",它做不到——因为 LLM 没有读你数据库的权限

我当时犯的错误就是:只写了 Prompt,寄希望于 AI 自己"知道"订单状态。

解决方案:Prompt 限制 + 外部工具调用

第一个教训是:给AI划禁区,并且要有「钩子」让它能触发真实查询

我用 LangChain4j 的 @Tool 注解做了一个订单查询工具:

@Component
@RequiredArgsConstructor
public class OrderToolService {
    private final OrderFeignClient orderFeignClient;

    @Tool("根据订单号查询订单信息")
    public String getOrderInfo(@P("订单编号,如 ORDER20240512001") String orderNo) {
        AiOrderProductDto dto = orderFeignClient.getByOrderNo(orderNo);
        if (dto == null) return "订单不存在";
        
        StringBuilder sb = new StringBuilder();
        sb.append("订单编号:").append(dto.getOrderNo()).append("\n");
        sb.append("订单总金额:").append(dto.getTotalAmount()).append(" 元\n");
        // ... 拼接完整信息
        return sb.toString();
    }
}

核心思路是:把真实数据查询封装成 Tool,AI 只负责理解用户意图,触发了 Tool 就走真实数据库,而不是让 AI 自己编。

同时 Prompt 里加了一条铁律:

【核心强制办公规则】
所有订单信息、物流状态、支付金额,必须以后台数据库真实数据为准,
禁止虚构、估算、编造。若查询无对应数据,如实告知「暂无匹配数据」。

这一步做完后,AI 终于不乱编订单了。但新的问题来了——它跟订单服务怎么通信?

四、坑二:跨服务调用踩得我头皮发麻

问题现象

mall-ai 是一个独立的微服务,订单数据在 mall-order 里。两个服务之间隔着一层网络。

我当时写好了 OrderToolService,启动,调接口,报错:

java.net.ConnectException: Connection refused
Caused by: java.net.UnknownHostException: mall-order

根因分析

这就是典型的微服务间调用没通。我漏了三样东西:

  1. Nacos 注册中心没配对 —— mall-ai 根本没注册到 Nacos,或者注册了但命名空间不一致
  2. Feign 的负载均衡依赖没加 —— SpringCloud 2020 之后 spring-cloud-starter-netflix-ribbon 被移除了,必须手动引入 loadbalancer
  3. 包名不一致导致 Feign 接口扫描不到 —— 我这个项目有个历史遗留问题,部分模块包名是 cn.li.qingxiao,其他是 cn.liqingxiao,少了个 qing

解决方案

一步一脚印排查:

第一步:确认 Nacos 配置

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        namespace: public        # 确保所有服务都在同一个 namespace
        group: DEFAULT_GROUP     # 同一个 group
        enabled: true
        register-enabled: true

这里有个易错点:namespace 用的不是名称,是 ID!如果你在 Nacos 控制台创建了自定义命名空间,YAML 里填的是那个 UUID 字符串,不是中文名。

第二步:补上 loadbalancer 依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

这一步不加上,Feign 拿到服务名后不知道往哪个实例转发,就报 UnknownHostException

第三步:统一包名

这个坑最蠢。因为复制粘贴的时候 cn.liqingxiao 被截断成了 cn.li.qingxiao@FeignClient 接口扫描时根本找不到对应的类。

排查方法:看启动日志里的 ComponentScan 扫描路径,确认它扫到了你的 Feign 接口没。

// ✅ 正确
package cn.liqingxiao.mall.feign.order;

// ❌ 错误(历史遗留)
package cn.li.qingxiao.mall.feign.order;

改完这三处,服务间调用终于通了。AI 说"查订单",Feign 远程调用 mall-order 拿数据,再返回给 AI 组织语言回答。

五、坑三:AI 没有记忆,问完就忘

问题现象

用户说:“帮我查一下刚才那个订单。”

AI 回:“请问您的订单编号是多少?”

用户又说了一遍订单号,AI 回:“好的,已查到,订单已发货。”

然后用户问:“这个订单用什么快递发的?”

AI 回:“请问您说的是哪个订单?”

根因分析

大模型是无状态的。每次调用都是独立的,它不记得上一句话说了什么。如果你不做对话记忆管理,就会出现这种"金鱼脑"情况。

解决方案:Redis 存储对话历史

我用了 LangChain4j 的 @MemoryId 注解,配合 Redis 做持久化:

// AiService 接口定义
@AiService(wiringMode = EXPLICIT, chatModel = "ollamaChatModel")
public interface AiService {
    @SystemMessage(SYSTEM_ADMIN_AI_PROMPT)
    String msg(@MemoryId String memoryId, @UserMessage String question);
}

// ChatService 实现
public String chat(String memoryId, String question) {
    // 先查 Tool(订单查询)
    Matcher matcher = ORDER_PATTERN.matcher(question);
    if (matcher.find()) {
        String orderNo = matcher.group();
        return orderToolService.getOrderInfo(orderNo);
    }
    
    // 没有触发 Tool,走 AI 对话
    String s = buildRagPrompt(question);
    String answer = aiService.msg(memoryId, s);
    
    // 手动存 Redis
    String key = CHAT_MEMORY + memoryId;
    stringRedisTemplate.opsForList().rightPush(key, "用户:" + question);
    stringRedisTemplate.opsForList().rightPush(key, "AI:" + answer);
    return answer;
}

重点是 @MemoryId同一个 memoryId 的对话会被自动合并上下文传给大模型,这样 AI 才知道之前聊过什么。

我用的是 List 结构存储,而不是简单的 key-value:

  • 每次对话都 rightPush 追加
  • 查历史时直接 range(0, -1) 取全部
  • 这样也方便做历史记录展示

六、坑四:RAG 知识库召回的内容驴唇不对马嘴

问题现象

用户问:“退货流程是什么?”

AI 开始背商品详情,或者回答完全不相关的内容。

根因分析

我用了 RAG(检索增强生成)——把电商知识库(PDF 文档)向量化,用户提问时先检索相关内容,再让 AI 基于检索结果回答。

但初期效果极差,问题出在三个地方:

  1. 分片策略不合理:100个字一片,语义被切碎了
  2. Embedding 模型太弱:qwen2:0.5b 的向量效果有限
  3. 检索到的内容没有排序:5条片段一股脑塞给 AI,AI 被噪声干扰

解决方案

核心是 RAG 管线的构建:

public String buildRagPrompt(String question) {
    // 1. 问题向量化
    Embedding questionEmbedding = embeddingModel.embed(question).content();
    
    // 2. 向量检索,取TOP-5
    List<EmbeddingMatch<TextSegment>> matchList 
        = embeddingStore.findRelevant(questionEmbedding, 5);
    
    // 3. 提取分段文本
    List<TextSegment> list = matchList.stream()
        .map(EmbeddingMatch::embedded).toList();
    
    // 4. 组装 Prompt:把检索到的知识喂给 AI
    StringBuilder context = new StringBuilder();
    context.append("请根据以下资料回答,不要编造:\n");
    for (TextSegment segment : list) {
        context.append(segment.text()).append("\n");
    }
    context.append("\n用户问题:").append(question);
    return context.toString();
}

PDF 解析部分用 PDFBox:

public void parsePdf(String fileName) throws IOException {
    InputStream in = pdfResource.getInputStream();
    PDDocument load = PDDocument.load(in);
    String text = new PDFTextStripper().getText(load);
    
    Document document = new Document(text);
    // 分片策略:100字一段,重叠10字,保留语义连贯
    DocumentSplitter recursive = DocumentSplitters.recursive(100, 10);
    List<TextSegment> split = recursive.split(document);
    
    // 批量向量化 + 入库
    Response<List<Embedding>> listResponse = embeddingModel.embedAll(split);
    embeddingStore.addAll(listResponse.content(), split);
}

几个调优经验:

  • 分片大小:100-200字比较合适,太短丢失语义,太长超出上下文窗口
  • 重叠字符:10-20字,保证切分处不丢失关键信息
  • TOP-K 取值:5 条比较稳妥,太多会引入噪声
  • Prompt 强调"不要编造":RAG 的精髓是让 AI 闭嘴——没检索到就说不知道

七、架构全景:一张图看懂 mall-ai 怎么跑通的

先看整体的模块依赖关系:

用户请求 → mall-ai (AI模块)
              │
              ├── 判断是否有订单号?
              │    ├── 是 → OrderToolService → Feign → mall-order (订单服务)
              │    │                                            └── MySQL 查询真实数据
              │    └── 否 → RAG检索 (向量库) → Ollama qwen2:0.5b → 生成回答
              │
              ├── Redis 记忆存储 ← 对话历史
              │
              └── Nacos 服务注册发现 ← 所有服务都注册在这里

你可能会问:为什么订单查询不走 RAG,而是直接走 Feign?

答案是:订单数据是实时变化的,RAG 里的知识是静态的(退货政策、常见问题),不可能把每个订单都塞进向量库。所以凡是涉及实时业务数据的,必须 Feign 远程调用,走数据库查实时数据。

而 RAG 适合的场景是:静态知识问答 —— “退货政策是什么?”、“你们用什么快递?”、“保修期多久?”

八、面试实战:这些才是面试官想听的

我把踩坑过程中提炼出的核心问题整理了一下,这些都是面试 Java 后端时的高频考点:

Q1:微服务之间调用你用的什么?遇到过什么问题?

套路回答:OpenFeign + Nacos 服务发现。踩过两个坑——LoadBalancer 依赖缺失包名不一致导致的扫描不到

你说出这两个细节,面试官就知道你是真干过的。

Q2:RAG 的原理是什么?你怎么保证 AI 不瞎编?

套路回答:RAG = 检索 + 生成。先向量化知识库,用户提问时检索相关片段,拼到 Prompt 里让 AI 基于资料回答。关键点:Prompt 强制约束 + Tool 机制兜底,涉及实时数据的走 Tool 调用数据库。

Q3:对话记忆你是怎么处理的?

套路回答:Redis 存储 + @MemoryId 机制,不是把所有历史都塞给 AI,而是控制轮数,太长的历史做摘要压缩。这个回答能展示你对成本和性能有考量。

Q4:为什么选择 LangChain4j 而不是直接调 HTTP?

套路回答@Tool 注解机制是核心价值——它可以声明式地把 Spring Bean 暴露给 AI 调用,比手写 JSON Schema 调用 Function Calling 省太多事。而且集成了 Spring Boot,配置统一。

Q5:Embedding 模型选型怎么看?

套路回答:生产环境建议用 text-embedding-ada-002bge-large-zh,本地测试可以用 qwen2:0.5b 。模型越大效果越好,但响应时间也越长,需要根据搜索和排序的效果做权衡。

九、总结 + 避坑清单

最后,把这几个月的踩坑经验整理成清单,后面做 AI + 微服务的朋友可以少走弯路:

架构设计层面

  1. AI 模块必须独立部署,不能跟业务服务耦合在一起,否则 AI 的高负载会拖垮订单服务
  2. Tool 机制是核心,所有涉及真实数据的操作都要走 Tool,不要让 AI 直接回答
  3. Feign 接口要做降级,调订单服务超时时别让 AI 卡死,给个友好提示

RAG 效果层面

  1. Prompt 里必须加「禁止编造」,这一点再怎么强调都不过分
  2. 分片大小和重叠参数一定要调,不要用默认值
  3. 知识库的质量决定了 AI 回答的底线,给 AI 喂垃圾,它就回垃圾

运维层面

  1. 本地模型推理很慢,qwen2:0.5b 在 CPU 上跑一次要 5-8 秒,建议上 GPU 或者用云端 API
  2. Redis 记忆记得设过期时间,不然对话历史无限积累,内存撑爆
  3. Nacos 配置核对三要素:namespace、group、service-name 三个维度都要对齐

希望这篇文章能给正在做电商微服务 + AI 的朋友一些启发。这些坑我自己一个个踩过来的,能帮一个是一个。

如果有问题欢迎评论区交流,看到就回。

代码已上传 GitHub(mall-Pro 项目),搜索 mall-ai 模块即可

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐