构建LangChain应用程序的示例代码:16、具有插件检索功能的自定义代理(Custom Agent with PlugIn Retrieval)

自定义代理与插件检索

这个笔记本结合了两个概念,以构建一个能够与AI插件交互的自定义代理:

自定义代理与工具检索:这引入了检索多个工具的概念,这在尝试使用任意多的插件时非常有用。
自然语言API链:这在OpenAPI端点周围创建了自然语言包装器。这很有用,因为(1)插件在幕后使用OpenAPI端点,(2)将它们包装在NLAChain中可以让路由器代理更容易地调用它们。

这个笔记本引入的新思想是使用检索来选择不是工具本身,而是要使用的OpenAPI规范集。然后我们可以从这些OpenAPI规范生成工具。这在尝试让代理使用插件时非常有用。可能更有效的是先选择插件,然后选择端点,而不是直接选择端点。这是因为插件可能包含更多有用的信息用于选择。

设置环境

进行必要的导入等。

import re
from typing import Union

from langchain.agents import (
    AgentExecutor,
    AgentOutputParser,
    LLMSingleActionAgent,
)
from langchain.chains import LLMChain
from langchain.prompts import StringPromptTemplate
from langchain_community.agent_toolkits import NLAToolkit
from langchain_community.tools.plugin import AIPlugin
from langchain_core.agents import AgentAction, AgentFinish
from langchain_openai import OpenAI

设置LLM

llm = OpenAI(temperature=0)

设置插件

加载并索引插件

urls = [
    "https://datasette.io/.well-known/ai-plugin.json",
    "https://api.speak.com/.well-known/ai-plugin.json",
    "https://www.wolframalpha.com/.well-known/ai-plugin.json",
    "https://www.zapier.com/.well-known/ai-plugin.json",
    "https://www.klarna.com/.well-known/ai-plugin.json",
    "https://www.joinmilo.com/.well-known/ai-plugin.json",
    "https://slack.com/.well-known/ai-plugin.json",
    "https://schooldigger.com/.well-known/ai-plugin.json",
]

AI_PLUGINS = [AIPlugin.from_url(url) for url in urls]

工具检索器

我们将使用向量存储为每个工具描述创建嵌入。然后,对于传入的查询,我们可以为该查询创建嵌入,并进行相似性搜索以找到相关工具。

from langchain_community.vectorstores import FAISS
from langchain_core.documents import Document
from langchain_openai import OpenAIEmbeddings

embeddings = OpenAIEmbeddings()
docs = [
    Document(
        page_content=plugin.description_for_model,
        metadata={"plugin_name": plugin.name_for_model},
    )
    for plugin in AI_PLUGINS
]
vector_store = FAISS.from_documents(docs, embeddings)
toolkits_dict = {
    plugin.name_for_model: NLAToolkit.from_llm_and_ai_plugin(llm, plugin)
    for plugin in AI_PLUGINS
}

retriever = vector_store.as_retriever()

def get_tools(query):
    # 获取文档,其中包含要使用的插件
    docs = retriever.invoke(query)
    # 获取工具包,每个插件一个
    tool_kits = [toolkits_dict[d.metadata["plugin_name"]] for d in docs]
    # 获取工具:每个端点一个单独的NLAChain
    tools = []
    for tk in tool_kits:
        tools.extend(tk.nla_tools)
    return tools

我们现在可以测试这个检索器,看看它是否有效。

tools = get_tools("今天我可以和我的小孩做什么")
[t.name for t in tools]

tools = get_tools("我可以买什么衬衫")
[t.name for t in tools]

提示模板

提示模板相当标准,因为我们实际上并没有改变提示模板中的逻辑,而是改变了检索的执行方式。

设置基础模板

