构建 RAG 应用程序
RAG 是什么
RAG 是一种利用额外数据增强 LLM 知识的技术。
rag (retrieval augmented generation) 是 langchain 中一个重要的方法,它可以利用外部信息作为知识库,增强语言大模型的能力。
LangChain 有许多组件旨在帮助构建问答应用程序以及更广泛的 RAG 应用程序。
为了熟悉这些,我们将基于文本数据源构建一个简单的问答应用程序。
在此过程中,我们将介绍一个典型的问答架构,讨论相关的 LangChain 组件,并重点介绍更先进的问答技术的其他资源。
我们还将看到 LangSmith 如何帮助我们跟踪和理解我们的应用程序。随着我们的应用程序变得越来越复杂,LangSmith 将变得越来越有帮助。
介绍的两个 RAG 用例是:
RAG 核心流程步骤
- 1.RAG 需要用到的外部数据(例如:网页)
- 支持的内容加载器: https://python.langchain.com/docs/modules/data_connection/document_loaders/
- 支持 自定义结构、CSV、本地文件+后缀(.txt、.py、.md)、HTML、JSON、Markdown、DOCX, XLSX and PPTX、PDF
- 2.RAG 需要对外部数据进行分割
- 3.RAG 需要对分割后的数据进行向量化,并存储
- Chroma 用于做文本想量化
- 文本相似度检索内容 retriever.invoke (retriever = vectorstore.as_retriever())
- 4.RAG rag_chain 需要由 retriever + prompt + llm 构建
- 基于内容检索内容,做总结回答 rag_chain.invoke
- RunnableParallel 轻松返回 检索到的文档和答案。
Prompt 可以自定义:PromptTemplate
LLM 可以自定义不同的接口平台:OpenAI、Ollama(Facebook AI 团队)、ChatAnthropic(Anthropic 公司)、ChatCohere(Cohere Technologies 公司)
rag_chain 可以加入 history 上下文记忆,并且还支持 多回话。
- 1.可以手动用变量存储聊天记录。然后传入到下次 invoke 里面
- 2.配置 情景化 prompt + 调用 RunnableWithMessageHistory,存到 session 内存里(自己可以持久化存储到数据库)
回答的2中方式:
- 可以一次性返回结果: rag_chain.invoke
- 可以流式输出: rag_chain.stream,可以进行格式化输出
- 其中过滤按照结果 contextualize_q_chain 情境化理解输出
- 其中过滤按照结果 Retriever 的回答进行输出
多用户支持 namespace (https://app.pinecone.io/ 云存储地址)
LangSmith 不是必需的,但它很有帮助。用于记录跟踪。
典型的 RAG 应用程序有两个主要组件:
Indexing 索引:
用于从源获取数据并为其建立索引的管道。这通常发生在离线状态。
- Load 加载:首先我们需要加载数据。 为此,我们将使用 DocumentLoaders
- Split 分割:文本分割器 将大文本分割
Documents
成更小的文本块。这对于索引数据和将其传递到模型都很有用,因为大块更难搜索并且不适合模型的有限上下文窗口。 - Store 储存:我们需要某个地方来存储和索引我们的分割,以便以后可以搜索它们。这通常是使用 VectorStore 和 Embeddings 模型来完成的。
Retrieval and generation 检索和生成
实际的 RAG 链,它在运行时接受用户查询并从索引中检索相关数据,然后将其传递给模型。
在本演练中,我们将使用 OpenAI 聊天模型和嵌入以及 Chroma 矢量存储,但此处显示的所有内容都适用于任何 ChatModel或 LLM、 Embeddings以及 VectorStore或 Retriever。
RAG 项目实战
import os
# 记得自己配置代理
os.environ['OPENAI_API_KEY'] = "sk-xxx"
# 您使用 LangChain 构建的许多应用程序将包含多个步骤以及多次调用 LLM 调用。
# 随着这些应用程序变得越来越复杂,能够检查链或代理内部到底发生了什么变得至关重要。最好的方法是使用LangSmith。
# 请注意,LangSmith 不是必需的,但它很有帮助。
# LangSmith: https://smith.langchain.com/
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "ls__xxx"
import bs4
from langchain import hub
from langchain_community.document_loaders import WebBaseLoader
from langchain_chroma import Chroma
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import ChatOpenAI
# 1. 1. 索引 - 加载,对博客的内容进行分块和索引
loader = WebBaseLoader(
web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
bs_kwargs=dict(
parse_only=bs4.SoupStrainer(
class_=("post-content", "post-title", "post-header")
)
),
)
docs = loader.load()
# 2. 索引 - 分割
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)
# 3. 索引 Indexing - 存储 Store
# 按照 cosine 余弦相似度进行匹配,返回6个相似度高的
vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())
# 使用博客的相关片段检索并生成
retriever = vectorstore.as_retriever()
# retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 6})
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
# 4. prompt + llm + chain
prompt = hub.pull("rlm/rag-prompt")
llm = ChatOpenAI(model="gpt-3.5-turbo-0125")
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# 5. invoke,并返回结果
rag_chain.invoke("What is Task Decomposition?") # 什么是任务分解
# cleanup
vectorstore.delete_collection()
# 查看 LangSmith 轨迹 : <https://smith.langchain.com/public/1c6ca97e-445b-4d00-84b4-c7befcbc59fe/r>