Claude code 课程:工具使用-2.你的第一个简单工具

文章翻译自官网github.com/anthropics/...

你的第一个简单工具

在上一课中,我们介绍了工具使用的工作流程。现在是时候实际动手实现一个简单的工具使用示例了。回顾一下,工具使用过程最多有 4 个步骤:

向 Claude 提供工具和用户提示:(API 请求)

  • 定义你希望 Claude 能够使用的工具集,包括它们的名称、描述和输入模式。

  • 提供一个可能需要使用这些工具中的一个或多个来回答的用户提示。

Claude 使用工具:(API 响应)

  • Claude 评估用户提示,并决定是否有任何可用工具能帮助解决用户的查询或任务。如果有,它还会决定使用哪些工具以及使用什么输入。

  • Claude 输出格式正确的工具使用请求。

  • API 响应的 stop_reason 将为 tool_use,表明 Claude 想要使用外部工具。

提取工具输入、运行代码并返回结果:(API 请求)

  • 在客户端,你需要从 Claude 的工具使用请求中提取工具名称和输入。

  • 在客户端运行实际的工具代码。

  • 通过在新的用户消息中包含 tool_result 内容块来继续对话,从而将结果返回给 Claude。

Claude 使用工具结果来制定响应:(API 响应)

  • 收到工具结果后,Claude 将使用该信息来制定对原始用户提示的最终响应。

我们将从一个简单的演示开始,只需要与 Claude "交谈" 一次(别担心,我们很快就会看到更有趣的例子!)。这意味着我们暂时不会涉及步骤 4。我们会让 Claude 回答一个问题,Claude 会请求使用工具来回答,然后我们提取工具输入、运行代码并返回结果值。

如今的大型语言模型在数学运算方面表现不佳,如下列代码所示。

我们让 Claude "计算 1984135 乘以 9343116":

python

运行

ini 复制代码
from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv()

client = Anthropic()

# 一个相对简单的数学问题
response = client.messages.create(
    model="claude-3-haiku-20240307",
    messages=[{"role": "user", "content":"Multiply 1984135 by 9343116. Only respond with the result"}],
    max_tokens=400
)
print(response.content[0].text)
18555375560

多次运行上述代码可能会得到不同的答案,但这是 Claude 给出的一个答案:

18593367726060

实际正确答案是:

18538003464660

Claude 的答案略有偏差,相差了 55364261400!

工具使用来解围!

Claude 不擅长进行复杂的数学运算,所以让我们通过提供计算器工具来增强 Claude 的能力。

下面是一个解释该过程的简单图表:

chickens_calculator.png

第一步是定义实际的计算器函数,并确保它能独立于 Claude 运行。我们将编写一个非常简单的函数,它需要三个参数:

  • 操作,如 "加" 或 "乘"

  • 两个操作数

以下是基本实现:

python

运行

python 复制代码
def calculator(operation, operand1, operand2):
    if operation == "add":
        return operand1 + operand2
    elif operation == "subtract":
        return operand1 - operand2
    elif operation == "multiply":
        return operand1 * operand2
    elif operation == "divide":
        if operand2 == 0:
            raise ValueError("Cannot divide by zero.")
        return operand1 / operand2
    else:
        raise ValueError(f"Unsupported operation: {operation}")

请注意,这个简单的函数用途非常有限,因为它只能处理像 234 + 213 或 3 * 9 这样的简单表达式。这里的重点是通过一个非常简单的教学示例来了解工具的使用过程。

让我们测试一下我们的函数,确保它能正常工作。

python

运行

scss 复制代码
calculator("add", 10, 3)
13
calculator("divide", 200, 25)
8.0

下一步是定义我们的工具并告诉 Claude 关于它的信息。定义工具时,我们要遵循非常特定的格式。每个工具定义包括:

  • name:工具的名称。必须匹配正则表达式 ^[a-zA-Z0-9_-]{1,64}$。

  • description:关于工具的功能、使用场景和行为的详细纯文本描述。

  • input_schema:一个 JSON Schema 对象,定义了工具的预期参数。

不熟悉 JSON Schema?在这里了解更多

以下是一个假设工具的简单示例:

json

css 复制代码
{
  "name": "send_email",
  "description": "Sends an email to the specified recipient with the given subject and body.",
  "input_schema": {
    "type": "object",
    "properties": {
      "to": {
        "type": "string",
        "description": "The email address of the recipient"
      },
      "subject": {
        "type": "string",
        "description": "The subject line of the email"
      },
      "body": {
        "type": "string",
        "description": "The content of the email message"
      }
    },
    "required": ["to", "subject", "body"]
  }
}

