Skip to content

병렬 도구 사용 (Parallel Tool Use)

병렬 도구 호출을 켜고, 포맷하고, 끄는 방법, 그리고 메시지 기록 관리와 문제 해결까지 다루는 문서예요.

기본적으로 Claude는 하나의 응답에서 여러 도구를 호출할 수 있어요. 이 페이지에서는 그 호출들을 어떻게 실행하고, 병렬 처리가 계속 동작하도록 메시지 기록을 어떻게 포맷하며, 필요할 때 병렬 도구 사용을 어떻게 끄는지를 설명합니다. 하나씩만 호출하는 흐름은 도구 호출 처리 문서를 참고하세요.

실행 의미론 (Execution semantics)

Claude가 도구를 호출할 때 응답의 stop_reasontool_use가 되고, 한 번의 어시스턴트 턴 안에 여러 개의 tool_use 블록이 들어갈 수 있어요. 이 호출들을 어떻게 실행할지는 당신의 결정입니다. API가 실행 순서를 정해 주지는 않아요. 병렬로 실행하거나(Promise.all, asyncio.gather), 나타난 순서대로 순차적으로 실행하거나, 도구에 맞는 어떤 조합으로든 실행할 수 있죠.

전략은 도구가 하는 일에 맞춰 고르면 돼요. 서로 독립적이고 읽기 전용인 연산은 보통 병렬로 실행해도 안전하고, 대기 시간(latency)도 더 짧아져요. 반면 부수 효과(side effect)가 있거나 상태를 공유하거나 순서가 중요한 도구는 순차적으로 실행하는 편이 나을 수 있습니다.

어떤 전략을 쓰든, tool_use 블록 하나마다 tool_result 하나씩을 다음 사용자 메시지에 모아서 반환해야 해요. 각 결과는 tool_use_id로 해당 호출에 짝지어 주고, 모든 tool_result 블록은 그 메시지 안에서 어떤 텍스트 내용보다 먼저 위치시켜야 합니다. 전체 포맷 규칙은 도구 호출 처리 문서에 있어요. 특정 호출을 실행하지 않기로 했다면(예: 배치를 순차 실행하다가 앞선 호출이 실패한 경우) 그래도 그 호출에 대해 is_error: true와 짧은 설명을 담은 tool_result를 돌려줘야 합니다.

{
  "type": "tool_result",
  "tool_use_id": "toolu_02",
  "is_error": true,
  "content": "Not executed: the preceding write_file call failed."
}

컴퓨터 사용 도구브라우저 사용 도구는 더 엄격해요. Claude가 한 턴에 이 도구들의 멤버 호출을 여러 개 돌려준다면(배치 액션) 나타난 순서대로 순차 실행하고, 첫 번째 실패에서 멈춰야 합니다. 각 도구는 건너뛴 호출에 대해 반환할 정확한 텍스트를 정의해 두고 있어요.

병렬 도구 호출 테스트하기

아래 스크립트는 병렬 도구 호출을 유발할 요청을 보내고, 응답에 그 호출들이 들어 있는지 확인하며, 병렬 처리가 계속 동작하도록 도구 결과를 포맷해 주는 예시예요. 환경에 ANTHROPIC_API_KEY를 설정해 두고 실행하면 됩니다:

client = Anthropic()

# 도구 정의
tools = [
    {
        "name": "get_weather",
        "description": "Get the current weather in a given location",
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city and state, e.g. San Francisco, CA",
                }
            },
            "required": ["location"],
        },
    },
    {
        "name": "get_time",
        "description": "Get the current time in a given timezone",
        "input_schema": {
            "type": "object",
            "properties": {
                "timezone": {
                    "type": "string",
                    "description": "The timezone, e.g. America/New_York",
                }
            },
            "required": ["timezone"],
        },
    },
]

# 병렬 도구 호출이 있는 테스트 대화
messages = [
    {
        "role": "user",
        "content": "What's the weather in SF and NYC, and what time is it there?",
    }
]

# 초기 요청
print("Requesting parallel tool calls...")
response = client.messages.create(
    model="claude-opus-5", max_tokens=1024, messages=messages, tools=tools
)

