Claude Platform Docs
  • Messages
  • Managed Agents
  • 관리자

Search...
⌘K
첫 단계
Claude 소개빠른 시작
Claude로 빌드하기
기능 개요Messages API 사용하기중지 사유 및 폴백거부 및 폴백폴백 크레딧
모델 기능
확장 사고적응형 사고Effort작업 예산 (베타)고속 모드 (리서치 프리뷰)구조화된 출력인용스트리밍 Messages배치 처리검색 결과스트리밍 거부다국어 지원임베딩
도구
개요도구 사용 작동 방식튜토리얼: 도구 사용 에이전트 빌드하기도구 정의도구 호출 처리병렬 도구 사용Tool Runner (SDK)엄격한 도구 사용서버 도구웹 검색 도구웹 가져오기 도구코드 실행 도구어드바이저 도구도구 검색 도구메모리 도구Bash 도구텍스트 편집기 도구컴퓨터 사용 도구문제 해결
도구 인프라
도구 레퍼런스도구 컨텍스트 관리도구 조합프롬프트 캐싱과 도구 사용프로그래밍 방식 도구 호출세분화된 도구 스트리밍
컨텍스트 관리
컨텍스트 윈도우압축컨텍스트 편집프롬프트 캐싱대화 중 시스템 메시지오케스트레이션 모드 빌드하기캐시 진단 (베타)토큰 카운팅
파일 작업
Files APIPDF 지원
스킬
개요빠른 시작모범 사례엔터프라이즈용 스킬API에서의 스킬
MCP
원격 MCP 서버MCP 커넥터
클라우드 플랫폼의 Claude
Amazon BedrockAmazon Bedrock (레거시)AWS의 Claude PlatformGoogle CloudMicrosoft Foundry

Log in
튜토리얼: 도구 사용 에이전트 빌드하기
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Claude Platform Docs

Solutions

  • AI agents
  • Code modernization
  • Coding
  • Customer support
  • Education
  • Financial services
  • Government
  • Life sciences

Partners

  • Claude on AWS
  • Claude on Google Cloud

Learn

  • Blog
  • Courses
  • Use cases
  • Connectors
  • Customer stories
  • Engineering at Anthropic
  • Events
  • Powered by Claude
  • Service partners
  • Startups program

Company

  • Anthropic
  • Careers
  • Economic Futures
  • Research
  • News
  • Responsible Scaling Policy
  • Security and compliance
  • Transparency

Learn

  • Blog
  • Courses
  • Use cases
  • Connectors
  • Customer stories
  • Engineering at Anthropic
  • Events
  • Powered by Claude
  • Service partners
  • Startups program

Help and security

  • Availability
  • Status
  • Support
  • Discord

Terms and policies

  • Privacy policy
  • Responsible disclosure policy
  • Terms of service: Commercial
  • Terms of service: Consumer
  • Usage policy
Messages/도구

튜토리얼: 도구를 사용하는 에이전트 구축하기

단일 도구 호출에서 프로덕션 수준의 에이전틱 루프까지 단계별로 안내하는 가이드입니다.

이 튜토리얼은 다섯 개의 동심원 링 구조로 캘린더 관리 에이전트를 구축합니다. 각 링은 완전하고 실행 가능한 프로그램이며, 이전 링에 정확히 하나의 개념만 추가합니다. 튜토리얼을 마치면 에이전틱 루프를 직접 작성한 다음, 이를 Tool Runner SDK 추상화로 대체하게 됩니다.

예제 도구는 create_calendar_event입니다. 이 도구의 스키마는 중첩된 객체, 배열, 선택적 필드를 사용하므로, 단순한 평면 문자열이 아닌 현실적인 입력 형태를 Claude가 어떻게 처리하는지 확인할 수 있습니다.



모든 링은 독립적으로 실행됩니다. 어떤 링이든 새 파일에 복사하면 이전 링의 코드 없이도 실행됩니다.

링 1: 단일 도구, 단일 턴

가능한 가장 작은 도구 사용 프로그램입니다. 하나의 도구, 하나의 사용자 메시지, 하나의 도구 호출, 하나의 결과로 구성됩니다. 코드에는 상세한 주석이 달려 있어 각 줄을 도구 사용 라이프사이클에 대응시킬 수 있습니다.

요청은 사용자 메시지와 함께 tools 배열을 전송합니다. Claude가 도구를 호출하기로 결정하면, 응답은 stop_reason: "tool_use"와 함께 도구 이름, 고유한 id, 구조화된 input을 포함하는 tool_use 콘텐츠 블록을 반환합니다. 코드는 도구를 실행한 다음, 호출의 id와 일치하는 tool_use_id를 가진 tool_result 블록에 결과를 담아 다시 전송합니다.

