Claude Platform Docs
Messages工具

教程:构建一个使用工具的智能体

从单次工具调用到生产就绪的智能体循环的引导式演练。

本教程以五个同心环的方式构建一个日历管理智能体。每一环都是一个完整、可运行的程序,在前一环的基础上恰好增加一个概念。到最后,您将亲手编写出 "agentic loop"(智能体循环),然后用 Tool Runner SDK 抽象来替换它。

示例工具是 create_calendar_event。它的 schema 使用了嵌套对象、数组和可选字段,因此您将看到 Claude 如何处理真实的输入结构,而不仅仅是单个扁平字符串。

第 1 环:单个工具,单轮对话

最小的工具使用程序:一个工具、一条用户消息、一次工具调用、一个结果。代码带有大量注释,便于您将每一行对应到工具使用生命周期

请求在用户消息之外还发送一个 tools 数组。当 Claude 判断需要调用工具时,响应会返回 stop_reason: "tool_use" 以及一个 tool_use 内容块,其中包含工具名称、唯一的 id 和结构化的 input。您的代码运行该工具,然后在一个 tool_result 块中将结果发回,该块的 tool_use_id 与调用中的 id 相匹配。

# 第 1 环:单个工具,单轮对话。

import json

import anthropic

# 创建客户端。它会从环境变量中读取 ANTHROPIC_API_KEY。
client = anthropic.Anthropic()

# 定义一个工具。input_schema 是一个 JSON Schema 对象,用于描述
# Claude 调用此工具时应传递的参数。此 schema
# 包含嵌套对象(recurrence)、数组(attendees)和可选
# 字段,这比扁平的字符串参数更接近真实世界的工具。
tools = [
    {
        "name": "create_calendar_event",
        "description": "Create a calendar event with attendees and optional recurrence.",
        "input_schema": {
            "type": "object",
            "properties": {
                "title": {"type": "string"},
                "start": {"type": "string", "format": "date-time"},
                "end": {"type": "string", "format": "date-time"},
                "attendees": {
                    "type": "array",
                    "items": {"type": "string", "format": "email"},
                },
                "recurrence": {
                    "type": "object",
                    "properties": {
                        "frequency": {"enum": ["daily", "weekly", "monthly"]},
                        "count": {"type": "integer", "minimum": 1},
                    },
                },
            },
            "required": ["title", "start", "end"],
        },
    }
]

# 将用户的请求连同工具定义一起发送。Claude 会根据
# 请求内容和工具描述来决定是否调用该工具。
response = client.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "auto", "disable_parallel_tool_use": True},
    messages=[
        {
            "role": "user",
            "content": "Schedule a 30-minute sync with alice@example.com and bob@example.com on Monday, March 30, 2026 at 10am.",
        }
    ],
)

# 当 Claude 调用工具时,响应的 stop_reason 为 "tool_use",
# 并且 content 数组中会包含一个 tool_use 块以及可能的文本。
print(f"stop_reason: {response.stop_reason}")

# 查找 tool_use 块。响应中 tool_use 块之前可能包含文本块,
# 因此应遍历 content 数组,而不是假定其位置。
tool_use = next(block for block in response.content if block.type == "tool_use")
print(f"Tool: {tool_use.name}")
print(f"Input: {tool_use.input}")

# 执行工具。在真实系统中,这里会调用您的日历 API。
# 此处结果是硬编码的,以保持示例的自包含性。
result = {"event_id": "evt_123", "status": "created"}

# 将结果发回。tool_result 块需放在 user 消息中,
# 其 tool_use_id 必须与上面 tool_use 块中的 id 匹配。
# 同时包含助手的上一条响应,以便 Claude 拥有完整的历史记录。
followup = client.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "auto", "disable_parallel_tool_use": True},
    messages=[
        {
            "role": "user",
            "content": "Schedule a 30-minute sync with alice@example.com and bob@example.com on Monday, March 30, 2026 at 10am.",
        },
        {"role": "assistant", "content": response.content},
        {
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": tool_use.id,
                    "content": json.dumps(result),
                }
            ],
        },
    ],
)