template = """
尽你所能回答问题,但要像海盗那样说话。你可以使用以下工具:
{tools}

使用以下格式:

问题:你必须回答的输入问题
思考:你应该总是思考要做什么
动作:要采取的动作,应该是[{tool_names}]之一
动作输入:动作的输入
观察:动作的结果
...(这个思考/动作/动作输入/观察可以重复N次)
思考:我现在知道最终答案了
最终答案:原始输入问题的最终答案

开始!记住在给出最终答案时要像海盗一样说话。多用“Arg”
问题:{input}
{agent_scratchpad}
"""

自定义提示模板现在有了一个tools_getter的概念,我们在输入时调用它来选择要使用的工具

from typing import Callable

设置提示模板

class CustomPromptTemplate(StringPromptTemplate):
    # 使用的模板
    template: str
    ############### 新增 ######################
    # 可用工具的列表
    tools_getter: Callable
prompt = CustomPromptTemplate(
    template=template,
    tools_getter=get_tools,
    # 这省略了`agent_scratchpad`、`tools`和`tool_names`变量,因为这些是动态生成的
    # 包括`intermediate_steps`变量,因为这是必需的
    input_variables=["input", "intermediate_steps"],
)

输出解析器

输出解析器与前一个笔记本相同,因为我们没有改变输出格式的任何内容。

class CustomOutputParser(AgentOutputParser):
    def parse(self, llm_output: str) -> Union[AgentAction, AgentFinish]:
        # 检查代理是否应该完成
        if "Final Answer:" in llm_output:
            return AgentFinish(
                # 返回值通常总是一个带有单个`output`键的字典
                # 目前不建议尝试其他任何东西:)
                return_values={"output": llm_output.split("Final Answer:")[-1].strip()},
                log=llm_output,
            )
        # 解析动作和动作输入
        regex = r"Action\s*\d*\s*:(.*?)\nAction\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)"
        match = re.search(regex, llm_output, re.DOTALL)
        if not match:
            raise ValueError(f"无法解析LLM输出: `{llm_output}`")
        action = match.group(1).strip()
        action_input = match.group(2)
        # 返回动作和动作输入
        return AgentAction(
            tool=action, tool_input=action_input.strip(" ").strip('"'), log=llm_output
        )

output_parser = CustomOutputParser()

设置LLM、停止序列和代理

与前一个笔记本相同

llm = OpenAI(temperature=0)

LLM链由LLM和提示组成

llm_chain = LLMChain(llm=llm, prompt=prompt)
tool_names = [tool.name for tool in tools]
agent = LLMSingleActionAgent(
    llm_chain=llm_chain,
    output_parser=output_parser,
    stop=["\nObservation:"],
    allowed_tools=tool_names,
)

使用代理

现在我们可以使用它了!

agent_executor = AgentExecutor.from_agent_and_tools(
    agent=agent, tools=tools, verbose=True
)

agent_executor.run("我可以买什么衬衫")

抱歉遗漏了总结部分,以下是对整个文档的总结:

总结

本文介绍了如何创建一个自定义代理,该代理能够与AI插件进行交互。通过结合工具检索和自然语言API链的概念,我们构建了一个系统,它使用检索来选择OpenAPI规范集,然后基于这些规范生成工具。这种方法在代理尝试使用插件时特别有用,因为插件可能包含更多有助于选择的信息。

首先,我们设置了环境,导入了必要的模块,并初始化了LLM(语言模型)。接着,我们加载并索引了一系列插件,为每个插件创建了工具描述的嵌入,并使用向量存储进行相似性搜索以找到相关工具。

然后,我们定义了一个自定义的提示模板,该模板包括一个tools_getter函数,它在输入时调用以选择要使用的工具。我们还实现了一个输出解析器,用于解析LLM的输出,并确定代理应该采取的动作。

最后,我们组合了LLM链、输出解析器和停止序列,构建了LLMSingleActionAgent代理,并使用AgentExecutor运行了代理,以执行特定的任务。

通过这种方法,我们可以更灵活地构建能够与各种AI插件交互的自定义代理,从而扩展代理的功能并提高其效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hugo_Hoo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值