Skip to main content

自定义LLM代理(带有ChatModel)

本笔记本介绍了如何基于聊天模型创建自己的自定义代理。

LLM聊天代理由三个部分组成:

  • PromptTemplate:这是用于指导语言模型如何操作的提示模板
  • ChatModel:这是驱动代理的语言模型
  • stop序列:指示LLM在找到此字符串时停止生成
  • OutputParser:确定如何将LLMOutput解析为AgentAction或AgentFinish对象

LLMAgent在AgentExecutor中使用。这个AgentExecutor可以大致看作是一个循环,它:

  1. 将用户输入和任何先前的步骤传递给代理(在本例中为LLMAgent)
  2. 如果代理返回AgentFinish,则直接将其返回给用户
  3. 如果代理返回AgentAction,则使用它调用工具并获取Observation
  4. 重复以上步骤,将AgentActionObservation传递回代理,直到发出AgentFinish

AgentAction是一个包含actionaction_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:这些是先前的(AgentActionObservation)对的元组。通常不直接传递给模型,但是提示模板以特定方式格式化它们。
  • 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输出解析为AgentActionAgentFinish。这通常严重依赖于使用的提示。

这是您可以更改解析以进行重试、处理空格等的地方。

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。"