LangChain Query Analysis 知识点详解
本文档整理自 LangChain 中文网教程:
本文面向初学者,只聚焦知识点和实现思路,不展开环境安装、API Key、Notebook 配置等准备内容。
一、Query Analysis 是什么
1. 是什么
Query Analysis,可以翻译成“查询分析”。
它的核心意思是:
在真正检索之前,先分析用户的问题,把自然语言问题转换成更适合程序执行的查询结构
也就是说,系统不会直接把用户原话拿去做搜索,而是先做一次“问题理解”。
比如用户问:
查找 2023 年发布的 RAG 相关视频
系统不会只是把整句话原样丢给向量数据库,而是会先拆成:
Search(query="RAG", publish_year=2023)
这里已经把一个自然语言问题拆成了两个部分:
query:真正的搜索主题publish_year:附加过滤条件
2. 解决什么问题
它解决的是这样一种情况:
用户的问题不只是“搜什么”,还包含“怎么筛”
例如用户问题里可能包含:
- 年份
- 时间范围
- 作者
- 类别
- 数据来源
如果系统只是做普通向量检索,往往只能理解“主题大概是什么”,却不一定能正确执行这些结构化条件。
3. 为什么重要
因为真实业务里的问题,往往不是纯主题检索,而是“主题 + 条件”的组合。
例如:
- 2023 年的 RAG 视频
- 某位作者写过的文档
- 最近一个月的政策文件
- 某一类合同模板
这些问题如果不先做查询分析,检索结果通常会“不完全错”,但经常“不够准”。
二、为什么普通向量检索不够
1. 是什么
普通向量检索的典型流程是:
文档转向量 -> 用户问题转向量 -> 计算相似度 -> 返回最相近的文档块
这个流程特别适合找“语义接近”的文本内容。
例如问题:
how do I build a RAG agent
如果某些视频字幕里大量出现:
- RAG
- build
- retrieval
- agent
那么普通相似度搜索通常能找到不错的结果。
2. 解决什么问题
普通向量检索擅长解决的是:
我想找和这个主题最相关的内容
比如:
- 什么是 RAG
- 如何构建 RAG
- 多模态 LLM 教程
这些问题主要关注“主题相关性”。
3. 为什么它会失效
如果用户问题变成:
videos on RAG published in 2023
这里就不只是语义问题了,而是同时包含:
- 主题:RAG
- 条件:published in 2023
向量检索会优先关注整句文本和文档正文的语义接近程度,但它不会天然把:
published in 2023
理解成一个必须执行的“年份过滤条件”。
所以可能出现这种现象:
- 找到的内容确实和 RAG 有关
- 但返回的视频是 2024 年发布的
这就说明系统只做了“语义匹配”,没有做“结构化筛选”。
三、这篇教程的核心思路
1. 是什么
教程的核心思路可以概括成一句话:
先分析问题,再检索
更完整一点的流程是:
用户问题 -> 查询分析 -> 结构化查询 -> 检索执行 -> 返回结果
2. 解决什么问题
这样做以后,系统就不会只拿用户原话做搜索,而是会先问自己:
- 主题到底是什么
- 有没有年份条件
- 有没有别的过滤条件
然后再决定应该怎么检索。
3. 为什么这比直接检索更稳
因为程序执行检索时,最怕的是“信息混在一起”。
例如:
查找 2023 年发布的 RAG 相关视频
如果把这整句话原样拿去搜,程序并不知道:
- 哪部分是主题
- 哪部分是过滤条件
但如果先分析成:
Search(query="RAG", publish_year=2023)
后续代码就很清楚:
query去做相似度搜索publish_year去做元数据过滤
这就是查询分析真正提升效果的地方。
四、文档加载阶段
1. 是什么
教程先使用 YouTubeLoader 加载一批 LangChain 官方 YouTube 视频的字幕内容。
示例代码的核心逻辑是:
docs = []
for url in urls:
docs.extend(YoutubeLoader.from_youtube_url(url, add_video_info=True).load())
这里得到的 docs 不是普通字符串列表,而是 LangChain 的 Document 对象列表。
每个 Document 通常包含两部分:
page_content:正文内容metadata:元数据
2. 解决什么问题
这一阶段解决的是“数据从哪里来”的问题。
因为检索系统必须先有可搜索的数据,后面才能谈 embedding、向量库、过滤、查询分析。
3. 为什么不只加载正文
教程特意打开了:
add_video_info=True
这样除了视频字幕正文之外,还能拿到一些元数据,例如:
titlepublish_dateauthorview_countlength
这些元数据非常重要,因为:
- 它们可以用于展示结果
- 它们可以作为过滤条件
- 它们为查询分析提供了“可被筛选的字段”
如果只有正文,没有元数据,那么后面就很难做“按年份筛选”这种事情。
五、metadata 是什么
1. 是什么
metadata 可以理解成“文档的附加说明信息”。
正文是视频说了什么,metadata 是这个视频本身的一些属性。
例如:
{
"title": "OpenGPTs",
"publish_date": "2024-01-31 00:00:00",
"author": "LangChain",
"length": 1530
}
2. 解决什么问题
metadata 解决的是:
正文只能回答“内容相关吗”,metadata 才能回答“它属于哪一类”
例如:
- 这篇文档是谁写的
- 这个视频是哪一年发布的
- 这条记录属于什么类型
3. 为什么在 Query Analysis 里尤其重要
因为查询分析的目标之一,就是识别用户问题中的过滤条件。
而过滤条件最终必须落在某个具体字段上。
比如:
2023 年最后要落到publish_year某位作者最后要落到author
所以没有 metadata,查询分析只能“分析出来”,却没地方真正执行。
六、publish_year 为什么要单独提取
1. 是什么
教程虽然已经拿到了 publish_date,但还是额外添加了:
doc.metadata["publish_year"]
它是从完整发布日期里拆出来的年份整数。
例如:
2024-01-31 00:00:00 -> 2024
2. 解决什么问题
它解决的是“日期字段不方便直接过滤”的问题。
很多时候,用户不是问:
2024-01-31 发布的视频
而是问:
2024 年发布的视频
如果直接拿完整日期字符串去比较,会比较麻烦。
而提前拆出 publish_year 之后,过滤条件就变得很简单。
3. 为什么这一点很重要
因为它体现了一个很重要的工程思想:
为了后面的检索和过滤更简单,前面可以先把数据处理成更适合检索的形态
也就是说,查询分析并不只是模型层面的事,数据预处理同样重要。
七、向量索引是什么
1. 是什么
教程接下来建立了一个向量索引,过程包括:
- 切分文本
- 生成 embedding
- 把向量和文档存进 Chroma
2. 解决什么问题
它解决的是“怎么让文档可搜索”的问题。
如果只是把文档放在普通列表里,程序并不知道:
- 哪段文本和问题最接近
- 哪些内容应该排在前面
向量索引让系统可以基于语义相似度找到最相关的文本块。
3. 为什么要先切块
教程用了:
RecursiveCharacterTextSplitter(chunk_size=2000)
这是因为视频字幕通常很长,如果整篇作为一个向量,会带来几个问题:
- 语义太分散
- 用户问题往往只对应其中一小段
- 检索结果太粗
切块之后,每块内容更集中,检索粒度更细,命中效果通常更好。
八、Embedding 是什么
1. 是什么
Embedding 可以理解成“把文本转换成一串能表示语义的数字”。
在教程里使用的是:
OpenAIEmbeddings(model="text-embedding-3-small")
2. 解决什么问题
它解决的是:
如何让程序比较两段文本在语义上是否接近
人类看得懂“这两段话都在讲 RAG”,但程序直接看字符串是做不到的。
Embedding 把文本转换成向量后,程序就能计算相似度。
3. 为什么它不是“生成答案”
很多初学者会把 embedding 模型和聊天模型混淆。
其实两者职责不同:
- 聊天模型:负责理解、生成、回答
- embedding 模型:负责把文本变成向量
Query Analysis 教程里,两种模型都在发挥作用,但作用完全不同。
九、Chroma 是什么
1. 是什么
Chroma 是教程中使用的向量数据库。
它的作用是保存:
- 文本块
- 向量
- 元数据
2. 解决什么问题
它解决的是:
如何把“文档 + 向量 + 元数据”组织成一个可查询的索引
这样后面就可以做两类操作:
- 相似度检索
- 基于 metadata 的过滤
3. 为什么它在这篇教程里特别合适
因为这篇教程既要做:
- 语义搜索
又要做:
publish_year过滤
Chroma 正好支持这种方式:
vectorstore.similarity_search(query, filter=_filter)
所以它很适合拿来演示“查询分析 + 元数据过滤”的组合思路。
十、Search 模型是什么
1. 是什么
教程定义了一个 Pydantic 模型:
class Search(BaseModel):
query: str
publish_year: Optional[int]
这就是本教程最核心的数据结构之一。
2. 解决什么问题
它解决的是“如何把用户问题变成结构化查询”的问题。
如果没有这个结构,模型可能会返回一大段自然语言解释,后面的程序很难稳定处理。
有了这个结构之后,模型输出会更像:
Search(query="RAG", publish_year=2023)
程序就可以明确知道每个字段分别是什么意思。
3. 为什么只设计两个字段
因为这篇教程是入门示例,它只想演示最基本的思想:
query用于语义搜索publish_year用于年份过滤
这两个字段已经足够把“Query Analysis 的核心价值”展示出来。
后面如果你做更复杂的系统,还可以继续增加字段,例如:
authorstart_yearend_yeartopic
十一、with_structured_output 是什么
1. 是什么
教程使用了:
llm.with_structured_output(Search)
它的意思可以简单理解成:
让模型尽量按 Search 这个结构返回结果
2. 解决什么问题
它解决的是“大模型输出格式不稳定”的问题。
如果没有结构化输出,模型可能会返回:
- 一段自然语言说明
- 一个半结构化句子
- 格式不固定的文本
这样后面的代码就很难稳定处理。
3. 为什么它在 Query Analysis 中很关键
因为 Query Analysis 的重点不是“让模型自由发挥”,而是“让模型把问题整理成程序能消费的格式”。
所以结构化输出几乎是这篇教程的关键桥梁:
- 前面连接自然语言问题
- 后面连接检索函数
十二、查询生成链是什么
1. 是什么
教程用下面这些组件构成了一个“查询生成链”:
ChatPromptTemplateRunnablePassthroughChatOpenAIwith_structured_output(Search)
最后形成:
query_analyzer = {"question": RunnablePassthrough()} | prompt | structured_llm
2. 解决什么问题
它解决的是:
如何把用户原始问题,自动转换成 Search 结构
也就是说,用户输入的是一句自然语言,链输出的是一个结构化对象。
3. 为什么这一步很重要
因为这一步相当于在“用户”和“检索系统”之间加了一层翻译器。
用户只需要自然地提问,而系统内部会自动把问题整理成:
- 搜索主题
- 过滤条件
这正是 Query Analysis 的核心落地点。
十三、retrieval 函数是什么
1. 是什么
教程最后定义了:
def retrieval(search: Search) -> List[Document]:
if search.publish_year is not None:
_filter = {"publish_year": {"$eq": search.publish_year}}
else:
_filter = None
return vectorstore.similarity_search(search.query, filter=_filter)
2. 解决什么问题
它解决的是:
如何把结构化查询真正执行到向量数据库上
也就是说,前面模型已经分析出了:
- 主题是什么
- 条件是什么
现在这一步要做的是把这些分析结果真正变成检索行为。
3. 为什么它是整篇教程最关键的落地代码
因为只有写到这里,查询分析才不是“纸上谈兵”。
前面的代码更多是在做准备:
- 读文档
- 建索引
- 定义结构
- 生成结构化输出
而 retrieval() 才真正决定:
query怎么用于搜索publish_year怎么用于过滤
它把“问题分析”连接到了“实际检索”。
十四、filter 是什么
1. 是什么
教程中的过滤条件写法是:
{"publish_year": {"$eq": search.publish_year}}
这是 Chroma 支持的一种过滤语法。
其中:
publish_year是元数据字段名$eq表示等于
2. 解决什么问题
它解决的是:
只在满足某个 metadata 条件的文档里做检索
也就是说,不是先搜全库再人工看年份,而是系统在检索阶段就把不符合条件的文档排除掉。
3. 为什么这比“只做语义搜索”更准确
因为它把“主题相关”和“条件满足”同时考虑进来了。
最终系统不只是找:
- 最像 RAG 的内容
而是找:
- 最像 RAG,并且年份等于 2023 的内容
这才真正符合用户原始意图。
十五、retrieval_chain 是什么
1. 是什么
教程把查询分析器和检索函数串起来:
retrieval_chain = query_analyzer | retrieval
这个链的含义可以理解成:
先分析问题,再执行检索
2. 解决什么问题
它解决的是“把多个步骤拼成一个完整工作流”的问题。
用户只需要输入一句问题,系统内部就会自动完成:
- 问题分析
- 结构化查询生成
- 检索执行
3. 为什么这种写法适合初学者理解
因为它把整个流程串得非常清楚。
你可以把它看成:
自然语言问题 -> Search 对象 -> 检索结果
这比一堆零散函数更容易帮助初学者理解整体链路。
十六、这篇教程最核心的思想
1. 是什么
整篇教程真正想表达的核心思想是:
检索效果不仅取决于向量库和 embedding,也取决于用户问题是否先被正确翻译成检索请求
2. 解决什么问题
它解决的是很多初学者的一个误区:
只要向量库搭好了,检索自然就会准
实际上并不是这样。
用户问题如果本身没有被拆解清楚,即使向量库很好,检索也还是会偏。
3. 为什么这是学习 RAG 很重要的一课
因为它提醒你:
- RAG 不只是“建向量库”
- 检索前的问题理解也非常关键
也就是说,一个好的检索系统,往往不只是:
- 文档切块
- embedding
- 向量搜索
还包括:
- 查询重写
- 条件抽取
- 元数据过滤
十七、和你当前 analysis.py 的对应关系
1. 是什么
你当前写的 analysis.py,本质上就是在复现这篇教程的主线结构。
2. 对应关系
可以大致这样对应:
-
加载视频字幕和元数据
对应YoutubeLoader -
增加
publish_year
对应元数据预处理 -
切分文档、生成向量、建立 Chroma
对应向量索引阶段 -
普通
similarity_search(...)
对应“无查询分析的检索” -
class Search(BaseModel)
对应结构化查询模式 -
with_structured_output(Search)
对应查询分析输出 -
retrieval(search: Search)
对应真正的过滤检索逻辑
3. 为什么这对你有帮助
因为你现在不是在零散地学几个 LangChain API,而是在学一个完整思路:
当用户问题里含有结构化条件时,先分析问题,再检索
这会让你以后学:
- Self Query Retriever
- 多查询生成
- 复杂 metadata 过滤
- 完整 RAG 链
都更容易理解。
十八、初学者最该记住的结论
1. 结论一
用户原问题不一定适合直接拿去检索。
2. 结论二
普通向量检索主要擅长“主题匹配”,不天然擅长“条件过滤”。
3. 结论三
Query Analysis 的本质,是把自然语言问题变成结构化查询。
4. 结论四
结构化查询里的不同字段,通常承担不同职责:
query负责语义搜索publish_year负责 metadata 过滤
5. 结论五
更稳的检索通常不是只靠一个组件,而是:
语义搜索 + 元数据过滤 + 查询分析
一起工作。
十九、你接下来可以怎么继续学
1. 先做什么
先把你当前的 analysis.py 跑通,并且确认自己能看懂这几部分:
- 文档加载
- metadata 增加
- 向量索引
- 普通检索
- 查询分析
- 过滤检索
2. 再做什么
建议你自己尝试修改问题,例如:
- 查找 2024 年的 RAG 视频
- 查找多模态相关视频
- 查找 SQL 相关教程
- 查找 2023 年的多模态教程
这样你会更直观地观察:
- 普通检索什么时候够用
- 查询分析什么时候明显更有价值
3. 最后再学什么
等你熟悉这篇教程之后,可以继续往这些方向学:
- 多条件过滤
- 多查询生成
- Self Query Retriever
- 把查询分析接入完整 RAG 回答链
二十、一句话总结
这篇教程真正要教你的不是某几个 API,而是一个非常重要的检索思想:
当用户问题里既有“搜索主题”,又有“结构化条件”时,不要直接把原问题丢给向量库,而应该先做查询分析,再执行“语义搜索 + 元数据过滤”。

400

被折叠的 条评论
为什么被折叠?



