如何让你的RAG应用程序返回来源
在这部分,我们模拟构建的链,让结果更具灵活性。"input": lambda x: x["input"], # 输入查询"context": lambda x: format_docs(x["context"]), # 上下文| prompt # 格式化查询和上下文为prompt| llm # 生成响应| StrOutputParser() # 强制转换为字符串通过这种方法,我们不仅能传递检索步骤
老铁们,今天我们来聊聊如何在RAG(Retrieval-Augmented Generation)应用中返回生成答案时使用的来源。在问答应用中,展示用来生成答案的来源对于用户来说可是相当重要的。说白了,最简单的方法就是让你构建的链条返回每次生成中检索到的文档。
本文将基于Lilian Weng的博客文章《LLM Powered Autonomous Agents》和RAG教程中构建的Q&A应用,介绍两种方法来实现这一目标:
- 使用内置的
create_retrieval_chain,默认情况下返回来源; - 使用简单的LCEL实现,来展示其工作原理。
同时,我们还会讲解如何将Sources结构化为模型的响应,使得模型能够报告它在生成答案时具体使用了哪些来源。
准备工作
依赖项
我们将在这个示例中使用OpenAI的embeddings和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()
LangSmith
在使用LangChain创建应用时,应用中可能包含多个调用LLM的步骤。随着应用的复杂性增加,能看到链条或代理内部具体发生了什么就变得至关重要。LangSmith是个不错的选择,尽管不是必需的。如果想使用LangSmith,注册后要确保设置环境变量以开始记录追踪:
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = getpass.getpass()
使用create_retrieval_chain
先选择一个LLM(如OpenAI、Anthropic等),然后进行配置,比如安装和设置API Key。
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")
在这里,我们展示使用create_retrieval_chain构建的问答应用:
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
# 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()
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()
# 2. 将检索器融入问答链
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?"})
# 结果中,"context"包含LLM用来生成"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 # 格式化查询和上下文为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"})
通过这种方法,我们不仅能传递检索步骤返回的文档,还可以在模型的响应中更清晰地展示模型依赖的上下文。
老铁们,今天的技术分享就到这里,希望对大家有帮助。开发过程中遇到问题也可以在评论区交流~
更多推荐
所有评论(0)