로드 중

병렬 도구 호출: 지연 시간 줄이기

강의 310분
로드 중

병렬 도구 호출: 지연 시간 줄이기

요약대본

Claude는 한 번의 응답으로 여러 도구를 요청할 수 있으며, 여러분의 코드는 이를 함께 실행할 수 있습니다. 이 영상에서는 가상의 엔지니어링 컨설팅 회사인 Meridian의 작업 현장을 점검합니다. 한 번에 하나씩 도구를 호출하면 19.9초가 걸리지만, 병렬로 도구를 호출하면 10.8초가 걸립니다.

이 영상의 데모는 2026년 9월에 녹화되었습니다. Claude Sonnet 5 (claude-sonnet-5)에서 실행되었습니다. 최신 모델에서는 결과가 다를 수 있습니다.

도구 호출을 한 번에 하나씩 실행하면 사용자는 매번 차례대로 기다려야 합니다.

Claude가 요청하는 도구들이 서로 의존하지 않는 경우, Claude는 한 번의 응답으로 그 도구들을 모두 요청할 수 있습니다. Claude는 기본적으로 이렇게 동작하므로, 여러분의 루프는 모든 도구 호출을 처리하고 각각에 대해 결과를 반환해야 합니다.

하나의 응답에 담긴 세 번의 도구 호출

병렬 도구 사용이란 하나의 응답에 여러 개의 tool_use 블록이 담기는 것을 의미합니다.

Meridian에는 작업 현장이 현장 작업 준비가 되었는지 확인하는 Python 스크립트가 있습니다. 현장 점검에는 서로 의존하지 않는 세 가지 조회가 필요합니다: 프로젝트 상태, 현장의 날씨, 허가 상태입니다.

다음은 Meridian의 현장 점검에 대한 Claude의 응답으로, Claude가 원하는 각 도구마다 하나의 tool_use 블록이 포함되어 있습니다.

json
{
  "role": "assistant",
  "stop_reason": "tool_use",
  "content": [
    {"type": "tool_use", "id": "toolu_01", "name": "get_project_status",
     "input": {"job_number": "MEG-2026-0142"}},
    {"type": "tool_use", "id": "toolu_02", "name": "get_site_weather",
     "input": {"site": "Riverside"}},
    {"type": "tool_use", "id": "toolu_03", "name": "get_permit_state",
     "input": {"permit_id": "P-88213"}}
  ]
}

이 응답의 stop_reason은 tool_use이며, 각 도구 호출은 고유한 id를 가지고 있습니다.

모든 도구 호출 실행하기

하나의 도구 호출만을 위해 작성된 루프는 첫 번째 tool_use 블록만 읽습니다. 이 경우 결과를 하나만 보내게 되고, 나머지 두 개의 도구 호출은 결과를 받지 못하며, API는 요청을 거부합니다. 그러므로 응답에 있는 모든 tool_use 블록을 수집해야 합니다.

python
calls = [b for b in resp.content if b.type == "tool_use"]

다음으로, 이 도구 호출들을 동시에 실행합니다. 각 조회는 대부분의 시간을 다른 시스템의 응답을 기다리는 데 소비합니다. 각각에 별도의 스레드를 부여하면, 세 가지 대기 시간이 누적되지 않고 서로 겹치게 됩니다.

python
with ThreadPoolExecutor() as pool:
    results = list(pool.map(run_tool, calls))

run_tool은 하나의 도구 호출을 실행하고 그 결과를 반환하며, 이후 두 섹션에서 이를 구축해 나갑니다.

도구 호출들이 데이터만 읽고 서로 의존하지 않는 경우에는, Meridian의 조회처럼 이를 동시에 실행해도 대체로 안전합니다. 도구가 데이터를 변경하거나 반드시 정해진 순서대로 실행되어야 하는 경우에는, 하나씩 차례대로 실행하는 편이 더 나을 수 있습니다.

모든 결과를 하나의 메시지로 돌려보내기

세 개의 도구 호출이 모두 실행되면, Claude는 그 결과를 기다리고 있습니다. 각 결과는 tool_result 블록으로 돌아가며, 그 tool_use_id는 해당 결과가 답변하는 도구 호출의 id를 담고 있습니다.

python
result = {"type": "tool_result", "tool_use_id": call.id}

모든 tool_use 블록은 하나의 tool_result를 받으며, 이들은 모두 다음 사용자 메시지에 함께 담깁니다.

python
messages.append({"role": "assistant", "content": resp.content})
messages.append({"role": "user", "content": results})
상단 띠는 도구 호출 세 개가 담긴 Claude의 단일 응답을 보여 줍니다. toolu_01 get_project_status, toolu_02 get_site_weather, toolu_03 get_permit_state입니다. 그 아래 왼쪽 패널에는 체크 표시가 있으며, 도구 결과 세 개를 모두 담은 사용자 메시지 하나를 보여 줍니다. 오른쪽 패널에는 X 표시가 있으며, 결과를 하나씩 담은 사용자 메시지 세 개를 보여 줍니다.

세 가지 결과를 모두 담은 하나의 사용자 메시지는 Claude가 계속 병렬로 도구를 호출하도록 유지합니다.

만약 여러분의 루프가 각 결과를 별도의 메시지로 보낸다면, 대화는 마치 Claude가 한 번에 하나의 도구만 요청한 것처럼 읽히게 됩니다. 이는 Claude가 병렬 도구 호출을 더 적게 하게 되는 흔한 원인입니다.

잘못된 도구 호출에는 오류 결과를 반환하기

세 개의 도구 호출을 한 번에 실행한다는 것은 하나는 실패하고 나머지 둘은 성공할 수 있다는 의미입니다. 여러분의 코드가 예외를 발생시키고 이를 아무도 처리하지 않으면, 루프가 중단되고 정상적인 두 결과도 함께 사라집니다.

그러므로 실패도 결과로 돌려보내세요. 이는 오류 메시지를 content로 담고 is_error: true를 추가한 평범한 tool_result입니다. 이 메시지는 무엇이 잘못되었는지와 Claude가 다음에 무엇을 시도해야 하는지를 설명해야 합니다.

python
try:
    result["content"] = HANDLERS[call.name](**call.input)
except Exception as exc:
    result["content"] = f"{call.name} failed: {exc}"
    result["is_error"] = True

HANDLERS는 도구 이름을 각 도구의 작업을 수행하는 Python 함수에 매핑한 딕셔너리입니다. 각 함수는 문자열을 반환하는데, tool_result의 content는 문자열 또는 콘텐츠 블록의 리스트이기 때문입니다.

루프 전체 조립하기

위의 코드 조각들은 아래의 Meridian 루프에 맞춰 들어갑니다. 스크립트는 이미 client, tools, prompt, HANDLERS를 설정해 두었습니다.

로드 중

핵심 요약

  • 응답에 있는 모든 도구 호출을 실행하고, 서로 의존하지 않는 경우에는 동시에 실행하세요.
  • 각 도구 호출에 대해 하나의 tool_result를 돌려보내고, 이를 모두 하나의 사용자 메시지에 담으세요.
  • 각 결과를 별도의 메시지로 보내지 마세요. 이는 병렬 도구 사용을 줄이기 때문입니다.
  • 실패는 is_error: true를 담은 결과로 돌려보내서, 하나의 잘못된 도구 호출이 루프를 멈추지 않도록 하세요.