使用RAG和LangChain代理构建你的定制聊天机器人

2024年08月30日 由 alex 发表 110 0

自从 ChatGPT 流行起来后,许多公司开始想要创建自己的聊天机器人来服务客户。就业市场上也出现了法学硕士工程师、聊天机器人工程师/培训师/经理等职位。


1


使用LLM构建聊天机器人,让LLM能够回答企业内部知识等问题,不仅仅是插入信息和调用LLM API,还需要大量的预处理和后处理措施,需要定制开发。


然而,除了复杂的预处理和后处理之外,构建一个可以实时更新信息的定制聊天机器人基本上可以通过 RAG 和代理来实现。


本文将介绍如何通过Langchain代理构建一个具有检索和Google搜索功能的聊天机器人。


检索增强生成 (RAG)

当我们想要LLM学习额外的知识(例如企业内部信息,常见问题解答等)但内容太长而无法放入提示中(由于输入令牌限制)时,我们需要RAG来帮助我们检索文档。


下图很好的阐述了RAG概念:


2


让我们从右上角的索引部分开始。

首先,我们要准备好包含我们希望 LLM 掌握的知识的文档,然后将这些长文本分解成许多块,并将它们转换成嵌入式,存储在向量存储中。


然后,在输入用户查询句的同时,我们也将查询句转换成嵌入式。然后计算查询嵌入和向量存储中的嵌入之间的向量相似度,找出最相似的 k 块。

最后,我们在提示语中插入语块的原始句子,让 LLM 掌握相对知识来回答用户查询。


现在,让我们来看看如何使用 Langchain 创建 RAG。我要检索的文档是这篇论文:Retrieval-Augmented Generation for Large Language Models: A Surv


废话不多说,让我们来看看代码:


from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import FAISS
from langchain.embeddings import OpenAIEmbeddings
from langchain.chains import RetrievalQA, create_retrieval_chain
from langchain.chat_models import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain.chains.combine_documents import create_stuff_documents_chain

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}"
)
rag_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", system_prompt),
        ("human", "{input}"),
    ]
)
def get_qa_chain(pdf_path):
    # read file
    loader = PyPDFLoader(pdf_path)
    documents = loader.load()
    
    # split your docs into texts chunks
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
    texts = text_splitter.split_documents(documents)
    
    # embed the chunks into vectorstore (FAISS)
    embeddings = OpenAIEmbeddings()
    vectorstore = FAISS.from_documents(texts, embeddings)
    
    # create retriever and rag chain
    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 链的对话:


3


看起来还不错。虽然 LLM 可能已经有了关于 RAG 的相对知识,但 rag_chain 的答案是基于 rag.pdf 的。


此外,你还可以使用 result['context'] 查看 RAG 检索到的最相似的 k 块。


不过,你会注意到,无论你问什么问题,它都会执行搜索并生成响应,即使你只是说 “你好”。


这时,你可能会想,如果它能自己决定一个问题是否需要搜索,并根据不同的问题执行不同的功能呢?这就是代理的概念。


代理

现在,我们的目标是让 LLM 决定是否检索客户的问题,并根据不同的问题执行不同的功能。


我们可以像下图一样构建一个 LLM。我们使用两个 LLM 来实现这一目标。


绿色 LLM 决定客户问题应使用哪种工具(RAG、Google Search 或 No Need),然后执行工具检索信息。

然后将检索到的信息插入蓝色 LLM 的提示中,指示它使用这些信息为客户生成回复。


9


我们也可以使用 Langchain 的代理来实现这一目标。我认为两种方法各有利弊。


如果不太复杂,可以使用提示工程让 LLM 决定使用哪种工具。使用 Langchain 代理需要的精力较少,但可能会更复杂、更难调试,而且 Langchain 版本在不断更新,命令和应用程序也在不断修订。


除了上一段描述的 rag_chain,我还想赋予代理 Google 搜索的能力。换句话说,我将为代理提供两种工具: 用于检索 pdf 的 RAG 和 Google 搜索。


我们已经完成了 rag_chain,可以通过 searchAPI 实现 Google 搜索的功能。在这里,我使用的是 Serper。


你可以访问 Serper 网站,注册一个免费账户以获得一个令牌,该令牌有 2500 个免费查询。Langchain 还提供了 GooleSerperAPIWrapp,帮助我们只需一行代码即可实现!


