跳转至

构建 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 索引:

用于从源获取数据并为其建立索引的管道。这通常发生在离线状态。

  1. Load 加载:首先我们需要加载数据。 为此,我们将使用 DocumentLoaders
  2. Split 分割文本分割器 将大文本分割Documents成更小的文本块。这对于索引数据和将其传递到模型都很有用,因为大块更难搜索并且不适合模型的有限上下文窗口。
  3. Store 储存:我们需要某个地方来存储和索引我们的分割,以便以后可以搜索它们。这通常是使用 VectorStore 和 Embeddings 模型来完成的。

Retrieval and generation 检索和生成

实际的 RAG 链,它在运行时接受用户查询并从索引中检索相关数据,然后将其传递给模型。

  1. Retrieve 检索:给定用户输入,使用Retriever从存储中检索相关的分割 。
  2. generation 生成ChatModel / LLM使用包含问题和检索到的数据的提示生成答案

在本演练中,我们将使用 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>


回到顶部