赞
踩
今天,我们将综合以上技能,完成 网络数据+RAG 问答的实践,并且学习如何在返回结果中添加结果的来源(原文档)。
在结果中添加该结果的参考来源是RAG问答中非常重要的一环,一方面让我们更加了解答案的生成原理和参考内容,防止参考错误的文档,另一方面,可以展示给用户,我们的答案是有参考的,不是胡说,增加信任度。例如下面这个检索工具的展示,有了来源之后,显得更加专业和更高的可信度:
参考:
python
复制代码
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()
代码中以加载 https://lilianweng.github.io/posts/2023-06-23-agent/
链接的数据为例。
使用 WebBaseLoader
进行数据加载。WebBaseLoader
是LangChain封装的专门用于加载网页数据的类。其定义和初始化参数如下,原理就是利用 urllib
加载html页面,然后通过BeautifulSoup
进行Html
解析,找出其中指定tag
的内容。以上代码中 class_=("post-content", "post-title", "post-header")
表明只提取HTML
页面中这些tag
的数据。
python 复制代码 class WebBaseLoader(BaseLoader): """Load HTML pages using `urllib` and parse them with `BeautifulSoup'.""" def __init__( self, web_path: Union[str, Sequence[str]] = "", header_template: Optional[dict] = None, verify_ssl: bool = True, proxies: Optional[dict] = None, continue_on_failure: bool = False, autoset_encoding: bool = True, encoding: Optional[str] = None, web_paths: Sequence[str] = (), requests_per_second: int = 2, default_parser: str = "html.parser", requests_kwargs: Optional[Dict[str, Any]] = None, raise_for_status: bool = False, bs_get_text_kwargs: Optional[Dict[str, Any]] = None, bs_kwargs: Optional[Dict[str, Any]] = None, session: Any = None, ) -> None: """Initialize loader. Args: web_paths: Web paths to load from. requests_per_second: Max number of concurrent requests to make. default_parser: Default parser to use for BeautifulSoup. requests_kwargs: kwargs for requests raise_for_status: Raise an exception if http status code denotes an error. bs_get_text_kwargs: kwargs for beatifulsoup4 get_text bs_kwargs: kwargs for beatifulsoup4 web page parsing """
怎么查看网页中想要提取的数据的tag?参考这篇文章:【提效】让GPT帮你写爬虫程序,不懂爬虫也能行
指定分块方式:RecursiveCharacterTextSplitter
,这个在之前咱们也介绍过(这篇文章),它就是将文本块分成 1000 字左右的段,相邻段之间有 200 字左右的重复,以保证相邻段之间的上下文连贯。
python
复制代码
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)
使用 Chroma
作为向量数据库,向量化计算采用 OpenAIEmbeddings
接口和模型。
python
复制代码
vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())
将向量数据库作为 retriever。
python
复制代码
retriever = vectorstore.as_retriever()
python 复制代码 prompt = hub.pull("rlm/rag-prompt") llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0) def format_docs(docs): return "\n\n".join(doc.page_content for doc in docs) rag_chain = ( {"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt | llm | StrOutputParser() )
(1)首先是Prompt,直接使用 hub.pull("rlm/rag-prompt")
加载一个Prompt模板,也可以自己写。加载到的Prompt模板内容:
(2)以上Prompt接收两个参数:
context
和 question
,所以chain组装的第一步就是传递这两个参数。
(3)整体解释下以上 rag_chain 的数据流:
context
Key里,连同 question
Key内容一起给 prompt通过 invoke
函数运行。
python
复制代码
result = rag_chain.invoke("What is Task Decomposition?")
print(result)
别忘了所有的依赖:
python
复制代码
import bs4
from langchain import hub
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import Chroma
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
运行结果:
加入Sources很简单,主要改下 Chain 的组装:
python
复制代码
from langchain_core.runnables import RunnableParallel
rag_chain_from_docs = (
RunnablePassthrough.assign(context=(lambda x: format_docs(x["context"])))
| prompt
| llm
| StrOutputParser()
)
rag_chain_with_source = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
).assign(answer=rag_chain_from_docs)
先不管它是如何实现的,先运行看下结果:
python
复制代码
result = rag_chain_with_source.invoke("What is Task Decomposition")
print(result)
看到结果后应该就对这段程序有了一个感性的认识。下面我们来看下这段程序是如何实现的。
从 rag_chain_with_source
开始看。
python
复制代码
rag_chain_with_source = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
).assign(answer=rag_chain_from_docs)
它使用了 RunnableParallel
来传递 context
的值 和 question
的值。
RunnableParallel().assign()
实现的功能就是将以上{}
的内容传递给assign
函数的参数,也就是传递给rag_chain_from_docs
。
那么rag_chain_from_docs
中RunnablePassthrough.assign(context=(lambda x: format_docs(x["context"])))
,这里的x
就知道是什么了:{"context":xxxx, "question":xxxx}
。x["context"]
也就是将检索出的文档进行组装。
然后rag_chain_from_docs
的返回值:answer=rag_chain_from_docs
,就是将返回值填到 "answer"
为Key的值中。
最后,rag_chain_with_source
的返回值就是刚开始的 "context"
, "question"
,再加上后面的 "answer"
。
简单总结一下本文内容。
本文利用 LangChain 实现了一个完整的问答RAG应用。
其中RAG中的数据源采用加载网页数据的形式获取,而不是采用之前实践中传统的本地知识库(加载本地PDF文件)的方式。
然后我们还在RAG的返回中增加了参考文本的输出,这是之前我们没有实践过的,算是一点新知识。在实现这个功能的过程中,最主要的是学会使用 LangChain 中提供的 RunnablePassthrough
和 RunnableParallel
进行值的传递。
作为一名热心肠的互联网老兵,我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。
但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的 AI大模型资料
包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。