自从ChatGPT流行以来,许多公司也开始创建自己的聊天机器人,为客户服务。就业市场上也出现了例如LLM工程师、聊天机器人工程师/培训师/经理等职位。
https://miro.medium.com/v2/resize:fit:1400/1*iGdFJTHMIG79N2HChWaooQ.gif
使用大型语言模型(LLM)来构建聊天机器人,并让LLM能够回答企业内部知识内容的问题,不仅仅是插入信息并调用LLM API,还需要许多预处理和后处理的措施,这些措施需要定制开发。(您的数据源可能包含大量图像、视频或数据库,这些内容对LLM来说难以理解)
然而,除了复杂的预处理和后处理步骤,通过RAG和代理技术构建一个可以实时更新信息的定制聊天机器人实际上是可以做到的。
本文会教你如何使用Langchain代理构建一个具有检索功能和与Google搜索集成的聊天机器人。
当我们希望LLM学习额外的知识,比如内部企业信息、FAQ等,但这些内容太长,无法放入提示中(因为输入令牌的限制),我们就可以用RAG来帮我们找文档。
下面的图形很好地解释了RAG的概念:
大型语言模型的检索增强生成:综述
让我们从右上角的索引部分开始。
首先,我们准备包含我们希望大型语言模型(LLM)了解的知识的文档,接着,我们将这些长文本拆分成多个小块,并将其转换为嵌入,存储到向量数据库中。
然后,当用户输入查询语句时,我们同时将查询语句转换为嵌入。接着计算查询嵌入与向量存储中的嵌入之间的向量相似性,以找出与查询最相似的k个片段。最后,我们将这些片段的原始句子插入提示中,这样LLM能更好地回答用户的查询。
尽管这个想法相当直观,但仍有多种方法可以进一步提高检索增强生成技术的性能。文章《用于大型语言模型的检索增强生成:综述》(Retrieval-Augmented Generation for Large Language Models: A Survey) 概述了几种方法。如果基本的 RAG 不适合你的场景,你可以参考这篇文章。
现在,我们来看看如何使用Langchain来创建一个RAG。我将检索这篇论文:关于大型语言模型的检索增强生成综述。
直接上代码,直接看代码。
from langchain.document_loaders import PyPDFLoader as PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter as RecursiveCharacterTextSplitter from langchain.vectorstores import FAISS as FAISS from langchain.embeddings import OpenAIEmbeddings as OpenAIEmbeddings from langchain.chains import RetrievalQA, create_retrieval_chain as create_retrieval_chain from langchain.chat_models import ChatOpenAI as ChatOpenAI from langchain_core.prompts import ChatPromptTemplate as ChatPromptTemplate from langchain.chains.combine_documents import create_stuff_documents_chain as create_stuff_documents_chain system_prompt = ( "你是一个用于问答任务的助手。" "使用以下检索到的上下文来回答问题。" "如果你不知道答案,请说你不知道。" "最多用三句话回答,且回答要简洁。" "\n\n" "{context}" ) rag_prompt = ChatPromptTemplate.from_messages( [ ("system", system_prompt), ("human", "{input}"), ] ) def get_qa_chain(pdf_path): # 读取文件 loader = PyPDFLoader(pdf_path) documents = loader.load() # 将文档分割成文本块 text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100) texts = text_splitter.split_documents(documents) # 将文本块嵌入到向量存储中(FAISS) embeddings = OpenAIEmbeddings() vectorstore = FAISS.from_documents(texts, embeddings) # 创建检索器和RAG链 retriever = vectorstore.as_retriever(search_kwargs={"k": 3}) question_answer_chain = create_stuff_documents_chain(llm=ChatOpenAI(model_name='gpt-4o-mini', temperature=0), prompt=rag_prompt) rag_chain = create_retrieval_chain(retriever, question_answer_chain) return rag_chain rag_chain = get_qa_chain('RAG.pdf')
基本上,我只是从官方教程中复制粘贴的。你可以看到Langchain已经打包了很多东西,包括加载PDF、嵌入向量、向量存储、向量搜索以及连接LLM。想想从头开始写这些代码就让人感到很累。
这里,我使用的是OpenAI的GPT-4,记得在环境变量中设置你的令牌。
现在让我们来看看这个与RAG链的对话:
看起来还不算太差。虽然大型语言模型(LLM)可能已经具备关于RAG的一定相关知识,但rag_chain的回答则是基于rag.pdf的文件。
此外,你可以通过 result['context'] 查看RAG检索出的最相似的k个段落。
然而,无论你问什么,甚至是“你好”,它都会进行搜索并给出回复。
到这个时候,你可能在想,如果它能够自己决定是否需要搜索,并根据不同的问题执行不同的操作会怎样?这就是所谓的代理(agent)。
我们现在要做的是让大模型决定是否为客户的问题检索信息,并根据不同的问题执行相应功能。
我们可以像下图所示那样构建一个大型语言模型(LLM)。我们用两个LLM来实现这个目标。
绿色的大型语言模型(LLM)根据客户的问题选择使用哪个工具(RAG、Google搜索或不需要工具),然后使用该工具检索信息。检索到的信息随后被插入到蓝色的大型语言模型的提示中,指示它利用这些信息为客户生成回复。
我们也可以使用Langchain的代理工具做到这一点。我觉得每种方式都有利有弊。
如果不是很复杂,你可以使用提示设计让大型语言模型决定使用哪个工具。使用Langchain代理会更省事,但会更复杂一些,更难排查问题,并且Langchain版本不断在更新,其命令和应用在不断调整。
但我有点太懒了,不想把所有细节都写出来,那我们就直接用Langchain来做这个智能代理吧。😅
除了上一段中提到的rag_chain
之外,我还想让代理具备谷歌搜索的能力。简单来说,我将为代理提供两个工具:RAG用于检索PDF文件,以及谷歌搜索。
我们已经完成了rag_chain模块,可以通过searchAPI实现类似Google的搜索功能。在这里我使用了Serper。
你可以访问Serper网站并注册一个免费账户以获取一个token,该token提供2500次免费查询。Langchain还提供了一个GoogleSerperAPIWrapper
,只需一行代码就能搞定!
从langchain_community.utilities模块导入GoogleSerperAPIWrapper search = GoogleSerperAPIWrapper()
咱们来做个简单的搜索功能测试,看看效果如何。
好的,现在呢,我们可以用 search
(搜索) 来用谷歌搜索并返回搜索结果的字符串。
将 rag_chain
和 search
打包成一个工具。
从langchain.agents导入Tool, AgentExecutor, create_react_agent tools = [ Tool( name="RAG", func=rag_chain.invoke, description="当你需要回答关于检索增强生成(RAG)相关问题时,这个工具非常有用" ), Tool( name="Google搜索", description="当你不知道答案或问题不涉及法律时,可以使用Google搜索来查找答案", func=search.run, ) ]
工具中的 description
用于让大型语言模型明白应该用哪个工具,因此,根据您的需求,尽可能清晰地编写。
现在,我们设计一个提示来引导代理按照我们希望的方式思考,并给出我们想要的回答。
character_prompt = """回答以下问题,尽可能详细和准确。你可以使用以下工具: {tools} 对于需要工具的问题,你应该首先查阅提供的知识库。如果在知识库中找不到相关信息,则使用谷歌进行搜索。 在使用工具时,你必须遵循以下格式: 1. 思考:是否需要使用工具?是 2. 行动:应采取的行动,应是 [{tool_names}] 中的一个 3. 行动输入: 4. 结果: 当你需要回复人类时,或者你无需使用工具时,你必须使用以下格式: 1. 思考:是否需要使用工具?否 2. 答案:[你的回答] 请确保你的输出严格按照上述格式。 开始! 前一次对话历史: {chat_history} 问题:{input} 思考:{agent_scratchpad} """
你可能会想,你可以查看一下langchain的prompt hub,找一些示例,然后根据需要做一些调整。
from langchain import hub hub.pull("hwchase17/react")
此代码段应保持不变。
接下来,使用 create_react_agent
将工具和提示打包成一个代理,并将其称为代理,然后使用 AgentExecutor
运行该代理以生成响应。我也借此机会加入了记忆功能。
from langchain.prompts.prompt import PromptTemplate as 模板 from langchain.chains.conversation.memory import ConversationBufferWindowMemory as 窗口内存 chat_model = ChatOpenAI(model_name='gpt-4', temperature=0, streaming=True, verbose=True, max_tokens=1024, ) # 创建聊天模型 提示模板 = PromptTemplate.from_template(character_prompt) 代理 = create_react_agent(chat_model, tools, 提示模板) # 创建代理 内存 = 窗口内存(memory_key='chat_history', k=5, return_messages=True, output_key="output") # 创建内存 代理链 = AgentExecutor(agent=代理, tools=tools, memory=内存, max_iterations=5, handle_parsing_errors=True, verbose=True, ) # 创建代理链
这个 agent_chain
可以用于对话场景中。当用户输入一个问题时,它会判断是否需要使用工具,如果需要,还会决定使用哪个工具,并执行相应动作以获取工具的回复。
接着它会检查是否有足够的信息。如果有,它会根据收集到的信息生成回复。如果没有,它将继续执行动作,直到达到最大迭代次数为止。
注意,它可能会遇到一个情况:LLM 不会按照我们在提示里给的指令来回应,这会导致 LLM 反馈错误。你会发现它已经收集了足够的信息来回答问题,但它回复 抱歉,我目前没有足够的信息来回答您的问题...
所以,在这里,我试着使用提示工程来解决这个问题。你可以看到我一再强调了必须按照以下格式、在任何‘Action’或‘Final Answer’之前,必须先写‘Thought’。确保你的回答严格按照上述格式。
我也用强大的 gpt-4 模型。因此我发现较大的模型更擅长遵循我们的提示指令。
现在,我们来看看 agent_chain
的情况:
它似乎反应很灵敏。通过增加一些前端开发,你可以在网页上和这个聊天机器人互动。
最后,我将这个代理整理成以下的流程图。
我认为这种架构适用于多种场景的聊天机器人;你只需将RAG检索知识库替换成自己的内容即可。
RAG(检索增强生成技术):为自然语言处理带来新突破
检索增强的生成式语言模型调研