工具调用教程 Tool Use CC BY-NC 4.0 · Anthropic ← 首页
第一章
工具调用基础
了解什么是工具调用、它为何重要,以及工具调用的完整工作流程。

学习目标

  • 理解什么是工具调用
  • 了解工具调用的典型应用场景
  • 掌握工具调用的高层次工作流程

什么是工具调用?

工具调用(Tool Use,也称为函数调用)是指通过定义并调用外部工具或函数来扩展 Claude 能力的一种方式。我们可以为 Claude 提供一组预定义的工具,Claude 可以在任意时刻决定调用这些工具。工具允许我们编写能够执行特定任务或计算的代码——这些任务可能是 Claude 单靠自身无法完成的。简而言之:工具调用是增强 Claude 功能的重要途径。

为什么工具调用如此重要?

工具调用是一项颠覆性的功能,它释放了 Claude 的真正潜力,开启了无限可能。这是一个能够显著提升应用程序价值与影响力的核心组件。

  • 扩展 Claude 的能力——通过定义和调用外部工具,你可以让 Claude 完成原本无法做到的任务,打造更强大、更多功能的应用程序。
  • 与现有系统集成——工具调用可以将 Claude 与你现有的系统、数据库或 API 无缝对接,从而在保留既有基础设施的同时充分发挥 Claude 的价值。
  • 自动化复杂任务——通过定义封装了复杂逻辑的工具,你可以简化业务流程、减少人工操作,Claude 会在合适的时机智能调用相应工具。
  • 提升用户体验——为 Claude 配备合适的工具,使其能够提供更准确、更具上下文感知的个性化回应,用户可以用自然语言与应用程序交互。
  • 灵活扩展与定制——随着用户需求的变化,你可以随时增删工具,快速迭代,确保应用程序持续保持竞争力。

常见应用场景

以下是工具调用在各行业中的典型应用:

  • 信息检索——从外部数据源(数据库、API、Web 服务)获取数据,例如天气信息、股票行情或新闻资讯。
  • 数学计算——执行 Claude 内置能力难以胜任的复杂数学运算,如金融计算、科学计算或统计分析。
  • 数据处理——对各类格式的数据进行处理、转换或转化,包括数据格式化、提取、转换等。
  • 与外部系统交互——发送邮件、触发通知、控制 IoT 设备等。
  • 内容生成——根据用户输入或预定义模板生成图像、图表或格式化文档。

更具体的企业级应用案例:

  • 企业数据集成:将 Claude 与 CRM、ERP、ITSM 等企业系统对接,实现自动化工作流与个性化客户支持。
  • 金融分析与报告:分析财务数据、生成投资报告、评估风险、确保合规。
  • 医疗诊断辅助:集成电子病历与医疗知识库,协助医疗专业人员制定个性化诊疗方案。
  • 智能客服自动化:连接知识库与工单系统,实现自动化客户支持并缩短响应时间。
  • 软件开发辅助:集成 IDE、版本控制和项目管理工具,协助开发者编写代码、定位缺陷。

工具调用的工作原理

重要提示
Claude 本身并不会执行任何代码。我们告诉 Claude 有哪些工具可以调用,由 Claude 决定何时调用、调用参数是什么,然后由我们的代码实际运行工具逻辑,再将结果反馈给 Claude。

此外,Claude 没有任何内置的服务端工具。所有工具必须在每次 API 请求中由开发者显式提供,这意味着你需要定义工具的名称、描述和输入 Schema,并在 Claude 请求时负责实际执行工具代码。这给了你对工具能力的完全掌控权。

使用工具调用的完整流程如下:

1
提供工具与用户提示(API 请求)

定义 Claude 可访问的工具集合,包括名称、描述和输入 Schema;同时提供可能需要使用这些工具才能回答的用户提示。

2
Claude 调用工具(API 响应)

Claude 评估用户提示并决定是否使用工具。如需使用,Claude 会决定调用哪个工具及其输入参数,并输出格式正确的工具调用请求。此时 stop_reasontool_use

