前言

大语言模型一般只会在自己学习的知识中进行搜索,无法根据每一步的输出内容来决定下一步的 action, 这个时候就需要用到 Agent 技术,Agent 其实就是封装了一系列的 tools 工具,当我们给 LLM 一个复杂的任务时,它会将该任务拆解为一步一步的,然后根据对应的上下文内容,选择使用哪一个或多个工具。

其中现在最流行的 Langchain 的核心之一就是封装了很多的 Agent 可以让我们调用,接下来我将演示如何通过 AgentExecutor 构造一个 Langchain Agent 并使用它。

前提条件

定义 tools

import os

from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores.dashvector import DashVector
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain.tools.retriever import create_retriever_tool
from langchain_community.embeddings.dashscope import DashScopeEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.messages import HumanMessage

class TestAgent:

    def __init__(self):
        """在阿里云控制台开通 DASHSCOPE 和 DASHVECTOR"""
        os.environ["DASHSCOPE_API_KEY"] = ""
        os.environ["DASHVECTOR_API_KEY"] = ""
        os.environ["DASHVECTOR_ENDPOINT"] = ""

        # 一系列 tool 让 langchain 选择使用
        self.tools = []

    def build_retriever_tool(self):
        """爬取网站内容切分成 chunks 嵌入向量数据库"""
        loader = WebBaseLoader("https://docs.smith.langchain.com/overview")
        docs = loader.load()
        documents = RecursiveCharacterTextSplitter(
            chunk_size=1000, chunk_overlap=200
        ).split_documents(docs)
        vector = DashVector.from_documents(documents, DashScopeEmbeddings(), collection_name="langchain")
        retriever = vector.as_retriever()
        retriever_tool = create_retriever_tool(
            retriever,
            "langsmith_search",
            # 注意这里的 description 很重要,它描述的意思决定了 langchain 是否根据当前上下文选择该工具
            # 因此,description 要尽量描述的和 tool 所做内容吻合
            "Search for information about LangSmith. For any questions about LangSmith, you must use this tool!",
        )
        self.tools.append(retriever_tool)
  • 这里我们定义了一个关于 retriever_tool: 关于向 LLM 询问 LangSmith 的问题,将使用到该工具
  • 注意 description 很重要

使用大模型调用 tools

接下来我们向阿里云的通义千问大模型询问关于 LangSmith 的相关信息,它将会调用我们的 tools。

import os

from langchain.agents import AgentExecutor
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores.dashvector import DashVector
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain.tools.retriever import create_retriever_tool
from langchain_community.embeddings.dashscope import DashScopeEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.messages import HumanMessage

class TestAgent:

    def __init__(self):
        """在阿里云控制台开通 DASHSCOPE 和 DASHVECTOR"""
        os.environ["DASHSCOPE_API_KEY"] = ""
        os.environ["DASHVECTOR_API_KEY"] = ""
        os.environ["DASHVECTOR_ENDPOINT"] = ""

        self.model = ChatTongyi()

        # 一系列 tool 让 langchain 选择使用
        self.tools = []

    def build_retriever_tool(self):
        """爬取网站内容切分成 chunks 嵌入向量数据库"""
        loader = WebBaseLoader("https://docs.smith.langchain.com/overview")
        docs = loader.load()
        documents = RecursiveCharacterTextSplitter(
            chunk_size=1000, chunk_overlap=200
        ).split_documents(docs)
        vector = DashVector.from_documents(documents, DashScopeEmbeddings(), collection_name="langchain")
        retriever = vector.as_retriever()
        retriever_tool = create_retriever_tool(
            retriever,
            "langsmith_search",
            # 注意这里的 description 很重要,它描述的意思决定了 langchain 是否根据当前上下文选择该工具
            # 因此,description 要尽量描述的和 tool 所做内容吻合
            "Search for information about LangSmith. For any questions about LangSmith, you must use this tool!",
        )
        self.tools.append(retriever_tool)

    def invoke_llm_with_tools(self, query=None):
        """
        向 LLM 提出问题,让大模型结合自己所学习的内容和我们自己构造的 tools 来回答
        """
        model_with_tools = self.model.bind_tools(self.tools)

        response = model_with_tools.invoke([HumanMessage(content=query)])
        print(f"ContentString: {response.content}")
        # 可以查看 LLM 在回答我们的问题的时候用到了哪些 tool
        print(f"ToolCalls: {response.tool_calls}")

    def create_agent_executor(self):
        """通过 AgentExecutor 将我们的 tools 封装为 Agent"""
        from langchain import hub
        from langchain.agents import create_tool_calling_agent
        from langchain.agents import AgentExecutor

        prompt = hub.pull("hwchase17/openai-functions-agent")
        agent = create_tool_calling_agent(self.model, self.tools, prompt)
        self.agent_executor = AgentExecutor(agent=agent, tools=self.tools)

