LLM导购生成:如何让AI不说谎、不编造、不乱推荐
受控生成:给AI戴上紧箍咒
AI"胡说八道"的一天
用户问:"有没有适合敏感肌的洗面奶?"
AI回答:
"为您推荐以下商品:
资生堂温和氨基酸洗面奶
- 采用日本进口氨基酸配方
- 富含玻尿酸和神经酰胺
- 原价¥299,现在限时¥199!
- 库存充足,下单即发货
兰蔻敏感肌修护洁面乳
- 添加独家专利成分X-PRO
- 获得美国FDA认证
- 特价¥399,买二送一!"
问题来了:
- ❌ 系统里根本没有资生堂这款商品(AI编造的)
- ❌ "神经酰胺"、"X-PRO"都是AI瞎编的成分
- ❌ 价格完全是臆造的,实际价格是¥259
- ❌ "买二送一"促销活动并不存在
- ❌ 库存其实只剩2件了
这就是典型的LLM幻觉(Hallucination)问题:AI会自信满满地编造事实。
在电商场景,这是致命的:
- 推荐不存在的商品 → 用户投诉
- 编造价格信息 → 法律风险
- 虚假库存 → 客服爆炸
那么,如何让AI只基于真实数据回答,不编造、不胡说?
这就是"受控生成"(Controlled Generation)要解决的问题。
受控生成的核心思路
传统LLM生成(无约束)
ini
prompt = f"用户查询:{query}"
response = llm.invoke(prompt)
# AI自由发挥,天马行空
问题:
- ❌ AI会调用训练数据中的"常识"(但可能过时或错误)
- ❌ AI会"脑补"细节(编造成分、功效)
- ❌ AI会生成看起来合理但完全虚假的信息
受控生成(强约束)
ini
prompt = f"""
用户查询:{query}
【你只能使用以下商品信息】:
- 商品A:标题、描述、价格、库存
- 商品B:标题、描述、价格、库存
【严格规则】:
1. 只能推荐给定的商品
2. 不得编造任何信息
3. 价格必须使用提供的数据
4. 库存状态必须准确
"""
response = llm.invoke(prompt)
# AI被"约束"在给定的信息范围内
关键区别:
| 维度 | 无约束生成 | 受控生成 |
|---|---|---|
| 信息来源 | LLM训练数据 + 想象 | 提供的上下文数据 |
| 准确性 | 不可控 | 可验证 |
| 幻觉率 | 高(20-40%) | 低(<5%) |
| 法律风险 | 高 | 低 |
| 用户信任 | 低 | 高 |
五层防护机制
我们设计了五层防护,确保AI不会乱说话。
第一层:严格的System Prompt
这是AI的"行为准则":
ini
system_prompt = """
你是一个专业的电商商品导购助手。
【严格遵守的约束】:
1. 只能推荐给定的商品,不得编造或提及未提供的商品
2. 不得编造商品功效、成分或特性
3. 价格、库存、促销信息必须严格使用提供的实时数据,不得猜测
4. 推荐理由必须可回溯到商品的具体字段(标题、描述、标签等)
5. 如果某商品缺货,必须明确告知用户
【回答格式】:
1. 简短理解用户需求
2. 推荐2-3款最合适的商品,每款商品包含:
- 商品名称
- 推荐理由(基于商品特性)
- 价格和促销信息
- 库存状态
3. 给出选择建议
保持专业、友好的语气,回答简洁明了。
"""
关键点:
- ✅ 明确禁止编造
- ✅ 强制使用提供的数据
- ✅ 要求可追溯性
- ✅ 规定具体格式
第二层:结构化上下文
不是简单地把商品信息扔给AI,而是精心格式化:
python
def _format_product_info(
self,
product_context: List[ProductContext],
real_time_data: Dict[str, ProductRealTimeData]
) -> str:
"""格式化商品信息"""
info_list = []
for ctx in product_context:
sku_id = ctx.sku_id
rt_data = real_time_data.get(sku_id)
# 清晰标注每个字段
info = f"【{ctx.title}】\n"
info += f"SKU: {sku_id}\n"
info += f"商品描述: {ctx.description}\n"
info += f"产品亮点: {', '.join(ctx.highlights)}\n"
if ctx.instructions:
info += f"使用说明: {ctx.instructions}\n"
# 实时数据(价格、库存)单独标注
if rt_data:
info += f"当前价格: ¥{rt_data.price}\n"
info += f"库存状态: {'有货' if rt_data.stock > 0 else '缺货'}({rt_data.stock}件)\n"
if rt_data.promotion:
info += f"促销活动: {rt_data.promotion}\n"
info_list.append(info)
return "\n---\n".join(info_list)
示例输出:
makefile
【美赞臣安儿宝A+婴幼儿配方奶粉3段】
SKU: SKU_1001
商品描述: 美国原装进口,添加DHA和ARA,支持宝宝智力发育
产品亮点: 进口奶源, DHA配方, 易消化
使用说明: 每次30ml温水加一勺奶粉,摇匀后喂养
当前价格: ¥259.0
库存状态: 有货(45件)
促销活动: 限时8折
---
【惠氏启赋蓝钻婴幼儿奶粉3段】
SKU: SKU_1002
商品描述: 爱尔兰原装进口,添加OPO结构脂,亲和人体更易吸收
产品亮点: 进口奶源, OPO结构脂, 促进吸收
当前价格: ¥368.0
库存状态: 有货(12件)
为什么要这样格式化?
- 字段明确标注:AI知道哪些是标题、哪些是价格
- 实时数据突出:价格和库存单独标注,避免AI猜测
- 结构清晰 :用分隔符
---区分不同商品 - 信息完整:所有必要信息都提供,AI不需要"脑补"
第三层:结构化输出(Structured Output)
传统方式的问题:
ini
# AI可能返回任意格式
response = """
我推荐美赞臣奶粉!它有DHA配方,价格才199元,超划算!
还有惠氏的也不错,促销价299。
"""
# 怎么提取SKU?怎么验证价格?
结构化输出方案:
python
from pydantic import BaseModel, Field
class LLMGenerateResponse(BaseModel):
"""强制LLM返回特定结构"""
response: str = Field(description="生成的导购回答")
recommended_skus: List[str] = Field(description="推荐的SKU ID列表")
# LangChain 1.1.0新特性
structured_llm = llm.with_structured_output(LLMGenerateResponse)
# AI必须返回这个结构
result: LLMGenerateResponse = structured_llm.invoke(messages)
# 类型安全,直接访问
print(result.response)
print(result.recommended_skus) # ['SKU_1001', 'SKU_1002']
优势:
- ✅ 强制AI返回特定格式
- ✅ 类型安全,避免解析错误
- ✅ 便于后续验证(知道AI推荐了哪些SKU)
第四层:多维度校验
即使有了约束,AI仍可能"打擦边球"。所以需要机器校验:
python
def _validate_response(
self,
response_text: str,
recommended_skus: List[str],
valid_skus: Set[str],
real_time_data: Dict[str, ProductRealTimeData]
) -> None:
"""多维度校验生成内容"""
# 1️⃣ 校验SKU合法性
invalid_skus = set(recommended_skus) - valid_skus
if invalid_skus:
raise ValidationError(f"LLM推荐了未提供的商品: {invalid_skus}")
# 2️⃣ 校验价格信息
price_pattern = r'¥\s*([\d,]+(?:.\d+)?)'
found_prices = re.findall(price_pattern, response_text)
for price_str in found_prices:
price = float(price_str.replace(',', ''))
valid_prices = [rt.price for rt in real_time_data.values()]
if price not in valid_prices:
raise ValidationError(f"发现未授权的价格: ¥{price}")
# 3️⃣ 校验库存状态
if '有货' in response_text or '缺货' in response_text:
for sku, rt_data in real_time_data.items():
if sku in response_text:
expected_status = '有货' if rt_data.stock > 0 else '缺货'
# 验证库存描述是否匹配
if expected_status == '缺货' and '有货' in response_text:
raise ValidationError(f"商品{sku}已缺货,但生成文本说有货")
# 4️⃣ 可扩展:校验成分、功效等
# ...
校验流程:
markdown
AI生成回答
↓
提取推荐的SKU → 验证是否在给定范围
↓
提取价格 → 验证是否匹配实时数据
↓
提取库存描述 → 验证是否准确
↓
全部通过 → 返回给用户
↓
任何失败 → 触发Fallback
第五层:Fallback降级机制
如果AI生成失败或校验不通过,使用模板生成:
python
def _generate_fallback_response(
self,
query: str,
product_context: List[ProductContext],
real_time_data: Dict[str, ProductRealTimeData]
) -> str:
"""基于模板的安全降级方案"""
response = f"根据您查询"{query}",为您推荐以下商品:\n\n"
for i, ctx in enumerate(product_context[:3], 1):
rt_data = real_time_data.get(ctx.sku_id)
response += f"{i}. {ctx.title}\n"
response += f" 特点: {', '.join(ctx.highlights[:3])}\n"
if rt_data:
response += f" 价格: ¥{rt_data.price}"
if rt_data.promotion:
response += f" ({rt_data.promotion})"
response += "\n"
if rt_data.stock > 0:
response += f" 库存充足\n"
else:
response += f" 暂时缺货\n"
response += "\n"
return response.strip()
Fallback的特点:
- ✅ 完全基于模板,零幻觉风险
- ✅ 格式固定,质量稳定
- ✅ 所有信息都可追溯
- ❌ 缺乏个性化和灵活性
何时触发Fallback?
python
try:
# AI生成
llm_response = structured_llm.invoke(messages)
# 校验
self._validate_response(...)
return llm_response
except (Exception, ValidationError) as e:
print(f"LLM生成失败: {e},使用Fallback")
return self._generate_fallback_response(...)
实际生成效果
来看一个完整的生成案例。
输入数据
用户查询: "不上火的奶粉,适合6个月宝宝"
商品上下文:
ini
product_context = [
ProductContext(
sku_id="SKU_1001",
title="美赞臣安儿宝A+婴幼儿配方奶粉3段",
description="美国原装进口,添加DHA和ARA,支持宝宝智力发育",
highlights=["进口奶源", "DHA配方", "易消化", "温和配方"],
instructions="每次30ml温水加一勺奶粉,摇匀后喂养"
),
ProductContext(
sku_id="SKU_1002",
title="惠氏启赋蓝钻婴幼儿奶粉3段",
description="爱尔兰原装进口,添加OPO结构脂,亲和人体更易吸收",
highlights=["进口奶源", "OPO结构脂", "促进吸收", "低乳糖"],
instructions="温水冲调,避免过热破坏营养"
)
]
real_time_data = {
"SKU_1001": ProductRealTimeData(
price=259.0,
stock=45,
promotion="限时8折"
),
"SKU_1002": ProductRealTimeData(
price=368.0,
stock=12,
promotion=None
)
}
AI生成的回答
markdown
根据您的需求,为您推荐以下适合6个月宝宝的温和配方奶粉:
1. **美赞臣安儿宝A+婴幼儿配方奶粉3段**
推荐理由:这款奶粉采用温和配方,添加DHA和ARA支持宝宝智力发育,
进口奶源品质有保障,易消化的特点特别适合宝宝娇嫩的肠胃。
当前价格:¥259(限时8折优惠)
库存状态:库存充足(45件)
2. **惠氏启赋蓝钻婴幼儿奶粉3段**
推荐理由:爱尔兰进口奶源,添加OPO结构脂,更亲和人体,促进营养吸收。
低乳糖配方温和不刺激,适合敏感宝宝。
当前价格:¥368
库存状态:库存有限(12件)
选择建议:如果您更看重性价比,美赞臣A+正在限时8折,是不错的选择。
如果希望更温和、更易吸收,惠氏启赋的OPO配方值得考虑。
校验通过:
- ✅ 推荐的SKU都在给定范围内
- ✅ 价格信息准确(259和368)
- ✅ 库存描述准确(45件=库存充足,12件=库存有限)
- ✅ 促销信息准确(8折)
- ✅ 推荐理由可追溯到商品特性
对比:无约束生成
如果没有约束,AI可能这样回答:
markdown
为您推荐以下不上火的奶粉:
1. **美赞臣安儿宝A+**
含有独家专利的益生元配方,能有效防止宝宝上火便秘。❌ 编造"益生元"
临床研究表明,92%的宝宝使用后排便顺畅。❌ 编造数据
特价¥199!❌ 价格错误(实际259)
全国包邮,48小时送达!❌ 编造服务
2. **惠氏启赋**
添加天然乳脂球膜,模拟母乳成分。❌ 夸大功效
荣获2023年度最佳婴儿奶粉奖。❌ 编造奖项
原价¥499,现价¥299!❌ 价格错误(实际368)
3. **雀巢能恩** ❌ 系统里根本没有这款商品!
瑞士原装进口,100%有机奶源...
看到区别了吗?无约束的AI会:
- 编造成分和功效
- 瞎编价格和促销
- 推荐不存在的商品
- 制造虚假数据
核心代码实现
完整的生成服务实现:
python
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Field
class LLMGenerateResponse(BaseModel):
"""结构化输出"""
response: str = Field(description="导购回答")
recommended_skus: List[str] = Field(description="推荐的SKU列表")
class LLMGenerateService:
def __init__(self, llm: ChatOpenAI):
self.llm = llm
# System Prompt(约束)
self.prompt = ChatPromptTemplate.from_messages([
("system", """你是专业的电商导购助手。
【严格约束】:
1. 只能推荐给定的商品
2. 不得编造信息
3. 价格、库存必须使用提供的数据
4. 推荐理由必须可追溯
5. 缺货商品必须明确告知
【回答格式】:
1. 理解用户需求
2. 推荐2-3款商品(名称、理由、价格、库存)
3. 给出选择建议"""),
("human", "用户查询: {query}\n\n商品信息:\n{product_info}")
])
def generate(self, input_data):
# 1. 格式化商品信息
product_info = self._format_product_info(
input_data.product_context,
input_data.real_time_data
)
# 2. 调用LLM(结构化输出)
messages = self.prompt.invoke({
"query": input_data.query,
"product_info": product_info
})
try:
# 使用结构化输出
structured_llm = self.llm.with_structured_output(
LLMGenerateResponse
)
llm_response = structured_llm.invoke(messages)
# 3. 校验
valid_skus = {ctx.sku_id for ctx in input_data.product_context}
self._validate_response(
llm_response.response,
llm_response.recommended_skus,
valid_skus,
input_data.real_time_data
)
# 4. 提取实际引用
actual_refs = self._extract_actual_references(
llm_response.response,
valid_skus
)
return LLMGenerateOutput(
response=llm_response.response,
referenced_skus=actual_refs,
recommended_skus=llm_response.recommended_skus,
generation_type="llm"
)
except Exception as e:
print(f"LLM生成失败: {e},使用Fallback")
# 5. Fallback
fallback_response = self._generate_fallback_response(
input_data.query,
input_data.product_context,
input_data.real_time_data
)
return LLMGenerateOutput(
response=fallback_response,
generation_type="fallback"
)
性能优化技巧
1. 控制上下文长度
商品信息可能很长,需要精简:
python
def _format_product_info(self, product_context, real_time_data):
"""精简格式化"""
info_list = []
for ctx in product_context[:5]: # 🔥 只取前5个商品
# 限制描述长度
desc = ctx.description[:200] # 🔥 最多200字
# 限制亮点数量
highlights = ', '.join(ctx.highlights[:5]) # 🔥 最多5个
info = f"【{ctx.title}】\n"
info += f"SKU: {ctx.sku_id}\n"
info += f"描述: {desc}\n"
info += f"亮点: {highlights}\n"
# ...
return "\n---\n".join(info_list)
Token节省:
makefile
优化前: 10个商品 × 500字 = 5000 tokens
优化后: 5个商品 × 250字 = 1250 tokens
节省75%
2. 缓存商品数据
避免每次生成都查数据库:
python
# 模块级缓存
_cached_product_map = None
def _get_product_map():
global _cached_product_map
if _cached_product_map is None:
db = ProductDatabase()
_cached_product_map = {p["sku_id"]: p for p in db.products}
return _cached_product_map
class LLMGenerateService:
def __init__(self, llm):
self.llm = llm
self.product_map = _get_product_map() # 使用缓存
3. 实际引用提取
结构化输出的recommended_skus是AI声称推荐的商品,但实际生成文本中可能没有提及。需要提取实际引用:
python
def _extract_actual_references(self, response_text, available_skus):
"""从文本中提取实际引用的SKU"""
referenced = []
for sku in available_skus:
if sku in response_text:
referenced.append(sku)
return referenced
# 使用
actual_refs = self._extract_actual_references(
llm_response.response,
valid_skus
)
# 这样可以知道AI真正推荐了哪些商品
为什么需要这个?
AI可能返回:
json
{
"recommended_skus": ["SKU_1001", "SKU_1002", "SKU_1003"],
"response": "我推荐SKU_1001和SKU_1002..."
}
实际只提到了两个SKU,第三个没有在文本中出现。
效果对比
测试场景
100个真实用户查询,每个查询3-5个候选商品
| 指标 | 无约束生成 | 受控生成 |
|---|---|---|
| 幻觉率(编造商品) | 18% | 0% ✅ |
| 价格错误率 | 35% | 2% ✅ |
| 库存错误率 | 28% | 1% ✅ |
| 推荐准确率 | 72% | 94% ✅ |
| 用户满意度 | 3.2/5 | 4.6/5 ✅ |
| 平均生成时间 | 1.2s | 1.5s |
| 每次成本 | $0.0002 | $0.00025 |
关键改进:
- ✅ 幻觉率从18%降到0%
- ✅ 价格准确率从65%提升到98%
- ✅ 用户满意度从3.2提升到4.6
代价:
- ❌ 生成时间略慢(增加了校验环节)
- ❌ 成本略高(更详细的prompt)
但这个代价完全值得!
真实案例
Case 1:编造商品
arduino
无约束:推荐了"SK-II神仙水洗面奶"(系统没有)
受控:只推荐给定的商品 ✅
Case 2:价格虚假
无约束:实际¥259的商品说¥199
受控:严格使用实时价格数据 ✅
Case 3:夸大功效
arduino
无约束:"临床证明可消除痘印"(编造)
受控:"温和配方,适合敏感肌"(基于商品描述) ✅
高级功能:动态调整回答风格
可以根据用户画像调整生成风格:
python
def _build_system_prompt(self, user_profile=None):
"""根据用户画像调整prompt"""
base_prompt = """你是专业的导购助手..."""
if user_profile:
# 价格敏感用户
if user_profile.get("price_sensitive"):
base_prompt += "\n特别注意:用户对价格敏感,优先推荐性价比高的商品。"
# 品质追求用户
if user_profile.get("quality_focused"):
base_prompt += "\n特别注意:用户注重品质,优先推荐高端商品。"
# 新手用户
if user_profile.get("is_newbie"):
base_prompt += "\n特别注意:用户是新手,解释要详细易懂。"
return base_prompt
监控和告警
生产环境必须监控:
python
import logging
from datetime import datetime
class GenerationMonitor:
def __init__(self):
self.logger = logging.getLogger(__name__)
self.metrics = {
"total_generations": 0,
"llm_success": 0,
"fallback_used": 0,
"validation_failures": 0
}
def log_generation(self, result, input_data):
"""记录生成结果"""
self.metrics["total_generations"] += 1
if result.generation_type == "llm":
self.metrics["llm_success"] += 1
elif result.generation_type == "fallback":
self.metrics["fallback_used"] += 1
# 告警:Fallback使用率过高
if self._fallback_rate() > 0.1: # 超过10%
self.logger.warning(f"Fallback rate high: {self._fallback_rate():.2%}")
# 记录详细日志
self.logger.info(f"""
Generation Result:
- Query: {input_data.query}
- Type: {result.generation_type}
- SKUs: {result.recommended_skus}
- Timestamp: {datetime.now()}
""")
def _fallback_rate(self):
total = self.metrics["total_generations"]
return self.metrics["fallback_used"] / total if total > 0 else 0
踩坑经验
1. System Prompt要简洁
错误示范:
ini
system_prompt = """
你是一个专业的电商商品导购助手,需要帮助用户选择最合适的商品...
(省略1000字详细说明)
"""
Token消耗太大,而且AI可能抓不住重点。
正确做法:
ini
system_prompt = """
你是电商导购助手。
【严格约束】:
1. 只推荐给定商品
2. 不得编造信息
3. 价格库存使用提供数据
【格式】:
1. 理解需求
2. 推荐商品
3. 给出建议
"""
简洁、结构化、重点突出。
2. 价格校验要考虑精度
python
# ❌ 直接比较浮点数
if price == rt_data.price:
pass
# ✅ 允许小误差
if abs(price - rt_data.price) < 0.01:
pass
浮点数精度问题可能导致校验失败。
3. 结构化输出的兼容性
旧版LangChain不支持with_structured_output:
python
def _use_structured_output(self):
"""检查是否支持结构化输出"""
return hasattr(self.llm, 'with_structured_output')
if self._use_structured_output():
# 使用新API
structured_llm = self.llm.with_structured_output(...)
else:
# 降级到传统方式
response = self.llm.invoke(...)
# 手动解析JSON
4. Fallback要足够简单
Fallback是兜底方案,不要太复杂:
python
# ❌ Fallback还调用其他AI服务
def fallback():
summary = another_llm.summarize(...)
return template.format(summary)
# ✅ 纯模板,零风险
def fallback():
return f"推荐{product.title},价格¥{price}"
写在最后
受控生成的核心是约束和验证:
- 约束输入:System Prompt、格式化上下文
- 约束输出:结构化输出、格式规范
- 验证结果:SKU校验、价格校验、库存校验
- 降级保障:Fallback机制
- 持续监控:告警、日志
技术选型建议:
- 电商导购:必须受控生成(法律风险)
- 内容创作:可以宽松一点(追求创意)
- 客服问答:中等约束(平衡准确和体验)
关键原则:
在需要准确性的场景,永远不要完全信任LLM。 给它约束,验证它的输出,准备好降级方案。
希望这篇文章能帮到正在做LLM应用的同学。欢迎交流~
参考资源
- LangChain : python.langchain.com/
- Pydantic : docs.pydantic.dev/
- OpenAI Structured Outputs : platform.openai.com/docs/guides...
技术栈
bash
langchain-openai # LLM集成
pydantic # 结构化输出
regex # 内容校验
本文基于真实生产环境的LLM导购系统,代码示例经过简化处理。受控生成是LLM应用的核心能力之一。