这个名为 send_email 的工具需要以下输入:

  • to:字符串类型,必填

  • subject:字符串类型,必填

  • body:字符串类型,必填

下面是另一个名为 search_product 的工具定义:

json

json 复制代码
{
  "name": "search_product",
  "description": "Search for a product by name or keyword and return its current price and availability.",
  "input_schema": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "The product name or search keyword, e.g. 'iPhone 13 Pro' or 'wireless headphones'"
      },
      "category": {
        "type": "string",
        "enum": ["electronics", "clothing", "home", "toys", "sports"],
        "description": "The product category to narrow down the search results"
      },
      "max_price": {
        "type": "number",
        "description": "The maximum price of the product, used to filter the search results"
      }
    },
    "required": ["query"]
  }
}

这个工具有 3 个输入:

  • 必需的 query 字符串,表示产品名称或搜索关键词

  • 可选的 category 字符串,必须是预定义的值之一,用于缩小搜索范围。注意定义中的 "enum"。

  • 可选的 max_price 数字,用于筛选低于特定价格点的结果

我们的计算器工具定义

让我们为我们前面编写的计算器函数定义相应的工具。我们知道计算器函数有 3 个必需的参数:

  • operation - 只能是 "add"、"subtract"、"multiply" 或 "divide"

  • operand1 - 应该是一个数字

  • operand2 - 也应该是一个数字

以下是工具定义:

python

运行

makefile 复制代码
calculator_tool = {
    "name": "calculator",
    "description": "A simple calculator that performs basic arithmetic operations.",
    "input_schema": {
        "type": "object",
        "properties": {
            "operation": {
                "type": "string",
                "enum": ["add", "subtract", "multiply", "divide"],
                "description": "The arithmetic operation to perform."
            },
            "operand1": {
                "type": "number",
                "description": "The first operand."
            },
            "operand2": {
                "type": "number",
                "description": "The second operand."
            }
        },
        "required": ["operation", "operand1", "operand2"]
    }
}

练习

让我们以以下函数为例,练习编写格式正确的工具定义:

python

运行

python 复制代码
def inventory_lookup(product_name, max_results):
    return "this function doesn't do anything"
    # 你不需要修改这个函数或对它做任何操作!

这个假设的 inventory_lookup 函数应该像这样调用:

python

运行

scss 复制代码
inventory_lookup("AA batteries", 4)

inventory_lookup("birthday candle", 10)

你的任务是编写相应的、格式正确的工具定义。假设在你的工具定义中,这两个参数都是必需的。

向 Claude 提供我们的工具

现在回到我们之前的计算器函数。在这一点上,Claude 对计算器工具一无所知!它只是一个小小的 Python 字典。当我们向 Claude 发出请求时,我们可以传递一个工具列表来 "告知" Claude。现在让我们尝试一下:

python

运行

ini 复制代码
response = client.messages.create(
    model="claude-3-haiku-20240307",
    messages=[{"role": "user", "content": "计算1984135乘以9343116。只返回结果"}],
    max_tokens=300,
    # 告诉Claude我们的工具
    tools=[calculator_tool]
)

接下来,让我们看看 Claude 给我们的响应:

plaintext

python 复制代码
response
ToolsBetaMessage(id='msg_01UfKwdmEsgTh99wfpgW4NJ7', content=[ToolUseBlock(id='toolu_015wQ7Wipo589yT9B3YTwjF1', input={'operand1': 1984135, 'operand2': 9343116, 'operation': 'multiply'}, name='calculator', type='tool_use')], model='claude-3-haiku-20240307', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=Usage(input_tokens=420, output_tokens=93))
ToolsBetaMessage(id='msg_01UfKwdmEsgTh99wfpgW4NJ7', content=[ToolUseBlock(id='toolu_015wQ7Wipo589yT9B3YTwjF1', input={'operand1': 1984135, 'operand2': 9343116, 'operation': 'multiply'}, name='calculator', type='tool_use')], model='claude-3-haiku-20240307', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=Usage(input_tokens=420, output_tokens=93))

你可能会注意到我们的响应看起来和平时有点不同!具体来说,我们现在得到的不是一个普通的 Message,而是一个 ToolsMessage。

此外,我们可以查看 response.stop_reason,会发现 Claude 停止是因为它决定该使用工具了:

python

运行

arduino 复制代码
response.stop_reason
'tool_use'

