自定义代理与插件检索
这个笔记本结合了两个概念,以构建一个能够与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插件交互的自定义代理,从而扩展代理的功能并提高其效率。