3
提取输入、运行代码、返回结果(API 请求)

在客户端提取 Claude 请求的工具名称与输入参数,执行实际的工具代码,然后通过包含 tool_result 内容块的新用户消息将结果返回给 Claude。

4
Claude 利用工具结果生成最终响应(API 响应)

Claude 接收到工具结果后,利用该信息构建对原始用户提示的最终回答。步骤 3 和 4 是可选的——对于某些工作流,你可能只需要工具调用的结果本身,无需将结果返回给 Claude。


案例演示:股票价格查询工具

假设我们正在构建一个允许用户向 Claude 询问股市信息的聊天应用,Claude 本身不了解实时股价,因此我们需要为其提供一个 get_stock_price 工具。

步骤 0:编写工具函数

在告知 Claude 工具的存在之前,我们首先要实现工具的实际功能:

def get_stock_price(company):
    # 向股票市场 API 发送请求,获取指定公司的实时股价
    # 返回包含当前股价信息的字典
    ...

步骤 1:定义工具并发送 API 请求

定义工具的名称、描述和输入 Schema,并将其传入 API 请求:

tool_definition = {
    "name": "get_stock_price",
    "description": "Retrieves the current stock price for a given company",
    "input_schema": {
        "type": "object",
        "properties": {
            "company": {
                "type": "string",
                "description": "The company name to fetch stock data for"
            }
        },
        "required": ["company"]
    }
}

response = client.messages.create(
    model="claude-opus-4-5",
    messages=[{"role": "user", "content": "How many shares of General Motors can I buy with $500?"}],
    max_tokens=500,
    tools=[tool_definition]
)

步骤 2:Claude 发出工具调用请求(API 响应)

Claude 判断需要使用工具,返回如下格式的响应,stop_reasontool_use

{
  "stop_reason": "tool_use",
  "tool_use": {
    "name": "get_stock_price",
    "input": {
      "company": "General Motors"
    }
  }
}

步骤 3:执行工具,返回结果

提取工具名称与输入,执行实际代码,将结果通过 tool_result 返回:

# 工具执行结果
stock_result = {"symbol": "GM", "price": 43.09}

# 将结果返回给 Claude
messages.append({
    "role": "user",
    "content": [{
        "type": "tool_result",
        "tool_use_id": response.content[0].id,
        "content": str(stock_result)
    }]
})

步骤 4:Claude 生成最终回答

以当前每股 43.09 美元的价格来看,您可以用 500 美元购买约 11 股通用汽车股票。

🧠 知识检验

问题 1:以下哪些场景适合使用工具调用?

(a) 需要根据订单 ID 从数据库检索订单信息的客服机器人
(b) 帮助用户改善语法和写作风格的写作助手
(c) 根据用户风险偏好提供个性化投资建议的金融顾问
(d) 能够根据用户偏好预订机票和酒店的旅行机器人
查看答案
a、c 和 d
场景 (a)、(c)、(d) 均涉及从外部数据源检索信息、执行计算或与外部系统交互,这些都是工具调用的典型场景。场景 (b) 通常可以由 Claude 内置的语言能力处理,无需工具调用。

问题 2:请将工具调用的步骤排列为正确顺序:

(a) Claude 利用工具结果构建对原始提示的最终回答
(b) 客户端代码提取工具名称与输入参数
(c) Claude 评估提示并决定使用工具,输出工具调用请求
(d) 客户端执行工具代码并将结果返回给 Claude
(e) 客户端代码向 Claude 提供工具列表和用户提示
查看答案
正确顺序:e → c → b → d → a
先提供工具和提示 → Claude 决定调用工具 → 提取工具输入 → 执行工具代码 → Claude 生成最终回答
第二章
构建你的第一个工具
通过一个简单的计算器示例,亲手实现工具调用的完整流程。

现在我们来动手实现一个简单的工具调用示例。当代大型语言模型在数学运算上常常力不从心,以下代码就展示了这个问题——让 Claude 计算 1984135 × 9343116:

from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv()
client = Anthropic()

response = client.messages.create(
    model="claude-haiku-4-5",
    messages=[{"role": "user", "content": "Multiply 1984135 by 9343116. Only respond with the result"}],
    max_tokens=300
)
print(response.content[0].text)

Claude 的回答很可能是错误的。解决方案是:为 Claude 提供一个计算器工具!

定义工具函数

首先编写实际的计算器函数:

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:
            return "Error: Division by zero"
        return operand1 / operand2
    else:
        return "Error: Unknown operation" 

编写工具定义

工具定义(Tool Definition)是我们向 Claude 描述工具的方式,包含名称、功能描述和输入 Schema。以下是一个完整的示例,同时也展示了其他格式参考:

一个 send_email 工具的定义示例:

{
  "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 recipient's email address"
      },
      "subject": {
        "type": "string",
        "description": "The subject line of the email"
      },
      "body": {
        "type": "string",
        "description": "The body text of the email message"
      }
    },
    "required": ["to", "subject", "body"]
  }
}

一个带有枚举值(enum)和可选参数的 search_product 工具:

{
  "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"
      },
      "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"
      }
    },
    "required": ["query"]
  }
}

现在,我们来定义计算器工具:

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 number."
            },
            "operand2": {
                "type": "number",
                "description": "The second number."
            }
        },
        "required": ["operation", "operand1", "operand2"]
    }
}

完整流程演示

将工具传入 API 请求,让 Claude 知道可以使用这个工具:

response = client.messages.create(
    model="claude-haiku-4-5",
    messages=[{"role": "user", "content": "Multiply 1984135 by 9343116. Only respond with the result"}],
    max_tokens=300,
    tools=[calculator_tool]   # 告知 Claude 工具的存在
)

print(response.stop_reason)   # tool_use

从响应中提取工具调用信息并执行:

# 提取工具名称和输入参数
tool_name = response.content[0].name
tool_inputs = response.content[0].input

print("Claude 想调用的工具:", tool_name)
print("调用参数:", tool_inputs)

# 执行工具
operation = tool_inputs["operation"]
operand1 = tool_inputs["operand1"]
operand2 = tool_inputs["operand2"]

result = calculator(operation, operand1, operand2)
print("计算结果:", result)   # 18538003464660 ✓
注意事项
如果我们问 Claude 一个不需要工具的问题(例如"翡翠是什么颜色?"),Claude 有时会过于积极地尝试使用计算器工具。一个简单的修复方法是在系统提示词中说明:You have access to tools, but only use them when necessary. If a tool is not required, respond as normal.
response = client.messages.create(
    model="claude-haiku-4-5",
    system="You have access to tools, but only use them when necessary. "
           "If a tool is not required, respond as normal",
    messages=[{"role": "user", "content": "What color are emeralds?"}],
    max_tokens=400,
    tools=[calculator_tool]
)

print(response.stop_reason)    # end_turn(而非 tool_use)
print(response.content[0].text)  # "Emeralds are green in color."

练习

编写工具定义

请为以下函数编写一个格式正确的工具定义,假设两个参数均为必填项:

def inventory_lookup(product_name, max_results):
    # 在库存中查找指定产品
    # product_name: 产品名称(字符串)
    # max_results: 返回结果的最大数量(整数)
    return "结果列表" 

使用方式:

inventory_lookup("AA batteries", 4)
inventory_lookup("birthday candle", 10)
查看参考答案
inventory_tool = {
    "name": "inventory_lookup",
    "description": "Look up the inventory count for a specific product.",
    "input_schema": {
        "type": "object",
        "properties": {
            "product_name": {
                "type": "string",
                "description": "The name of the product to look up."
            },
            "max_results": {
                "type": "integer",
                "description": "The maximum number of results to return."
            }
        },
        "required": ["product_name", "max_results"]
    }
}
第三章
用工具调用强制结构化输出
利用工具调用机制让 Claude 输出结构化的 JSON 数据,适用于情感分析、实体提取等场景。

