转载:小红书 AI产品赵哥
前言🔖
一路走来,我们聊了组件、聊了数据库、聊了架构优化。已经有产品同学搞出了 AI 应用产品的 MVP。
然后,你们就迎来了最 “酸爽” 的阶段:验收与排障。你发现你的 AI 经常一本正经地胡说八道(幻觉),明明文档里的内容它死活搜不到(检索失效),昨天还能跑的代码今天升级个库就报错(兼容性崩塌)。
很多产品经理在这个阶段会非常焦虑,觉得是模型不够聪明,或者技术选型错了。其实,大部分问题都是工程实现的细节没处理好。
今天这篇笔记,我把过去一年在几个项目中遇到的最高频的坑汇总出来,并给出基于 LangChain 的具体解法。这是一本给产品经理的《LangChain 避坑参考书》。
一、RAG 幻觉:如何让 AI 别那么放肆?🔖
现象描述:
- 你上传了一份公司《员工手册》,里面规定 “病假只发 80% 工资”。
- 用户问:“病假工资怎么算?”
- AI 回答:“根据规定,病假期间全额发放工资,并有额外慰问金。”
这是最可怕的错误。AI 用极其自信的语气说了假话。在金融、医疗、法律领域,这种错误是致命的。
根因分析:
- 知识混淆:模型底座(比如 GPT-4)训练时看过太多 “良心企业” 的语料,它把通用知识和你的私有知识搞混了。
- 注意力涣散:检索回来的文档片段太长,包含无关信息,模型没有抓到重点。
LangChain 解法:引用源(Citations)与思维链验证
不要只给开发提需求说 “别让它乱说”,你要给出具体的工程指令:“必须实现引用来源功能”。
🔹1. 强制要求 Citations(引用)
在 Prompt 中强制要求模型列出它引用的文档 ID 或页码。如果模型列不出来,就说明它在瞎编。
LangChain 提供了 create_citation_fuzzy_match_chain 等工具,但目前最好的方式是利用 Tool Calling(工具调用) 结构。
# 定义一个带有"引用字段"的结构
class AnswerWithCitation(BaseModel):
answer: str = Field(description="针对用户问题的回答")
citation: str = Field(description="回答依据的原文句子")
source_id: int = Field(description="来源文档的ID")
# 强制模型输出这种结构
llm_with_tools = llm.with_structured_output(AnswerWithCitation)
# 效果:
# 如果模型想瞎编, 它会发现填不出 citation 字段,
# 这时通常会触发模型的自我修正, 或者直接返回"未找到相关信息"。
🔹2. IDK 策略 (I Don’t Know)
在 System Prompt 中,必须把 “不知道” 的优先级提至最高。
错误示范:“请尽可能回答用户问题。”
正确示范:“你是一个基于检索内容的回答助手。只允许使用下文中提供的【Context】来回答。如果【Context】中没有答案,必须直接回复‘文档中未提及’,严禁使用你自带的知识补充。”
具象化细节:
我们在做某法律助手时发现,仅仅写 “严禁编造” 是不够的。我们后来改成了:“如果你的回答在上下文中找不到原文作为证据,你的系统积分将被扣除。” 对,PUA 大模型有时候真的管用。
二、检索失效:明明有,为什么搜不到?🔖
现象描述:
- 文档里明明写了 “错误代码 503 代表服务过载”。
- 用户问:“报错 503 是什么意思?”
- 检索系统返回了一堆 “502 错误”、“服务部署流程” 的文档,偏偏把关于 503 的那一段漏掉了。
根因分析:
这是 纯向量检索(Dense Retrieval)的硬伤。
向量检索是基于 “语义相似度” 的。在向量空间里,“503” 和 “502” 可能挨得很近(都是错误码),或者 “503” 这个数字被 Embedding 模型忽略了。
对于专有名词、精确数字、缩写词,向量检索的效果往往不如传统的关键词搜索。
LangChain 解法:混合检索(Hybrid Search)
这是目前生产环境的标配。不要只用向量搜索。
🔹1. EnsembleRetriever(集成检索器)
LangChain 提供了一个神器叫 EnsembleRetriever。它的作用是把 ** 关键词搜索(BM25)和向量搜索(Vector Store)** 的结果加权合并。
- BM25:就像以前的 Google,擅长精确匹配关键字(503、X-2000 型号)。
- Vector:擅长理解语义(“服务器崩了”=“服务不可用”)。
from langchain.retrievers import BM25Retriever, EnsembleRetriever
# 1. 建立关键词检索器
bm25_retriever = BM25Retriever.from_documents(docs)
bm25_retriever.k = 5 # 取前5个
# 2. 建立向量检索器(假设 vector_store 已经建好)
vector_retriever = vector_store.as_retriever(search_kwargs={"k": 5})
# 3. 混合!
# weights=[0.5, 0.5] 表示两者权重相等
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.5, 0.5]
)
# 现在的 ensemble_retriever 既能搜到语义相关的,也能搜到精确匹配的
产品经理 Insight:
当测试反馈 “搜不到精准词” 时,不要让开发去换 Embedding 模型,那个成本太高。直接要求加上 BM25 混合检索,立竿见影。
🔹2. 上下文重排序(Re-ranking)
有时候不是没搜到,而是搜到了排在第 10 位。模型通常只看前 5 个文档。
这时候需要引入 Reranker(重排序模型)。它像一个严格的考官,把检索回来的 10 个粗糙结果,精细地打分重排。
在 LangChain 中,可以使用 ContextualCompressionRetriever 配合 CohereRerank 或 BGE-Reranker。这能让检索准确率提升 20% 以上。
三、Lost in the Middle:长上下文的陷阱🔖
现象描述:
- 为了让 AI 掌握更多信息,你把检索到的 20 段文档(Total 10k Tokens)全塞给了 GPT-4。
- 结果发现,AI 只记得第一段和最后一段的内容,中间文档里提到的关键信息被它无视了。
根因分析:
这是 LLM 的固有缺陷。研究表明,大模型对 Context 的首尾部分关注度最高,中间部分容易 “注意力坍塌”。
LangChain 解法:
LangChain 提供了一个专门的工具来解决这个问题:长上下文重排序(LongContextReorder)。
它的逻辑很简单但很有效:把检索到的最相关的文档,物理移动到 Context 的头部和尾部,把不太相关的文档放在中间。
from langchain_community.document_transformers import LongContextReorder # 假设 docs 是检索回来的 10 个文档,按相关性从高到低排序 reordering = LongContextReorder() # 执行重排序 # 结果会变成: [第1相关, 第3相关, ... 第10相关 ..., 第4相关, reordered_docs = reordering.transform_documents(docs) # 这样,最重要的第1和第2个文档,分别占据了 Prompt 的开头和结尾
产品经理 Insight:
如果你的应用场景涉及长文档综述或者大量碎片信息整合,必须在这个环节做优化,否则 Token 费花了,效果却出不来。
四、组件兼容性:代码昨晚还能跑。。。我真是疯了我。。。🔖
现象描述:
- 周五下班前 Demo 跑通了,周一早上演示时报错
ImportError或者Method deprecated。 - 开发一查,说是 LangChain 周末自动升级了个小版本,API 变了。
根因分析:
LangChain 是目前迭代速度最快的开源项目之一(有时一天发三个版本)。为了追求新功能,它经常做破坏性更新(Breaking Changes)。而且,LangChain 生态极其庞大,依赖包(OpenAI SDK、Pydantic、NumPy)之间的版本冲突如家常便饭。
LangChain 解法:LCEL 与版本锁定
🔹1. 拥抱 LCEL (LangChain Expression Language)
在之前的笔记里我反复提到 LCEL。它不仅仅是语法糖,它是稳定性的保障。
LangChain 旧版的许多 “黑盒链”(如 RetrievalQA、ConversationalRetrievalChain)封装太重,一旦底层逻辑变动,上层就挂。
而 LCEL 使用的是原子操作(prompt | model | parser),这些基础原语非常稳定,极少变动。
建议:新项目全部使用 LCEL 写法,尽量少用 langchain.chains 下那些封装好的类。
🔹2. 锁定 requirements.txt
这对产品经理来说是管理要求。不要让开发写 langchain 这种不带版本号的依赖。
必须锁死主版本号和次版本号:
langchain==1.1.0 langchain-community==0.4.1 langchain-openai==1.1.0
🔹3. 使用 LangSmith 进行回归测试
LangChain 官方的 LangSmith 平台不仅能看日志,还能做测试。
你可以录制一组 50 个 “黄金问答对”。每次升级库版本之前,先跑一遍这 50 个 Case。如果准确率从 95% 掉到了 80%,坚决不升级。
五、结构化输出的不稳定🔖
现象描述:
- 你要求 AI 返回 JSON 格式:
{ 'name': 'Old K', 'age': 30 }。 - AI 有时候心情好会返回 JSON。
- 有时候会返回:“好的,这是你要的 JSON:json { …} ”。
- 有时候会返回:
{ 'name': 'Old K', 'age': 30, }(注意那个多余的逗号)。
这会导致你的后端 JSON 解析器报错,整个服务崩溃。
根因分析:
大模型本质是生成文本的,它不懂 JSON 语法。早期的做法是用正则表达式去修补,这非常脆弱。
LangChain 解法:Pydantic Output Parser & Tool Binding
现在解决这个问题的标准答案只有一个字:Pydantic。
LangChain 深度集成了 Pydantic 库。你只需要定义好 Python 的类,LangChain 会自动生成 Prompt 指令,并在模型输出错误时自动重试。
更高级的做法是使用模型原生的 Tool Calling(在 LangChain 中通过 .with_structured_output() 实现)。
代码块
# 再次强调,这是目前最稳的写法
class UserInfo(BaseModel):
name: str
age: int
# 大部分现代模型(GPT-3.5+,Claude 3)都支持这种原生模式
# 模型不会输出废话,直接输出符合 Schema 的对象
chain = prompt | llm.with_structured_output(UserInfo)
产品经理 Insight:
如果你的产品经常因为 “格式解析错误” 而报错,去检查代码。如果开发还在用 RegexParser 或者在 Prompt 里写 “请不要输出 Markdown 符号”,请让他们立刻重构,改用 with_structured_output。
六、来,总结一下吧🔖
这一篇笔记里,我们不再谈论那些令人兴奋的新概念,而是面对冷冰冰的现实问题。
- RAG 幻觉 →→ 用引用(Citations)和工具绑定来约束。
- 检索失效 →→ 用 ** 混合检索(Ensemble)和重排序(Rerank)** 来补救。
- 长文遗忘 →→ 用 ** 重排序(Reorder)** 把关键信息顶到两头。
- 代码崩坏 →→ 拥抱 LCEL,锁死版本。
其实,AI 应用开发的门槛不仅在于写出第一行代码,更在于如何处理这 1% 的 Edge Case(边缘情况)。
当你能熟练地指挥团队使用这些手段去排坑时,你手里的产品才能真正从 Demo 走向 Production。