LangChain 入门学习第五篇-使用本地 Embeddings 实现向量检索 RAG

💡 原文中文,约3500字,阅读约需9分钟。
📝

内容提要

本文是LangChain系列第五篇教程,介绍使用教学版HashEmbeddings实现向量检索RAG。流程包括切分Markdown文档、生成向量、存入InMemoryVectorStore,并通过Retriever检索后交给模型回答。HashEmbeddings虽非语义模型,但演示了Embeddings、VectorStore和Retriever接口的使用。真实项目中可替换为OpenAIEmbeddings。下一篇将解决索引持久化问题。

🔎

延伸解读

接口抽象的价值

本文通过教学版HashEmbeddings演示了LangChain中Embeddings、VectorStore和Retriever接口的用法。关键在于,只要实现了embed_documents和embed_query方法,就能接入InMemoryVectorStore,后续替换为OpenAIEmbeddings等真实模型时,RAG链路无需大改。这体现了LangChain抽象层带来的灵活性,让开发者可以专注于业务逻辑,而不必绑定特定实现。

教学版Embeddings的局限

HashEmbeddings并非语义模型,它只是将文本hash到固定维度向量,无法理解同义表达,检索质量有限。因此,它仅适合教学演示,不能用于生产环境。真实项目中应使用如OpenAIEmbeddings等语义模型,但前提是本地或远程服务提供相应的embedding接口。

内存向量库的临时性

InMemoryVectorStore将索引存储在内存中,程序结束后索引即消失,每次运行都需要重新构建,效率低下。本文明确指出这一限制,并预告下一篇将解决索引持久化问题。对于实际应用,持久化索引是必要的,以避免重复计算和加快启动速度。

Q&A

LangChain 中如何实现向量检索 RAG?

在 LangChain 中实现向量检索 RAG 的流程是:先将 Markdown 文档切分成 chunk,然后用 Embeddings 模型(如 HashEmbeddings)生成向量,存入 VectorStore(如 InMemoryVectorStore),再通过 Retriever 检索相关 chunk,最后将检索结果作为上下文与问题一起交给 LLM 生成回答。

HashEmbeddings 是什么?它有什么优缺点?

HashEmbeddings 是文章作者编写的一个教学版 Embeddings 实现,它通过将文本 token 哈希到固定维度向量并归一化来生成向量。优点是无需额外模型、完全本地可运行、符合 LangChain 的 Embeddings 接口;缺点是不是语义向量,不能理解同义表达,检索质量不如真实 embedding 模型。

为什么需要 Embeddings 而不是关键词检索?

关键词检索基于字面命中,无法处理语义相近但字面不同的情况。向量检索将文本转换为向量并比较相似度,真实 embedding 模型能将语义相近的文本映射到相近位置,从而提升检索效果。

InMemoryVectorStore 有什么特点?

InMemoryVectorStore 是 LangChain 自带的内存向量库,数据只存在于当前进程中,程序结束后索引就会丢失,因此适合演示流程,不适合生产环境。

如何运行第五篇的 demo?

在 code/langchain-demo 目录下运行命令:uv run python -m chapter05.vector_rag "Python 函数式编程讲了什么?"。如果需要绕开外部 PYTHONPATH,可以使用 env -u PYTHONPATH .venv/bin/python -m chapter05.vector_rag "Python 函数式编程讲了什么?"。

真实项目中如何替换 HashEmbeddings?

真实项目中可以将 HashEmbeddings 替换为 OpenAIEmbeddings,例如:from langchain_openai import OpenAIEmbeddings; embeddings = OpenAIEmbeddings(model="text-embedding-3-small", api_key=settings.api_key, base_url=settings.base_url)。前提是 OpenAI 兼容服务提供 embedding 模型。

LangChain 中 Retriever 接口有什么好处?

使用 LangChain 的标准 Retriever 接口,后续如果换成别的向量库,只要它也提供 retriever,后面的 RAG 代码就不用大改,体现了 LangChain 抽象层的意义。

🏷️

标签

➡️

继续阅读