# 병렬 도구 호출 확인
tool_uses = [block for block in response.content if block.type == "tool_use"]
print(f"\n✓ Claude made {len(tool_uses)} tool calls")

if len(tool_uses) > 1:
    print("✓ Parallel tool calls detected!")
    for tool in tool_uses:
        print(f"  - {tool.name}: {tool.input}")
else:
    print("✗ No parallel tool calls detected")

# 도구 실행을 흉내 내고 결과를 올바르게 포맷
tool_results = []
for tool_use in tool_uses:
    if tool_use.name == "get_weather":
        if "San Francisco" in str(tool_use.input):
            result = "San Francisco: 68°F, partly cloudy"
        else:
            result = "New York: 45°F, clear skies"
    else:  # get_time
        if "Los_Angeles" in str(tool_use.input):
            result = "2:30 PM PST"
        else:
            result = "5:30 PM EST"

    tool_results.append(
        {"type": "tool_result", "tool_use_id": tool_use.id, "content": result}
    )

# 도구 결과와 함께 대화 이어가기
messages.extend(
    [
        {"role": "assistant", "content": response.content},
        {"role": "user", "content": tool_results},  # 모든 결과를 하나의 메시지에!
    ]
)

# 최종 응답 받기
print("\nGetting final response...")
final_response = client.messages.create(
    model="claude-opus-5", max_tokens=1024, messages=messages, tools=tools
)

final_text = next(
    block.text for block in final_response.content if block.type == "text"
)
print(f"\nClaude's response:\n{final_text}")

# 포맷 검증
print("\n--- Verification ---")
print(f"✓ Tool results sent in single user message: {len(tool_results)} results")
print("✓ No text before tool results in content array")
print("✓ Conversation formatted correctly for future parallel tool use")

마지막의 요약 줄은 병렬 처리를 유지하는 두 가지 포맷 규칙을 다시 짚어 줘요. 모든 도구 결과를 하나의 사용자 메시지로 돌려주고, 그 메시지 안에서 결과 앞에 텍스트 내용이 오지 않아야 한다는 것이죠.

병렬 도구 사용 극대화하기

Claude 4 이상 모델은 요청이 여러 도구로 이득을 볼 때 기본적으로 병렬 도구 호출을 해요. 모든 모델에 대해, 맞춤 프롬프트(prompting)를 통해서도 병렬 도구 호출 가능성을 높일 수 있습니다.

병렬 도구 사용 끄기

병렬 도구 사용은 기본적으로 켜져 있어요. 끄려면 tool_choice 객체 안에 disable_parallel_tool_use: true를 설정하면 됩니다. 최상위 요청 파라미터가 아니라 tool_choice 안에 넣는 필드라는 점을 기억하세요. 효과는 tool_choice 타입에 따라 달라집니다.

최대 한 번의 도구 호출

tool_choice 타입이 auto(기본값)일 때 disable_parallel_tool_use: true를 설정하면, Claude는 응답당 최대 한 개의 도구만 호출해요. 그래도 아무 도구 없이 일반 텍스트로 답할 수는 있습니다. 아래 예시에서 표시된 줄들이 표준 도구 사용 요청과 달라진 전부예요:

client = Anthropic()

response = client.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    tools=[
        {
            "name": "get_weather",
            "description": "Get the current weather in a given location",
            "input_schema": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "The city and state, e.g. San Francisco, CA",
                    }
                },
                "required": ["location"],
            },
        }
    ],
    tool_choice={"type": "auto", "disable_parallel_tool_use": True},
    messages=[
        {
            "role": "user",
            "content": "What is the weather in San Francisco and New York?",
        }
    ],
)
print(response.content)

정확히 한 번의 도구 호출

tool_choice 타입이 any 또는 tool일 때 disable_parallel_tool_use: true를 설정하면, Claude는 정확히 한 개의 도구만 호출해요. Claude Fable 5.1과 Claude Mythos 5.1은 이런 tool_choice 타입을 지원하지 않아요(도구 사용 강제하기 참고). 아래 예시는 any를 썼어요. 같은 필드가 tool에서도 동작합니다.

