零售行业 AI 落地——智能客服和个性化推荐的真实挑战
零售行业 AI 落地——智能客服和个性化推荐的真实挑战
我接触过的零售 AI 项目里,失败率是最高的。
失败原因不是技术不行,是做之前没搞清楚零售场景的特殊性。团队拿着在其他行业验证过的方案,直接套到零售里,然后发现:在数据库里查个库存怎么这么慢?用户问"这个有货吗"怎么老是回答错?推荐结果为什么用户完全不感兴趣?
零售行业有三个特殊性,是其他行业没有的:
一、SKU 数量极多。大型零售商动辄几百万 SKU,小品牌也有几万。推荐系统要处理的商品候选集规模,比其他行业大一到两个数量级。
二、实时库存约束。用户问"有货吗",你必须实时查,不能用缓存,因为库存每分钟都在变。这和知识库问答完全不同——知识是相对静态的,库存是实时动态的。
三、促销复杂性。零售的促销规则是噩梦级别的:满减、折扣、叠加优惠、会员价、地区价、时间限制……同一个商品可能因为你的会员等级、下单时间、购物车里的其他商品,最终价格完全不同。AI 客服必须能处理这种复杂度,否则回答出来的价格是错的,直接导致投诉。
智能客服的核心挑战
零售智能客服要处理的问题,远比"问答检索"复杂。
用户的一句"这个手机有没有黑色 256G 的",背后要做的事情是:
- 意图识别:这是一个库存查询意图,不是推荐意图
- 实体提取:商品是"这个手机"(需要从对话上下文获取具体型号),颜色是"黑色",规格是"256G"
- 实时库存查询:调库存系统,查指定 SKU 的库存
- 结合库存情况生成回答:有货就直接说,无货要推荐替代品
这一套流程,每一步都要做对,任何一步错了,用户体验就崩了。
更难的场景是追踪查询:
- "我上次买的那个洗发水,还有没有那个款"(需要查历史订单,识别商品)
- "有没有比这个便宜一点的"(需要理解"便宜一点"是相对当前商品,而不是绝对价格)
- "我的订单什么时候到"(需要查实时物流信息)
意图识别 + 实时库存查询的设计
意图分类
零售客服的意图比通用客服复杂得多,需要细化分类:
public enum RetailIntentType {
// 商品查询类
PRODUCT_AVAILABILITY, // 有货/库存查询
PRODUCT_PRICE, // 价格查询(含促销)
PRODUCT_SPEC, // 规格/参数查询
PRODUCT_COMPARISON, // 商品对比
PRODUCT_RECOMMENDATION, // 推荐
// 订单类
ORDER_STATUS, // 订单状态
ORDER_TRACKING, // 物流追踪
ORDER_CANCEL, // 取消订单
ORDER_RETURN, // 退换货
// 促销类
PROMOTION_QUERY, // 促销活动查询
COUPON_USAGE, // 优惠券使用方式
// 售后类
COMPLAINT, // 投诉
FEEDBACK // 反馈
}实体识别 + 意图判断
@Service
@Slf4j
public class RetailIntentClassifier {
private final ChatClient chatClient;
private static final String CLASSIFICATION_PROMPT = """
你是一个零售电商的客服意图识别助手。
请分析用户消息,识别意图和关键实体。
用户消息:{userMessage}
对话历史:{conversationHistory}
返回JSON格式:
{
"primaryIntent": "意图类型",
"confidence": 0.0-1.0,
"entities": {
"productId": "商品ID(如果能从上下文确定)",
"productName": "商品名称(用户提到的)",
"sku": {
"color": "颜色",
"size": "规格",
"storage": "存储容量"
},
"orderId": "订单ID",
"quantity": "数量"
},
"needsRealTimeData": true/false, // 是否需要查询实时数据
"dataNeeded": ["INVENTORY", "PRICE", "ORDER_STATUS", "LOGISTICS"] // 需要哪些数据
}
""";
public RetailIntentResult classify(String userMessage, ConversationContext context) {
String historyText = buildHistoryText(context.getRecentMessages());
String prompt = CLASSIFICATION_PROMPT
.replace("{userMessage}", userMessage)
.replace("{conversationHistory}", historyText);
String response = chatClient.prompt()
.user(prompt)
.call()
.content();
return parseIntentResult(response);
}
private String buildHistoryText(List<ConversationMessage> messages) {
if (messages == null || messages.isEmpty()) return "无历史对话";
return messages.stream()
.map(m -> m.getRole() + ": " + m.getContent())
.collect(Collectors.joining("\n"));
}
}实时数据查询适配器
@Service
public class RetailDataAdapter {
private final InventoryService inventoryService;
private final PriceService priceService;
private final OrderService orderService;
private final LogisticsService logisticsService;
/**
* 根据意图识别结果,查询所需的实时数据
*/
public RetailRealTimeData fetchRealTimeData(RetailIntentResult intent,
String userId) {
RetailRealTimeData data = new RetailRealTimeData();
if (intent.getDataNeeded().contains("INVENTORY")) {
String productId = intent.getEntities().getProductId();
if (productId != null) {
InventoryInfo inventory = inventoryService.getRealTimeInventory(
productId,
intent.getEntities().getSku()
);
data.setInventory(inventory);
}
}
if (intent.getDataNeeded().contains("PRICE")) {
String productId = intent.getEntities().getProductId();
if (productId != null) {
// 价格查询需要用户ID(影响会员价格)
PriceInfo price = priceService.getEffectivePrice(productId, userId);
data.setPrice(price);
}
}
if (intent.getDataNeeded().contains("ORDER_STATUS") ||
intent.getDataNeeded().contains("LOGISTICS")) {
String orderId = intent.getEntities().getOrderId();
if (orderId != null) {
OrderInfo order = orderService.getOrderDetail(orderId);
data.setOrder(order);
if (order != null && intent.getDataNeeded().contains("LOGISTICS")) {
LogisticsInfo logistics = logisticsService.getTrackingInfo(
order.getTrackingNumber()
);
data.setLogistics(logistics);
}
}
}
return data;
}
}零售客服核心服务
@Service
@Slf4j
public class RetailCustomerService {
private final RetailIntentClassifier intentClassifier;
private final RetailDataAdapter dataAdapter;
private final VectorStore productKnowledgeBase; // 产品知识库(规格、FAQ等)
private final ChatClient chatClient;
private final ConversationContextManager contextManager;
public CustomerServiceResponse handleQuery(CustomerServiceRequest request) {
String userId = request.getUserId();
String userMessage = request.getMessage();
String sessionId = request.getSessionId();
// 1. 获取对话上下文
ConversationContext context = contextManager.getContext(sessionId);
// 2. 意图识别
RetailIntentResult intent = intentClassifier.classify(userMessage, context);
log.info("意图识别结果: {}, 置信度: {}", intent.getPrimaryIntent(), intent.getConfidence());
// 3. 获取实时数据(库存、价格、订单等)
RetailRealTimeData realTimeData = dataAdapter.fetchRealTimeData(intent, userId);
// 4. 从产品知识库检索相关知识(产品规格、政策等静态知识)
List<Document> knowledgeDocs = retrieveRelevantKnowledge(userMessage, intent);
// 5. 生成回答
String answer = generateAnswer(userMessage, intent, realTimeData, knowledgeDocs, context);
// 6. 更新对话上下文
contextManager.updateContext(sessionId, userMessage, answer, intent);
return CustomerServiceResponse.builder()
.answer(answer)
.intent(intent.getPrimaryIntent())
.sessionId(sessionId)
.build();
}
private List<Document> retrieveRelevantKnowledge(String query, RetailIntentResult intent) {
// 根据意图类型决定检索策略
String searchQuery = buildSearchQuery(query, intent);
return productKnowledgeBase.similaritySearch(
SearchRequest.query(searchQuery)
.withTopK(3)
.withSimilarityThreshold(0.7)
);
}
private String buildSearchQuery(String originalQuery, RetailIntentResult intent) {
// 增强搜索查询,加上意图和实体信息
StringBuilder queryBuilder = new StringBuilder(originalQuery);
if (intent.getEntities().getProductName() != null) {
queryBuilder.append(" ").append(intent.getEntities().getProductName());
}
return queryBuilder.toString();
}
private String generateAnswer(String userMessage, RetailIntentResult intent,
RetailRealTimeData realTimeData,
List<Document> knowledgeDocs,
ConversationContext context) {
// 构建系统提示,根据意图类型有不同的侧重
String systemPrompt = buildSystemPrompt(intent.getPrimaryIntent());
// 构建包含所有信息的用户提示
String userPrompt = buildUserPrompt(userMessage, realTimeData, knowledgeDocs, context);
return chatClient.prompt()
.system(systemPrompt)
.user(userPrompt)
.call()
.content();
}
private String buildSystemPrompt(RetailIntentType intentType) {
String basePrompt = """
你是一个专业的零售电商客服助手。
回答原则:
1. 只根据提供的数据回答,不要编造库存、价格、订单信息
2. 如果没有所需的实时数据,说明无法确认并引导用户其他途径查询
3. 回答简洁友好,避免过长
4. 如果商品缺货,主动推荐替代品(如果有相关信息)
""";
return switch (intentType) {
case PRODUCT_AVAILABILITY -> basePrompt + "\n当前处理库存查询,重点说明是否有货、库存数量。";
case PRODUCT_PRICE -> basePrompt + "\n当前处理价格查询,要包含原价、当前价,如有促销要说明。";
case ORDER_STATUS, ORDER_TRACKING -> basePrompt + "\n当前处理订单查询,要包含订单状态、物流信息。";
case PROMOTION_QUERY -> basePrompt + "\n当前处理促销查询,要清晰说明优惠规则和使用条件。";
default -> basePrompt;
};
}
private String buildUserPrompt(String userMessage, RetailRealTimeData data,
List<Document> knowledgeDocs, ConversationContext context) {
StringBuilder sb = new StringBuilder();
sb.append("用户问题:").append(userMessage).append("\n\n");
// 实时数据
if (data.getInventory() != null) {
sb.append("【实时库存数据】\n");
sb.append("商品:").append(data.getInventory().getProductName()).append("\n");
sb.append("规格:").append(data.getInventory().getSkuDescription()).append("\n");
sb.append("库存状态:").append(data.getInventory().isInStock() ? "有货" : "无货").append("\n");
if (data.getInventory().isInStock()) {
sb.append("可售数量:").append(data.getInventory().getAvailableQuantity()).append("\n");
}
sb.append("\n");
}
if (data.getPrice() != null) {
sb.append("【价格信息】\n");
sb.append("原价:").append(data.getPrice().getOriginalPrice()).append("元\n");
sb.append("当前售价:").append(data.getPrice().getCurrentPrice()).append("元\n");
if (data.getPrice().getPromotionDescription() != null) {
sb.append("促销说明:").append(data.getPrice().getPromotionDescription()).append("\n");
}
sb.append("\n");
}
if (data.getOrder() != null) {
sb.append("【订单信息】\n");
sb.append("订单号:").append(data.getOrder().getOrderId()).append("\n");
sb.append("订单状态:").append(data.getOrder().getStatus()).append("\n");
sb.append("下单时间:").append(data.getOrder().getOrderTime()).append("\n");
if (data.getLogistics() != null) {
sb.append("物流状态:").append(data.getLogistics().getLatestStatus()).append("\n");
sb.append("预计送达:").append(data.getLogistics().getEstimatedDelivery()).append("\n");
}
sb.append("\n");
}
// 产品知识
if (!knowledgeDocs.isEmpty()) {
sb.append("【产品知识参考】\n");
knowledgeDocs.forEach(doc -> sb.append(doc.getContent()).append("\n\n"));
}
return sb.toString();
}
}个性化推荐:LLM + 协同过滤的混合方案
纯 LLM 做推荐有一个根本问题:LLM 不知道每个用户的历史行为数据。你不可能把一个用户几年的购买记录全部塞进 context。
纯协同过滤做推荐有另一个问题:对新用户、新商品、小众商品效果差(冷启动和长尾问题)。
混合方案:协同过滤负责"用户可能会喜欢什么",LLM 负责"怎么从候选商品里选出最适合当前场景的"。
@Service
@Slf4j
public class HybridRecommendationService {
private final CollaborativeFilteringEngine cfEngine;
private final ChatClient chatClient;
private final VectorStore productVectorStore;
public List<ProductRecommendation> recommend(RecommendationRequest request) {
String userId = request.getUserId();
String currentContext = request.getContext(); // 用户当前看的商品/页面
int targetCount = request.getTargetCount();
// 1. 协同过滤:生成候选商品列表(返回多于目标数量的候选)
List<String> cfCandidates = cfEngine.getCandidates(userId, targetCount * 5);
// 2. 向量语义检索:基于当前上下文的相关商品
List<Document> semanticCandidates = productVectorStore.similaritySearch(
SearchRequest.query(currentContext).withTopK(targetCount * 3)
);
// 3. 合并候选集,去重
Set<String> allCandidateIds = new LinkedHashSet<>();
allCandidateIds.addAll(cfCandidates);
semanticCandidates.forEach(doc ->
allCandidateIds.add((String) doc.getMetadata().get("productId"))
);
// 4. 获取候选商品详情
List<ProductInfo> candidates = fetchProductDetails(
new ArrayList<>(allCandidateIds).subList(0, Math.min(30, allCandidateIds.size()))
);
// 5. LLM 重排序:根据当前场景和用户偏好,从候选集里选出最终推荐
List<ProductRecommendation> finalRecs = rerankWithLLM(
candidates, userId, currentContext, request.getUserProfile(), targetCount
);
return finalRecs;
}
private List<ProductRecommendation> rerankWithLLM(
List<ProductInfo> candidates,
String userId,
String currentContext,
UserProfile userProfile,
int targetCount) {
// 构建候选商品摘要(不把全部信息都给LLM,避免token过多)
String candidatesSummary = candidates.stream()
.map(p -> String.format("ID:%s | %s | 价格:%.0f元 | 类别:%s | 评分:%.1f",
p.getProductId(), p.getName(), p.getPrice(), p.getCategory(), p.getRating()))
.collect(Collectors.joining("\n"));
String prompt = """
你是一个零售推荐助手。请从以下候选商品中,为用户选择最合适的%d件推荐。
【当前场景】
%s
【用户画像】
年龄段:%s
偏好品类:%s
价格敏感度:%s
【候选商品列表】
%s
选择标准:
1. 与当前场景的相关性(最重要)
2. 与用户偏好的匹配度
3. 性价比(结合价格敏感度)
4. 商品质量(评分)
请返回JSON格式:
{
"recommendations": [
{
"productId": "商品ID",
"reason": "推荐理由(一句话,对用户显示的)",
"relevanceScore": 0.0-1.0
}
]
}
""".formatted(
targetCount,
currentContext,
userProfile.getAgeGroup(),
String.join("、", userProfile.getPreferredCategories()),
userProfile.getPriceSensitivity(),
candidatesSummary
);
String response = chatClient.prompt()
.user(prompt)
.call()
.content();
return parseRecommendationResult(response, candidates);
}
}零售 AI 应用架构
几个踩坑经验
坑一:库存缓存导致"有货但买不到"
早期我在库存查询前加了 Redis 缓存(TTL 5 分钟),想减少对库存系统的压力。结果出现了"AI 说有货,用户下单发现没有"的情况。
零售库存必须实时查,5 分钟的 TTL 在促销时段根本不够。解决方案:去掉缓存,直接查,同时对库存系统做好限流和熔断。
坑二:促销价格计算逻辑放进了 Prompt
一开始我把促销规则用自然语言描述写进了 Prompt,让 LLM 来计算最终价格。比如:"满300减50,叠加9折会员价,再减去购物车的满200减30券……"
LLM 算数是会出错的。有个用户问最终价格,LLM 给出了一个算法上完全合理但实际数字计算错误的答案。
解决方案:价格计算永远在代码里做,LLM 只负责解释,不负责计算。把算好的价格和促销明细作为数据传给 LLM,让它生成解释性的文字。
坑三:推荐被热销商品垄断
协同过滤的经典问题:热销商品得到更多曝光,更多购买,更多曝光……形成马太效应。最终推荐列表里永远是那几款热销品,长尾商品毫无机会。
通过在 LLM 重排序的 Prompt 里加入"适当包含非热门商品,给用户带来新鲜感"来缓解,同时在候选集生成时强制加入一定比例的新品和长尾品。
总结
零售 AI 落地,技术上要记住两点:
实时数据和静态知识要分开处理:库存、价格、订单是实时数据,调系统 API;产品知识、政策说明是静态知识,用 RAG。两种数据的处理方式根本不同,不要搞混。
LLM 负责理解和生成,不负责计算:价格计算、库存判断、数量统计,这些都在代码里做。LLM 的输出只是面向用户的自然语言回答。
把这两点做对了,零售 AI 客服的质量会有显著提升。
