大模型应用开发 langchain和 langgraph 之 RAG 入门
在我们看完 Langchain 基础之后,RAG 就是第二个难点。本文探索 langchain 中的 RAG 以及部分 langgraph RAG。
一、什么是 RAG ?
Retrieval Augmented Generation 检索增强生成。
简单的说就是大模型,虽然比较全面,但是在一些细节上还是有很多的不同,这个时候我们就可以通过上下文的方式,给大模型一些额外的数据增强大模型的知识库,然后生成内容。RAG 从数据源到 LLM 输出,大致分为三个流程:
- 索引
- 检索
- 生成
先看一张经典的图片:
二、索引
索引页分为三个部分:
- 读取数据源 load
- 分割文档 split
- 向量存储 embed + vector
2.1) Load
因为数据源是多种多样的,langchain 中使用 Loader 对各种数据源进行支持。可以在 document_loaders 找到对象。一些常用的:
- txt
- json
- csv
- docx
- markdown
- web content
- ....
from langchain_community.document_loaders import PyPDFLoader
from langchain_community.document_loaders import TextLoader
from langchain_community.document_loaders import CSVLoader
from langchain_community.document_loaders import Docx2txtLoader
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_community.document_loaders import UnstructuredHTMLLoader
from langchain_community.document_loaders import JSONLoader
from langchain_community.document_loaders import WebBaseLoader
这里以 pdf 为例从 loader 到 docs 的过程:
poetry add pypdf2 # 需要pdf 相关库的支持
from langchain_community.document_loaders import PyPDFLoader
pdf_loader = PyPDFLoader(full_path)
docs = loader.load()
读取到的数据结构是 Document 对象
from langchain_core.documents import Document
以下是 Document 的数据结构:
Document 对象继承自 BaseMedia
, 包含了 id
和 metadata
属性:
2.2) Split
首先我们要明白为什么要 Split ?
其实也很简单,大模型处理的内容是有限的,大量数据文件,分割处理更加有利于大模型对数据内容理解。
当然 langchain 也提供了单独的一个库 langchain_text_splitters
处理 Split:
poetry add langchain_text_splitters
pip install langchain_text_splitters
# 字符文本分割
from langchain_text_splitters import CharacterTextSplitter
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_text_splitters import TokenTextSplitter
from langchain_text_splitters import SpacyTextSplitter
from langchain_text_splitters import SentenceTransformersTokenTextSplitter
from langchain_text_splitters import NLTKTextSplitter
from langchain_text_splitters import KonlpyTextSplitter
以上包含七种同的 TextSplitter,这里以 RecursiveCharacterTextSplitter
使用
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
# tiktoken_encoder
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base", chunk_size=100, chunk_overlap=0
)
# document
splits = text_splitter.split_documents(docs)
# text
texts = text_splitter.split_text(document)
- chunk_size 指定字符串和
- chunk_overlap 重叠文本的长度
2.3)Embed + Store
Store 这里也可以分为两个部分:
- 嵌入式模型
- 向量存储
这个两个部分 langchain 也给我们封装好了,我们需要熟悉嵌入式模型的用法。
embeddings
模型
嵌入式模型种类也非常多,不同的模型能力也有不同。 目前 langchain 支持的 embeddings。
- openai
- ollama
- HuggingFace
- Cohere
- zhipu
- ...
有本地部署的,有在线付费的,有开源免费的,总之根据自己的需求探索使用。这里使用 OpenAI 的 embeddings 和 ZhipuAI embeddings 为例进行讲解。
- OpenAI:
OpenAI 目前有三个种模型如果你是自己测试 embeddings 模型:
from langchain_openai import OpenAIEmbeddings
embeddings_model = OpenAIEmbeddings()
- 国内的我们以 zhipu AI 的 embeddings 为例:
简单的封装一下:
from langchain_community.embeddings import ZhipuAIEmbeddings
import dotenv
class ZhipuEmbeddings():
api_key = dotenv.get_key(".env", "api_key")
@classmethod
def create_zhipu_embeddings(self, model_name):
embeddings = ZhipuAIEmbeddings(
model=model_name,
api_key=self.api_key
)
return embeddings
当然你也可以在 text_embedding 查看 langchain 支持 embeddings 模型。
VectorStore
langchain 支持了众多的 vector store 这里我们简单的进行分类,这些是 langchain 中给到和一些常用的向量存储数据库。
- 内存:InMemoryVectorStore
- 本地存储:Chroma/FAISS
- 其他类型:
- Pinecone
- Milvus
- Qdrant
- 传统数据库对
- PostgreSQL +
pgvector
- Supabase
- Redis
- ...
- PostgreSQL +
# InMemoryVectorStore
from langchain_core.vectorstores import InMemoryVectorStore
vector_store = InMemoryVectorStore(embedding=SomeEmbeddingModel())
# Chroma
from langchain_chroma import Chroma
vectorstore = Chroma.from_documents(
documents,
embedding=OpenAIEmbeddings(),
)
# FAISS
from langchain_community.vectorstores import FAISS
vectorstore = FAISS.from_documents(texts, embeddings)
# Milvus
from langchain_milvus import Milvus
URI = "./milvus_example.db"
vector_store = Milvus(
embedding_function=embeddings,
connection_args={"uri": URI},
)
三、检索和生成
先看一个经典的图
一个问题来,先经过向量的检索,然后生成新的提示词,然后给到大模型,最后得到答案。
四、VectorStore 的核心方法
- add_texts(texts, metadatas):将文本及其元数据嵌入并存储到 VectorStore。
- similarity_search(query, k):基于查询向量,返回最相似的 k 个结果。
- similarity_search_with_score:使用距离运行相似性搜索。
- delete(ids):根据指定的 ID 删除向量。
- from_documents(documents):从文档列表构建向量存储。
- save_local(path) 和 load_local(path):将向量存储保存到本地或从本地加载。
五、retriever 检索和生成
5.1)检索
将 VectorStore 转换为检索器: as_retriever
query = "my query"
# 搜索文档
docs = vectorstore.similarity_search(query)
# 转成检索器
retriever = vector_store.as_retriever()
# 检索
retrieved_docs = retriever.invoke("What are the approaches to Task Decomposition?")
5.2) 生成
准备好了这一切,从读取数据库,到分割成 Document, 到 embeddings model + 向量存储。下面就是使用 Langchian 的 chain 组合上传文进行生成内容了。
|
chain 模型
在生成 retriever 之后,retriever 做来 context 的值传递给 chain 后面的内容,当然也可以使用 create_retrieval_chain api 来创建一个 rag_chain。
# retrieve 作为 chain 中的 context
# chain
chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
)
create_retrieval_chain
# create_retrieval_chain
from langchain.chains import create_retrieval_chain
rag_chain = create_retrieval_chain(retriever, question_answer_chain)
六、langgraph 中的 RAG
LangGraph是一个用于使用LLMs构建有状态、多参与者应用程序的库,用于创建代理和多代理工作流程。
langgraph 的 RAG 是 langgraph 架构 与 langchain 有所不同,这里我们简单的实现一个 Langgraph 的 RAG:
- chatbot + pdf 的 RAG
poetry init
poetry add langchain langgraph python-dotenv langchain-openai langchain-text-splitters langchain-community pypdf zhipui
在国内我们的大模型选择zhipu的大模型。首先通过 质谱开放平台获取 zhipu 的 API KEY 和 url 地址。
下面我们基于 Jupyter Notebook
+ VSCode 插件做一个来完成一个简单的 PDF RAG 应用。
6.1)设置 env 环境文件
这里的 OPEN_AI_KEY 就是 zhipu ai 的key
6.2)读取 env 文件环变量
6.3)定义 zhipu ai 变量
6.4)定义 zhipu ai 的模型
6.5)定义质谱ai的 embeddings 模型
6.6)定义 langchain 读取 pdf 文件
6.7)对读取的 pdf 文件进行分割
6.8)使用内存型向量数据存储
6.9)使用 langchain hub 中的提示器
6.10)定义 langgraph 的 State
6.11)定义 langgraph 的两个节点
- retrieve:根据问题进行向量相似搜素,返回检索的上下文
- generate: 提供问题和上下文,调用大模型生成,然后返回
6.12)组合 langgraph 节点和、并编译
6.13)传递问题并回答
我们问题是: 用中文回答,本书主要讲的内容,100字以内
, 我们看到回答也很简单,在 100 字之内,符合预期。
6.14) 绘制一个 langgraph 的图
七、小结
本文主要梳理 Langchain 的 RAG 的使用和流程。Langchain 中关于 RAG 基础工具已经帮助我们解决了,我们可以借助这些工具方便快捷的写一个简单的 RAG 应用。langchain 也提供了另外一种架构 langgraph 完成 RAG, RAG 的流程与 langchain 不一样的地方在于 RAG 检索在 langgraph 的节点上完成。