client = Anthropic()

response = client.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    tools=[
        {
            "name": "get_weather",
            "description": "Get the current weather in a given location",
            "input_schema": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "The city and state, e.g. San Francisco, CA",
                    }
                },
                "required": ["location"],
            },
        }
    ],
    tool_choice={"type": "any", "disable_parallel_tool_use": True},
    messages=[
        {
            "role": "user",
            "content": "What is the weather in San Francisco and New York?",
        }
    ],
)
print(response.content)

문제 해결 (Troubleshooting)

Claude가 기대한 대로 병렬 도구 호출을 하지 않는다면, 아래 흔한 원인들을 확인해 보세요.

1. 잘못된 도구 결과 포맷

가장 흔한 원인은 대화 기록에서 도구 결과를 잘못 포맷한 경우예요. 이렇게 하면 Claude가 병렬 호출을 피하도록 "학습"하게 됩니다. 병렬 도구 사용과 관련해 구체적으로는:

  • 틀림: 도구 결과마다 별도의 사용자 메시지로 나눠 보내는 것
  • 맞음: 모든 도구 결과를 하나의 사용자 메시지에 모아 보내는 것
// 틀림: 사용자 메시지가 나뉘면 병렬 도구 사용이 줄어든다
[
  {"role": "assistant", "content": [tool_use_1, tool_use_2]},
  {"role": "user", "content": [tool_result_1]},
  {"role": "user", "content": [tool_result_2]}  // 별도의 메시지
]

// 맞음: 모든 결과를 하나의 사용자 메시지로 보내면 병렬 도구 사용이 유지된다
[
  {"role": "assistant", "content": [tool_use_1, tool_use_2]},
  {"role": "user", "content": [tool_result_1, tool_result_2]}  // 하나의 메시지
]

다른 포맷 규칙은 도구 호출 처리 문서를 참고하세요.

2. 약한 프롬프트

기본 프롬프트로는 충분하지 않을 수 있어요. 병렬 도구 사용 극대화하기 섹션의 더 강력한 시스템 프롬프트를 사용해 보세요.

3. 병렬 도구 사용 측정하기

병렬 도구 호출이 실제로 동작하는지 확인하려면:

messages = []  # 실행 전반에서 client.messages.create가 반환한 메시지 객체들

tool_call_messages = [
    msg for msg in messages if any(block.type == "tool_use" for block in msg.content)
]
total_tool_calls = sum(
    len([block for block in msg.content if block.type == "tool_use"])
    for msg in tool_call_messages
)
avg_tools_per_message = (
    total_tool_calls / len(tool_call_messages) if tool_call_messages else 0.0
)
print(f"Average tools per message: {avg_tools_per_message}")
# 병렬 호출이 동작 중이라면 1.0보다 커야 한다

4. 배치 내 호출이 서로 의존하는 것처럼 보일 때

실행 순서는 당신의 선택이에요. 도구에 순서 의존성이 있다면 배치를 순차 실행하고 첫 실패에서 멈추는 것이 유효한 전략이고, 컴퓨터 사용브라우저 사용 도구에는 요구되는 방식이기도 해요. 실행하지 않은 호출에는 is_error: true를 돌려줍니다. 병렬로 실행했는데 전제 조건이 아직 끝나지 않아 실패한 호출이 있다면, 자연스러운 오류 메시지와 함께 is_error: true를 반환하면 돼요. Claude는 다음 턴에 그 호출을 다시 발행할 거예요. 서로 의존하는 호출이 함께 나타나는 걸 줄이려면 시스템 프롬프트에 이 문장을 추가하세요: "Only batch tool calls that are independent of each other."

다음 단계

SDK의 Tool Runner 추상화를 쓰면 에이전트 루프, 오류 래핑, 타입 안전성을 자동으로 처리할 수 있어요.

tool_use 블록을 파싱하고 tool_result 응답을 포맷하며 is_error로 오류를 처리하는 방법을 익혀보세요.

도구 스키마를 지정하고 효과적인 설명을 작성하며, Claude가 언제 도구를 호출할지 제어하는 방법을 살펴보세요.