使用 Claude 3.5 和 Python 构建 ReAct AI Agent

发布于 2024-10-12 14:16
浏览
0收藏

Reason+Act (ReAct) Agent 能够通过将思维链推理与外部工具访问和迭代求解能力相结合,来执行复杂的推理任务。

ReAct Agent的工作原理

ReAct Agent的核心组件是系统提示,它定义了Agent的整体行为(我们稍后将看到一个示例)。

处理从用户提示开始,用户提示请求解决某个问题。系统提示指示Agent对问题进行推理,并在适当的时候选择一个外部工具来帮助解决问题。

Agent调用工具,并在收到响应后决定是否需要进一步处理。如果需要,它可以再次请求帮助。Agent会在推理和行动(调用工具)之间迭代,直到它解决了问题,然后将结果返回给用户。

下图说明了这个过程。

使用 Claude 3.5 和 Python 构建 ReAct AI Agent-AI.x社区

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

收藏
回复
举报
回复
相关推荐