# 获得工具结果后,Claude 会生成最终的自然语言
# 回答,此时 stop_reason 变为 "end_turn"。
print(f"stop_reason: {followup.stop_reason}")
final_text = next(block for block in followup.content if block.type == "text")
print(final_text.text)

预期结果

Output
stop_reason: tool_use
Tool: create_calendar_event
Input: {'title': 'Sync', 'start': '2026-03-30T10:00:00', 'end': '2026-03-30T10:30:00', 'attendees': ['alice@example.com', 'bob@example.com']}
stop_reason: end_turn
I've scheduled your 30-minute sync with Alice and Bob for Monday, March 30 at 10am.

第一个 stop_reasontool_use,因为 Claude 正在等待日历结果。在您发送结果之后,第二个 stop_reasonend_turn,其内容是面向用户的自然语言。

第 2 环:智能体循环

第 1 环假设 Claude 恰好只调用一次工具。实际任务通常需要多次调用:Claude 可能会创建一个事件,读取确认信息,然后再创建另一个。解决办法是使用一个 while 循环,持续运行工具并将结果反馈回去,直到 stop_reason 不再是 "tool_use"

另一个变化是对话历史。不要在每次请求时从头重建 messages 数组,而是维护一个持续的列表并向其追加内容。每一轮都能看到完整的先前上下文。

# 第 2 环:代理循环。

import json

import anthropic

client = anthropic.Anthropic()

tools = [
    {
        "name": "create_calendar_event",
        "description": "Create a calendar event with attendees and optional recurrence.",
        "input_schema": {
            "type": "object",
            "properties": {
                "title": {"type": "string"},
                "start": {"type": "string", "format": "date-time"},
                "end": {"type": "string", "format": "date-time"},
                "attendees": {
                    "type": "array",
                    "items": {"type": "string", "format": "email"},
                },
                "recurrence": {
                    "type": "object",
                    "properties": {
                        "frequency": {"enum": ["daily", "weekly", "monthly"]},
                        "count": {"type": "integer", "minimum": 1},
                    },
                },
            },
            "required": ["title", "start", "end"],
        },
    }
]


def run_tool(name, tool_input):
    if name == "create_calendar_event":
        return {"event_id": "evt_123", "status": "created", "title": tool_input["title"]}
    return {"error": f"Unknown tool: {name}"}


# 将完整的对话历史保存在列表中,以便每一轮都能看到先前的上下文。
messages = [
    {
        "role": "user",
        "content": "Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com.",
    }
]

response = client.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "auto", "disable_parallel_tool_use": True},
    messages=messages,
)

# 循环直到 Claude 不再请求工具。每次迭代运行所请求的
# 工具,将结果追加到历史记录中,然后让 Claude 继续。
while response.stop_reason == "tool_use":
    tool_use = next(block for block in response.content if block.type == "tool_use")
    result = run_tool(tool_use.name, tool_use.input)

    messages.append({"role": "assistant", "content": response.content})
    messages.append(
        {
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": tool_use.id,
                    "content": json.dumps(result),
                }
            ],
        }
    )

    response = client.messages.create(
        model="claude-opus-5",
        max_tokens=1024,
        tools=tools,
        tool_choice={"type": "auto", "disable_parallel_tool_use": True},
        messages=messages,
    )

final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)

预期结果

Output
I've set up your weekly team standup for the next 4 Mondays at 9am with Alice, Bob, and Carol invited.

循环可能运行一次或多次,取决于 Claude 如何拆解任务。您的代码不再需要事先知道。

第 3 环:多个工具,并行调用

智能体很少只有一种能力。添加第二个工具 list_calendar_events,这样 Claude 就可以在创建新事件之前检查现有日程。

当 Claude 需要进行多个相互独立的工具调用时,它可能会在单个响应中返回多个 tool_use 块。您的循环需要处理所有这些块,并在一条用户消息中将所有结果一起发回。请遍历 response.content 中的每一个 tool_use 块,而不仅仅是第一个。

# 第 3 环:多个工具,并行调用。

import json

import anthropic

client = anthropic.Anthropic()

