LangChain Agent代理的核心思想是使用LLM作为大脑自动思考,自动决策选择执行不同的动作,最终完成我们的目标任务。
提示:从开发角度理解,就是我们提前开发好各种各样功能的API,然后给Agent一个任务,让LLM自己分析要调用哪个API可以完成任务。
举个例子方便理解LangChain Agent要解决的问题。
例如:
我们要调研下“docker是否可以作为生产环境部署方案”,我们首先会去百度搜索”docker介绍”,浏览下搜索结果,然后进一步搜索”docker部署的优缺点”,浏览下结果等等,最后得出结论。
LangChain Agent就是要模拟这个过程,我可以提前封装一序列工具(例如:百度搜索、提取URL内容等工具),然后给通过Agent下发一个目标任务“docker是否可以作为生产环境部署方案”,Agent就会构造提示词去调用大语言模型LLM,要实现这个目标任务,下一步执行什么动作(就是要调用那个工具),AI就会返回要调用的工具,代码就去执行这个工具,然后把工具执行的结果,回传给AI,再问下一步执行什么工具,反复执行这个过程就可以完成前面提到的任务。
提示:这个是自从GPT模型发布以来,相当炸裂的能力,让LLM作为大脑,主动思考,然后调用我们开发好的各种API,这样LLM的能力就相当强大,目前这个特性还在实验阶段,会反复调用LLM所以相当费token,执行一个任务下来分分钟几万个token,想省钱,建议先让LLM执行简单的逻辑判断任务。
核心概念
下面介绍相关的组件&概念
Agent(代理)
Agent可以理解成我们的助手,代理我们去做一些决策,在LangChain agent底层实现就是通过LLM决定下一步执行什么动作(或者说调用什么API),这里有个知名的ReAct模式,就是描述AI决策过程,感兴趣的同学可以去了解下。
LangChain针对不同的场景提供了几种不同类型的代理类型。
Tools(工具)
Tools我觉得理解成API更合适,就是我们提前封装好的各种功能的API,目的是扩展LLM的能力,由LLM根据问题决定调用那个具体的API来完成任务。
Toolkits(工具集合)
工具集合,通常提供给LLM的工具不会是一个、两个,会提供一组可用的工具给LLM,让LLM完成任务的时候有更多的能力选择。
AgentExecutor
代理执行器是负责执行LLM选择的工具(API)。以下是此运行时的伪代码:
# 通过LLM拿到需要执行的工具
next_action = agent.get_action(...)
# 如果没有完成任务,循环执行
while next_action != AgentFinish:
# 执行动作
observation = run(next_action)
# 执行下一步要执行的工具
next_action = agent.get_action(..., next_action, observation)
return next_action
执行过程虽然不复杂,执行器处理了很多细节问题主要包括:
- 处理agent选择不存在的工具的情况
- 处理工具出错的情况
- 处理agent产生无法解析为工具调用的输出的情况
- 调试问题。
快速开始
本节介绍LangChain的Agent的基础用法
1. 加载LLM
首先,让我们加载我们将用来控制代理程序(agent)的语言模型(llm)。
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
2. 定义工具
接下来,我们定义一些工具,让Agent调用。我们将编写一个非常简单的 Python 函数来计算传入单词的长度。
from langchain.agents import tool
@tool
def get_word_length(word: str) -> int:
"""返回单词的长度。"""
return len(word)
get_word_length.invoke("abc")
注意:函数注释非常重要,它告诉LLM调用它能解决什么问题,
get_word_length
函数就是告诉LLM调用他可以计算单词长度。
3
定义工具集合
tools = [get_word_length]
3. 创建提示(prompt)
现在让我们创建提示(prompt)。因为OpenAI Function Calling已经针对工具使用进行了优化,所以我们几乎不需要关于推理或输出格式的任何说明。我们只有两个输入变量:input
和agent_scratchpad
。input
代表用户输入的问题,agent_scratchpad
是agent
的调用指令占位符,运行的时候会往提示词模板(prompt template)插入工具调用指令。
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"你是一个非常强大的助手,但不了解当前事件。",
),
("user", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
]
)
4. 将工具绑定到LLM
代理程序如何知道可以使用哪些工具?
这里依赖的是OpenAI的工具调用功能(有很多模型都支持类似的功能),我们只要把定义好的工具调用格式告诉模型就行。
# 将工具绑定到模型中
llm_with_tools = llm.bind_tools(tools)
5. 创建代理(agent)
将之前的内容整合在一起,我们现在可以创建代理程序了。我们将导入最后两个实用工具函数:一个用于将中间步骤(代理动作、工具输出)格式化为可发送给模型的输入消息的组件,另一个用于将输出消息转换为代理动作/代理结束。
from langchain.agents.format_scratchpad.openai_tools import format_to_openai_tool_messages
from langchain.agents.output_parsers.openai_tools import OpenAIToolsAgentOutputParser
# 定义一个chain,
# step1: 为prompt模板准备参数,从agent调用输入中提取input和intermediate_steps两个参数,input由用户输入,intermediate_steps由agent生成
# step2: 根据step1的参数,格式化prompt template
# step3: 调用模型
# step4: 处理模型输出
agent = (
{
"input": lambda x: x["input"],
"agent_scratchpad": lambda x: format_to_openai_tool_messages(x["intermediate_steps"]),
}
| prompt
| llm_with_tools
| OpenAIToolsAgentOutputParser()
)
定义agent执行器
from langchain.agents import AgentExecutor
# verbose = True代表在控制台打印详细的日志
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
让我们通过示例来展示代理程序的运行:
# 调用agent
list(agent_executor.stream({"input": "eudca这个单词有几个字母"}))
agent输出日志示例
> 进入新的代理程序执行链...
调用: 使用 `{'word': 'educa'}` 参数来运行 `get_word_length`
单词"educa"中有5个字母。
> 完成程序执行链。
通过这个例子,我们展示了代理程序的完整流程。
为Agent增加记忆功能
如果我们希望Agent记住之前说的内容,其实也很简单,就是把AI返回的内容插入到提示词(prompt)中一起提交给AI就行。
修改提示词模板
下面我们修改提示词模板(prompt template),增加对话历史模板变量
from langchain.prompts import MessagesPlaceholder
# 在消息模板中插入一个对话历史的占位符(chat_history),用来插入历史对话记录
MEMORY_KEY = "chat_history"
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"你是个很厉害的助手,但不擅长计算单词的长度。",
),
MessagesPlaceholder(variable_name=MEMORY_KEY),
("user", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
]
)
修改agent流程定义
修改agent流程定义,为提示词模板(prompt template)提供对话历史数据,如下代码,新增chat_history
参数处理
agent = (
{
"input": lambda x: x["input"],
"agent_scratchpad": lambda x: format_to_openai_tool_messages(
x["intermediate_steps"]
),
"chat_history": lambda x: x["chat_history"],
}
| prompt
| llm_with_tools
| OpenAIToolsAgentOutputParser()
)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
调用agent的时候提供对话历史数据
# 对话历史记录
chat_history = []
input1 = "educa这个单词有几个字母?"
# 调用agent,同时传入对话历史数据
result = agent_executor.invoke({"input": input1, "chat_history": chat_history})
# 调用agent结束后,把对话结果保存下来
chat_history.extend(
[
HumanMessage(content=input1),
AIMessage(content=result["output"]),
]
)
# 再次调用agent,同时传入对话历史数据
agent_executor.invoke({"input": "这个词真的存在吗?", "chat_history": chat_history})
实际业务场景,你可以把对话历史保存到数据库中,根据业务需要把数据插入到提示词(prompt)中即可。
提示:大模型(LLM)的记忆功能,目前基本上都是通过把历史对话(chat history)内容插入到提示词(prompt)中提交给LLM实现,LangChain只是提供了一些封装,你可以选择不使用,自己把对话历史拼接到提示词模板(prompt template)即可。