学习目标

  • 了解为何工具调用是获取结构化 JSON 输出的有效途径
  • 掌握 tool_choice 参数的使用方式
  • 实践情感分析和实体提取两个经典场景

核心思路

当我们需要 Claude 输出结构化的 JSON 数据时,可以利用工具调用机制:定义一个带有特定 JSON Schema 的"虚拟工具",然后强制 Claude 调用该工具。由于工具调用本质上要求 Claude 输出符合 Schema 的 JSON,这就保证了输出的结构化和一致性。

情感分析示例

不使用工具调用时,Claude 输出的是自由文本;使用工具调用后,我们可以强制 Claude 输出结构化的情感分析结果:

# 定义情感分析工具
sentiment_tool = {
    "name": "record_sentiment",
    "description": "Record the sentiment analysis result for a piece of text.",
    "input_schema": {
        "type": "object",
        "properties": {
            "sentiment": {
                "type": "string",
                "enum": ["positive", "negative", "neutral"],
                "description": "The overall sentiment of the text."
            },
            "score": {
                "type": "number",
                "description": "Confidence score from 0.0 to 1.0."
            },
            "explanation": {
                "type": "string",
                "description": "Brief explanation of the sentiment."
            }
        },
        "required": ["sentiment", "score", "explanation"]
    }
}

def analyze_sentiment(text):
    response = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=500,
        tools=[sentiment_tool],
        tool_choice={"type": "tool", "name": "record_sentiment"},  # 强制使用此工具
        messages=[{"role": "user", "content": f"Analyze the sentiment of this text: {text}"}]
    )
    return response.content[0].input

result = analyze_sentiment("I absolutely love this product! It's amazing.")
print(result)
# {"sentiment": "positive", "score": 0.98, "explanation": "..."} 

使用 tool_choice 强制工具调用

tool_choice 参数有三种取值:

含义
{"type": "auto"}默认模式,Claude 自行决定是否使用工具
{"type": "any"}Claude 必须使用某个工具,但可以自由选择
{"type": "tool", "name": "xxx"}强制 Claude 使用指定工具

实体提取示例

从文本中提取结构化的实体信息(人物、地点、组织等):

entity_tool = {
    "name": "extract_entities",
    "description": "Extract named entities from text.",
    "input_schema": {
        "type": "object",
        "properties": {
            "people": {
                "type": "array",
                "items": {"type": "string"},
                "description": "Names of people mentioned."
            },
            "organizations": {
                "type": "array",
                "items": {"type": "string"},
                "description": "Names of organizations mentioned."
            },
            "locations": {
                "type": "array",
                "items": {"type": "string"},
                "description": "Names of locations mentioned."
            },
            "dates": {
                "type": "array",
                "items": {"type": "string"},
                "description": "Dates or time references mentioned."
            }
        },
        "required": ["people", "organizations", "locations", "dates"]
    }
}

def extract_entities(text):
    response = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=500,
        tools=[entity_tool],
        tool_choice={"type": "tool", "name": "extract_entities"},
        messages=[{"role": "user", "content": f"Extract all named entities from this text: {text}"}]
    )
    return response.content[0].input

text = "On January 15th, Apple CEO Tim Cook met with Microsoft's Satya Nadella in Seattle."
result = extract_entities(text)
print(result)
# {"people": ["Tim Cook", "Satya Nadella"], "organizations": ["Apple", "Microsoft"], ...}

Wikipedia 摘要提取

更复杂的结构化提取示例——从 Wikipedia 文章中提取标准化摘要:

wikipedia_summary_tool = {
    "name": "save_wikipedia_summary",
    "description": "Save a structured summary of a Wikipedia article.",
    "input_schema": {
        "type": "object",
        "properties": {
            "title": {"type": "string", "description": "Article title"},
            "summary": {"type": "string", "description": "2-3 sentence summary"},
            "key_facts": {
                "type": "array",
                "items": {"type": "string"},
                "description": "List of 3-5 key facts"
            },
            "categories": {
                "type": "array",
                "items": {"type": "string"},
                "description": "Relevant categories or topics"
            }
        },
        "required": ["title", "summary", "key_facts", "categories"]
    }
}

