Skip to content

도구 호출 처리 (Handle Tool Calls)

Claude한테 도구를 쓰라고 시키면, 응답에 tool_use 블록이 들어와요. 우리는 그 블록을 읽어서 실제 도구를 실행하고, 그 결과를 tool_result 블록으로 다시 돌려주는 거죠. 이 페이지는 그 전체 흐름을 다룹니다 — Claude 응답에서 tool_use 블록을 읽고, 회신에서 tool_result 블록을 제대로 포맷하며, 오류가 났을 때는 is_error로 알리는 방법까지요.

이걸 자동으로 처리해 주는 SDK 추상화가 있는데, 그건 Tool Runner라는 이름이라 따로 문서로 정리돼 있어요. 수동으로 도구 실행을 제어할 필요가 없는 보통 경우엔 Tool Runner가 알아서 처리해 주니까, 이 페이지는 도구 실행을 직접 제어하고 싶을 때 보면 돼요.

Claude의 응답 모양은 우리가 클라이언트 도구를 쓰는지 서버 도구를 쓰는지에 따라 달라집니다. 두 경우를 따로 살펴볼게요.

클라이언트 도구의 결과 처리하기

클라이언트 도구를 쓴 응답은 stop_reasontool_use로 오고, tool_use 콘텐츠 블록이 하나 이상 딸려 옵니다. 각 블록에는 이런 정보가 들어 있어요.

  • id: 이 도구 사용 블록을 식별하는 고유한 값이에요. 나중에 도구 결과를 짝지을 때 이 id를 사용해요.
  • name: 사용 중인 도구의 이름이에요.
  • input: 도구에 전달할 입력을 담은 객체이고, 도구의 input_schema 규칙을 따릅니다.

여기서 한 가지 더 짚을 게 있어요. 컴퓨터 사용(computer use)이나 브라우저 사용(browser use) 도구 세트의 멤버인 경우, tool_use 블록에 toolset_name 필드가 하나 더 붙어요. 값은 "computer" 또는 "browser" 중 하나죠. 이때 블록의 namescreenshot이나 navigate처럼 Claude가 실제로 호출하는 멤버 도구의 이름이라서, 이런 블록은 nametoolset_name 두 필드를 모두 보고 디스패치해야 해요.

클라이언트 도구에 대한 도구 사용 응답을 받으면, 우리는 다음 순서로 처리하면 됩니다.

  1. tool_use 블록에서 name, id, input을 뽑아요.
  2. 그 도구 이름에 해당하는, 코드베이스 안의 실제 도구를 실행하되, 도구 input을 그대로 전달해요.
  3. roleuser인 새 메시지를 보내 대화를 이어가는데, tool_result 타입의 content 블록을 넣고 그 안에 아래 정보를 담아요.
  4. tool_use_id: 이 결과가 답하는 도구 사용 요청의 id예요.
  5. content (선택): 도구의 결과예요. 문자열 형태("content": "15 degrees")일 수도 있고, 중첩된 콘텐츠 블록 목록("content": [{"type": "text", "text": "15 degrees"}])일 수도 있으며, 문서 블록 목록("content": [{"type": "document", "source": {"type": "text", "media_type": "text/plain", "data": "15 degrees"}}])일 수도 있어요. 이 콘텐츠 블록은 text, image, document, 또는 search_result 타입을 쓸 수 있습니다.
  6. is_error (선택): 도구 실행 중 오류가 났다면 true로 설정해요.

컴퓨터 사용이나 브라우저 사용 멤버 블록에 답하는 tool_result는, tool_use 블록과 동일한 toolset_name 값을 그대로 반환해야 해요. 이걸 빼먹으면 멤버 결과가 거부됩니다. 그리고 content도 더 제한적이에요. 멤버 결과는 textimage 블록만 포함할 수 있고, 브라우저 사용 결과는 여기에 browser_state 블록을 하나 더 추가할 수 있어요 (탭 관리 멤버는 그 블록만 반환합니다).

포맷 요구 사항 — 놓치기 쉬운 부분

