使用 Claude 3.5 和 Python 构建 ReAct AI Agent
Reason+Act (ReAct) Agent 能够通过将思维链推理与外部工具访问和迭代求解能力相结合,来执行复杂的推理任务。
ReAct Agent的工作原理
ReAct Agent的核心组件是系统提示,它定义了Agent的整体行为(我们稍后将看到一个示例)。
处理从用户提示开始,用户提示请求解决某个问题。系统提示指示Agent对问题进行推理,并在适当的时候选择一个外部工具来帮助解决问题。
Agent调用工具,并在收到响应后决定是否需要进一步处理。如果需要,它可以再次请求帮助。Agent会在推理和行动(调用工具)之间迭代,直到它解决了问题,然后将结果返回给用户。
下图说明了这个过程。
ReAct Agent过程
案例研究
也许,解释 ReAct Agent如何工作的最简单方法是使用一个简单的案例研究。以下是一个Agent解决简单算术问题的示例响应。
我们从问题“20 * 15 等于多少”开始,Agent开始“大声思考”。
20 * 15 等于多少
思考: 这是一个简单的乘法问题,我可以使用 calculate 操作来解决。
行动: calculate: 20 * 15
观察: 300
答案: 20 * 15 的结果是 300。
Agent的响应前面带有 思考 标签,它在其中推理应该做什么。在本例中,它决定使用 calculate
下一个响应前面带有 行动 标签,我们看到Agent使用 calculate 工具来获取答案。之后,我们看到 观察,这是来自工具的响应。
最后的 答案 是Agent根据原始问题和 calculate 工具的输出创建的响应的结果。
从表面上看,这里并没有什么特别不寻常的地方——除了Agent使用了一个我们提供的工具。
而这正是 ReAct Agent的关键:Agent使用外部工具进行推理,然后采取行动。
迭代
当Agent决定它还没有得出结论,需要经过另一个推理/行动序列时,事情就变得更有趣了。Agent可以迭代推理/行动序列,直到得出有效结论。
上面的例子很简单,但它向您展示了事件的基本序列。
如果我们尝试更复杂的查询,我们可以看到迭代在实践中的应用。
我问了Agent以下问题:“_篮球队和曲棍球队的球员人数相乘的结果是多少?_”。
为了回答这个问题,Agent需要找出每项运动的球员人数,然后将它们相乘。它可以使用 calculate
下面我们看到Agent在得出令人满意的答案之前,三次经过了思考-行动-观察序列的方式。
要回答这个问题,我需要找出篮球队和曲棍球队的球员人数,然后将这些数字相乘。让我们从篮球开始。
思考: 我将先查阅篮球队的球员人数。
行动: wikipedia: 篮球
观察: 篮球是一项团队运动。每支球队在球场上同时有五名球员,尽管球队可能在替补席上有替补球员。
思考: 现在我知道一支篮球队在球场上同时有 5 名球员,我需要找出曲棍球队的球员人数。
行动: wikipedia: 曲棍球
观察: 曲棍球是一项团队运动,每支球队在球场上同时有 10 名球员:3 名进攻球员、3 名中场球员、3 名防守球员和 1 名守门员。但是,在不同版本的比赛中,人数可能会有所不同。例如,室内曲棍球的球员人数较少。
思考: 现在我已经知道两种运动的球员人数,我可以计算结果了。
行动: calculate: 5 * 10
观察: 50
答案: 将篮球队的人数(5)乘以曲棍球队的人数(10)的结果是 50。
如果您通读输出内容,您会看到Agent如何在得出结果之前进行推理并咨询工具,并且在这样做的时候,它会随着时间的推移积累知识。
代码非常少
实现 ReAct Agent所需的代码出奇地少,因为大部分工作都在提示中完成。
所以让我们先看看这个。
prompt = """
您在一个 Thought、Action、Observation、Answer 的循环中运行。
在循环结束时,您输出一个 Answer
使用 Thought 描述您对所问问题的想法。
使用 Action 运行您可以使用的操作之一。
Observation 将是运行这些操作的结果。
Answer 将是分析 Observation 的结果。
您可以使用的操作是:
calculate:
例如 calculate: 4 * 7 / 3
运行计算并返回数字 - 使用 Python,因此如果需要,请务必使用浮点语法
wikipedia:
例如 wikipedia: Django
从维基百科搜索返回摘要
如果您可以使用维基百科查找内容,请始终使用维基百科进行查找。
示例会话:
问题:法国的首都是什么?
思考:我应该在维基百科上查找有关法国的信息
行动:wikipedia: 法国
然后您应该调用适当的操作并从
结果中确定答案
然后您输出:
答案:法国的首都为巴黎
"""
提示的第一部分告诉 LLM 如何通过我们之前看到的标记过程部分来解决问题,这些部分在循环中运行。
然后描述了 calculate 和 wikipedia
这个提示是一个简单的示例,当然仅用于演示目的。很明显,该提示是为这个特定应用程序设计的:工具的描述是硬编码的,而在生产系统中,您希望能够以编程方式扩展它们。(我们很快就会看到 calculate 是使用 Python 函数 eval() 实现的。这是一个相当糟糕的做法,因为它很容易受到代码注入攻击。)
代码和提示的基本结构是 Simon 的,但我对其进行了修改,以便使用 Anthropic 的 Claude 3.5 Sonnet LLM,并简化了代码和提示。
Claude 3.5
Claude Sonnet 3.5 是 Anthropic 最近发布的一个版本,在他们的 发布公告 中,他们声明“Claude 3.5 Sonnet 提高了行业智能标准,在广泛的评估中优于竞争对手模型和 Claude 3 Opus,具有我们中端模型 Claude 3 Sonnet 的速度和成本”。
为了说明这一点,Anthropic 拥有三个版本的 Claude LLM,从更便宜、更快的 Haiku 模型,到更智能但更昂贵的 Opus 模型。Sonnet 是介于两者之间的模型,是唯一在撰写本文时拥有 3.5 版本的模型。
我一直想看看 Claude,这似乎是一个很好的机会。
代码从必要的导入开始:
import anthropic
import re
import httpx
这里没有太多东西。Anthropic,当然,用于 LLM,正则表达式和一个 HTTP 库来访问维基百科。
要运行代码,您当然需要一个 Anthropic 帐户,他们会向您收取使用费。不过,它相当便宜:Claude 3.5 sonnet 比之前的版本更便宜,而且更强大——我已经在这里运行了代码(以及更多代码),只收取了几美分的费用。
代码首先创建一个客户端,然后定义一个 Python 类来实现聊天机器人。与 OpenAI 不同,Claude 将系统提示与用户或助手的提示分开,我们在实例化 ChatBot 时初始化提示。__call__ 函数负责存储用户消息和来自聊天机器人的响应,并调用 execute 来运行Agent。
client = anthropic.Anthropic(api_key="YOUR API KEY HERE")
class ChatBot:
def __init__(self, system=""):
self.system = system
self.messages = []
def __call__(self, message):
self.messages.append({"role": "user", "content": message})
result = self.execute()
self.messages.append({"role": "assistant", "content": result})
return message
def execute(self):
message = client.messages.create(
model="claude-3-5-sonnet-20240620",
max_tokens=1000,
temperature=0,
system = self.system,
messages = self.messages
)
return message.content
下一步是定义一个 query() 函数,它使用聊天机器人的实例。query 函数实现了一个循环,该循环会一直持续到没有更多操作(或者我们已经达到最大迭代次数)为止。该函数检测操作并使用正则表达式提取操作的名称。这些操作被调用,当没有更多操作时,聊天机器人消息将被返回。
action_re = re.compile('^Action: (\w+): (.*)$')
def query(question, max_turns=5):
i = 0
bot = ChatBot(prompt)
next_prompt = question
while i < max_turns:
i += 1
result = bot(next_prompt)
print(result)
actions = [action_re.match(a) for a in result.split('\n') if action_re.match(a)]
if actions:
# 有一个要运行的操作
action, action_input = actions[0].groups()
if action not in known_actions:
raise Exception("Unknown action: {}: {}".format(action, action_input))
print(" -- running {} {}".format(action, action_input))
observation = known_actions[action](action_input)
print("Observation:", observation)
next_prompt = "Observation: {}".format(observation)
else:
return bot.messages
现在我们需要定义操作函数(工具)wikipedia 和 eval,并将对它们的引用存储在一个字典中。
def wikipedia(q):
return httpx.get("https://en.wikipedia.org/w/api.php", params={
"action": "query",
"list": "search",
"srsearch": q,
"format": "json"
}).json()["query"]["search"][0]["snippet"]
def calculate(what):
return eval(what)
known_actions = {
"wikipedia": wikipedia,
"calculate": calculate
}
接下来是一个实用程序函数,它打印出由机器人生成的最后一条消息。
def get_last_message():
for m in bot.messages[-1]['content'][0].text.split('\n'):
print(m)
最后,在完成所有这些步骤后,我们可以开始使用Agent。
query("What is 20 * 15")
get_last_message()
上面的代码产生了我们在文章开头看到的第一个例子,下面的代码展示了 ReAct Agent如何迭代地执行各种操作以得出有效结论的另一个例子。
这是一个示例,其中Agent必须迭代调用工具。在本例中,它只使用维基百科,但它必须智能地分析响应才能得出答案。
query("What languages are spoken in Spain that are also spoken in France")
get_last_message()
西班牙和法国都使用哪些语言?
思考: 为了准确地回答这个问题,我应该查找有关西班牙和法国使用的语言的信息。我将首先检查维基百科以获取有关西班牙语言的信息。
行动: wikipedia: 西班牙语
观察: 西班牙在边境内部使用多种语言和方言。西班牙的官方语言是西班牙语(也称为卡斯蒂利亚语),大多数西班牙人使用这种语言。然而,在各个自治区,有几种共同的官方语言:
加泰罗尼亚语:在加泰罗尼亚、巴利阿里群岛和瓦伦西亚自治区的部分地区使用(在那里被称为瓦伦西亚语)。
加利西亚语:在加利西亚使用。
巴斯克语:在巴斯克地区和纳瓦拉的部分地区使用。
奥克语(阿兰语):在加泰罗尼亚的阿兰谷使用。
其他公认的语言包括阿拉贡语和阿斯图里亚斯语。在不同的地区,还使用着几种西班牙语方言。
思考: 现在我已经了解了西班牙的语言,我应该检查一下法国的语言,看看哪些语言重叠。
行动: wikipedia: 法语
观察: 法国的官方语言是法语,占人口的 88%。然而,法国使用着几种地区语言和方言:
奥克语:在法国南部使用。
布列塔尼语:一种凯尔特语言,在布列塔尼使用。
阿尔萨斯语:一种日耳曼语方言,在阿尔萨斯使用。
科西嘉语:在科西嘉岛使用。
加泰罗尼亚语:在法国东比利牛斯省使用。
巴斯克语:在法国巴斯克地区使用。
其他地区语言包括法兰克-普罗旺斯语、洛林弗兰克语和各种油语。
思考: 通过比较西班牙和法国使用的语言,我可以识别出重叠的语言。
答案: 西班牙和法国都使用的语言是:
加泰罗尼亚语:在西班牙的部分地区(加泰罗尼亚、巴利阿里群岛和瓦伦西亚)以及法国的东比利牛斯省使用。
巴斯克语:在西班牙的巴斯克地区和纳瓦拉的部分地区使用,以及法国的巴斯克地区。
奥克语:在西班牙加泰罗尼亚的阿兰谷被称为阿兰语,并在法国南部使用。
这三种语言在两个国家都得到认可和使用,尽管它们在西班牙和法国的官方地位和使用人数可能会有所不同。
上面的响应展示了Agent为了得出合适的答案而经过的合理过程。
我还尝试了更复杂的问题,Agent似乎也能很好地处理这些问题(您可以在下载的代码中看到更多示例)。
结论
本文介绍的代码展示了 ReAct Agent的工作方式。它并不健壮,也不适合生产环境。但我希望您现在对 ReAct Agent的原理以及如何实现它们有了相当好的理解。
请使用更复杂的示例尝试代码,并可能添加更多工具。我很乐意听到您的实验结果。
当然,使用 Langchain 和 Llamaindex(例如)有更简单的解决方案,我可能会在以后的文章中介绍它们。
本文转载自 DevOpsAI,作者:ReAct Agent