from langchain_community.utilities import GoogleSerperAPIWrapper
search = GoogleSerperAPIWrapper()


让我们对搜索功能做一个简单的测试:


5


好了,现在我们可以使用 search 进行谷歌搜索,并用字符串返回搜索结果。


让我们把 rag_chain 和 search 打包成一个工具:


from langchain.agents import Tool, AgentExecutor, create_react_agent
tools = [
    Tool(
        name="RAG",
        func=rag_chain.invoke,
        description="Useful when you're asked Retrieval Augmented Generation(RAG) related questions"
    ),
    Tool(
        name="Google Search",
        description="For answering questions that are not related to legal or when you don't know the answer, use Google search to find the answer",
        func=search.run,
    )
]


工具中的描述用于让 LLM 知道应该使用哪种工具,因此,请根据你的要求尽可能清楚地编写描述。


现在,我们设计一个提示来限制代理的思维方式,然后输出我们想要的响应。


character_prompt = """Answer the following questions as best you can. You have access to the following tools:
{tools}
For any questions requiring tools, you should first search the provided knowledge base. If you don't find relevant information from provided knowledge base, then use Google search to find related information.
To use a tool, you MUST use the following format:
1. Thought: Do I need to use a tool? Yes
2. Action: the action to take, should be one of [{tool_names}]
3. Action Input: the input to the action
4. Observation: the result of the action
When you have a response to say to the Human, or if you do not need to use a tool, you MUST use the following format:
1. Thought: Do I need to use a tool? No
2. Final Answer: [your response here]
It's very important to always include the 'Thought' before any 'Action' or 'Final Answer'. Ensure your output strictly follows the formats above.
Begin!
Previous conversation history:
{chat_history}
Question: {input}
Thought: {agent_scratchpad}
"""


你可能会想,我怎么能写出这么长的提示呢?你可以查看 langchain 的提示中心,找到一些示例,然后做一些调整。


from langchain import hub
hub.pull("hwchase17/react")


接下来,使用 create_react_agent 将工具和提示打包成一个代理,然后使用 AgentExecutor 运行代理并生成响应。我还借此机会在这里添加了内存机制。


from langchain.prompts.prompt import PromptTemplate
from langchain.chains.conversation.memory import ConversationBufferWindowMemory
chat_model = ChatOpenAI(model_name='gpt-4',
                        temperature=0,
                        streaming=True,
                        verbose=True,
                        max_tokens=1024,
                        )
prompt = PromptTemplate.from_template(character_prompt)
agent = create_react_agent(chat_model, tools, prompt)
memory = ConversationBufferWindowMemory(memory_key='chat_history', k=5, return_messages=True, output_key="output")
agent_chain = AgentExecutor(agent=agent,
                            tools=tools,
                            memory=memory,
                            max_iterations=5,
                            handle_parsing_errors=True,
                            verbose=True,
                            )


该代理链可用于对话。当用户输入问题时,它将决定是否使用工具、使用哪种工具,并执行操作以获得工具的回复。

然后,它会检查是否有足够的信息。如果有,它会根据收集到的信息生成回复。如果没有,它会继续执行操作,直到达到最大迭代次数。


请注意,它可能会遇到一个问题: LLM 在从提示到回复的过程中没有遵循我们的指令,这将导致 LLM 解析错误。你会发现,LLM 已经收集了足够的信息来回答问题,但它却回复说:对不起,我没有足够的信息来回答你的问题......


因此,我在这里尝试使用提示工程师来缓解这一问题。你可以看到,我反复强调你必须使用以下格式:在任何 “操作 ”或 “最终答案 ”之前都必须包含 “想法”,这一点非常重要。确保你的输出严格遵循上述格式。


我还使用了功能强大的 gpt-4 模型。因为我发现较大的模型能更好地遵从我们的提示指令


现在,让我们来看看 agent_chain 的性能:


6


7


8


它的反应似乎非常好~通过添加一些前端开发,你可以在网页上与这个聊天机器人进行交互。


最后,我把这个代理整理成了下面的流程图。

我相信这是一个可以用于多种场景的聊天机器人架构,你只需用自己的内容替换 RAG 检索知识库即可。


9

文章来源:https://medium.com/@peaceful0907/build-your-customized-chatbot-with-rag-and-langchain-agent-0eae1923702e
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
写评论取消
回复取消