response.content 包含一个列表,其中有一个 ToolUseBlock,它本身包含了工具名称和输入的信息:

python

运行

python 复制代码
response.content
[ToolUseBlock(id='toolu_015wQ7Wipo589yT9B3YTwjF1', input={'operand1': 1984135, 'operand2': 9343116, 'operation': 'multiply'}, name='calculator', type='tool_use')]

python

运行

lua 复制代码
tool_name = response.content[0].name
tool_inputs = response.content[0].input

print("Claude想要调用的工具名称:", tool_name)
print("Claude想要传入的参数:", tool_inputs)
Claude想要调用的工具名称: calculator
Claude想要传入的参数: {'operand1': 1984135, 'operand2': 9343116, 'operation': 'multiply'}

下一步就是简单地获取 Claude 提供给我们的工具名称和输入,并用它们来实际调用我们之前编写的计算器函数。然后我们就会得到最终答案!

python

运行

ini 复制代码
operation = tool_inputs["operation"]
operand1 = tool_inputs["operand1"]
operand2 = tool_inputs["operand2"]

result = calculator(operation, operand1, operand2)
print("结果是", result)
结果是 18538003464660

我们得到了正确答案 18538003464660!!!我们没有依赖 Claude 来正确完成数学运算,而是简单地向 Claude 提出问题,并在必要时让它使用我们提供的工具。

重要提示

如果我们问 Claude 一些不需要使用工具的问题,在这种情况下,比如与数学或计算无关的内容,我们可能希望它正常回应。Claude 通常会这样做,但有时它会非常渴望使用工具!

举个例子,有时 Claude 会尝试使用计算器,即使使用它毫无意义。让我们看看当我们问 Claude"祖母绿是什么颜色的?" 时会发生什么:

python

运行

ini 复制代码
response = client.messages.create(
    model="claude-3-haiku-20240307",
    messages=[{"role": "user", "content":"祖母绿是什么颜色的?"}],
    max_tokens=400,
    tools=[calculator_tool]
)
response
ToolsBetaMessage(id='msg_01Dj82HdyrxGJpi8XVtqEYvs', content=[ToolUseBlock(id='toolu_01Xo7x3dV1FVoBSGntHNAX4Q', input={'operand1': 0, 'operand2': 0, 'operation': 'add'}, name='calculator', type='tool_use')], model='claude-3-haiku-20240307', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=Usage(input_tokens=409, output_tokens=89))

Claude 给了我们这样的回应:

plaintext

python 复制代码
ToolsBetaMessage(id='msg_01Dj82HdyrxGJpi8XVtqEYvs', content=[ToolUseBlock(id='toolu_01Xo7x3dV1FVoBSGntHNAX4Q', input={'operand1': 0, 'operand2': 0, 'operation': 'add'}, name='calculator', type='tool_use')], model='claude-3-haiku-20240307', role='assistant', stop_reason='tool_use', stop_sequence=None, type='message', usage=Usage(input_tokens=409, output_tokens=89))

Claude 想让我们调用计算器工具?一个非常简单的解决方法是调整我们的提示或添加一个系统提示,内容大致如下:你可以使用工具,但只在必要时使用。如果不需要工具,就正常回应:

python

运行

ini 复制代码
response = client.messages.create(
    model="claude-3-haiku-20240307",
    system="你可以使用工具,但只在必要时使用。如果不需要工具,就正常回应",
    messages=[{"role": "user", "content":"祖母绿是什么颜色的?"}],
    max_tokens=400,
    tools=[calculator_tool]
)
response
ToolsBetaMessage(id='msg_01YRRfnUUhP1u5ojr9iWZGGu', content=[TextBlock(text='Emeralds are green in color.', type='text')], model='claude-3-haiku-20240307', role='assistant', stop_reason='end_turn', stop_sequence=None, type='message', usage=Usage(input_tokens=434, output_tokens=12))

现在 Claude 会给出恰当的回应,不会在没有意义的情况下强行使用工具。我们得到的新回应是:

plaintext

arduino 复制代码
'祖母绿是绿色的。'

我们还可以看到,stop_reason 现在是 end_turn 而不是 tool_use。

python

运行

arduino 复制代码
response.stop_reason
'end_turn'

整合在一起

python

运行

python 复制代码
def calculator(operation, operand1, operand2):
    if operation == "add":
        return operand1 + operand2
    elif operation == "subtract":
        return operand1 - operand2
    elif operation == "multiply":
        return operand1 * operand2
    elif operation == "divide":
        if operand2 == 0:
            raise ValueError("不能除以零。")
        return operand1 / operand2
    else:
        raise ValueError(f"不支持的操作:{operation}")


