如何让你的RAG应用程序返回来源信息
在问答应用中,展示用于生成答案的信息来源对用户而言非常重要。本文将探讨如何通过链式返回生成过程中检索到的文档,实现这一目标。我们将基于Lilian Weng在RAG教程中的博客文章中构建的Q&A应用为例,介绍两种方法:
- 使用内置的
create_retrieval_chain
,默认返回来源。 - 使用简单的LCEL实现,展示其工作原理。
并且,我们还会展示如何将来源信息结构化到模型响应中,使模型能够报告所使用的具体信息来源。
环境准备
依赖项
我们将使用OpenAI嵌入和Chroma向量存储。以下是所需安装的包:
%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()
# import dotenv
# dotenv.load_dotenv()
LangSmith
在使用LangChain构建的应用程序中,应用程序越复杂,能够检查链或代理内部逻辑就越重要。LangSmith虽然不是必须的,但对调试很有帮助。如需使用LangSmith,请注册并设置环境变量:
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = getpass.getpass()
使用create_retrieval_chain
首先,选择一个LLM:
pip install -qU langchain-openai
import getpass
import os
os.environ["OPENAI_API_KEY"] = getpass.getpass()
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")
示例应用
以下是基于Lilian Weng博客文章的Q&A应用示例:
import bs4
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_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)
# 使用API代理服务提高访问稳定性
result = rag_chain.invoke({"input": "What is Task Decomposition?"})
print(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
)
response = chain.invoke({"input": "What is Task Decomposition?"})
print(response)
常见问题和解决方案
-
网络限制问题:在某些地区可能会面临访问API的限制,建议使用API代理服务来提高访问稳定性。
-
结果不准确:确保向量存储的正确性和检索逻辑的严谨性。
总结和进一步学习资源
这种方法为实现问答应用提供了一个灵活的架构,可以有效地管理和报告信息来源。建议进一步学习LangChain和LangSmith的文档以深入了解其功能。
参考资料
如果这篇文章对你有帮助,欢迎点赞并关注我的博客。您的支持是我持续创作的动力!
—END—