Skip to main content

自定义LLM代理

本笔记本介绍如何创建自己的自定义LLM代理。

LLM代理由三个部分组成:

  • PromptTemplate:这是用于指导语言模型做什么的提示模板
  • LLM:这是驱动代理的语言模型
  • 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代理。

设置环境

进行必要的导入等操作。

from langchain.agents import Tool, AgentExecutor, LLMSingleActionAgent, AgentOutputParser
from langchain.prompts import StringPromptTemplate
from langchain import OpenAI, SerpAPIWrapper, LLMChain
from typing import List, Union
from langchain.schema import AgentAction, AgentFinish, OutputParserException
import re

设置工具

设置代理可能想要使用的任何工具。这可能需要放在提示中(以便代理知道要使用这些工具)。

# 定义代理可以使用的工具
search = SerpAPIWrapper()
tools = [
Tool(
name = "Search",
func=search.run,
description="用于回答有关当前事件的问题时很有用"
)
]

提示模板

这指示代理要做什么。通常,模板应包括:

  • tools:代理可以访问的工具以及如何何时调用它们。
  • intermediate_steps:这些是先前的(AgentActionObservation)对的元组。通常不直接传递给模型,但是提示模板以特定的方式格式化它们。
  • input:通用用户输入
# 设置基本模板
template = """Answer the following questions as best you can, but speaking as a pirate might speak. 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

Begin! Remember to speak as a pirate when giving your final answer. Use lots of "Arg"s

Question: {input}
{agent_scratchpad}"""

# 设置一个提示模板
class CustomPromptTemplate(StringPromptTemplate):
# 要使用的模板
template: str
# 可用工具的列表
tools: List[Tool]

def format(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])
return self.template.format(**kwargs)

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 OutputParserException(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!

llm = OpenAI(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("How many people live in canada as of 2023?")


> Entering new AgentExecutor chain...
Thought: I need to find out the population of Canada in 2023
Action: Search
Action Input: Population of Canada in 2023

Observation:The current population of Canada is 38,658,314 as of Wednesday, April 12, 2023, based on Worldometer elaboration of the latest United Nations data. I now know the final answer
Final Answer: Arrr, there be 38,658,314 people livin' in Canada as of 2023!

> Finished chain.


"Arrr, there be 38,658,314 people livin' in Canada as of 2023!"

添加记忆(内存) Memory

如果要向代理添加记忆(内存),您需要:

  1. 在自定义提示中添加聊天历史的位置
  2. 在代理执行器中添加一个记忆对象。
# 设置基本模板
template_with_history = """Answer the following questions as best you can, but speaking as a pirate might speak. 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

Begin! Remember to speak as a pirate when giving your final answer. Use lots of "Arg"s

Previous conversation history:
{history}

New question: {input}
{agent_scratchpad}"""

prompt_with_history = CustomPromptTemplate(
template=template_with_history,
tools=tools,
# 这里省略了`agent_scratchpad`、`tools`和`tool_names`变量,因为这些是动态生成的
# 这里包括了`intermediate_steps`变量,因为这是需要的
input_variables=["input", "intermediate_steps", "history"]
)

llm_chain = LLMChain(llm=llm, prompt=prompt_with_history)

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

from langchain.memory import ConversationBufferWindowMemory

memory=ConversationBufferWindowMemory(k=2)

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

agent_executor.run("How many people live in canada as of 2023?")
    > Entering new AgentExecutor chain...
Thought: I need to find out the population of Canada in 2023
Action: Search
Action Input: Population of Canada in 2023

Observation:The current population of Canada is 38,658,314 as of Wednesday, April 12, 2023, based on Worldometer elaboration of the latest United Nations data. I now know the final answer
Final Answer: Arrr, there be 38,658,314 people livin' in Canada as of 2023!

> Finished chain.

"Arrr, there be 38,658,314 people livin' in Canada as of 2023!"
agent_executor.run("how about in mexico?")
    > Entering new AgentExecutor chain...
Thought: I need to find out how many people live in Mexico.
Action: Search
Action Input: How many people live in Mexico as of 2023?

Observation:The current population of Mexico is 132,679,922 as of Tuesday, April 11, 2023, based on Worldometer elaboration of the latest United Nations data. Mexico 2020 ... I now know the final answer.
Final Answer: Arrr, there be 132,679,922 people livin' in Mexico as of 2023!

> Finished chain.

"Arrr, there be 132,679,922 people livin' in Mexico as of 2023!"