calculator_tool = {
    "name": "calculator",
    "description": "一个简单的计算器,可执行基本的算术运算。",
    "input_schema": {
        "type": "object",
        "properties": {
            "operation": {
                "type": "string",
                "enum": ["add", "subtract", "multiply", "divide"],
                "description": "要执行的算术运算。",
            },
            "operand1": {"type": "number", "description": "第一个操作数。"},
            "operand2": {"type": "number", "description": "第二个操作数。"},
        },
        "required": ["operation", "operand1", "operand2"],
    },
}


def prompt_claude(prompt):
    messages = [{"role": "user", "content": prompt}]
    response = client.messages.create(
        model="claude-3-haiku-20240307",
        system="你可以使用工具,但只在必要时使用。如果不需要工具,就正常回应",
        messages=messages,
        max_tokens=500,
        tools=[calculator_tool],
    )

    if response.stop_reason == "tool_use":
        tool_use = response.content[-1]
        tool_name = tool_use.name
        tool_input = tool_use.input

        if tool_name == "calculator":
            print("Claude想要使用计算器工具")
            operation = tool_input["operation"]
            operand1 = tool_input["operand1"]
            operand2 = tool_input["operand2"]

            try:
                result = calculator(operation, operand1, operand2)
                print("计算结果是:", result)
            except ValueError as e:
                print(f"错误:{str(e)}")

    elif response.stop_reason == "end_turn":
        print("Claude不想使用工具")
        print("Claude的回应是:")
        print(response.content[0].text)

python

运行

scss 复制代码
prompt_claude("我有23只鸡,但有2只飞走了。还剩下多少只?")
Claude想要使用计算器工具
计算结果是: 21

python

运行

scss 复制代码
prompt_claude("201乘以2是多少")
Claude想要使用计算器工具
计算结果是: 402

python

运行

scss 复制代码
prompt_claude("给我写一首关于海洋的俳句")
Claude不想使用工具
Claude的回应是:
这是一首关于海洋的俳句:

浩瀚蓝舒展,
浪涛拍岸沙有声,
海洋抚慰歌。

练习

你的任务是帮助构建一个使用 Claude 的研究助手。用户可以输入他们想要研究的主题,并将一系列维基百科文章链接保存到一个 markdown 文件中,供以后阅读。我们可以尝试直接让 Claude 生成文章 URL 列表,但 Claude 在处理 URL 方面并不可靠,可能会虚构文章 URL。此外,在 Claude 的训练截止日期之后,合法的文章可能已经迁移到了新的 URL。相反,我们将使用一个连接到真实维基百科 API 的工具来实现这一点!

我们会为 Claude 提供一个工具,该工具接受 Claude 生成的一系列可能的维基百科文章标题(但这些标题可能是虚构的)。我们可以使用这个工具搜索维基百科,找到实际的维基百科文章标题和 URL,以确保最终列表中的所有文章都是真实存在的。然后,我们会将这些文章 URL 保存到一个 markdown 文件中,供以后阅读。

我们为你提供了两个辅助函数:

python

运行

python 复制代码
import wikipedia
def generate_wikipedia_reading_list(research_topic, article_titles):
    wikipedia_articles = []
    for t in article_titles:
        results = wikipedia.search(t)
        try:
            page = wikipedia.page(results[0])
            title = page.title
            url = page.url
            wikipedia_articles.append({"title": title, "url": url})
        except:
            continue
    add_to_research_reading_file(wikipedia_articles, research_topic)

def add_to_research_reading_file(articles, topic):
    with open("output/research_reading.md", "a", encoding="utf-8") as file:
        file.write(f"## {topic} \n")
        for article in articles:
            title = article["title"]
            url = article["url"]
            file.write(f"* [{title}]({url}) \n")
        file.write(f"\n\n")

第一个函数 generate_wikipedia_reading_list 需要传入一个研究主题,比如 "夏威夷的历史" 或 "世界各地的海盗",以及一个我们让 Claude 生成的潜在维基百科文章名称列表。该函数使用 wikipedia 包搜索相应的真实维基百科页面,并构建一个包含文章标题和 URL 的字典列表。