결과를 돌려줄 때 지켜야 할 포맷 규칙이 있는데, 여기서 실수하는 경우가 꽤 많으니 정확히 짚고 넘어갈게요.

  • 도구 결과 블록은 메시지 기록에서 대응하는 도구 사용 블록 바로 뒤에 와야 해요. 어시스턴트의 도구 사용 메시지와 사용자의 도구 결과 메시지 사이에 다른 어떤 메시지도 들어가면 안 됩니다.
  • 도구 결과를 담은 사용자 메시지에서는, tool_result 블록이 content 배열의 맨 앞에 와야 해요. 모든 텍스트는 모든 도구 결과 뒤에 와야 합니다.
  • 어시스턴트 턴이 아직 결과 블록이 없는 서버 도구도 호출한 경우라면, 사용자 메시지는 tool_result 블록만 포함해야 해요. 결과 뒤에 텍스트가 오면 턴이 조기에 끝나버려요. Claude가 직접 호출한 서버 도구의 경우엔, 요청이 해결되지 않은 서버 도구의 이름을 명시하는 400 오류와 함께 실패합니다. 이 내용은 '중지 이유 및 폴백' 문서를 참고하면 됩니다.

예를 들어, 아래처럼 tool_result 앞에 텍스트를 넣으면 400 오류가 발생해요.

{
  "role": "user",
  "content": [
    { "type": "text", "text": "Here are the results:" }, // ❌ tool_result 앞에 텍스트
    { "type": "tool_result", "tool_use_id": "toolu_01" /* ... */ }
  ]
}

어시스턴트 턴이 클라이언트 도구만 호출한 경우라면, 아래처럼 tool_result 뒤에 텍스트가 오는 건 올바른 형태입니다.

{
  "role": "user",
  "content": [
    { "type": "tool_result", "tool_use_id": "toolu_01" /* ... */ },
    { "type": "text", "text": "What should I do next?" } // ✅ tool_result 뒤에 텍스트
  ]
}

만약 "tool_use ids were found without tool_result blocks immediately after" 같은 오류를 받으면, 도구 결과가 위 규칙대로 포맷됐는지 확인해 보세요.

보안: 도구 결과는 신뢰할 수 없는 입력

도구 결과는 웹 페이지, 수신 이메일, 사용자 업로드, 서드파티 API처럼 우리가 통제할 수 없는 소스에서 온 콘텐츠를 담고 있는 경우가 많아요. 이런 콘텐츠는 전부 신뢰할 수 없는 것으로 취급해야 합니다. 공격자가 이런 콘텐츠에 영향력을 행사할 수 있다면, Claude의 방향을 바꾸려는 지시를 몰래 심어둘 수도 있어요 — 이걸 간접 프롬프트 인젝션(indirect prompt injection)이라고 해요. 그래서 신뢰할 수 없는 콘텐츠는 시스템 프롬프트나 일반 사용자 text 블록에 넣지 말고, 반드시 tool_result 블록 안에 넣어야 해요. 추가적인 강화 방법은 '탈옥 및 프롬프트 인젝션 완화' 문서에서 다룹니다.

도구 결과를 받은 뒤에는, Claude가 그 정보를 사용해서 원래 사용자 프롬프트에 대한 응답 생성을 계속 이어가요.

서버 도구의 결과 처리하기

서버 도구는 우리 몫이 아닙니다. Claude가 도구를 내부적으로 실행하고, 추가적인 사용자 상호작용 없이 결과를 응답에 직접 통합해요.

참고로 응답에는 클라이언트 tool_use 블록과, 결과 블록이 없는 server_tool_use 블록이 함께 포함될 수 있어요. 그 서버 도구 호출은 아직 완료되지 않은 것이고, 결과 블록은 이후 응답에서 도착합니다. 이 경우 클라이언트 도구에 대한 tool_result 블록만 담은 사용자 메시지로 회신하되, 동일한 tools 배열을 유지해야 해요. Claude가 직접 호출한 서버 도구의 경우엔, API가 해당 요청에서 이를 실행하고 다음 응답이 그 결과 블록으로 시작해요. 자세한 건 '중지 이유 및 폴백' 문서를 참고하세요.

다른 API와의 차이점