tools = [
    {
        "name": "create_calendar_event",
        "description": "Create a calendar event with attendees and optional recurrence.",
        "input_schema": {
            "type": "object",
            "properties": {
                "title": {"type": "string"},
                "start": {"type": "string", "format": "date-time"},
                "end": {"type": "string", "format": "date-time"},
                "attendees": {
                    "type": "array",
                    "items": {"type": "string", "format": "email"},
                },
                "recurrence": {
                    "type": "object",
                    "properties": {
                        "frequency": {"enum": ["daily", "weekly", "monthly"]},
                        "count": {"type": "integer", "minimum": 1},
                    },
                },
            },
            "required": ["title", "start", "end"],
        },
    },
    {
        "name": "list_calendar_events",
        "description": "List all calendar events on a given date.",
        "input_schema": {
            "type": "object",
            "properties": {
                "date": {"type": "string", "format": "date"},
            },
            "required": ["date"],
        },
    },
]


def run_tool(name, tool_input):
    if name == "create_calendar_event":
        return {"event_id": "evt_123", "status": "created", "title": tool_input["title"]}
    if name == "list_calendar_events":
        return {"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}
    return {"error": f"Unknown tool: {name}"}


messages = [
    {
        "role": "user",
        "content": "Check what I have next Monday, then schedule a planning session that avoids any conflicts.",
    }
]

response = client.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    tools=tools,
    messages=messages,
)

while response.stop_reason == "tool_use":
    # 单个响应可以包含多个 tool_use 块。处理所有这些块,
    # 并将所有结果一起放在一条用户消息中返回。
    tool_results = []
    for block in response.content:
        if block.type == "tool_use":
            result = run_tool(block.name, block.input)
            tool_results.append(
                {
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": json.dumps(result),
                }
            )

    messages.append({"role": "assistant", "content": response.content})
    messages.append({"role": "user", "content": tool_results})

    response = client.messages.create(
        model="claude-opus-5",
        max_tokens=1024,
        tools=tools,
        messages=messages,
    )

final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)

预期结果

Output
I checked your calendar for next Monday and found an existing meeting from 2pm to 3pm. I've scheduled the planning session for 10am to 11am to avoid the conflict.

有关并发执行和顺序保证的更多信息,请参阅并行工具使用

第 4 环:错误处理

工具会失败。日历 API 可能会拒绝参与者过多的事件,或者日期格式可能有误。当工具抛出错误时,请使用 is_error: true 将错误消息发回,而不是让程序崩溃。Claude 会读取错误信息,并可以使用修正后的输入重试、向用户请求澄清,或解释该限制。

# 第 4 环:错误处理。

import json

import anthropic

client = anthropic.Anthropic()

tools = [
    {
        "name": "create_calendar_event",
        "description": "Create a calendar event with attendees and optional recurrence.",
        "input_schema": {
            "type": "object",
            "properties": {
                "title": {"type": "string"},
                "start": {"type": "string", "format": "date-time"},
                "end": {"type": "string", "format": "date-time"},
                "attendees": {
                    "type": "array",
                    "items": {"type": "string", "format": "email"},
                },
                "recurrence": {
                    "type": "object",
                    "properties": {
                        "frequency": {"enum": ["daily", "weekly", "monthly"]},
                        "count": {"type": "integer", "minimum": 1},
                    },
                },
            },
            "required": ["title", "start", "end"],
        },
    },
    {
        "name": "list_calendar_events",
        "description": "List all calendar events on a given date.",
        "input_schema": {
            "type": "object",
            "properties": {
                "date": {"type": "string", "format": "date"},
            },
            "required": ["date"],
        },
    },
]


def run_tool(name, tool_input):
    if name == "create_calendar_event":
        if "attendees" in tool_input and len(tool_input["attendees"]) > 10:
            raise ValueError("Too many attendees (max 10)")
        return {"event_id": "evt_123", "status": "created", "title": tool_input["title"]}
    if name == "list_calendar_events":
        return {"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}
    raise ValueError(f"Unknown tool: {name}")


messages = [
    {
        "role": "user",
        "content": "Schedule an all-hands with everyone: " + ", ".join(f"user{i}@example.com" for i in range(15)),
    }
]

response = client.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    tools=tools,
    messages=messages,
)

while response.stop_reason == "tool_use":
    tool_results = []
    for block in response.content:
        if block.type == "tool_use":
            try:
                result = run_tool(block.name, block.input)
                tool_results.append(
                    {"type": "tool_result", "tool_use_id": block.id, "content": json.dumps(result)}
                )
            except Exception as exc:
                # 发出失败信号,以便 Claude 重试或请求澄清。
                tool_results.append(
                    {
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": str(exc),
                        "is_error": True,
                    }
                )

    messages.append({"role": "assistant", "content": response.content})
    messages.append({"role": "user", "content": tool_results})

    response = client.messages.create(
        model="claude-opus-5",
        max_tokens=1024,
        tools=tools,
        messages=messages,
    )

final_text = next(block for block in response.content if block.type == "text")
print(final_text.text)

预期结果

Output
I tried to schedule the all-hands but the calendar only allows 10 attendees per event. I can split this into two sessions, or you can let me know which 10 people to prioritize.

is_error 标志是与成功结果的唯一区别。Claude 会看到该标志和错误文本,并做出相应的响应。完整的错误处理参考请参阅处理工具调用

第 5 环:Tool Runner SDK 抽象

第 2 环到第 4 环手工编写了同样的循环:调用 API、检查 stop_reason、运行工具、追加结果、重复。Tool Runner 会替您完成这些工作。将每个工具定义为一个函数,把列表传给 tool_runner,并在循环完成后获取最终消息。错误包装、结果格式化和对话管理都在内部处理。

每个 SDK 都提供了一个辅助工具,可将普通函数转换为可运行的工具,并从其签名推导出输入 schema;下面的选项卡展示了每种语言的惯用写法。

# 第 5 环:Tool Runner SDK 抽象层。

import json

import anthropic
from anthropic import beta_tool

client = anthropic.Anthropic()


@beta_tool
def create_calendar_event(
    title: str,
    start: str,
    end: str,
    attendees: list[str] | None = None,
    recurrence: dict | None = None,
) -> str:
    """Create a calendar event with attendees and optional recurrence.

    Args:
        title: Event title.
        start: Start time in ISO 8601 format.
        end: End time in ISO 8601 format.
        attendees: Email addresses to invite.
        recurrence: Dict with 'frequency' (daily, weekly, monthly) and 'count'.
    """
    if attendees and len(attendees) > 10:
        raise ValueError("Too many attendees (max 10)")
    return json.dumps({"event_id": "evt_123", "status": "created", "title": title})


@beta_tool
def list_calendar_events(date: str) -> str:
    """List all calendar events on a given date.

    Args:
        date: Date in YYYY-MM-DD format.
    """
    return json.dumps({"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]})


final_message = client.beta.messages.tool_runner(
    model="claude-opus-5",
    max_tokens=1024,
    tools=[create_calendar_event, list_calendar_events],
    messages=[
        {
            "role": "user",
            "content": "Check what I have next Monday, then schedule a planning session that avoids any conflicts.",
        }
    ],
).until_done()

for block in final_message.content:
    if block.type == "text":
        print(block.text)

预期结果

Output
I checked your calendar for next Monday and found an existing meeting from 2pm to 3pm. I've scheduled the planning session for 10am to 11am to avoid the conflict.

输出与第 3 环完全相同。区别在于代码:行数大约减少一半,没有手动循环,并且 schema 与实现放在一起。

您构建了什么

您从一次硬编码的工具调用开始,最终得到了一个具备生产形态的智能体,它能够处理多个工具、并行调用和错误,然后又将这一切收拢到 Tool Runner 中。在此过程中,您见到了工具使用协议的每一个组成部分:tool_use 块、tool_result 块、tool_use_id 匹配、stop_reason 检查以及 is_error 信号。

后续步骤

Schema 规范与最佳实践。

完整的 SDK 抽象参考。

修复常见的工具使用错误。

Was this page helpful?