if __name__ == '__main__':
    a = TestAgent()
    a.build_retriever_tool()
    a.invoke_llm_with_tools("告诉我关于 LangSmith 的相关信息")

封装 Agent 调用 tools

可以通过 AgentExecutor 将我们的 tools 封装为 Agent, 然后通过 Agent 执行查询。

import os

from langchain.agents import AgentExecutor
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores.dashvector import DashVector
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain.tools.retriever import create_retriever_tool
from langchain_community.embeddings.dashscope import DashScopeEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.messages import HumanMessage

class TestAgent:

    def __init__(self):
        """在阿里云控制台开通 DASHSCOPE 和 DASHVECTOR"""
        os.environ["DASHSCOPE_API_KEY"] = ""
        os.environ["DASHVECTOR_API_KEY"] = ""
        os.environ["DASHVECTOR_ENDPOINT"] = ""

        self.model = ChatTongyi()

        # 一系列 tool 让 langchain 选择使用
        self.tools = []

        self.agent_executor = None

    def build_retriever_tool(self):
        """爬取网站内容切分成 chunks 嵌入向量数据库"""
        loader = WebBaseLoader("https://docs.smith.langchain.com/overview")
        docs = loader.load()
        documents = RecursiveCharacterTextSplitter(
            chunk_size=1000, chunk_overlap=200
        ).split_documents(docs)
        vector = DashVector.from_documents(documents, DashScopeEmbeddings(), collection_name="langchain")
        retriever = vector.as_retriever()
        retriever_tool = create_retriever_tool(
            retriever,
            "langsmith_search",
            # 注意这里的 description 很重要,它描述的意思决定了 langchain 是否根据当前上下文选择该工具
            # 因此,description 要尽量描述的和 tool 所做内容吻合
            "Search for information about LangSmith. For any questions about LangSmith, you must use this tool!",
        )
        self.tools.append(retriever_tool)

    def invoke_llm_with_tools(self, query=None):
        """
        向 LLM 提出问题,让大模型结合自己所学习的内容和我们自己构造的 tools 来回答
        """
        model_with_tools = self.model.bind_tools(self.tools)

        response = model_with_tools.invoke([HumanMessage(content=query)])
        print(f"ContentString: {response.content}")
        # 可以查看 LLM 在回答我们的问题的时候用到了哪些 tool
        print(f"ToolCalls: {response.tool_calls}")

    def create_agent_executor(self):
        """通过 AgentExecutor 将我们的 tools 封装为 Agent"""
        from langchain import hub
        from langchain.agents import create_tool_calling_agent
        from langchain.agents import AgentExecutor

        prompt = hub.pull("hwchase17/openai-functions-agent")
        agent = create_tool_calling_agent(self.model, self.tools, prompt)
        self.agent_executor = AgentExecutor(agent=agent, tools=self.tools)

    def query_with_agent(self, query=None):
        """通过 agent 执行查询,将会调用到我们的 tools"""
        res = self.agent_executor.invoke({"input": query})
        print(f"ContentString: {res['output']}")

if __name__ == '__main__':
    a = TestAgent()
    a.build_retriever_tool()
    # a.invoke_llm_with_tools("告诉我关于 LangSmith 的相关信息")
    a.create_agent_executor()
    a.query_with_agent("告诉我关于 LangSmith 的相关信息")

保存查询历史上下文

上边的查询都是没有历史上下文的,如果说我们需要像试用 ChatGPT 一样可以多次提问,并且带有历史上下文,该如何实现呢?
其实就是一种很直观简单的方式,将历史上下文给保存起来,然后在每次提问的时候,将历史上下文带上。

import os

from langchain.agents import AgentExecutor
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores.dashvector import DashVector
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain.tools.retriever import create_retriever_tool
from langchain_community.embeddings.dashscope import DashScopeEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.messages import HumanMessage, AIMessage


