#电商项目接入AI客服被问懵了?RAG + Feign跨服务调用的4个血泪坑,面试官听完直点头
一、背景:领导一句话,我开始了「AI+电商」的不归路
“小X,你看现在 ChatGPT 这么火,咱们商城也搞个 AI 客服吧,用户问订单、查物流,让AI直接回答,省得招那么多客服。”
领导说得轻巧。我当时心里想的是:不就是调个 API 嘛,Python 几行代码的事。
但真正动手才发现——这是在微服务架构里给已有电商系统装一个 AI 大脑,坑多到我想骂人。
当时我们用的技术栈是 SpringCloud Alibaba + Nacos + Feign + Redis,数据库 MySQL,服务拆分得很细:mall-order、mall-product、mall-user、mall-seckill……各自独立部署。
而我要做的是:新起一个 mall-ai 模块,让它能看懂用户的咨询,能查订单数据,还得记住上下文。听起来是不是很简单?
结果第一个版本上线就崩了 —— AI 回答完全不沾边,用户说"帮我看看昨天那个红色衣服的订单",AI 回了一句"我是一个二次元少年,今年18岁"。
嗯???
二、核心难题:让AI「说人话」+「干实事」为什么这么难?
我的需求其实很明确:
- AI 要能理解电商业务场景(查订单、查物流、处理售后)
- AI 要能调用真实订单数据(不能瞎编)
- AI 要记住上下文(用户连续问,不能失忆)
- 所有功能跑在微服务架构里,不能把其他模块拖垮
听起来就是个 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
根因分析
这就是典型的微服务间调用没通。我漏了三样东西:
- Nacos 注册中心没配对 ——
mall-ai根本没注册到 Nacos,或者注册了但命名空间不一致 - Feign 的负载均衡依赖没加 —— SpringCloud 2020 之后
spring-cloud-starter-netflix-ribbon被移除了,必须手动引入loadbalancer - 包名不一致导致 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 基于检索结果回答。
但初期效果极差,问题出在三个地方:
- 分片策略不合理:100个字一片,语义被切碎了
- Embedding 模型太弱:qwen2:0.5b 的向量效果有限
- 检索到的内容没有排序: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-002 或 bge-large-zh,本地测试可以用 qwen2:0.5b 。模型越大效果越好,但响应时间也越长,需要根据搜索和排序的效果做权衡。
九、总结 + 避坑清单
最后,把这几个月的踩坑经验整理成清单,后面做 AI + 微服务的朋友可以少走弯路:
架构设计层面
- AI 模块必须独立部署,不能跟业务服务耦合在一起,否则 AI 的高负载会拖垮订单服务
- Tool 机制是核心,所有涉及真实数据的操作都要走 Tool,不要让 AI 直接回答
- Feign 接口要做降级,调订单服务超时时别让 AI 卡死,给个友好提示
RAG 效果层面
- Prompt 里必须加「禁止编造」,这一点再怎么强调都不过分
- 分片大小和重叠参数一定要调,不要用默认值
- 知识库的质量决定了 AI 回答的底线,给 AI 喂垃圾,它就回垃圾
运维层面
- 本地模型推理很慢,qwen2:0.5b 在 CPU 上跑一次要 5-8 秒,建议上 GPU 或者用云端 API
- Redis 记忆记得设过期时间,不然对话历史无限积累,内存撑爆
- Nacos 配置核对三要素:namespace、group、service-name 三个维度都要对齐
希望这篇文章能给正在做电商微服务 + AI 的朋友一些启发。这些坑我自己一个个踩过来的,能帮一个是一个。
如果有问题欢迎评论区交流,看到就回。
代码已上传 GitHub(mall-Pro 项目),搜索 mall-ai 模块即可。
更多推荐




所有评论(0)