도구 사용을 분리하거나 tool이나 function같은 특수 역할을 따로 쓰는 다른 API들과 달리, Claude API는 도구를 userassistant 메시지 구조에 직접 통합합니다. 즉 메시지는 text, image, tool_use, tool_result 블록의 배열로 이뤄지고, user 메시지는 클라이언트 콘텐츠와 tool_result를, assistant 메시지는 AI가 생성한 콘텐츠와 tool_use를 담아요. 이 구조 자체가 Claude API의 특징이니까, 다른 API 습관이 있다면 이 부분을 먼저 인지해 두는 게 좋아요.

is_error로 오류 처리하기

Claude와 도구를 쓸 때 발생할 수 있는 오류는 크게 몇 가지 유형으로 나뉩니다.

도구 실행 오류 (Tool execution error)

도구가 실행 중에 오류를 던지는 경우예요. 예를 들어 날씨 데이터를 가져오다가 네트워크 오류가 난 상황이죠. 이때는 content에 오류 메시지를 담고 "is_error": true를 함께 돌려주면 됩니다.

{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
      "content": "ConnectionError: the weather service API is not available (HTTP 500)",
      "is_error": true
    }
  ]
}

그러면 Claude는 이 오류를 사용자에게 전하는 응답에 반영해요. 예를 들면 "날씨 서비스 API를 사용할 수 없어서 현재 날씨를 가져오지 못했어요. 나중에 다시 시도해 주세요." 같은 식이죠.

여기서 짚을 게 하나 있어요. 오류 메시지를 설명적으로 써야 한다는 점입니다. "failed"처럼 막연한 오류 대신, 무엇이 잘못됐고 Claude가 다음에 뭘 시도하면 좋을지를 담아주세요. 예를 들어 "Rate limit exceeded. Retry after 60 seconds."처럼요. 이렇게 해야 Claude가 추측 없이 복구하거나 대응할 수 있어요.

잘못된 도구 이름 (Invalid tool name)

Claude가 도구를 쓰려는 시도 자체가 잘못된 경우예요. 예를 들어 필수 파라미터가 빠진 경우죠. 보통은 Claude가 도구를 올바르게 쓸 만큼 정보가 부족했기 때문이에요. 개발 중에는 도구 정의의 description 값을 더 상세한 설명으로 바꿔서 요청을 다시 해보는 게 가장 좋은 방법입니다. 아니면 오류를 가리키는 tool_result로 대화를 이어갈 수도 있는데, 그러면 Claude가 빠진 정보를 채워서 다시 도구를 시도해요.

{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
      "content": "Error: Missing required 'location' parameter",
      "is_error": true
    }
  ]
}

도구 요청이 잘못되었거나 파라미터가 빠진 경우, Claude는 수정을 가하면서 2~3회 재시도한 다음에야 사용자에게 사과합니다. 잘못된 도구 호출을 아예 없애고 싶다면, stricttrue로 설정하는 엄격한 도구 사용(strict tool use)을 쓰면 됩니다.

서버 도구 오류 (Server tool errors)

서버 도구가 오류를 만나면 (예: 웹 검색의 네트워크 문제), Claude가 그 오류를 투명하게 처리하고 사용자에게 대체 응답이나 설명을 시도해요. 클라이언트 도구와 달리 서버 도구는 우리가 is_error 결과를 처리할 필요가 없습니다.

웹 검색 도구의 경우 가능한 오류 코드는 다음과 같아요.

  • too_many_requests: 요청 한도(rate limit)를 초과했어요.
  • invalid_input: 검색 쿼리 파라미터가 잘못됐어요.
  • max_uses_exceeded: 웹 검색 도구 최대 사용 횟수를 넘었어요.
  • query_too_long: 쿼리가 최대 길이를 초과했어요.
  • unavailable: 내부 오류가 발생했어요.

다음 단계

이 흐름을 직접 다루고 싶지 않다면, SDK가 tool_use 루프와 결과 포맷, 재시도를 대신 관리해 주는 Tool Runner를 쓰는 걸 검토해 보세요. 그리고 한 턴에 여러 도구를 호출하는 응답을 다루는 병렬 도구 사용, Claude를 올바른 도구로 이끄는 스키마와 설명을 작성하는 도구 정의 문서도 이어서 보면 좋아요.


출처 (원문): Handle tool calls — Anthropic Claude Docs