自定义代理与插件检索使用Plug-and-Plai
设置环境
进行必要的导入等。
import re
from typing import Union
import plugnplai
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)
设置插件
加载并索引插件。
# 从plugnplai.com获取所有插件
urls = plugnplai.get_plugins()
# 仅获取ChatGPT验证的插件
urls = plugnplai.get_plugins(filter="ChatGPT")
# 仅获取经过测试的插件(进行中)
urls = plugnplai.get_plugins(filter="working")
AI_PLUGINS = [AIPlugin.from_url(url + "/.well-known/ai-plugin.json") 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("我可以买什么衬衫")
总结
本文介绍了如何使用plugnplai
库构建一个自定义代理,该代理能够检索并利用AI插件来执行任务。通过设置环境、配置语言模型、加载插件、创建工具检索器、定义提示模板和输出解析器、设置代理以及测试检索器,我们能够构建一个强大的系统,它可以动态地选择并使用最合适的工具来响应查询。这种结合了自定义代理和插件检索的方法,为开发智能自动化工具提供了一个灵活而强大的框架。