练习

产品评论分析器

请定义一个名为 analyze_review 的工具,用于分析电商产品评论。要求输出以下结构化字段:

  • rating:1-5 的整数评分(必填)
  • pros:优点列表(字符串数组,必填)
  • cons:缺点列表(字符串数组,必填)
  • summary:一句话总结(字符串,必填)
  • would_recommend:是否推荐(布尔值,必填)

然后使用该工具分析评论:"The product arrived quickly but the quality was disappointing. The materials feel cheap and it broke after a week of use."

第四章
完整工具调用工作流
通过一个真实的 Wikipedia 搜索工具,演示包含完整四步骤的工具调用循环。

学习目标

  • 实现完整的四步工具调用循环(包括步骤 3 和 4)
  • 掌握 tool_result 内容块的正确格式
  • 通过迭代优化代码与提示词

构建 Wikipedia 搜索工具

步骤 1:定义搜索函数

import wikipedia

def search_wikipedia(query):
    """Search Wikipedia and return a summary."""
    try:
        results = wikipedia.search(query, results=1)
        if not results:
            return "No results found."
        page = wikipedia.page(results[0], auto_suggest=False)
        return page.summary[:1500]  # 限制长度
    except wikipedia.exceptions.DisambiguationError as e:
        return f"Disambiguation: {e.options[:3]}"
    except Exception as e:
        return f"Error: {str(e)}" 

步骤 2:编写工具定义

wiki_tool = {
    "name": "search_wikipedia",
    "description": "Search Wikipedia for information on a topic and return a summary.",
    "input_schema": {
        "type": "object",
        "properties": {
            "query": {
                "type": "string",
                "description": "The search query to look up on Wikipedia."
            }
        },
        "required": ["query"]
    }
}

步骤 3:完整的四步循环

def answer_with_wikipedia(user_question):
    messages = [{"role": "user", "content": user_question}]

    # 步骤 1:发送请求,提供工具
    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=1000,
        tools=[wiki_tool],
        messages=messages
    )

    # 步骤 2:检查 Claude 是否要使用工具
    while response.stop_reason == "tool_use":
        tool_use_block = next(b for b in response.content if b.type == "tool_use")
        tool_name = tool_use_block.name
        tool_input = tool_use_block.input

        print(f"[调用工具] {tool_name}({tool_input})")

        # 步骤 3:执行工具
        tool_result = search_wikipedia(tool_input["query"])

        # 将 Claude 的响应和工具结果加入消息历史
        messages.append({"role": "assistant", "content": response.content})
        messages.append({
            "role": "user",
            "content": [{
                "type": "tool_result",
                "tool_use_id": tool_use_block.id,
                "content": tool_result
            }]
        })

        # 步骤 4:让 Claude 利用工具结果生成回答
        response = client.messages.create(
            model="claude-opus-4-5",
            max_tokens=1000,
            tools=[wiki_tool],
            messages=messages
        )

    # 提取最终文本回答
    return next((b.text for b in response.content if hasattr(b, "text")), "No answer.")

answer = answer_with_wikipedia("When was the Eiffel Tower built and how tall is it?")
print(answer)

改进代码

可以将工具执行逻辑抽象成一个通用的 dispatcher 函数:

def execute_tool(tool_name, tool_input, available_tools):
    """Execute a tool by name with given input."""
    tool_functions = {
        "search_wikipedia": search_wikipedia,
        # 可以在此添加更多工具
    }
    if tool_name in tool_functions:
        return tool_functions[tool_name](**tool_input)
    return f"Error: Unknown tool '{tool_name}'" 

改进提示词

通过系统提示词引导 Claude 何时使用工具:

response = client.messages.create(
    model="claude-opus-4-5",
    system="""You are a helpful assistant with access to Wikipedia.
Use the search_wikipedia tool whenever you need factual information
about people, places, events, or concepts. Always search Wikipedia
before answering factual questions.""",
    max_tokens=1000,
    tools=[wiki_tool],
    messages=messages
)
最佳实践
  • 工具描述要清晰准确——Claude 根据描述决定是否调用工具
  • 在系统提示词中说明工具的使用时机,避免工具被滥用或遗漏
  • 处理工具执行中可能出现的异常,并将错误信息作为 tool_result 返回给 Claude
  • 限制工具结果的长度,避免超出上下文窗口

练习

实现带工具的问答助手

在 Wikipedia 工具的基础上,添加第二个工具 get_current_date(返回当前日期),并让 Claude 能够在同一次对话中使用两个工具回答问题:"What is today's date, and how old is the Eiffel Tower?"

提示:由于问题需要两个工具,Claude 可能会在一次响应中同时请求调用两个工具,也可能依次调用。你的代码需要能够处理这两种情况。

第五章
工具选择策略
深入了解 tool_choice 参数,掌握 auto、any 和强制指定工具三种模式的使用场景。

三种 tool_choice 模式

1. Auto(自动,默认)

默认模式下,Claude 会自主判断是否需要使用工具。Claude 既可以使用工具,也可以直接回答。

response = client.messages.create(
    model="claude-haiku-4-5",
    max_tokens=500,
    tools=[calculator_tool],
    tool_choice={"type": "auto"},  # 这是默认值
    messages=[{"role": "user", "content": "What is 15% of 847?"}]
)
# Claude 会使用计算器工具

response = client.messages.create(
    model="claude-haiku-4-5",
    max_tokens=500,
    tools=[calculator_tool],
    tool_choice={"type": "auto"},
    messages=[{"role": "user", "content": "What is the capital of France?"}]
)
# Claude 不会使用工具,直接回答
提示词很重要
即使在 auto 模式下,工具描述和系统提示词的质量也会显著影响 Claude 何时选择使用工具。描述越清晰,Claude 的判断越准确。

2. 强制使用指定工具

设置 {"type": "tool", "name": "工具名"} 可以强制 Claude 使用特定工具,无论是否真的需要:

response = client.messages.create(
    model="claude-haiku-4-5",
    max_tokens=500,
    tools=[calculator_tool],
    tool_choice={"type": "tool", "name": "calculator"},  # 强制使用计算器
    messages=[{"role": "user", "content": "Tell me something interesting about the number 42"}]
)
# Claude 会被强制调用计算器,即使这个问题并不需要计算

print(response.stop_reason)  # tool_use
print(response.content[0].input)  # 强制输出的 JSON

这在需要确保输出格式一致性时非常有用(参见第三章的结构化输出模式)。

3. Any(任意工具)

设置 {"type": "any"} 要求 Claude 必须使用工具,但可以自由选择使用哪个。当有多个工具时尤其有用:

tools = [calculator_tool, wiki_tool, weather_tool]

response = client.messages.create(
    model="claude-haiku-4-5",
    max_tokens=500,
    tools=tools,
    tool_choice={"type": "any"},  # 必须使用某个工具,但可以自由选择
    messages=[{"role": "user", "content": "What is 25 * 4?"}]
)
# Claude 会选择计算器工具(最合适的选择)

response = client.messages.create(
    model="claude-haiku-4-5",
    max_tokens=500,
    tools=tools,
    tool_choice={"type": "any"},
    messages=[{"role": "user", "content": "Who invented the telephone?"}]
)
# Claude 会选择 Wikipedia 工具

选择策略汇总

场景 推荐 tool_choice 原因
通用助手 auto 让 Claude 自主判断,最灵活
强制结构化输出 {"type": "tool", "name": "xxx"} 确保输出符合特定 Schema
必须调用工具但不限类型 any 保证工具被使用,但允许 Claude 选择最合适的
工具对比测试 分别测试 auto / any 观察 Claude 的工具选择行为

练习

工具选择对比实验

