转载:小红书 AI产品赵哥
前言🔖
上一篇咱聊了向量数据库的选型,有朋友回去把自己公司的文档切片存进 FAISS 或 Chroma 之后,马上就遇到了新的小问题。
这也是目前 AI 产品经理上手 LangChain 最常见的一个现象:“Demo 猛如虎,实战二百五”。
- 当你只存了 10 个文档时,你问什么 AI 都能答对。
- 当你存了 10,000 个文档,涵盖了人事制度、技术文档、销售话术、财务报表时,你问:“怎么请假?”,AI 可能会根据销售话术回答你:“请假需要向客户提前报备”。
这就是 朴素 RAG(Naive RAG)的局限性。它只是简单粗暴地根据相似度检索,不管你的问题是什么类型,也不管你的数据有多少个领域。它把所有数据混在一个大池子里捞针。
今天咱们来聊的是企业级 RAG 必须跨越的门槛:Advanced RAG(高级 RAG)。
我们将利用 LangChain 强大的编排能力,实现两个核心逻辑:查询路由(Routing)和子问题拆解(Query Decomposition)。
这篇笔记稍微有点技术范儿,涉及架构逻辑,但大家不要担心,拔拔高,才能更强大。准备好了吗?咱走着!
痛点:为什么 “大一统” 的检索行不通?🔖
在企业环境里,知识库通常是异构的。
- 数据源不同:有的是 PDF 文档,有的是 SQL 数据库,有的是 API 接口。
- 领域不同:同样的关键词 “增长”,在产品或运营部门指用户增长,在市场部门可能指收入增长。
- 问题复杂度不同:“昨天的销售额是多少?” 是查数问题;“怎么配置 VPN?” 是流程问题;“比较一下 A 产品和 B 产品的优劣” 是推理问题。
如果你把这些都扔进同一个 Vector Store,用同一个 Prompt 去处理,结果必然是灾难性的。
LangChain 的解决思路是:分而治之。我们要构建一个智能调度层,在检索之前,先让 LLM 思考:“这个问题该去哪里查?该怎么查?”
核心技术一:查询路由(Query Routing)🔖
路由的核心思想是:专库专用。与其建立一个包含万物的巨大索引,不如建立多个小而精的索引。
- Index A:专门存技术文档。
- Index B:专门存 HR 员工手册。
- Index C:专门存法务合同。
当用户提问时,我们先经过一个 Router(路由器),判断意图,然后把问题分发给对应的索引。
🔹1.逻辑架构
用户的 Query →→ LLM 路由层(分类器)→→ 命中具体的 Retriever →→ 获取 Context →→ 生成答案。
🔹2.LangChain 实现代码(具象化)
LangChain 提供了非常优雅的路由实现方式。即使是产品经理,看懂下面的逻辑也不难。我们利用 LLM 的 Function Calling 能力来做精准分类。
from typing import Literal
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
# 1. 定义路由的数据结构
# 我们告诉 LLM,你必须从 "technical" 或 "hr" 中选一个
class RouteQuery(BaseModel):
"""根据用户的问题,将查询路由到合适的数据源。"""
datasource: Literal["technical", "hr"] = Field(
...,
description="如果是技术问题、代码问题,选 technical;如果是请假、福利、入职问题,选 hr"
)
# 2. 初始化强力模型(建议 GPT-5 或 GPT-4)
llm = ChatOpenAI(model="gpt-4", temperature=0)
# 3. 绑定工具(让模型强制输出结构化分类)
structured_llm_router = llm.with_structured_output(RouteQuery)
# 4. 定义路由提示词
system = """你是一个专业的分类员。你的任务是将用户的问题路由到正确的数据源。
不要回答问题,只负责分类。"""
route_prompt = ChatPromptTemplate.from_messages(
[
("system", system),
("human", "{question}"),
]
)
# 5. 构建路由链
router_chain = route_prompt | structured_llm_router
--- 测试效果 ---
Case A
print (router_chain.invoke ({"question": "怎么安装 Python 环境?"}))
输出: datasource='technical'
Case B
print (router_chain.invoke ({"question": "年假没休完可以折现吗?"}))
输出: datasource='hr'
产品经理 Insight:
在 PRD 中,你需要定义清楚 “路由规则”。也就是上面代码中 description 的部分。这部分描述写得越清楚,AI 分类就越准。不要只依赖开发去写,作为 PM,你要梳理业务边界,告诉开发:“涉及到报销的,全部分给财务库;涉及到合同的,全部分给法务库”。
核心技术二:子问题拆解(Query Decomposition)🔖
路由解决了 “去哪查” 的问题,但它解决不了复杂查询的问题。
场景描述:
用户问:“比较一下 LangChain 和 LlamaIndex 在 RAG 实现上的区别。”
朴素 RAG 的做法:
直接拿整句话去向量库搜。向量库可能会搜到介绍 LangChain 的文档,也可能搜到介绍 LlamaIndex 的文档。但很少有一篇文档是专门写这俩区别的。
结果就是:
检索回来的碎片信息拼凑不起来,回答不仅缺胳膊少腿,还容易瞎编。
LangChain 的解决思路:
把一个大问题,拆解成多个可以独立检索的小问题。
- “LangChain 的 RAG 实现特点是什么?”
- “LlamaIndex 的 RAG 实现特点是什么?”
- 拿到这两份资料后,再让 LLM 进行对比总结。
🔹1. 逻辑架构
用户 Query →→ LLM 拆解层 →→ 生成子问题列表 [Q1, Q2] →→ 分别检索 →→ 汇总 Context →→ 生成最终答案。
🔹2. LangChain 实现代码(MultiQueryRetriever)
这里我们不需要手写复杂的循环,LangChain 有一个现成的组件叫 MultiQueryRetriever,但为了让大家看懂逻辑,我展示一下核心的 Prompt 驱动拆解过程。
from langchain_core.output_parsers import StrOutputParser
# 1. 定义拆解 Prompt
# 这里的核心技巧是 Few-Shot (少样本),给 AI 几个拆解的例子
template = """你是一个智能助手。你的任务是根据用户的问题,生成
通常原本的问题太复杂,无法直接检索。请把它们拆解成更简单的问题。
请每行输出一个子问题。
示例 1:
输入:比较 iPhone 15 和 华为 Mate 60 的屏幕参数。
输出:
iPhone 15 屏幕参数是什么?
华为 Mate 60 屏幕参数是什么?
示例 2:
输入:{question}
输出:
"""
prompt_decomposition = ChatPromptTemplate.from_template(
# 2. 构建拆解链
# 模型 -> 解析成多行文本 -> 转换成列表
generate_queries_chain = (
prompt_decomposition
| ChatOpenAI(temperature=0)
| StrOutputParser()
| (lambda x: x.split("\n"))
)
# --- 测试效果 ---
user_question = "LangChain 和 Semantic Kernel 哪个更适合"
sub_questions = generate_queries_chain.invoke({"question"
print(sub_questions)
# 输出结果将是一个列表:
# [
# "LangChain 对 Java 开发的支持情况如何?",
# "Semantic Kernel 对 Java 开发的支持情况如何?"
# ]
拿到这个列表后,代码会遍历这个列表,分别去向量库检索,把找回来的 6 段话(假设每个问题找 3 段)拼在一起,最后扔给 llm 说:“基于以上信息,回答用户关于两者比较的问题。”
产品经理 Insight:
如果你的产品涉及对比分析、多步推理、长文总结,一定要在技术方案中要求加上 Decomposition 策略。否则,面对复杂问题,你的 AI 除了会说 “抱歉我不知道”,就是在一本正经地胡说八道。
进阶一下子:自查询检索器(Self-Querying)🔖
除了路由和拆解,还有一个非常高频的场景:结构化过滤。
场景描述:
用户问:“找一下 2023 年的老王写的关于 AI 的文章。”
朴素 RAG:
向量搜索是基于语义的。它会去库里找和 “2023 年”、“老王” 语义相近的词。但实际上,这里的 “2023 年” 和 “老王” 不应该参与向量计算,它们应该是过滤器(Filter)。
正确的 SQL 逻辑应该是:
SELECT * FROM docs WHERE year=2023 AND author=’ 老王 ‘ AND semantic_match (‘AI’)。
LangChain 的 SelfQueryRetriever:
它能自动把自然语言把其中的 Metadata(元数据)剥离出来。
伪代码逻辑展示
metadata_field_info = [AttributeInfo (name="author", description="文档的作者"),AttributeInfo (name="year", description="文档发布的年份")]
当用户问:找一下 2023 年老王的文章
SelfQueryRetriever 会自动将其转化为:
filter = {
"author": {"$eq": "老王"},
"year": { "$eq": 2023 }
}
query = "AI 文章"
这对对于合同检索(按金额、按签署日期)、简历库(按学历、按工作年限)等场景是刚需。
企业级 RAG 的完整形态🔖
把上面讲的结合起来,一个能在生产环境 “耐撕” 的 RAG 系统,它的数据流转应该是这样的:
- Input Guardrail(输入护栏):先判断用户是不是在问敏感词,或者是否在攻击系统。
- Query Analysis(查询分析 – 重点)
- Router:它是问 HR 还是问技术?
- Extractor:有没有时间、作者等过滤条件?
- Decomposer:问题太复杂吗?要不要拆?
- Retrieval(检索层):并行执行多个检索任务。
- Rerank(重排序):把找回来的几十个片段,用高精度的 Rerank 模型再排个序,取前 5 个。
- Generation(生成):带着这 5 个精准的片段,去问 GPT-5。
- Output Guardrail(输出护栏):检查 AI 有没有回答幻觉。
落地实战中的 3 个坑🔖
作为产品经理,在推行这种高级架构时,要注意以下代价:
🔹1.延迟(Latency)的增加
坑点:朴素 RAG 只需要请求一次 Embedding,查一次库,请求一次 LLM。速度很快。高级 RAG(路由 + 拆解)可能需要请求 3-4 次 LLM 才能生成答案。
解决方案:必须上流式输出(Streaming)。在后台进行拆解和检索时,前端要展示 “正在思考拆解步骤……”、“正在检索技术文档库……”,缓解用户焦虑。
🔹2.Token 成本的激增
坑点:每次拆解、每次路由,都是在消耗 Token。特别是 MultiQuery,把一个问题拆成 3 个,意味着你要做 3 次检索,上下文长度可能会翻 3 倍。
解决方案:路由和拆解层,尽量使用便宜的小模型(如 gpt-4.1-nano),只有最后的生成层才用 GPT-5。
🔹3.错误的路由比不路由更可怕
坑点:如果 Router 把一个 “技术问题” 错误地分到了 “HR 库”,那结果就是 0 分。
解决方案:设置兜底链(Default Chain)。如果 Router 觉得这问题既不像 HR 也不像技术,或者置信度低,就去查一个通用的大索引,或者直接利用 LLM 自身的知识库回答,并标注 “未检索到内部文档,以下基于通用知识回答”。
来,咱们总结一下吧🔖
这篇笔记的核心其实就一句话:不要让 LLM 傻傻地去检索,要先让它 “思考” 一下检索策略。
- Routing(路由)解决了数据源混乱的问题。
- Decomposition(拆解)解决了复杂逻辑推理的问题。
- Self-Query(自查询)解决了精确条件筛选的问题。
当你能跟开发团队聊出:“这个场景我们需要加一个 Router Chain,用 Function Calling 做分类,然后针对复杂 Case 上一个 MultiQuery Retriever” 时,你在团队里的技术话语权就立住了。
LangChain 最强大的地方,不仅仅是它封装了 API,而是它封装了这些 Cognitive Architectures(认知架构)。它让开发人员能够像搭积木一样,把这些高级的推理模式组装进产品里。