class TestAgent:

    def __init__(self):
        """在阿里云控制台开通 DASHSCOPE 和 DASHVECTOR"""
        os.environ["DASHSCOPE_API_KEY"] = ""
        os.environ["DASHVECTOR_API_KEY"] = ""
        os.environ["DASHVECTOR_ENDPOINT"] = ""

        self.model = ChatTongyi()

        # 一系列 tool 让 langchain 选择使用
        self.tools = []

        self.agent_executor = None

    def build_retriever_tool(self):
        """爬取网站内容切分成 chunks 嵌入向量数据库"""
        loader = WebBaseLoader("https://docs.smith.langchain.com/overview")
        docs = loader.load()
        documents = RecursiveCharacterTextSplitter(
            chunk_size=1000, chunk_overlap=200
        ).split_documents(docs)
        vector = DashVector.from_documents(documents, DashScopeEmbeddings(), collection_name="langchain")
        retriever = vector.as_retriever()
        retriever_tool = create_retriever_tool(
            retriever,
            "langsmith_search",
            # 注意这里的 description 很重要,它描述的意思决定了 langchain 是否根据当前上下文选择该工具
            # 因此,description 要尽量描述的和 tool 所做内容吻合
            "Search for information about LangSmith. For any questions about LangSmith, you must use this tool!",
        )
        self.tools.append(retriever_tool)

    def invoke_llm_with_tools(self, query=None):
        """
        向 LLM 提出问题,让大模型结合自己所学习的内容和我们自己构造的 tools 来回答
        """
        model_with_tools = self.model.bind_tools(self.tools)

        response = model_with_tools.invoke([HumanMessage(content=query)])
        print(f"ContentString: {response.content}")
        # 可以查看 LLM 在回答我们的问题的时候用到了哪些 tool
        print(f"ToolCalls: {response.tool_calls}")

    def create_agent_executor(self):
        """通过 AgentExecutor 将我们的 tools 封装为 Agent"""
        from langchain import hub
        from langchain.agents import create_tool_calling_agent
        from langchain.agents import AgentExecutor

        prompt = hub.pull("hwchase17/openai-functions-agent")
        agent = create_tool_calling_agent(self.model, self.tools, prompt)
        self.agent_executor = AgentExecutor(agent=agent, tools=self.tools)

    def query_with_agent(self, query=None):
        """通过 agent 执行查询,将会调用到我们的 tools"""
        res = self.agent_executor.invoke({"input": query})
        print(f"ContentString: {res['output']}")

    def query_with_history(self):
        """带有历史上下文的查询, 新的提问时,将历史提问和回答信息一起手动带上"""
        self.agent_executor.invoke({"input": "hi! my name is bob", "chat_history": []})
        self.agent_executor.invoke(
            {
                "chat_history": [
                    HumanMessage(content="hi! my name is bob"),
                    AIMessage(content="Hello Bob! How can I assist you today?"),
                ],
                "input": "what's my name?",
            }
        )

    def query_with_memory(self):
        """
        让 langchain 自己将历史信息带上,无需我们手动带上,我们只需要在本地或者数据库存储历史信息即可
        """
        from langchain_community.chat_message_histories import ChatMessageHistory
        from langchain_core.chat_history import BaseChatMessageHistory
        from langchain_core.runnables.history import RunnableWithMessageHistory

        # 这里为了简单直接使用字典保存历史信息,生产环境应该使用数据库保存
        store = {}

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

        agent_with_chat_history = RunnableWithMessageHistory(
            self.agent_executor,
            get_session_history,
            input_messages_key="input",
            history_messages_key="chat_history",
        )

        agent_with_chat_history.invoke(
            {"input": "hi! I'm bob"},
            config={"configurable": {"session_id": "<foo>"}},
        )

        res = agent_with_chat_history.invoke(
            {"input": "what's my name?"},
            config={"configurable": {"session_id": "<foo>"}},
        )
        print(res['output'])  # Your name is Bob.

if __name__ == '__main__':
    a = TestAgent()
    a.build_retriever_tool()
    # a.invoke_llm_with_tools("告诉我关于 LangSmith 的相关信息")
    a.create_agent_executor()
    # a.query_with_agent("告诉我关于 LangSmith 的相关信息")
    a.query_with_memory()
  • input_messages_key: 当前提问的内容的 key, 在上边的代码中就是 input, 该参数用于将当前提问内容加入历史。
  • history_messages_key:历史内容的 key, 用于加载历史消息
  • session_id: 用于保存当前对话内容,隔离不同对话

总结

本文总结了如何使用 Langchain 的 AgentExecutor 封装 tools, 构造我们自己的 Agent, 然后通过 LLM 调用,或者通过 Agent 调用,最后总结了如何让我们的 Agent 带有记忆功能,可以保存历史对话记录,这里为了简单就没有使用实际的数据库,实际生产中还是要使用数据库去保存这些信息的,感兴趣的朋友可以尝试下。

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