准备一套工具(计算器 + Wikipedia 搜索),使用相同的问题,分别测试 autoany{"type": "tool", "name": "calculator"} 三种模式,观察并记录每种模式下:

  • stop_reason 的值
  • Claude 选择了哪个工具(如果有)
  • 最终回答的质量

测试问题推荐:"What is 17% of 4,520?" 和 "Who wrote Romeo and Juliet?"

第六章
多工具聊天机器人
构建一个可以访问多个工具的完整聊天机器人,实现多轮对话与工具调用的完美结合。

本章将综合前面所有知识点,构建一个功能完整的多工具聊天机器人。我们将模拟一个简单的数据库,为聊天机器人提供多个可调用的工具。

构建模拟数据库

# 模拟客户数据库
CUSTOMERS = {
    "C001": {"name": "Alice Johnson", "email": "alice@example.com", "tier": "premium"},
    "C002": {"name": "Bob Smith", "email": "bob@example.com", "tier": "standard"},
    "C003": {"name": "Carol White", "email": "carol@example.com", "tier": "premium"},
}

# 模拟订单数据库
ORDERS = {
    "O001": {"customer_id": "C001", "product": "Widget Pro", "status": "shipped", "total": 149.99},
    "O002": {"customer_id": "C002", "product": "Basic Kit", "status": "processing", "total": 29.99},
    "O003": {"customer_id": "C001", "product": "Premium Pack", "status": "delivered", "total": 299.99},
}

定义工具函数

def get_customer_info(customer_id):
    """获取客户信息"""
    customer = CUSTOMERS.get(customer_id)
    if customer:
        return {"found": True, "customer_id": customer_id, **customer}
    return {"found": False, "error": f"Customer {customer_id} not found"}

def get_order_details(order_id):
    """获取订单详情"""
    order = ORDERS.get(order_id)
    if order:
        return {"found": True, "order_id": order_id, **order}
    return {"found": False, "error": f"Order {order_id} not found"}

def get_customer_orders(customer_id):
    """获取指定客户的所有订单"""
    customer_orders = [
        {"order_id": oid, **odata}
        for oid, odata in ORDERS.items()
        if odata["customer_id"] == customer_id
    ]
    if not customer_orders:
        return {"customer_id": customer_id, "orders": [], "count": 0}
    return {"customer_id": customer_id, "orders": customer_orders, "count": len(customer_orders)}

def cancel_order(order_id, reason):
    """取消订单"""
    order = ORDERS.get(order_id)
    if not order:
        return {"success": False, "error": f"Order {order_id} not found"}
    if order["status"] == "delivered":
        return {"success": False, "error": "Cannot cancel delivered orders"}
    return {"success": True, "order_id": order_id, "message": f"Order {order_id} cancelled. Reason: {reason}"}

定义工具列表

tools = [
    {
        "name": "get_customer_info",
        "description": "Retrieve customer information by customer ID.",
        "input_schema": {
            "type": "object",
            "properties": {
                "customer_id": {"type": "string", "description": "The customer ID (e.g., C001)"}
            },
            "required": ["customer_id"]
        }
    },
    {
        "name": "get_order_details",
        "description": "Get details of a specific order by order ID.",
        "input_schema": {
            "type": "object",
            "properties": {
                "order_id": {"type": "string", "description": "The order ID (e.g., O001)"}
            },
            "required": ["order_id"]
        }
    },
    {
        "name": "get_customer_orders",
        "description": "Get all orders for a specific customer.",
        "input_schema": {
            "type": "object",
            "properties": {
                "customer_id": {"type": "string", "description": "The customer ID"}
            },
            "required": ["customer_id"]
        }
    },
    {
        "name": "cancel_order",
        "description": "Cancel an order that hasn't been delivered yet.",
        "input_schema": {
            "type": "object",
            "properties": {
                "order_id": {"type": "string", "description": "The order ID to cancel"},
                "reason": {"type": "string", "description": "Reason for cancellation"}
            },
            "required": ["order_id", "reason"]
        }
    }
]

工具调度函数

import json

