导语 "人类区别于动物的根本标志在于制造和使用工具并进行劳动。 " Agent 赋予了大语言模型使用工具的能力。
LangChain 是一个大语言模型(LLM)驱动的应用程序框架,对 LLM 的使用进行了一些抽象和封装。其中有一些设计很巧妙,能够帮助我们更好的应用 LLM。本文主要调研了 LangChain 中的 Agent 如何将 LLM 和外部工具连接。
LLM 擅长处理语言和知识。但是也存在一些局限性,比如不擅长进行数学运算、无法从互联网上查询信息等。Agent 能够教会 LLM 使用工具从而实现更强大的功能。
下边的例子中我们提供了三个工具:搜索引擎、日历、股价查询。首先 Agent 决定解决这个问题需要按照什么样的顺序调用哪些工具,拿到工具的输出后再将工具的输出和原问题以及交给 LLM 处理,最终得到问题的答案。
图1 Agent 示例
制定使用工具的计划,执行这些步骤并得到问题最终解答的过程在 Langchains 中被称作 Agent。
Agent 分类
LangChains 把 Agent 分为两大类,Action Agents 和 Plan-and-Execute Agents。
名词解释
Tool: 实现特定功能的函数,输入和输出都为字符串
Action: 包含 Tool 名和 Tool 输入的结构体
Step: 包含 Action 和 Tool 输出的结构体
Action Agents
Action Agents 一次决定一个要执行的 Action,并且执行。伪代码如下:
- 接收到用户输入
- Agent 决定要使用的 Tool 和 Tool 的输入
- 调用 Tool,并记录 Tool 的输出结果
- 把 Tool 的调用历史传回给 Agent,Agent 决定下一步采取什么行动
- 重复上边的步骤直到 Agent 决定不再使用 Tool,然后把最终结果输出给用户
Plan-and-Execute Agents
Plan-and-Execute Agents 一次性计划出所有要执行的 Actions,一次性执行所有 Actions。伪代码如下:
- 接收到用户输入
- Agent 决定解决这个问题需要的步骤
- 按照上边的步骤一个一个执行,执行完毕后输出结果
MRKL Agent
我们以 Action Agents 中的 Chat MRKL Agent 为例,分析 LangChains 的实现。
MRKL 指符号神经网络,是将符号系统和神经网络结合起来。目前最广泛的实现就是,在神经网络前加一个路由器(router)或称适配器(adapter),将输入派遣到适合的神经网络上。通俗的讲就是让大模型能够调用外部工具以获得更准确的信息。
对于 MRKL Agent ,图 1 中的流程变成了下图:
图2 MRKL Agent
区别在于 MRKL 通过 LLM 进行计划,决定需要使用哪些工具和每个工具的输入。
LangChains 中采用 ReAct 框架 实现 MRKL。ReAct和其他方法的主要区别在于它能够将推理和行动结合起来,实现协同处理。传统的方法通常只关注推理过程,而忽略了行动的重要性。例如,在问答任务中,传统方法可能只生成答案,而不考虑如何采取行动来验证答案的正确性。相比之下,ReAct可以生成一系列与任务相关的行动,并将其与推理过程交替进行,从而更好地解决任务。
举个例子,在一个问答任务中,如果问题需要模型在维基百科上查找相关信息并进行推理,那么传统方法可能会仅仅依靠模型自身的知识库进行推理。但是ReAct可以通过生成一系列与任务相关的行动(例如打开维基百科页面、搜索特定关键字等),并将这些行动与推理过程交替进行来完成任务。这种协同处理方式可以提高模型在各种任务上的表现,并提供更好的人类可解释性和可信度。
示例分析
首先我们提出一个问题是:
Who is Olivia Wilde's boyfriend? What is his current age raised to the 0.23 power?
对于这个问题,需要从互联网搜索 Olivia Wilde's 的男朋友是谁,并且找到他的年龄。然后利用计算器算出 age ** 0.23 。最终给出结果。
我们来分析一下 Chat MRKL Agent 如何引导 LLM 思考和行动:
首先预设了一个 Prompt ,要求 LLM 可以使用指定工具(搜索和计算器)、按照 ReAct 框架(Thought、Action、Observation 的结构)引导 LLM 思考并行动并且按照指定格式输出结果。
为了方便测试,搜索工具 mock 实现:
scala
class Search extends Tool {
// mock 搜索 API
name = "search";
description =
"a search engine. useful for when you need to answer questions about current events. input should be a search query.";
call(question = '') {
// 带有关键词 age 时返回 27
if(question.includes('age')) return Promise.resolve(27)
// 带有关键词 boyfriend 时返回 Hello Kitty
if(question.includes('boyfriend')) return Promise.resolve('Hello Kitty')
// 其他情况不返回任何内容
}
Prompt:
vbnet
Answer the following questions as best you can. You have access to the following tools:
search: a search engine. useful for when you need to answer questions about current events. input should be a search query.
calculator: Useful for getting the result of a math expression. The input to this tool should be a valid mathematical expression that could be executed by a simple calculator.
The way you use the tools is by specifying a json blob, denoted below by $JSON_BLOB
Specifically, this $JSON_BLOB should have a "action" key (with the name of the tool to use) and a "action_input" key (with the input to the tool going here).
The $JSON_BLOB should only contain a SINGLE action, do NOT return a list of multiple actions. Here is an example of a valid $JSON_BLOB:
```
{
"action": "calculator",
"action_input": "1 + 2"
}
```
ALWAYS use the following format:
Question: the input question you must answer
Thought: you should always think about what to do
Action:
```
$JSON_BLOB
```
Observation: the result of the action
... (this Thought/Action/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question
Begin! Reminder to always use the exact characters `Final Answer` when responding.
Who is Olivia Wilde's boyfriend? What is his current age raised to the 0.23 power?
LLM 回答,表示需要调用 search 工具,输入是 Olivia Wilde boyfriend
vbnet
Thought: I need to use a search engine to find Olivia Wilde's boyfriend and then use a calculator to raise his age to the 0.23 power.
Action:
```
{
"action": "search",
"action_input": "Olivia Wilde boyfriend"
}
LangChains 代码调用搜索工具,得到结果是 Hello Kitty,带着历史记录继续向 LLM 提问
makefile
...(省略历史记录)
Action:
```
{
"action": "search",
"action_input": "Olivia Wilde boyfriend"
}
```
Observation: Hello Kitty
Thought:
LLM 回答, 表示答案中没有自己想要的年龄信息,要求继续用关键词 Olivia Wilde partner 调用 search 工具。
sql
Thought: The search result did not provide the information I needed. I need to refine my search query.
Action:
```
{
"action": "search",
"action_input": "Olivia Wilde partner"
}
LangChains 代码调用搜索工具,未得到任何结果,带着历史记录继续向 LLM 提问
makefile
...(省略历史记录)
Action:
```
{
"action": "search",
"action_input": "Olivia Wilde boyfriend"
}
```
Observation: undefined
Thought:
LLM 回答, 表示答案中没有自己的信息,要求进一步关键词 Olivia Wilde boyfriend age 调用 search 工具。
vbnet
Thought: I need to try a different approach. Maybe I can find Olivia Wilde's boyfriend's age and then use a calculator to raise it to the 0.23 power.
Action:
```
{
"action": "search",
"action_input": "Olivia Wilde boyfriend age"
}
LangChains 调用是search 工具,得到结果 27,带着历史记录继续向 LLM 提问
makefile
...(省略历史记录)
Action:
```
{
"action": "search",
"action_input": "Olivia Wilde boyfriend age"
}
```
Observation: 27
Thought:
LLM 回答, 表示自己已经得到了 Olivia Wilde's boyfriend's age 的年龄,下一步需要调用 calculator 工具 ,输入是 27^(0.23)
css
Now that I have found Olivia Wilde's boyfriend's age, I can use a calculator to raise it to the 0.23 power.
Action:
```
{
"action": "calculator",
"action_input": "27^(0.23)"
}
```
LangChains 代码调用 calculator 工具,得到结果 2.1340945944237553,带着历史记录继续向 LLM 提问
makefile
...(省略历史记录)
Action:
```
{
"action": "calculator",
"action_input": "27^(0.23)"
}
```
Observation: 2.1340945944237553
Thought:
LLM 回答, 表示自己已经得到了最终答案 2.1340945944237553
css
I have successfully calculated the answer to the question.
Final Answer: 2.1340945944237553
Langchainjs Agent 源码:
langchainjs/langchain/src/agents at main · hwchase17/langchainjs · GitHub
应用展望
Agent 赋予了 LLM 使用外部工具的能力,有很广阔的应用空间。与搜索引擎结合,让 LLM 能实时的从互联网上获取信息;与浏览器工具结合,让 LLM 能够使用浏览器,做一些自动化的操作;与文件系统结合,让 LLM 能与本地文件系统交互;甚至可以实现一个查询你个人信息的 Tool,与 LLM 结合就可以制作一个属于你自己的 J.A.R.V.I.S.。
参考文档:
Jurassic-X: Crossing the neuro-symbolic chasm with the MRKL system