自定义LLM代理(带有ChatModel)
本笔记本介绍了如何基于聊天模型创建自己的自定义代理。
LLM聊天代理由三个部分组成:
- PromptTemplate:这是用于指导语言模型如何操作的提示模板
- ChatModel:这是驱动代理的语言模型
stop
序列:指示LLM在找到此字符串时停止生成- OutputParser:确定如何将LLMOutput解析为AgentAction或AgentFinish对象
LLMAgent在AgentExecutor中使用。这个AgentExecutor可以大致看作是一个循环,它:
- 将用户输入和任何先前的步骤传递给代理(在本例中为LLMAgent)
- 如果代理返回
AgentFinish
,则直接将其返回给用户 - 如果代理返回
AgentAction
,则使用它调用工具并获取Observation
- 重复以上步骤,将
AgentAction
和Observation
传递回代理,直到发出AgentFinish
。
AgentAction
是一个包含action
和action_input
的响应。action
指的是要使用的工具,action_input
指的是该工具的输入。还可以提供log
作为更多的上下文(可用于日志记录、跟踪等)。
AgentFinish
是一个包含要发送回用户的最终消息的响应。这应该用于结束代理运行。
在本笔记本中,我们将介绍如何创建自定义LLM代理。
设置环境
进行必要的导入等操作。
pip install langchain
pip install google-search-results
pip install openai
from langchain.agents import Tool, AgentExecutor, LLMSingleActionAgent, AgentOutputParser
from langchain.prompts import BaseChatPromptTemplate
from langchain import SerpAPIWrapper, LLMChain
from langchain.chat_models import ChatOpenAI
from typing import List, Union
from langchain.schema import AgentAction, AgentFinish, HumanMessage
import re
from getpass import getpass
设置工具
设置代理可能需要使用的任何工具。这可能需要放在提示中(以便代理知道要使用这些工具)。
SERPAPI_API_KEY = getpass()
# 定义代理可以使用的工具来回答用户的查询
search = SerpAPIWrapper(serpapi_api_key=SERPAPI_API_KEY)
tools = [
Tool(
name = "Search",
func=search.run,
description="当您需要回答有关当前事件的问题时很有用"
)
]
Prompt模板
这指示代理要做什么。通常,模板应包括:
tools
:代理可以访问的工具以及如何何时调用它们。intermediate_steps
:这些是先前的(AgentAction
,Observation
)对的元组。通常不直接传递给模型,但是提示模板以特定方式格式化它们。input
:通用用户输入
# 设置基本模板
template = """Complete the objective as best you can. You have access to the following tools:
{tools}
Use the following format:
Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [{tool_names}]
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question
These were previous tasks you completed:
Begin!
Question: {input}
{agent_scratchpad}"""
# 设置一个提示模板
class CustomPromptTemplate(BaseChatPromptTemplate):
# 要使用的模板
template: str
# 可用工具的列表
tools: List[Tool]
def format_messages(self, **kwargs) -> str:
# 获取中间步骤(AgentAction,Observation元组)
# 以特定方式格式化它们
intermediate_steps = kwargs.pop("intermediate_steps")
thoughts = ""
for action, observation in intermediate_steps:
thoughts += action.log
thoughts += f"\nObservation: {observation}\nThought: "
# 将agent_scratchpad变量设置为该值
kwargs["agent_scratchpad"] = thoughts
# 从提供的工具列表创建一个tools变量
kwargs["tools"] = "\n".join([f"{tool.name}: {tool.description}" for tool in self.tools])
# 为提供的工具创建一个工具名称列表
kwargs["tool_names"] = ", ".join([tool.name for tool in self.tools])
formatted = self.template.format(**kwargs)
return [HumanMessage(content=formatted)]
prompt = CustomPromptTemplate(
template=template,
tools=tools,
# 这里省略了`agent_scratchpad`、`tools`和`tool_names`变量,因为这些是动态生成的
# 这里包括了`intermediate_steps`变量,因为这是需要的
input_variables=["input", "intermediate_steps"]
)
输出解析器
输出解析器负责将LLM输出解析为AgentAction
和AgentFinish
。这通常严重依赖于使用的提示。
这是您可以更改解析以进行重试、处理空格等的地方。
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"Could not parse LLM output: `{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_API_KEY = getpass()
llm = ChatOpenAI(openai_api_key=OPENAI_API_KEY, temperature=0)
定义停止序列
这很重要,因为它告诉LLM何时停止生成。
这严重依赖于您使用的提示和模型。通常,您希望这是您在提示中用于表示Observation
开始的令牌(否则,LLM可能会为您产生幻觉的观察结果)。
设置代理
现在我们可以将所有内容组合起来设置我们的代理
# 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("Search for Leo DiCaprio's girlfriend on the internet.")
> 进入新的AgentExecutor链...
Thought: 我应该使用可靠的搜索引擎获取准确的信息。
Action: Search
Action Input: "Leo DiCaprio girlfriend"
Observation: 他继续与吉赛尔·邦辰、巴尔·拉菲尔、布莱克·莱弗利、托妮·加恩和妮娜·阿格达尔等人约会,最后与现任女友卡米拉·莫罗内定居下来,后者比他小23岁。
我已经找到了问题的答案。
Final Answer: Leo DiCaprio的现任女友是Camila Morrone。
> 完成链。
"Leo DiCaprio的现任女友是Camila Morrone。"