def process_tool_call(tool_name, tool_input):
    """根据工具名称分发调用"""
    tool_functions = {
        "get_customer_info": get_customer_info,
        "get_order_details": get_order_details,
        "get_customer_orders": get_customer_orders,
        "cancel_order": cancel_order,
    }
    if tool_name in tool_functions:
        result = tool_functions[tool_name](**tool_input)
        return json.dumps(result, ensure_ascii=False)
    return json.dumps({"error": f"Unknown tool: {tool_name}"}),

完整聊天机器人

def run_customer_service_bot():
    """运行客户服务聊天机器人"""
    messages = []
    system_prompt = """You are a helpful customer service assistant for an e-commerce company.
You have access to tools to look up customer information and order details.
Always be polite and helpful. Use the available tools to answer customer questions accurately."""

    print("客户服务机器人已启动!输入 'quit' 退出。\n")

    while True:
        user_input = input("用户: ").strip()
        if user_input.lower() in ["quit", "exit", "退出"]:
            print("感谢您的使用,再见!")
            break
        if not user_input:
            continue

        messages.append({"role": "user", "content": user_input})

        # 循环处理工具调用
        while True:
            response = client.messages.create(
                model="claude-opus-4-5",
                system=system_prompt,
                max_tokens=1000,
                tools=tools,
                messages=messages
            )

            if response.stop_reason == "tool_use":
                # 处理所有工具调用(可能有多个)
                tool_results = []
                for block in response.content:
                    if block.type == "tool_use":
                        print(f"  [调用工具] {block.name}({block.input})")
                        result = process_tool_call(block.name, block.input)
                        tool_results.append({
                            "type": "tool_result",
                            "tool_use_id": block.id,
                            "content": result
                        })

                # 将 Claude 的响应和工具结果加入消息历史
                messages.append({"role": "assistant", "content": response.content})
                messages.append({"role": "user", "content": tool_results})

            elif response.stop_reason == "end_turn":
                # 获取最终文本回答
                final_text = next(
                    (b.text for b in response.content if hasattr(b, "text")), ""
                )
                if final_text:
                    print(f"\n助手: {final_text}\n")
                messages.append({"role": "assistant", "content": response.content})
                break

run_customer_service_bot()

提示词优化技巧

可以通过更详细的系统提示词进一步提升机器人的表现:

system_prompt = """You are a helpful customer service assistant.

Available tools:
- get_customer_info: Look up customer details by ID
- get_order_details: Get information about a specific order
- get_customer_orders: See all orders for a customer
- cancel_order: Cancel an undelivered order

Guidelines:
1. When a customer mentions their customer ID or order ID, use the appropriate tool immediately.
2. For cancellation requests, always confirm the order status first.
3. Premium tier customers should receive priority handling.
4. Be empathetic when handling complaints or cancellations.
5. If you don't have enough information to help, ask clarifying questions."""
已知问题
对于早期版本的 Claude(如 Opus 2),在处理多工具场景时,有时会在工具调用结果之后立即尝试再次调用工具,而不是先给出文本回答。解决方案是在系统提示词中明确说明:"After receiving tool results, always provide a text response to the user before making any additional tool calls unless absolutely necessary."

练习

扩展聊天机器人

在现有客户服务机器人的基础上添加以下功能:

  • 添加一个 update_shipping_address 工具,允许更改未发货订单的收货地址
  • 添加一个 apply_discount 工具,为 premium 客户的当前购物车应用折扣码
  • 确保机器人在调用这些工具前会进行适当的验证(例如:确认客户权限、订单状态)

进阶挑战

为聊天机器人添加对话记忆功能:将最近 5 次对话的摘要存储为上下文,使机器人能够参考历史对话提供更连贯的服务。

原始内容来源:本教程翻译自 Anthropic 官方开源课程 anthropics/courses — tool_use, 原版采用 CC BY-NC 4.0 授权。
中文翻译与排版:Maurice Wen | Claudepedia — Anthropic 官方学习资源中文站
本页面仅供学习用途,保留原始署名,不用于商业目的。