Loading...
    • 빌드
    • 관리
    • 모델 및 가격
    • 클라이언트 SDK
    • API 참조
    Search...
    ⌘K
    시작하기
    Claude 소개빠른 시작
    Claude로 구축하기
    기능 개요Messages API 사용중지 이유 처리
    모델 기능
    확장 사고적응형 사고노력빠른 모드 (베타: 리서치 프리뷰)구조화된 출력인용스트리밍 메시지일괄 처리검색 결과스트리밍 거부다국어 지원임베딩
    도구
    개요도구 사용 방법웹 검색 도구웹 가져오기 도구코드 실행 도구메모리 도구Bash 도구컴퓨터 사용 도구텍스트 편집기 도구
    도구 인프라
    도구 검색프로그래밍 방식 도구 호출세밀한 도구 스트리밍
    컨텍스트 관리
    컨텍스트 윈도우압축컨텍스트 편집프롬프트 캐싱토큰 계산
    파일 작업
    Files APIPDF 지원이미지 및 비전
    스킬
    개요빠른 시작모범 사례엔터프라이즈용 스킬API의 스킬
    MCP
    원격 MCP 서버MCP 커넥터
    프롬프트 엔지니어링
    개요프롬프트 모범 사례Console 프롬프트 도구
    테스트 및 평가
    성공 기준 정의 및 평가 구축Console의 평가 도구 사용지연 시간 줄이기
    가드레일 강화
    환각 줄이기출력 일관성 높이기탈옥 방지프롬프트 유출 줄이기
    리소스
    용어집
    릴리스 노트
    Claude Platform
    Console
    Log in
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...

    Solutions

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

    Partners

    • Amazon Bedrock
    • Google Cloud's Vertex AI

    Learn

    • Blog
    • Catalog
    • 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
    • Catalog
    • 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
    Documentation

    튜토리얼: 도구 사용 에이전트 만들기

    단일 도구 호출부터 프로덕션 수준의 에이전트 루프까지 단계별 안내.

    Was this page helpful?

    • 링 1: 단일 도구, 단일 턴
    • 링 2: 에이전트 루프
    • 링 3: 여러 도구, 병렬 호출
    • 링 4: 오류 처리
    • 링 5: Tool Runner SDK 추상화

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

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

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

    링 1: 단일 도구, 단일 턴

    가장 작은 도구 사용 프로그램: 하나의 도구, 하나의 사용자 메시지, 하나의 도구 호출, 하나의 결과. 코드에는 각 줄을 도구 사용 라이프사이클에 매핑할 수 있도록 상세한 주석이 달려 있습니다.

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

    #!/bin/bash
    # Ring 1: Single tool, single turn.
    # Source for <CodeSource> in build-a-tool-using-agent.mdx.
    
    # Define one tool as a JSON fragment. The input_schema is a JSON Schema
    # object describing the arguments Claude should pass when it calls this
    # tool. This schema includes nested objects (recurrence), arrays
    # (attendees), and optional fields, which is closer to real-world tools
    # than a flat string argument.
    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"]
        }
      }
    ]'
    
    USER_MSG="Schedule a 30-minute sync with [email protected] and [email protected] next Monday at 10am."
    
    # Send the user's request along with the tool definition. Claude decides
    # whether to call the tool based on the request and the tool description.
    RESPONSE=$(curl -s https://api.anthropic.com/v1/messages \
      -H "x-api-key: $ANTHROPIC_API_KEY" \
      -H "anthropic-version: 2023-06-01" \
      -H "content-type: application/json" \
      -d "$(jq -n \
        --argjson tools "$TOOLS" \
        --arg msg "$USER_MSG" \
        '{
          model: "claude-opus-4-6",
          max_tokens: 1024,
          tools: $tools,
          tool_choice: {type: "auto", disable_parallel_tool_use: true},
          messages: [{role: "user", content: $msg}]
        }')")
    
    # When Claude calls a tool, the response has stop_reason "tool_use"
    # and the content array contains a tool_use block alongside any text.
    echo "stop_reason: $(echo "$RESPONSE" | jq -r '.stop_reason')"
    
    # Find the tool_use block. A response may contain text blocks before the
    # tool_use block, so filter by type rather than assuming position.
    TOOL_USE=$(echo "$RESPONSE" | jq '.content[] | select(.type == "tool_use")')
    TOOL_USE_ID=$(echo "$TOOL_USE" | jq -r '.id')
    echo "Tool: $(echo "$TOOL_USE" | jq -r '.name')"
    echo "Input: $(echo "$TOOL_USE" | jq -c '.input')"
    
    # Execute the tool. In a real system this would call your calendar API.
    # Here the result is hardcoded to keep the example self-contained.
    RESULT='{"event_id": "evt_123", "status": "created"}'
    
    # Send the result back. The tool_result block goes in a user message and
    # its tool_use_id must match the id from the tool_use block above. The
    # assistant's previous response is included so Claude has the full history.
    ASSISTANT_CONTENT=$(echo "$RESPONSE" | jq '.content')
    FOLLOWUP=$(curl -s https://api.anthropic.com/v1/messages \
      -H "x-api-key: $ANTHROPIC_API_KEY" \
      -H "anthropic-version: 2023-06-01" \
      -H "content-type: application/json" \
      -d "$(jq -n \
        --argjson tools "$TOOLS" \
        --arg msg "$USER_MSG" \
        --argjson assistant "$ASSISTANT_CONTENT" \
        --arg tool_use_id "$TOOL_USE_ID" \
        --arg result "$RESULT" \
        '{
          model: "claude-opus-4-6",
          max_tokens: 1024,
          tools: $tools,
          tool_choice: {type: "auto", disable_parallel_tool_use: true},
          messages: [
            {role: "user", content: $msg},
            {role: "assistant", content: $assistant},
            {role: "user", content: [
              {type: "tool_result", tool_use_id: $tool_use_id, content: $result}
            ]}
          ]
        }')")
    
    # With the tool result in hand, Claude produces a final natural-language
    # answer and stop_reason becomes "end_turn".
    echo "stop_reason: $(echo "$FOLLOWUP" | jq -r '.stop_reason')"
    echo "$FOLLOWUP" | jq -r '.content[] | select(.type == "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은 Claude가 캘린더 결과를 기다리고 있기 때문에 tool_use입니다. 결과를 전송한 후, 두 번째 stop_reason은 end_turn이며 콘텐츠는 사용자를 위한 자연어입니다.

    링 2: 에이전트 루프

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

    또 다른 변경 사항은 대화 기록입니다. 각 요청마다 messages 배열을 처음부터 다시 구성하는 대신, 실행 중인 목록을 유지하고 여기에 추가합니다. 모든 턴은 이전의 완전한 컨텍스트를 볼 수 있습니다.

    예상 출력

    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 블록을 반복 처리하세요.

    예상 출력

    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는 오류를 읽고 수정된 입력으로 재시도하거나, 사용자에게 명확한 설명을 요청하거나, 제한 사항을 설명할 수 있습니다.

    예상 출력

    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 데코레이터를 사용하여 타입 힌트와 독스트링에서 스키마를 추론합니다. TypeScript SDK는 Zod 스키마와 함께 betaZodTool을 사용합니다.

    Tool Runner는 Python, TypeScript, Ruby SDK에서 사용할 수 있습니다. Shell 및 CLI 탭에는 코드 대신 참고 사항이 표시됩니다. 셸 기반 스크립트에는 링 4 루프를 유지하세요.

    예상 출력

    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 추상화 참조.

    문제 해결

    일반적인 도구 사용 오류 수정.

    #!/bin/bash
    # Ring 2: The agentic loop.
    # Source for <CodeSource> in build-a-tool-using-agent.mdx.
    
    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"]
        }
      }
    ]'
    
    run_tool() {
      local name="$1"
      local input="$2"
      if [ "$name" = "create_calendar_event" ]; then
        local title=$(echo "$input" | jq -r '.title')
        jq -n --arg title "$title" '{event_id: "evt_123", status: "created", title: $title}'
      else
        echo "{\"error\": \"Unknown tool: $name\"}"
      fi
    }
    
    # Keep the full conversation history in a JSON array so each turn sees prior context.
    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]."}]'
    
    call_api() {
      curl -s https://api.anthropic.com/v1/messages \
        -H "x-api-key: $ANTHROPIC_API_KEY" \
        -H "anthropic-version: 2023-06-01" \
        -H "content-type: application/json" \
        -d "$(jq -n --argjson tools "$TOOLS" --argjson messages "$MESSAGES" \
          '{model: "claude-opus-4-6", max_tokens: 1024, tools: $tools, tool_choice: {type: "auto", disable_parallel_tool_use: true}, messages: $messages}')"
    }
    
    RESPONSE=$(call_api)
    
    # Loop until Claude stops asking for tools. Each iteration runs the requested
    # tool, appends the result to history, and asks Claude to continue.
    while [ "$(echo "$RESPONSE" | jq -r '.stop_reason')" = "tool_use" ]; do
      TOOL_USE=$(echo "$RESPONSE" | jq '.content[] | select(.type == "tool_use")')
      TOOL_NAME=$(echo "$TOOL_USE" | jq -r '.name')
      TOOL_INPUT=$(echo "$TOOL_USE" | jq -c '.input')
      TOOL_USE_ID=$(echo "$TOOL_USE" | jq -r '.id')
      RESULT=$(run_tool "$TOOL_NAME" "$TOOL_INPUT")
    
      ASSISTANT_CONTENT=$(echo "$RESPONSE" | jq '.content')
      MESSAGES=$(echo "$MESSAGES" | jq \
        --argjson assistant "$ASSISTANT_CONTENT" \
        --arg tool_use_id "$TOOL_USE_ID" \
        --arg result "$RESULT" \
        '. + [
          {role: "assistant", content: $assistant},
          {role: "user", content: [{type: "tool_result", tool_use_id: $tool_use_id, content: $result}]}
        ]')
    
      RESPONSE=$(call_api)
    done
    
    echo "$RESPONSE" | jq -r '.content[] | select(.type == "text") | .text'
    #!/bin/bash
    # Ring 3: Multiple tools, parallel calls.
    # Source for <CodeSource> in build-a-tool-using-agent.mdx.
    
    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"]
        }
      }
    ]'
    
    run_tool() {
      case "$1" in
        create_calendar_event)
          jq -n --arg title "$(echo "$2" | jq -r '.title')" '{event_id: "evt_123", status: "created", title: $title}' ;;
        list_calendar_events)
          echo '{"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}' ;;
        *)
          echo "{\"error\": \"Unknown tool: $1\"}" ;;
      esac
    }
    
    MESSAGES='[{"role": "user", "content": "Check what I have next Monday, then schedule a planning session that avoids any conflicts."}]'
    
    call_api() {
      curl -s https://api.anthropic.com/v1/messages \
        -H "x-api-key: $ANTHROPIC_API_KEY" \
        -H "anthropic-version: 2023-06-01" \
        -H "content-type: application/json" \
        -d "$(jq -n --argjson tools "$TOOLS" --argjson messages "$MESSAGES" \
          '{model: "claude-opus-4-6", max_tokens: 1024, tools: $tools, messages: $messages}')"
    }
    
    RESPONSE=$(call_api)
    
    while [ "$(echo "$RESPONSE" | jq -r '.stop_reason')" = "tool_use" ]; do
      # A single response can contain multiple tool_use blocks. Process all of
      # them and return all results together in one user message.
      TOOL_RESULTS='[]'
      while read -r block; do
        NAME=$(echo "$block" | jq -r '.name')
        INPUT=$(echo "$block" | jq -c '.input')
        ID=$(echo "$block" | jq -r '.id')
        RESULT=$(run_tool "$NAME" "$INPUT")
        TOOL_RESULTS=$(echo "$TOOL_RESULTS" | jq --arg id "$ID" --arg result "$RESULT" \
          '. + [{type: "tool_result", tool_use_id: $id, content: $result}]')
      done < <(echo "$RESPONSE" | jq -c '.content[] | select(.type == "tool_use")')
    
      MESSAGES=$(echo "$MESSAGES" | jq \
        --argjson assistant "$(echo "$RESPONSE" | jq '.content')" \
        --argjson results "$TOOL_RESULTS" \
        '. + [{role: "assistant", content: $assistant}, {role: "user", content: $results}]')
    
      RESPONSE=$(call_api)
    done
    
    echo "$RESPONSE" | jq -r '.content[] | select(.type == "text") | .text'
    #!/bin/bash
    # Ring 4: Error handling.
    # Source for <CodeSource> in build-a-tool-using-agent.mdx.
    
    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"]
        }
      }
    ]'
    
    run_tool() {
      case "$1" in
        create_calendar_event)
          local count=$(echo "$2" | jq '.attendees | length // 0')
          if [ "$count" -gt 10 ]; then
            echo "ERROR: Too many attendees (max 10)"
            return 1
          fi
          jq -n --arg title "$(echo "$2" | jq -r '.title')" '{event_id: "evt_123", status: "created", title: $title}' ;;
        list_calendar_events)
          echo '{"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}' ;;
        *)
          echo "ERROR: Unknown tool: $1"
          return 1 ;;
      esac
    }
    
    EMAILS=$(seq 0 14 | sed 's/.*/user&@example.com/' | paste -sd, -)
    MESSAGES="[{\"role\": \"user\", \"content\": \"Schedule an all-hands with everyone: $EMAILS\"}]"
    
    call_api() {
      curl -s https://api.anthropic.com/v1/messages \
        -H "x-api-key: $ANTHROPIC_API_KEY" \
        -H "anthropic-version: 2023-06-01" \
        -H "content-type: application/json" \
        -d "$(jq -n --argjson tools "$TOOLS" --argjson messages "$MESSAGES" \
          '{model: "claude-opus-4-6", max_tokens: 1024, tools: $tools, messages: $messages}')"
    }
    
    RESPONSE=$(call_api)
    
    while [ "$(echo "$RESPONSE" | jq -r '.stop_reason')" = "tool_use" ]; do
      TOOL_RESULTS='[]'
      while read -r block; do
        NAME=$(echo "$block" | jq -r '.name')
        INPUT=$(echo "$block" | jq -c '.input')
        ID=$(echo "$block" | jq -r '.id')
        if OUTPUT=$(run_tool "$NAME" "$INPUT"); then
          TOOL_RESULTS=$(echo "$TOOL_RESULTS" | jq --arg id "$ID" --arg result "$OUTPUT" \
            '. + [{type: "tool_result", tool_use_id: $id, content: $result}]')
        else
          # Signal failure so Claude can retry or ask for clarification.
          TOOL_RESULTS=$(echo "$TOOL_RESULTS" | jq --arg id "$ID" --arg result "$OUTPUT" \
            '. + [{type: "tool_result", tool_use_id: $id, content: $result, is_error: true}]')
        fi
      done < <(echo "$RESPONSE" | jq -c '.content[] | select(.type == "tool_use")')
    
      MESSAGES=$(echo "$MESSAGES" | jq \
        --argjson assistant "$(echo "$RESPONSE" | jq '.content')" \
        --argjson results "$TOOL_RESULTS" \
        '. + [{role: "assistant", content: $assistant}, {role: "user", content: $results}]')
    
      RESPONSE=$(call_api)
    done
    
    echo "$RESPONSE" | jq -r '.content[] | select(.type == "text") | .text'
    #!/bin/bash
    # Ring 5: The Tool Runner SDK abstraction.
    # Source for <CodeSource> in build-a-tool-using-agent.mdx.
    
    # The Tool Runner SDK abstraction is available in the Python, TypeScript,
    # and Ruby SDKs. There is no equivalent for raw curl requests. Switch to
    # the Python or TypeScript tab to see Ring 5, or keep the Ring 4 loop as
    # your shell implementation.