然后它调用 add_to_research_reading_file,传入维基百科文章数据列表和整体研究主题。这个函数只是将每个维基百科文章的 markdown 链接添加到一个名为 output/research_reading.md 的文件中。文件名目前是硬编码的,并且该函数假设该文件存在。在这个仓库中它是存在的,但如果你在其他地方工作,你需要自己创建它。

思路是让 Claude "调用" generate_wikipedia_reading_list,传入一系列可能真实也可能不真实的文章标题列表。Claude 可能会传入以下文章标题列表,其中一些是真实的维基百科文章,一些则不是:

plaintext

css 复制代码
["海盗行为", "著名海盗船", "海盗的黄金时代", "海盗列表", "海盗与鹦鹉", "21世纪的海盗行为"]

generate_wikipedia_reading_list 函数会遍历这些文章标题,为任何实际存在的维基百科文章收集真实的文章标题和相应的 URL。然后它调用 add_to_research_reading_file,将内容写入 markdown 文件,供以后参考。

最终目标

你的任务是实现一个名为 get_research_help 的函数,它接受一个研究主题和所需的文章数量。这个函数应该使用 Claude 来实际生成可能的维基百科文章列表,并调用上面的 generate_wikipedia_reading_list 函数。以下是一些函数调用示例:

python

运行

scss 复制代码
get_research_help("世界各地的海盗", 7)

get_research_help("夏威夷历史", 3)

get_research_help("动物有意识吗?", 3)

在这 3 次函数调用之后,我们的输出文件 research_reading.md 会是这个样子(你可以在 output/research_reading.md 中自行查看):

research_reading.png

要完成这个任务,你需要做以下事情:

为 generate_wikipedia_reading_list 函数编写工具定义

实现 get_research_help 函数

向 Claude 编写一个提示,告诉它你需要帮助收集关于特定主题的研究,以及你希望它生成多少个文章标题

告诉 Claude 它可以使用的工具

发送你的请求给 Claude

检查 Claude 是否调用了工具。如果调用了,你需要将它生成的文章标题和主题传递给我们提供的 generate_wikipedia_reading_list 函数。该函数将收集实际的维基百科文章链接,然后调用 add_to_research_reading_file 将链接写入 output/research_reading.md

打开 output/research_reading.md 看看是否成功!

starter 代码

python

运行

python 复制代码
# 这是你的 starter 代码!
import wikipedia
def generate_wikipedia_reading_list(research_topic, article_titles):
    wikipedia_articles = []
    for t in article_titles:
        results = wikipedia.search(t)
        try:
            page = wikipedia.page(results[0])
            title = page.title
            url = page.url
            wikipedia_articles.append({"title": title, "url": url})
        except:
            continue
    add_to_research_reading_file(wikipedia_articles, research_topic)

def add_to_research_reading_file(articles, topic):
    with open("output/research_reading.md", "a", encoding="utf-8") as file:
        file.write(f"## {topic} \n")
        for article in articles:
            title = article["title"]
            url = article["url"]
            file.write(f"* [{title}]({url}) \n")
        file.write(f"\n\n")
        
def get_research_help(topic, num_articles=3):
   # 实现这个函数!
   pass

写在最后

我正在使用一个非常好用的Claude code 镜像站

「AICODEMIRROR」

不仅响应极速,不怕封禁,还有性价比,填写我的码:5OTTEB还有额外额度,大家一起来试试!

相关推荐
这张生成的图像能检测吗2 分钟前
(论文速读)探索多模式大型语言模型的视觉缺陷
人工智能·深度学习·算法·计算机视觉·语言模型·自然语言处理
এ᭄画画的北北13 分钟前
力扣-49.字母异位词分组
算法·leetcode
设计师小聂!13 分钟前
力扣热题100-------169.多数元素
java·数据结构·算法·leetcode·多数元素
18 分钟前
LeetCode Hot 100 买卖股票的最佳时机
算法·leetcode·职场和发展
海绵波波10735 分钟前
基于OpenCV的cv2.solvePnP方法实现头部姿态估计
人工智能·opencv·算法
DolphinDB1 小时前
基于 CEP 引擎的算法拆单与调度实践—基础篇
算法
啊阿狸不会拉杆1 小时前
《算法导论》第 3 章 - 函数的增长
开发语言·数据结构·c++·算法
蒟蒻小袁1 小时前
力扣面试150题--加一
算法·leetcode·职场和发展
CODE_RabbitV2 小时前
如何让 RAG 检索更高效?——大模型召回策略全解
人工智能·算法·机器学习
阑梦清川2 小时前
leetcode上面的一道关于使用递归进行二叉树的构建问题
算法