# 링 1: 단일 도구, 단일 턴.

import json

import anthropic

# 클라이언트를 생성합니다. 환경에서 ANTHROPIC_API_KEY를 읽어옵니다.
client = anthropic.Anthropic()

# 도구 하나를 정의합니다. input_schema는 Claude가 이 도구를 호출할 때
# 전달해야 하는 인수를 설명하는 JSON 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-4-8",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "auto", "disable_parallel_tool_use": True},
    messages=[
        {
            "role": "user",
            "content": "Schedule a 30-minute sync with [email protected] and [email protected] next Monday 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-4-8",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "auto", "disable_parallel_tool_use": True},
    messages=[
        {
            "role": "user",
            "content": "Schedule a 30-minute sync with [email protected] and [email protected] next Monday 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': ['[email protected]', '[email protected]']}
stop_reason: end_turn
I've scheduled your 30-minute sync with Alice and Bob for next Monday at 10am.

첫 번째 stop_reason은 tool_use입니다. Claude가 캘린더 결과를 기다리고 있기 때문입니다. 결과를 전송하면 두 번째 stop_reason은 end_turn이 되고, 콘텐츠는 사용자를 위한 자연어 응답입니다.

링 2: 에이전틱 루프

링 1은 Claude가 도구를 정확히 한 번만 호출한다고 가정했습니다. 실제 작업에서는 여러 번의 호출이 필요한 경우가 많습니다. Claude가 이벤트를 생성하고, 확인 내용을 읽은 다음, 또 다른 이벤트를 생성할 수도 있습니다. 해결책은 stop_reason이 더 이상 "tool_use"가 아닐 때까지 도구를 계속 실행하고 결과를 다시 전달하는 while 루프입니다.

또 다른 변경 사항은 대화 기록입니다. 각 요청마다 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: [email protected], [email protected], [email protected].",
    }
]

response = client.messages.create(
    model="claude-opus-4-8",
    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-4-8",
        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-4-8",
    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-4-8",
        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-4-8",
    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-4-8",
        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에 전달한 다음, 루프가 완료되면 최종 메시지를 가져오세요. 오류 래핑, 결과 포맷팅, 대화 관리는 내부적으로 처리됩니다.

Python SDK는 @beta_tool 데코레이터를 사용하여 타입 힌트와 docstring에서 스키마를 추론합니다. TypeScript SDK는 Zod 스키마와 함께 betaZodTool을 사용합니다. 다른 SDK들도 각자의 헬퍼로 동일한 패턴을 따릅니다. C#과 PHP에서는 BetaRunnableTool, Java와 Ruby에서는 타입이 지정된 도구 클래스, Go에서는 toolrunner.NewBetaToolFromJSONSchema를 사용합니다.



Tool Runner는 Python, TypeScript, C#, Go, Java, PHP, Ruby 등 7개 SDK 모두에서 사용할 수 있습니다. 전체 참조는 Tool Runner를 참조하세요. cURL 및 CLI 탭에는 코드 대신 안내 문구가 표시됩니다. curl 또는 CLI 기반 스크립트의 경우 링 4의 루프를 그대로 사용하세요.

# 링 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-4-8",
    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과 동일합니다. 차이점은 코드에 있습니다. 코드 줄 수가 약 절반으로 줄고, 수동 루프가 없으며, 스키마가 구현 바로 옆에 위치합니다.

구축한 내용

하드코딩된 단일 도구 호출에서 시작하여 여러 도구, 병렬 호출, 오류를 처리하는 프로덕션 형태의 에이전트를 완성했고, 이 모든 것을 Tool Runner로 압축했습니다. 그 과정에서 도구 사용 프로토콜의 모든 요소를 확인했습니다. tool_use 블록, tool_result 블록, tool_use_id 매칭, stop_reason 확인, is_error 신호 전달이 그것입니다.

다음 단계

도구 정의

스키마 명세 및 모범 사례.

Tool Runner 심층 분석

전체 SDK 추상화 참조.

문제 해결

일반적인 도구 사용 오류 해결.

Was this page helpful?

  • 링 1: 단일 도구, 단일 턴
  • 링 2: 에이전틱 루프
  • 링 3: 여러 도구, 병렬 호출
  • 링 4: 오류 처리
  • 링 5: Tool Runner SDK 추상화
  • 구축한 내용
  • 다음 단계