- 大家好,我是同学小张,日常分享AI知识和实战案例
- 欢迎 点赞 + 关注 👏,持续学习,持续干货输出。
- 一起交流💬,一起进步💪。
- 微信公众号也可搜【同学小张】 🙏
本站文章一览:
上篇文章我们学习了如何利用 LangChain 通过 URL 获取网页内容。本文我们继续学习利用 LangChain 进行网络数据抓取:我们将利用 LangChain 抓取网络数据来回答我们指定的问题(也就是类似 网络 + RAG)。
本文参考教程:https://python.langchain.com/docs/use_cases/web_scraping
0. 环境准备
要想成功运行本文所示的代码,需要做一下准备。
0.1 获取Google API key
首先,需要获取一个 Google API key。
(1)打开链接,登录你的Google账号(没有Google账号的请自行注册):
https://console.cloud.google.com/apis/api/customsearch.googleapis.com/credentials
(2)创建一个Project
(3)在你创建的 Project 页面(创建完后会自动跳转),点 API key,创建API key即可
(4)配置API key到你的代码中:将这个API key放到你的程序 .env 文件中作为环境变量加载。
GOOGLE_API_KEY = "YOUR GOOGLE API KEY"
0.2 获取 Google CSE ID
(1)登录链接,创建一个新的 Search Engine
(2)创建完后,Search engine ID 即为所需的 CSE ID。
(3)配置 CSE ID 到你的代码中:将这个 CSE ID 放到你的程序 .env 文件中作为环境变量加载。
GOOGLE_CSE_ID = "xxxxxxx"
0.3 安装依赖Python包
我的安装以下两个基本就够了,因为之前安装过 langchain、openai之类的。
pip install google-api-core==2.11.1 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple google-api-python-client==2.95.0
完整的安装依赖参考:
streamlit==1.25.0 langchain==0.0.244 chromadb==0.4.3 openai==0.27.8 html2text==2020.1.16 google-api-core==2.11.1 google-api-python-client==2.95.0 google-auth==2.22.0 google-auth-httplib2==0.1.0 googleapis-common-protos==1.59.1 tiktoken==0.4.0 faiss-cpu==1.7.4
1. 完整代码及解释
1.1 完整代码
from langchain.retrievers.web_research import WebResearchRetriever from langchain_community.utilities import GoogleSearchAPIWrapper from langchain_community.vectorstores import Chroma from langchain_openai import ChatOpenAI, OpenAIEmbeddings # Vectorstore vectorstore = Chroma( embedding_function=OpenAIEmbeddings(), persist_directory="./chroma_db_oai" ) # LLM llm = ChatOpenAI(temperature=0) # Search search = GoogleSearchAPIWrapper() # Initialize web_research_retriever = WebResearchRetriever.from_llm( vectorstore=vectorstore, llm=llm, search=search ) # Run import logging logging.basicConfig() logging.getLogger("langchain.retrievers.web_research").setLevel(logging.INFO) from langchain.chains import RetrievalQAWithSourcesChain user_input = "How do LLM Powered Autonomous Agents work?" qa_chain = RetrievalQAWithSourcesChain.from_chain_type( llm, retriever=web_research_retriever ) result = qa_chain({"question": user_input}) print(result)
1.2 代码研读
1.2.1 WebResearchRetriever
首先是代码中最重要的一个封装类:WebResearchRetriever。
它的使用方式如下:
# Initialize web_research_retriever = WebResearchRetriever.from_llm( vectorstore=vectorstore, llm=llm, search=search )
接收三个主要参数:
- 向量数据库:用来存储网页数据
- llm
- 检索引擎,这里的检索引擎 必须是 Google Search API
class WebResearchRetriever(BaseRetriever): """`Google Search API` retriever.""" search: GoogleSearchAPIWrapper = Field(..., description="Google Search API Wrapper")
再看下其构造过程:from_llm
函数
def from_llm( cls, vectorstore: VectorStore, llm: BaseLLM, search: GoogleSearchAPIWrapper, prompt: Optional[BasePromptTemplate] = None, num_search_results: int = 1, text_splitter: RecursiveCharacterTextSplitter = RecursiveCharacterTextSplitter( chunk_size=1500, chunk_overlap=150 ), ) -> "WebResearchRetriever": """Initialize from llm using default template. Args: vectorstore: Vector store for storing web pages llm: llm for search question generation search: GoogleSearchAPIWrapper prompt: prompt to generating search questions num_search_results: Number of pages per Google search text_splitter: Text splitter for splitting web pages into chunks Returns: WebResearchRetriever """ if not prompt: QUESTION_PROMPT_SELECTOR = ConditionalPromptSelector( default_prompt=DEFAULT_SEARCH_PROMPT, conditionals=[ (lambda llm: isinstance(llm, LlamaCpp), DEFAULT_LLAMA_SEARCH_PROMPT) ], ) prompt = QUESTION_PROMPT_SELECTOR.get_prompt(llm) # Use chat model prompt llm_chain = LLMChain( llm=llm, prompt=prompt, output_parser=QuestionListOutputParser(), ) return cls( vectorstore=vectorstore, llm_chain=llm_chain, search=search, num_search_results=num_search_results, text_splitter=text_splitter, )
这个函数用来初始化 WebResearchRetriever
,除了上面说的三个主要参数外,其额外提供了默认的Prompt模板
,text_splitter
,QuestionListOutputParser
等Retriever
过程所需的工具和内容。
默认的Prompt模板内容如下:
DEFAULT_SEARCH_PROMPT = PromptTemplate( input_variables=["question"], template="""You are an assistant tasked with improving Google search \ results. Generate THREE Google search queries that are similar to \ this question. The output should be a numbered list of questions and each \ should have a question mark at the end: {question}""", )
从这个Prompt大致可以看出WebResearchRetriever
的工作过程:
(1)根据用户的问题,利用大模型将该问题转化为3个与用户问题相近的Google搜索语句
(2)利用 Google CSE 搜索这几个问题,会得到一系列相关 URL
(3)利用上篇文章我们爬取网页内容的方法,将每个URL中的文本抓取出来
(4)对抓取出来的文本进行分块,向量存储(WebResearchRetriever
的工作到这里就结束了)
(5)然后就是其它模块使用RAG的流程:用户提问 —> 查询向量数据库 —> 大模型回答问题
整体流程示意图如下:
(1)-(4)步骤的源码如下,可以对照着看一下:
def _get_relevant_documents( self, query: str, *, run_manager: CallbackManagerForRetrieverRun, ) -> List[Document]: """Search Google for documents related to the query input. Args: query: user query Returns: Relevant documents from all various urls. """ # Get search questions logger.info("Generating questions for Google Search ...") result = self.llm_chain({"question": query}) logger.info(f"Questions for Google Search (raw): {result}") questions = result["text"] logger.info(f"Questions for Google Search: {questions}") # Get urls logger.info("Searching for relevant urls...") urls_to_look = [] for query in questions: # Google search search_results = self.search_tool(query, self.num_search_results) logger.info("Searching for relevant urls...") logger.info(f"Search results: {search_results}") for res in search_results: if res.get("link", None): urls_to_look.append(res["link"]) # Relevant urls urls = set(urls_to_look) # Check for any new urls that we have not processed new_urls = list(urls.difference(self.url_database)) logger.info(f"New URLs to load: {new_urls}") # Load, split, and add new urls to vectorstore if new_urls: loader = AsyncHtmlLoader(new_urls, ignore_load_errors=True) html2text = Html2TextTransformer() logger.info("Indexing new urls...") docs = loader.load() docs = list(html2text.transform_documents(docs)) docs = self.text_splitter.split_documents(docs) self.vectorstore.add_documents(docs) self.url_database.extend(new_urls) # Search for relevant splits # TODO: make this async logger.info("Grabbing most relevant splits from urls...") docs = [] for query in questions: docs.extend(self.vectorstore.similarity_search(query)) # Get unique docs unique_documents_dict = { (doc.page_content, tuple(sorted(doc.metadata.items()))): doc for doc in docs } unique_documents = list(unique_documents_dict.values()) return unique_documents
1.2.2 GoogleSearchAPIWrapper
这是 Google CSE 检索API的封装类。
class GoogleSearchAPIWrapper(BaseModel): """Wrapper for Google Search API."""
1.2.3 RetrievalQAWithSourcesChain
这是 LangChain 内封装的问答QA链,提问-给出答案,并带有答案来源Sources.
对检索到的文档进行问答,并引用其来源。当您希望答案响应在文本响应中具有来源时,请使用此选项。
使用方法:
qa_chain = RetrievalQAWithSourcesChain.from_chain_type( llm, retriever=web_research_retriever )
接收两个参数:
- llm:大模型
- retriver:检索器
其源码定义如下:
class RetrievalQAWithSourcesChain(BaseQAWithSourcesChain): """Question-answering with sources over an index.""" retriever: BaseRetriever = Field(exclude=True) """Index to connect to.""" reduce_k_below_max_tokens: bool = False """Reduce the number of results to return from store based on tokens limit""" max_tokens_limit: int = 3375 """Restrict the docs to return from store based on tokens, enforced only for StuffDocumentChain and if reduce_k_below_max_tokens is to true"""
2. 总结
本文我们主要学习了利用 LangChain进行网络文档 + RAG 的使用,重点看了 LangChain中WebResearchRetriever
的封装和实现原理。里面虽然使用的Google搜索,在国内有诸多限制,但是里面的实现思路是值得借鉴的:
(1)找到与用户问题相关的网页
- 用户提问转换为相似的搜索语句
- 通过检索API找到相关的网页URL
(2)文本获取与存储
- 爬取URL文本内容
- 分割文本并向量存储
(3)使用以上相关内容进行RAG增强检索,回答用户问题
如果觉得本文对你有帮助,麻烦点个赞和关注呗 ~~~
- 大家好,我是 同学小张,日常分享AI知识和实战案例
- 欢迎 点赞 + 关注 👏,持续学习,持续干货输出。
- 一起交流💬,一起进步💪。
- 微信公众号也可搜【同学小张】 🙏
本站文章一览: