컴퓨터 사용 도구

컴퓨터 사용 도구 (Computer use tool)

컴퓨터 사용 도구를 통해 Claude는 컴퓨터 환경과 상호작용할 수 있어요. 스크린샷 기능과 마우스/키보드 제어를 제공해서 자율적인 데스크톱 상호작용을 가능하게 해주죠.

컴퓨터 사용 도구는 Anthropic이 정의한 클라이언트 도구셋이에요. tools에 {"type": "computer_toolset_20260801"} 항목 하나를 추가하면 Claude가 screenshot, left_click, type, zoom 같은 17개의 구성원(member) 도구를 받고, 여러분의 애플리케이션이 여러분이 제어하는 환경에서 모든 호출을 실행해요. 현재 Claude Managed Agents에서는 사용할 수 없어요. Claude의 호출은 name이 구성원이고 "toolset_name": "computer"를 담는 tool_use 블록이며, 턴당 여러 개(즉 배치 행동)인 경우가 많아요.

웹페이지 안에 머무는 작업에는 브라우저 사용 도구가 더 잘 맞아요. 그 구성원 도구들은 페이지 자체를 읽고 행동하며 전체 데스크톱 환경이 필요 없거든요.

컴퓨터 사용은 `computer_toolset_20260801` 도구셋으로 Claude API와 [Google Cloud](https://platform.claude.com/docs/en/build-with-claude/claude-on-vertex-ai)에서 사용할 수 있어요. 지원 모델은 [호환성](https://platform.claude.com/docs/en/agents-and-tools/tool-use/computer-use-tool#compatibility) 참고.

기존 computer_20251124 통합은 이전 도구 버전 아래 나열된 모델에서 계속 작동하고, 이전 도구 버전은 도구셋을 지원하지 않는 모델과 플랫폼에서 베타로 계속 사용할 수 있어요. 업그레이드는 computer_20251124에서 마이그레이션, 베타 헤더는 이전 도구 버전 참고.

출처: 문서

본문

보안 고려 사항

컴퓨터 사용은 표준 API 기능과 구별되는 독특한 위험이 있어요. 인터넷과 상호작용할 때 이 위험이 더 커져요.

위험을 최소화하려면 다음과 같은 예방 조치를 고려하세요:
  1. 직접적인 시스템 공격이나 사고를 막기 위해 최소 권한의 전용 가상 머신이나 컨테이너를 사용하세요.
  2. 정보 도난을 막기 위해 계정 로그인 정보 같은 민감한 데이터를 모델에 주지 마세요.
  3. 악성 콘텐츠에 대한 노출을 줄이기 위해 인터넷 접근을 도메인 허용 목록으로 제한하세요.
  4. 실제 세상에 의미 있는 결과를 초래할 수 있는 결정과 명시적 동의가 필요한 작업(쿠키 수락, 금전 거래 완료, 서비스 약관 동의 등)에는 사람이 확인하도록 요청하세요.

어떤 상황에서는 Claude가 여러분의 지시와 충돌해도 콘텐츠에서 찾은 명령을 따를 수 있어요. 예를 들어 웹페이지나 이미지에 담긴 지시가 여러분의 지시를 덮어쓰거나 Claude가 실수하게 만들 수 있어요. 프롬프트 인젝션과 관련된 위험을 피하려면 Claude를 민감한 데이터와 행동에서 격리하는 예방 조치를 취하세요.

Anthropic은 모델이 이런 프롬프트 인젝션에 저항하도록 학습시켰고 방어 계층을 추가했어요. 컴퓨터 사용 도구를 쓰면 분류기가 도구가 돌려주는 것(스크린샷 같은)을 자동으로 스캔해 잠재적 프롬프트 인젝션을 표시해요. 분류기가 잠재적 프롬프트 인젝션을 찾아내면, 행동하기 전에 그 지시가 정말 사용자에게서 왔는지 확인하도록 모델을 자동으로 유도해요.

이 추가 보호는 모든 사용 사례에 이상적이진 않아요(사람이 루프에 없는 사용 사례 등). 끄고 싶다면 지원팀에 문의하세요. 위 예방 조치는 분류기가 있어도 여전히 중요해요.

제품에서 컴퓨터 사용을 활성화하기 전에 최종 사용자에게 관련 위험을 알리고 동의를 받으세요.

빠른 시작

Messages API 요청의 tools 배열에 {"type": "computer_toolset_20260801"}으로 컴퓨터 사용 도구셋을 추가하세요. 요청에 베타 헤더는 필요 없어요. 이 예제는 Claude가 컴퓨터 사용과 함께 전형적으로 쓰는 텍스트 편집기 도구와 bash 도구도 선언해요:

```bash cURL curl https://api.anthropic.com/v1/messages \ -H "content-type: application/json" \ -H "x-api-key: $ANTHR...KEY" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "claude-opus-5-5", "max_tokens": 1024, "tools": [ { "type": "computer_toolset_20260801" }, { "type": "text_editor_20250728", "name": "str_replace_based_edit_tool" }, { "type": "bash_20250124", "name": "bash" } ], "messages": [ { "role": "user", "content": "Save a picture of a cat to my desktop." } ] }' ```
ant messages create <<'YAML'
model: claude-opus-5-5
max_tokens: 1024
tools:
  - type: computer_toolset_20260801
  - type: text_editor_20250728
    name: str_replace_based_edit_tool
  - type: bash_20250124
    name: bash
messages:
  - role: user
    content: Save a picture of a cat to my desktop.
YAML
client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    tools=[
        {"type": "computer_toolset_20260801"},
        {"type": "text_editor_20250728", "name": "str_replace_based_edit_tool"},
        {"type": "bash_20250124", "name": "bash"},
    ],
    messages=[{"role": "user", "content": "Save a picture of a cat to my desktop."}],
)
print(response)
const client = new Anthropic();

const response = await client.messages.create({
  model: "claude-opus-5-5",
  max_tokens: 1024,
  tools: [
    {
      type: "computer_toolset_20260801"
    },
    {
      type: "text_editor_20250728",
      name: "str_replace_based_edit_tool"
    },
    {
      type: "bash_20250124",
      name: "bash"
    }
  ],
  messages: [{ role: "user", content: "Save a picture of a cat to my desktop." }]
});

console.log(response);
var client = new AnthropicClient();

var parameters = new MessageCreateParams
{
    Model = Model.ClaudeOpus5_5,
    MaxTokens = 1024,
    Tools =
    [
        new ComputerToolset20260801(),
        new ToolTextEditor20250728(),
        new ToolBash20250124(),
    ],
    Messages =
    [
        new MessageParam
        {
            Role = Role.User,
            Content = "Save a picture of a cat to my desktop.",
        },
    ],
};

var response = await client.Messages.Create(parameters);
Console.WriteLine(response);
client := anthropic.NewClient()

response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
	Model:     anthropic.ModelClaudeOpus5_5,
	MaxTokens: 1024,
	Tools: []anthropic.ToolUnionParam{
		{OfComputerToolset20260801: &anthropic.ComputerToolset20260801Param{}},
		{OfTextEditor20250728: &anthropic.ToolTextEditor20250728Param{}},
		{OfBashTool20250124: &anthropic.ToolBash20250124Param{}},
	},
	Messages: []anthropic.MessageParam{
		anthropic.NewUserMessage(anthropic.NewTextBlock("Save a picture of a cat to my desktop.")),
	},
})
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.RawJSON())
import com.anthropic.models.messages.ComputerToolset20260801;
// ...
import com.anthropic.models.messages.ToolBash20250124;
import com.anthropic.models.messages.ToolTextEditor20250728;

void main() {
    AnthropicClient client = AnthropicOkHttpClient.fromEnv();

    MessageCreateParams params = MessageCreateParams.builder()
        .model(Model.CLAUDE_OPUS_5_5)
        .maxTokens(1024L)
        .addTool(ComputerToolset20260801.builder().build())
        .addTool(ToolTextEditor20250728.builder().build())
        .addTool(ToolBash20250124.builder().build())
        .addUserMessage("Save a picture of a cat to my desktop.")
        .build();

    Message response = client.messages().create(params);
    IO.println(response);
}
$client = new Client();

$response = $client->messages->create(
    maxTokens: 1024,
    messages: [
        ['role' => 'user', 'content' => 'Save a picture of a cat to my desktop.'],
    ],
    model: 'claude-opus-5-5',
    tools: [
        ['type' => 'computer_toolset_20260801'],
        [
            'type' => 'text_editor_20250728',
            'name' => 'str_replace_based_edit_tool',
        ],
        [
            'type' => 'bash_20250124',
            'name' => 'bash',
        ],
    ],
);

echo $response;
client = Anthropic::Client.new

response = client.messages.create(
  model: "claude-opus-5-5",
  max_tokens: 1024,
  tools: [
    { type: "computer_toolset_20260801" },
    {
      type: "text_editor_20250728",
      name: "str_replace_based_edit_tool"
    },
    {
      type: "bash_20250124",
      name: "bash"
    }
  ],
  messages: [
    { role: "user", content: "Save a picture of a cat to my desktop." }
  ]
)

puts response

Claude가 데스크톱에서 행동할 때 응답의 stop_reason은 tool_use이고 구성원 도구를 이름 지은 tool_use 블록을 하나 이상 포함하며 각각 "toolset_name": "computer"를 담아요. 이 작업의 중간 어디쯤, Claude가 데스크톱 스크린샷을 본 뒤의 응답은 다음과 같이 보일 수 있어요:

{
  "id": "msg_01UZ3bXcQH8mTqNhVfL9eK2p",
  "type": "message",
  "role": "assistant",
  "model": "claude-opus-5-5",
  "content": [
    {
      "type": "text",
      "text": "I'll open the web browser to find a picture of a cat."
    },
    {
      "type": "tool_use",
      "id": "toolu_01WkoTUvSHDzTBu2xnGk8Ep8",
      "name": "left_click",
      "toolset_name": "computer",
      "input": { "coordinate": [512, 742] }
    },
    {
      "type": "tool_use",
      "id": "toolu_017nJn3RgSCkTMwuZDb4uUov",
      "name": "screenshot",
      "toolset_name": "computer",
      "input": {}
    }
  ],
  "stop_reason": "tool_use",
  "stop_sequence": null
}

애플리케이션은 여러분의 환경에서 각 호출을 순서대로 실행하고, tool_use 블록당 tool_result 블록 하나를 돌려주고, API를 다시 호출해요. 컴퓨터 사용이 어떻게 작동하는지가 그 루프를 설명하고, 이 페이지의 나머지는 그 구현 방법을 보여줘요.


컴퓨터 사용이 어떻게 작동하는지

* API 요청의 `tools` 배열에 컴퓨터 사용 도구셋(그리고 필요하면 다른 도구)을 추가하세요. * 데스크톱 상호작용을 요구하는 사용자 프롬프트를 포함하세요. 예: "Save a picture of a cat to my desktop." * Claude는 사용자 질문에 데스크톱에서 행동하는 것이 도움이 되는지 평가해요. * 그렇다면 Claude는 `screenshot`, `left_click`, `type` 같은 구성원 `tool_use` 블록을 하나 이상 담아 응답하고, 각각 `"toolset_name": "computer"`를 실어요. 이 블록이 여러 개인 응답은 [배치 행동](https://platform.claude.com/docs/en/agents-and-tools/tool-use/computer-use-tool#batch-actions)이에요. * API 응답의 `stop_reason`은 `tool_use`로, 도구 사용 요청을 알려줘요. * 응답의 모든 `tool_use` 블록을 순서대로 순회하세요. 각각 `name`과 함께 `toolset_name`으로 디스패치하고, 컨테이너나 가상 머신에서 그 블록의 `input`으로 그 행동을 수행하세요. * `tool_use` 블록당 `tool_result` 블록 하나를 담은 새 `user` 메시지로 대화를 계속하고, 각각 `tool_use_id`로 짝을 맞추며 `"toolset_name": "computer"`를 그대로 실어요. `screenshot`과 `zoom`에는 이미지를, 다른 행동에는 `OK` 같은 짧은 텍스트면 충분해요. * 행동이 실패하면 그 블록에 `is_error: true`를 돌려주고 [배치 행동](https://platform.claude.com/docs/en/agents-and-tools/tool-use/computer-use-tool#batch-actions)에 나온 대로 나머지 배치에 답하세요. * Claude는 도구 결과를 분석해 더 많은 행동이 필요한지 작업이 끝났는지 판단해요. * 더 많은 행동이 필요하다고 판단하면 다시 `tool_use` `stop_reason`으로 응답하므로 3단계로 돌아가세요. * 그렇지 않으면 사용자에게 텍스트 응답을 돌려줘요.

사용자 입력 없이 3단계와 4단계를 반복하는 것을 "에이전트 루프"라고 불러요(즉, Claude가 도구 사용 요청으로 응답하고 애플리케이션이 그 요청을 평가한 결과로 Claude에게 응답하는 것).

배치 행동

Claude는 클릭, 입력, 그리고 스크린샷 촬영 같은 짧은 행동 시퀀스를 계획하고 하나의 응답으로 함께 돌려줄 수 있어요. 이것을 배치 행동이라고 해요. 병렬 도구 사용과 같은 응답 형태를 쓰지만 한 가지 차이가 있어요. 블록을 동시에가 아니라 순서대로 실행한다는 점이죠.

세 행동 배치가 있는 응답은 이렇게 생겼어요:

{
  "role": "assistant",
  "content": [
    {
      "type": "tool_use",
      "id": "toolu_01HqCF3nJ4Vzr8sTkPZ2wxYA",
      "name": "left_click",
      "toolset_name": "computer",
      "input": { "coordinate": [640, 60] }
    },
    {
      "type": "tool_use",
      "id": "toolu_01Ppr3sZ3TnE9m6VUu4RyH2K",
      "name": "type",
      "toolset_name": "computer",
      "input": { "text": "pictures of cats" }
    },
    {
      "type": "tool_use",
      "id": "toolu_01Xf5W1sD8Q9aBcJ7kLmN2pQ",
      "name": "screenshot",
      "toolset_name": "computer",
      "input": {}
    }
  ]
}

다음 user 메시지에 모두 담아서 tool_use 블록당 tool_result 블록 하나를 tool_use_id로 짝을 맞춰 돌려주세요. 구성원 도구의 모든 결과는 "toolset_name": "computer"를 담아야 해요. 생략하거나 tool_use 블록과 다른 도구셋을 이름 지은 결과는 거부돼요. screenshot과 zoom 결과만 이미지가 필요하고, 다른 구성원은 OK 같은 짧은 텍스트 확인이면 충분해요(cursor_position은 좌표를 텍스트로 돌려줘요):

{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01HqCF3nJ4Vzr8sTkPZ2wxYA",
      "toolset_name": "computer",
      "content": [{ "type": "text", "text": "OK" }]
    },
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01Ppr3sZ3TnE9m6VUu4RyH2K",
      "toolset_name": "computer",
      "content": [{ "type": "text", "text": "OK" }]
    },
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01Xf5W1sD8Q9aBcJ7kLmN2pQ",
      "toolset_name": "computer",
      "content": [
        {
          "type": "image",
          "source": {
            "type": "base64",
            "media_type": "image/png",
            "data": "iVBORw0KGgo..."
          }
        }
      ]
    }
  ]
}

블록을 순서대로 실행하고 첫 실패에서 멈추세요. 배치의 뒤 행동은 보통 앞 행동에 의존해요. 이 예제의 type은 앞선 클릭이 포커스한 곳에 텍스트를 입력해요. 블록을 content에 나타난 순서대로 순차 실행하고, 하나가 실패하면 나머지를 실행하지 마세요. 모든 tool_use 블록은 여전히 tool_result가 필요하므로 다음과 같이 배치에 답하세요:

  • 성공한 각 행동에 대해 정상 결과를 돌려주세요.
  • 실패한 행동에 대해 무슨 문제였는지 텍스트 설명과 함께 is_error: true를 돌려주세요.
  • 배치의 이후 모든 행동에 다음 텍스트를 정확히 붙여 is_error: true를 돌려주세요(브라우저 사용 도구는 자체 정지 텍스트를 써요):
{
  "type": "tool_result",
  "tool_use_id": "toolu_01Xf5W1sD8Q9aBcJ7kLmN2pQ",
  "toolset_name": "computer",
  "is_error": true,
  "content": "Not executed: an earlier computer action in this turn failed."
}

그러면 Claude는 어떤 행동이 성공했고 어떤 것이 실패했으며 어떤 것이 건너뛰었는지를 보고 다음 턴에 다시 계획해요. 배치의 tool_use 블록 중 하나라도 답하지 않은 요청은 invalid_request_error로 거부되므로, 첫 블록만 읽는 에이전트 루프는 다음 호출에서 실패해요. 애플리케이션이 결과가 큰 행동을 사람이 확인하도록 요청한다면 각 블록 실행 전에 그 확인을 하세요. 배치가 한 턴 안에 여러 단계 행동을 완료할 수 있기 때문이에요.

Claude는 보통 배치를 screenshot으로 끝내서 다음에 무엇을 할지 결정하기 전에 결과를 관찰해요. 배치가 그렇게 끝나지 않으면 애플리케이션이 배치의 마지막 결과에 추가 image 블록으로 스크린샷을 붙여 Claude가 항상 화면의 현재 상태를 보게 할 수 있어요. 이러면 Claude가 요청할 때까지 기다리는 것보다 왕복을 아껴요. Claude가 모든 배치를 스크린샷으로 끝내도록 프롬프트할 수도 있어요(프롬프팅으로 모델 성능 최적화 참고).

컴퓨팅 환경

컴퓨터 사용은 Claude가 애플리케이션과 웹과 안전하게 상호작용할 수 있는 샌드박스 컴퓨팅 환경을 필요로 해요. 이 환경은 다음을 포함해요:

  1. 가상 디스플레이: Claude가 스크린샷으로 보고 마우스/키보드 행동으로 제어할 데스크톱 인터페이스를 렌더링하는 가상 X11 디스플레이 서버(Xvfb 사용).

  2. 데스크톱 환경: Linux에서 실행되는 창 관리자(Mutter)와 패널(Tint2)이 있는 가벼운 UI. Claude가 상호작용할 일관된 그래픽 인터페이스를 제공해요.

  3. 애플리케이션: Claude가 작업을 완료하는 데 쓸 수 있는 Firefox, LibreOffice, 텍스트 편집기, 파일 관리자 같은 사전 설치된 Linux 애플리케이션.

  4. 도구 구현: Claude의 추상 도구 요청("마우스 이동"이나 "스크린샷 촬영" 같은)을 가상 환경의 실제 작업으로 변환하는 통합 코드.

  5. 에이전트 루프: Claude와 환경 사이의 통신을 처리하고, Claude의 행동을 환경으로 보내고 결과(스크린샷, 명령 출력)를 Claude에게 돌려주는 프로그램.

컴퓨터 사용을 쓸 때 Claude는 이 환경에 직접 연결하지 않아요. 대신 애플리케이션이:

  1. Claude의 도구 사용 요청을 받고
  2. 컴퓨팅 환경의 행동으로 변환하며
  3. 결과(스크린샷과 명령 출력 같은)를 캡처하고
  4. 이 결과를 Claude에게 돌려줘요

보안과 격리를 위해 참조 구현은 이 모든 것을 환경을 보고 상호작용하기 위한 적절한 포트 매핑이 있는 Docker 컨테이너 안에서 실행해요.


컴퓨터 사용 구현 방법

기존 computer_20251124 통합을 업그레이드 중인가요? computer_20251124에서 마이그레이션부터 시작하세요. 이 절의 나머지는 새 통합과 마이그레이션 통합 모두에 적용돼요.

[컴퓨터 사용 참조 구현](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo)은 완전한 작동 예제예요. 컴퓨터 사용에 적합한 [컨테이너화된 환경](https://github.com/anthropics/anthropic-quickstarts/blob/main/computer-use-demo/Dockerfile), [컴퓨터 사용 도구](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo/computer_use_demo/tools)의 구현, Claude API를 호출하고 도구를 실행하는 [에이전트 루프](https://github.com/anthropics/anthropic-quickstarts/blob/main/computer-use-demo/computer_use_demo/loop.py), 그리고 컨테이너·루프·도구용 웹 인터페이스를 제공해요.

에이전트 루프 이해하기

컴퓨터 사용의 핵심은 "에이전트 루프"예요. Claude가 도구 행동을 요청하고, 애플리케이션이 실행하고, 결과를 Claude에게 돌려주는 순환 구조죠. 루프는 빠른 시작에서 만든 클라이언트, 컴퓨터 사용 도구셋만 선언한 tools 배열, 그리고 컴퓨터 사용 도구 구현 아래의 도구 호출 처리 헬퍼를 사용해요. 빠른 시작의 bash·텍스트 편집기 도구 같은 다른 도구도 선언했다면 같은 패스에서 tool_use 블록을 디스패치하세요. 헬퍼는 컴퓨터 사용 구성원 호출에만 답하고, 루프는 답한 호출이 없는 턴을 끝난 것으로 취급해요. 다음은 단순화한 예제예요:

```python Python def sampling_loop(model: str, messages: list[MessageParam], max_iterations: int = 10): """ Run the computer-use agent loop until Claude stops requesting tools or the iteration limit is reached. """ for _ in range(max_iterations): response = client.messages.create( model=model, max_tokens=4096, messages=messages, tools=TOOLS, )
      # Add Claude's response to the conversation history
      messages.append({"role": "assistant", "content": response.content})

      # Run the actions Claude requested, in order, and collect the results
      tool_results = process_tool_calls(response)
      if not tool_results:
          return messages  # No more tool use; task complete

      # Send every result back to Claude in a single user message
      messages.append({"role": "user", "content": tool_results})

  return messages

```typescript TypeScript
async function samplingLoop(
  model: string,
  messages: Anthropic.MessageParam[],
  maxIterations = 10,
): Promise<Anthropic.MessageParam[]> {
  // Run the computer-use agent loop until Claude stops requesting tools
  // or the iteration limit is reached.
  for (let i = 0; i < maxIterations; i++) {
    const response = await client.messages.create({
      model,
      max_tokens: 4096,
      messages,
      tools,
    });

    // Add Claude's response to the conversation history
    messages.push({ role: "assistant", content: response.content });

    // Run any tools Claude requested and collect results
    const toolResults = processToolCalls(response);
    if (toolResults.length === 0) {
      return messages; // No more tool use; task complete
    }

    // Send tool results back to Claude for the next iteration
    messages.push({ role: "user", content: toolResults });
  }

  return messages;
}
async Task<List<MessageParam>> SamplingLoop(
    Model model,
    List<MessageParam> messages,
    int maxIterations = 10
)
{
    // Run the computer-use agent loop until Claude stops requesting tools
    // or the iteration limit is reached.
    for (var i = 0; i < maxIterations; i++)
    {
        var response = await client.Messages.Create(
            new MessageCreateParams
            {
                Model = model,
                MaxTokens = 4096,
                Messages = messages,
                Tools = tools,
            }
        );

        // Add Claude's response to the conversation history
        messages.Add(
            new()
            {
                Role = Role.Assistant,
                Content = response
                    .Content.Select(block => new ContentBlockParam(block.Json))
                    .ToList(),
            }
        );

        // Run any tools Claude requested and collect results
        var toolResults = ProcessToolCalls(response);
        if (toolResults.Count == 0)
        {
            return messages; // No more tool use; task complete
        }

        // Send tool results back to Claude for the next iteration
        messages.Add(new() { Role = Role.User, Content = toolResults });
    }

    return messages;
}
// samplingLoop runs the computer-use agent loop until Claude stops
// requesting tools or the iteration limit is reached.
func samplingLoop(ctx context.Context, model anthropic.Model, messages []anthropic.MessageParam, maxIterations int) ([]anthropic.MessageParam, error) {
	for range maxIterations {
		response, err := client.Messages.New(ctx, anthropic.MessageNewParams{
			Model:     model,
			MaxTokens: 4096,
			Messages:  messages,
			Tools:     tools,
		})
		if err != nil {
			return nil, err
		}

		// Add Claude's response to the conversation history
		messages = append(messages, response.ToParam())

		// Run the actions Claude requested, in order, and collect the results
		toolResults := processToolCalls(response)
		if len(toolResults) == 0 {
			return messages, nil // No more tool use; task complete
		}

		// Send every result back to Claude in a single user message
		messages = append(messages, anthropic.NewUserMessage(toolResults...))
	}
	return messages, nil
}

/**
 * Run the computer-use agent loop until Claude stops requesting tools
 * or the iteration limit is reached.
 */
List<MessageParam> samplingLoop(Model model, List<MessageParam> messages, int maxIterations) {
    for (int i = 0; i < maxIterations; i++) {
        Message response = client.messages().create(MessageCreateParams.builder()
                .model(model)
                .maxTokens(4096)
                .messages(messages)
                .addTool(COMPUTER_TOOLSET)
                .build());

        // Add Claude's response to the conversation history
        messages.add(MessageParam.builder()
                .role(MessageParam.Role.ASSISTANT)
                .contentOfBlockParams(response.content().stream().map(ContentBlock::toParam).toList())
                .build());

        // Run any tools Claude requested and collect results
        List<ContentBlockParam> toolResults = processToolCalls(response);
        if (toolResults.isEmpty()) {
            return messages; // No more tool use; task complete
        }

        // Send tool results back to Claude for the next iteration
        messages.add(MessageParam.builder()
                .role(MessageParam.Role.USER)
                .contentOfBlockParams(toolResults)
                .build());
    }
    return messages;
}
/**
 * Run the computer-use agent loop until Claude stops requesting tools
 * or the iteration limit is reached.
 */
function samplingLoop(string $model, array $messages, int $maxIterations = 10): array
{
    global $client, $tools;

    for ($i = 0; $i < $maxIterations; $i++) {
        $response = $client->messages->create(
            model: $model,
            maxTokens: 4096,
            messages: $messages,
            tools: $tools,
        );

        // Add Claude's response to the conversation history
        $messages[] = MessageParam::with(role: Role::ASSISTANT, content: $response->content);

        // Run any tools Claude requested and collect results
        $toolResults = processToolCalls($response);
        if ($toolResults === []) {
            return $messages; // No more tool use; task complete
        }

        // Send tool results back to Claude for the next iteration
        $messages[] = MessageParam::with(role: Role::USER, content: $toolResults);
    }

    return $messages;
}
# Run the computer-use agent loop until Claude stops requesting tools
# or the iteration limit is reached.
def sampling_loop(model, messages, max_iterations: 10)
  max_iterations.times do
    response = CLIENT.messages.create(
      model: model,
      max_tokens: 4096,
      messages: messages,
      tools: TOOLS
    )

    # Add Claude's response to the conversation history
    messages << { role: "assistant", content: response.content }

    # Run the actions Claude requested, in order, and collect the results
    tool_results = process_tool_calls(response)
    return messages if tool_results.empty? # No more tool use; task complete

    # Send every result back to Claude in a single user message
    messages << { role: "user", content: tool_results }
  end

  messages
end

루프는 Claude가 도구를 전혀 요청하지 않고 응답할 때(작업 완료) 또는 최대 반복 한도에 도달할 때까지 계속돼요. 이 안전장치는 예상치 못한 API 비용을 초래할 수 있는 무한 루프를 방지해요.

프롬프팅으로 모델 성능 최적화

  1. 단순하고 잘 정의된 작업을 명시하고 각 단계에 명확한 지시를 제공하세요.
  2. Claude는 가끔 행동의 결과를 명시적으로 확인하지 않고 가정해요. 이를 막으려면 Claude에게 다음을 프롬프트하세요. After each step, take a screenshot and carefully evaluate if you have achieved the right outcome. Explicitly show your thinking: "I have evaluated step X..." If not correct, try again. Only when you confirm a step was executed correctly should you move on to the next one.
  3. 어떤 UI 요소(드롭다운이나 스크롤바 같은)는 Claude가 마우스 움직임으로 조작하기 까다로울 수 있어요. 이런 문제가 생기면 키보드 단축키를 쓰도록 모델을 프롬프트해보세요.
  4. 반복 가능한 작업이나 UI 상호작용에는 성공한 결과의 예시 스크린샷과 도구 호출을 프롬프트에 포함하세요.
  5. 모델이 로그인이 필요하면 <robot_credentials> 같은 XML 태그 안에 사용자 이름과 비밀번호를 프롬프트로 제공하세요. 로그인이 필요한 애플리케이션에서 컴퓨터 사용을 쓰면 프롬프트 인젝션으로 인해 나쁜 결과가 날 위험이 커져요. 모델에게 로그인 자격 증명을 제공하기 전에 탈옥과 프롬프트 인젝션 완화를 검토하세요.
  6. 사용자 턴의 content 배열을 만들 때 지시 텍스트를 스크린샷 이미지 앞에 두세요. 이미지를 처리하기 전에 대상 설명을 제공하면 클릭 정확도가 높아져요.
  7. Claude는 스크린샷의 기본 해상도에서 읽기 어려운 작은 텍스트나 특정 UI 요소(사이드바의 파일 이름, 탭 제목, 상태 표시줄 텍스트, 줄 번호, 버튼 라벨 같은)를 물어볼 때 zoom 행동으로 해당 영역을 전체 해상도로 살펴봐요. 기대할 때 Claude가 확대하지 않으면 화면 전체가 아니라 특정 영역이나 요소를 물어보세요.
  8. 모든 배치 행동이 스크린샷으로 끝나길 원하면 시스템 프롬프트에 그렇게 말하세요. 예: End each group of actions with a screenshot so you can verify the result before continuing.
자주 마주치는 명확한 문제가 반복되거나 Claude가 완료해야 할 작업을 미리 안다면 시스템 프롬프트로 Claude에게 그 작업을 성공적으로 수행하는 방법에 대한 명시적 팁이나 지시를 제공하세요. 여러 세션에 걸치는 에이전트는 구현 후에만이 아니라 각 세션 시작 시 종단 간 검증을 실행하세요. 브라우저 기반 검사는 코드 수준 검토만으로는 놓치는 이전 세션의 회귀를 잡아내요. 자세한 내용은 [장기 실행 에이전트를 위한 효과적인 하니스](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) 참고.

시스템 프롬프트

요청에 컴퓨터 사용 도구를 포함하면 API가 컴퓨터 사용 전용 시스템 프롬프트를 생성해요. 도구 사용 시스템 프롬프트와 비슷하지만 다음으로 시작해요:

You have access to a set of functions you can use to answer the user's question. This includes access to a sandboxed computing environment. You do NOT currently have the ability to inspect files or interact with external resources, except by invoking the below functions.

일반 도구 사용과 마찬가지로 사용자가 제공한 system 파라미터는 여전히 존중되고 결합된 시스템 프롬프트의 구성에 사용돼요.

사용 가능한 행동

각 행동은 컴퓨터 사용 도구셋의 구성원 도구예요. Claude는 "toolset_name": "computer"를 담는 tool_use 블록에서 구성원을 이름 짓고, 블록의 input은 action 필드 없이 그 구성원의 파라미터만 담아요. 도구셋에는 구성원 도구 17개가 있어요:

구성원 입력 설명
screenshot 없음 ({}) 전체 디스플레이를 캡처하고 이미지로 돌려줘요.
zoom region: [x0, y0, x1, y1], 검사할 영역의 왼쪽 위와 오른쪽 아래 모서리 디스플레이의 그 영역만 전체 해상도로 캡처하고, 보통 스크린샷 크기에 맞게 축소하면서 비율을 유지해 이미지로 돌려줘요. 작은 텍스트나 축소된 전체 스크린샷에서 읽기 어려운 밀집 UI를 읽게 해줘요.
left_click coordinate (선택): [x, y]; text (선택): 클릭 중 누를 수정자 키: shift, ctrl, alt, super(Command나 Windows 키), 또는 ctrl+shift 같은 + 결합 coordinate에서 마우스 왼쪽 버튼을 클릭하거나, coordinate를 생략하면 현재 커서 위치에서 클릭해요.
right_click, middle_click, double_click, triple_click left_click과 같음 다른 마우스 버튼과 여러 번 클릭.
left_click_drag start_coordinate: [x, y]; coordinate: [x, y]; text (선택): 수정자 키 start_coordinate에서 누르고 coordinate로 드래그한 뒤 놓아요.
mouse_move coordinate: [x, y] 클릭하지 않고 커서를 옮겨요. 예: 호버.
left_mouse_down, left_mouse_up 없음 ({}) 현재 커서 위치에서 마우스 왼쪽 버튼을 누르거나 놓아요. left_click_drag로 표현할 수 없는 드래그용. 먼저 mouse_move로 커서를 옮기세요.
cursor_position 없음 ({}) 커서의 현재 [x, y] 위치를 텍스트로 보고해요.
scroll scroll_direction: "up", "down", "left", "right"; scroll_amount: 휠 클릭 수; coordinate (선택): [x, y]; text (선택): 수정자 키 coordinate에서 또는 현재 커서 위치에서 스크롤해요.
type text: 입력할 문자열 현재 키보드 포커스에 리터럴 텍스트를 입력해요.
key text: 키나 "Return", "ctrl+s", "alt+Tab" 같은 + 결합; repeat (선택): 1~100, 기본 1 키나 키 조합을 repeat번 누른다.
hold_key text: 키나 조합; duration: 초, 최대 300 주어진 시간 동안 키를 누르고 있어요.
wait duration: 초, 최대 300 다음 행동 전에 멈춰요. 예: 애플리케이션이 로드되는 동안.

구성원을 구현할 때 다음을 기억하세요:

  • 좌표는 스크린샷 픽셀이에요. 모든 coordinate, start_coordinate, region 값과 cursor_position이 보고하는 위치는 여러분이 돌려주는 전체 디스플레이 스크린샷의 픽셀 공간이고, 원점은 왼쪽 위예요. 확대 이미지는 이것을 바꾸지 않아요. zoom 후에도 Claude는 항상 전체 스크린샷의 공간으로 좌표를 표현하고, 확대 이미지에 상대적으로는 절대 표현하지 않아요. 스크린샷을 돌려주기 전에 줄였다면 실제 디스플레이에 적용하기 전에 Claude의 좌표를 다시 키우세요(이미지 한도에 맞게 스크린샷 크기 조정 참고).
  • 모든 구성원이 기본으로 활성화되어 있어요. zoom도요. 환경이 확대 이미지를 만들 수 없으면 활성화해두고 오류를 돌려주지 말고 configs로 구성원을 숨기세요(도구 파라미터 참고). Claude가 숨겼거나 구현하지 않은 구성원을 호출하면 그 블록에 is_error: true인 tool_result를 돌려주세요.
  • (toolset_name, name) 쌍으로 디스패치하세요. 블록을 컴퓨터 행동으로 표시하는 것은 toolset_name이에요. 같은 요청의 커스텀 도구가 구성원과 이름을 공유할 수 있고, 이후 도구셋 버전이 구성원을 추가할 수 있으니까요(클라이언트 도구셋 참고).
각 예시는 Claude의 응답에 나타나는 완전한 `tool_use` 블록이에요.

위치에서 Shift+클릭. 예: 선택 영역 확장. hold_key와 달리 text는 그 클릭이나 스크롤 동안만 수정자를 누르고 있어요:

{
  "type": "tool_use",
  "id": "toolu_01Qg8m3XqC5aRy7tD2eS4jUg",
  "name": "left_click",
  "toolset_name": "computer",
  "input": { "coordinate": [500, 300], "text": "shift" }
}

한 지점에서 다른 지점으로 드래그:

{
  "type": "tool_use",
  "id": "toolu_01Ed6j9VnA3yPw5rB8cQ2gSe",
  "name": "left_click_drag",
  "toolset_name": "computer",
  "input": {
    "start_coordinate": [200, 300],
    "coordinate": [600, 300]
  }
}

휠을 세 번 클릭만큼 아래로 스크롤:

{
  "type": "tool_use",
  "id": "toolu_01Yc5h8UmZ2xNv4qA7bP9fRd",
  "name": "scroll",
  "toolset_name": "computer",
  "input": {
    "coordinate": [500, 400],
    "scroll_direction": "down",
    "scroll_amount": 3
  }
}

Tab을 네 번 누르기:

{
  "type": "tool_use",
  "id": "toolu_01Sb4g7TkY9wLu3pX6zM8eQc",
  "name": "key",
  "toolset_name": "computer",
  "input": { "text": "Tab", "repeat": 4 }
}

전체 해상도로 영역을 검사하려고 확대:

{
  "type": "tool_use",
  "id": "toolu_01Kf7k2WpB4zQx6sC9dR3hTf",
  "name": "zoom",
  "toolset_name": "computer",
  "input": { "region": [100, 200, 400, 350] }
}

커서 위치 보고. 이 호출에 스크린샷 픽셀로 위치를 주는 짧은 텍스트 결과로 답하세요. 예: X=512, Y=384:

{
  "type": "tool_use",
  "id": "toolu_01Ekh3vqB6yTs2mNc4Rw8pLd",
  "name": "cursor_position",
  "toolset_name": "computer",
  "input": {}
}

도구 파라미터

tools 배열의 도구셋 항목은 네 파라미터를 받아요. 브라우저 사용 도구셋과 공유하는 규칙은 클라이언트 도구셋 아래에 나열되어 있어요.

파라미터 필수 설명
type 예 computer_toolset_20260801
configs 아니요 구성원 이름을 키로 하는 구성원별 설정. 각 구성원은 enabled(17개 모두, zoom 포함, 기본 true)와 defer_loading(기본 false, 도구 검색용)을 받고, 생략한 구성원은 기본값을 유지해요.
cache_control 아니요 도구셋 정의의 프롬프트 캐싱 중단점. 항목 전용. 배치의 어떤 tool_use·tool_result 블록의 중단점은 그 배치의 끝에서 효과를 내요. 프롬프트 캐싱과 도구 사용 참고.
allowed_callers 아니요 ["direct"]만.

예를 들어 이 항목은 zoom을 구현하지 않는 환경을 위해 숨기고 도구셋 정의에 캐시 중단점을 설정해요:

{
  "type": "computer_toolset_20260801",
  "configs": {
    "zoom": { "enabled": false }
  },
  "cache_control": { "type": "ephemeral" }
}

에이전트 루프가 한 번에 한 행동만 실행할 수 있다면 tool_choice에서 disable_parallel_tool_use를 true로 설정하세요. 그러면 Claude가 턴당 최대 하나의 구성원 tool_use 블록을 돌려줘요(병렬 도구 사용 비활성화 참고).

항목은 이전 도구 버전의 다음 파라미터를 거부하고, 다음 중 하나라도 포함하면 요청이 invalid_request_error를 돌려줘요:

  • name: 구성원 이름은 도구셋 버전으로 고정돼요.
  • display_width_px, display_height_px, display_number: 좌표는 항상 여러분이 돌려주는 스크린샷의 픽셀 공간이에요.
  • enable_zoom: 확대는 configs로 제어하는 구성원 도구예요.

또한 항목은 computer_20251124 항목이나 computer라는 다른 도구와 같은 요청에 선언할 수 없어요. strict, input_examples, defer_loading 배치, tool_choice, 스트리밍, 호출자 제한은 클라이언트 도구셋 참고.

생각과 결합하기

컴퓨터 사용과 생각을 결합하려면 생각 참고.

이전 `computer_20251124` 도구의 경우, 그것을 사용하는 모델의 내부 벤치마킹이 다음 `effort` 설정을 제안해요:
  • Claude Opus 4.7: 기본값으로 high 사용. 고처리량이나 비용 민감 워크로드에는 low 사용.
  • Claude Sonnet 4.6과 Claude Opus 4.6: 기본값으로 medium 사용(정확도 대비 비용 최상). UI 작업에서 정확도를 높이지 않고 토큰 비용만 추가하는 max는 피하세요. 이 모델들에서 low는 생각을 완전히 끈 것보다 더 적은 출력 토큰을 써요(실수가 적으면 재시도가 적으니까). 비용 민감 루프에 강력한 선택이에요.

컴퓨터 사용을 다른 도구로 보강하기

컴퓨터 사용 옆에 다른 도구를 추가하려면 같은 tools 배열에 포함하세요. 빠른 시작 절이 bash 도구와 텍스트 편집기 도구로 이 패턴을 보여줘요. 여러분만의 커스텀 도구 정의도 같은 방식으로 추가할 수 있어요.

웹페이지 안에 머무는 작업에는 같은 요청에 브라우저 사용 도구를 선언할 수도 있어요. 두 도구셋은 각자 좌표 프레임에서 독립적으로 작동하고, screenshot이나 key처럼 이름을 공유하는 구성원에 대한 호출은 toolset_name으로 구별해요.

커스텀 컴퓨터 사용 환경 구축하기

참조 구현은 컴퓨터 사용을 시작하도록 돕기 위한 것이에요. Claude가 컴퓨터를 사용하게 하는 데 필요한 모든 구성 요소를 포함해요. 그러나 여러분의 필요에 맞게 컴퓨터 사용용 환경을 직접 구축할 수도 있어요. 다음이 필요해요:

  • Claude와 함께 컴퓨터 사용에 적합한 가상화 또는 컨테이너화 환경
  • 컴퓨터 사용 도구 행동의 구현
  • Claude API와 상호작용하고 여러분의 도구 구현으로 tool_use 결과를 실행하는 에이전트 루프
  • 사용자 입력으로 에이전트 루프를 시작하게 하는 API나 UI

컴퓨터 사용 도구 구현하기

컴퓨터 사용 도구는 스키마 없는 도구로 구현돼요. 이 도구를 쓸 때 다른 도구처럼 입력 스키마를 제공할 필요 없어요. 스키마는 Claude의 모델에 내장되어 있고 수정할 수 없으니까요.

Claude가 상호작용할 가상 디스플레이를 만들거나 기존 디스플레이에 연결하세요. 보통 Xvfb(X Virtual Framebuffer)나 유사 기술을 설정하는 것이에요. Claude가 요청할 수 있는 각 행동 유형을 처리하는 함수를 만드세요:
<CodeGroup exclude="shell">
  ```python Python
  # Placeholder image data; a real executor captures the screen and returns the PNG bytes
  PLACEHOLDER_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="


  def capture_screenshot() -> list[ImageBlockParam]:
      # screenshot answers with an image block rather than text: return the result content list
      return [
          {
              "type": "image",
              "source": {"type": "base64", "media_type": "image/png", "data": PLACEHOLDER_PNG},
          }
      ]


  def click(coordinate=None):
      if coordinate is None:
          return "clicked at current cursor"
      x, y = coordinate
      return f"clicked at ({x}, {y})"


  def type_text(text):
      return f"typed: {text}"


  def handle_computer_action(name, tool_input):
      match name:
          case "screenshot":
              return capture_screenshot()
          case "left_click":
              # coordinate is optional; without it, click where the cursor already is
              return click(tool_input.get("coordinate"))
          case "type":
              return type_text(tool_input["text"])
      # Handle other actions as needed
      raise ValueError(f"Unknown or unimplemented member: {name}")
  ```

  ```typescript TypeScript
  // Placeholder image data; a real executor captures the screen as PNG bytes
  const PLACEHOLDER_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";

  function captureScreenshot(): Anthropic.ImageBlockParam[] {
    // screenshot answers with an image block rather than text
    return [
      {
        type: "image",
        source: {
          type: "base64",
          media_type: "image/png",
          data: PLACEHOLDER_PNG,
        },
      },
    ];
  }

  function clickAt(x: number, y: number): string {
    return `clicked at (${x}, ${y})`;
  }

  function clickAtCursor(): string {
    return "clicked at the current cursor position";
  }

  function typeText(text: string): string {
    return `typed: ${text}`;
  }

  function handleComputerAction(
    action: string,
    input: unknown,
  ): string | Anthropic.ImageBlockParam[] {
    const params: object =
      typeof input === "object" && input !== null ? input : {};
    switch (action) {
      case "screenshot":
        return captureScreenshot();
      case "left_click":
        // coordinate is optional on the toolset; without one, click at the cursor
        if ("coordinate" in params && Array.isArray(params.coordinate)) {
          const [x, y] = params.coordinate;
          return clickAt(x, y);
        }
        return clickAtCursor();
      case "type":
        if ("text" in params) {
          return typeText(String(params.text));
        }
        break;
    }
    // Handle other actions as needed
    throw new Error(`Unknown or unimplemented member: ${action}`);
  }
  ```

  ```csharp C#
  // Placeholder image data; a real executor captures the screen and returns the PNG bytes
  const string PlaceholderPng = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";

  // screenshot answers with an image block rather than text: return the result content list
  List<Block> CaptureScreenshot() =>
      [
          new ImageBlockParam(
              new Base64ImageSource { Data = PlaceholderPng, MediaType = MediaType.ImagePng }
          ),
      ];

  string ClickAt(int x, int y) => $"clicked at ({x}, {y})";

  string ClickAtCursor() => "clicked at the current cursor position";

  string TypeText(string text) => $"typed: {text}";

  ToolResultBlockParamContent HandleComputerAction(
      string action,
      IReadOnlyDictionary<string, JsonElement> input
  ) =>
      action switch
      {
          "screenshot" => CaptureScreenshot(),
          // coordinate is optional on click members; without it, click where the cursor is
          "left_click" when input.TryGetValue("coordinate", out var xy) => ClickAt(
              xy[0].GetInt32(),
              xy[1].GetInt32()
          ),
          "left_click" => ClickAtCursor(),
          "type" => TypeText(input["text"].GetString()!),
          // Handle other actions as needed
          _ => throw new NotSupportedException($"Unknown or unimplemented member: {action}"),
      };
  ```

  ```go Go
  // placeholderPNG stands in for a real capture: an executor returns the
  // screen as base64-encoded PNG data.
  const placeholderPNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="

  // captureScreenshot returns an image block rather than text.
  func captureScreenshot() []anthropic.ToolResultBlockParamContentUnion {
  	return []anthropic.ToolResultBlockParamContentUnion{{
  		OfImage: &anthropic.ImageBlockParam{
  			Source: anthropic.ImageBlockParamSourceUnion{
  				OfBase64: &anthropic.Base64ImageSourceParam{
  					MediaType: anthropic.Base64ImageSourceMediaTypeImagePNG,
  					Data:      placeholderPNG,
  				},
  			},
  		},
  	}}
  }

  // textContent wraps text as tool_result content.
  func textContent(text string) []anthropic.ToolResultBlockParamContentUnion {
  	return []anthropic.ToolResultBlockParamContentUnion{
  		{OfText: &anthropic.TextBlockParam{Text: text}},
  	}
  }

  func clickAt(x, y int) string {
  	return fmt.Sprintf("clicked at (%d, %d)", x, y)
  }

  func clickAtCursor() string {
  	return "clicked at the current cursor position"
  }

  func typeText(text string) string {
  	return fmt.Sprintf("typed: %s", text)
  }

  func handleComputerAction(action string, params map[string]any) ([]anthropic.ToolResultBlockParamContentUnion, error) {
  	switch action {
  	case "screenshot":
  		return captureScreenshot(), nil
  	case "left_click":
  		// coordinate is optional; without it, click where the cursor already is
  		coord, ok := params["coordinate"].([]any)
  		if !ok {
  			return textContent(clickAtCursor()), nil
  		}
  		if len(coord) == 2 {
  			x, xok := coord[0].(float64)
  					y, yok := coord[1].(float64)
  			if xok && yok {
  				return textContent(clickAt(int(x), int(y))), nil
  			}
  		}
  	case "type":
  		if text, ok := params["text"].(string); ok {
  			return textContent(typeText(text)), nil
  		}
  	// Handle other actions as needed
  	default:
  		return nil, fmt.Errorf("unknown or unimplemented member: %s", action)
  	}
  	// Reached when a member's input is missing a field or a field has the wrong type
  	return nil, fmt.Errorf("invalid input for %s", action)
  }

  ```

  ```java Java
  /** Placeholder pixels; a real executor captures the screen and base64-encodes the PNG. */
  static final String PLACEHOLDER_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";

  ToolResultBlockParam.Content captureScreenshot() {
      ImageBlockParam image = ImageBlockParam.builder()
              .source(Base64ImageSource.builder()
                      .mediaType(Base64ImageSource.MediaType.IMAGE_PNG)
                      .data(PLACEHOLDER_PNG)
                      .build())
              .build();
      return ToolResultBlockParam.Content.ofBlocks(
              List.of(ToolResultBlockParam.Content.Block.ofImage(image)));
  }

  String clickAt(long x, long y) {
      return "clicked at (" + x + ", " + y + ")";
  }

  String clickAtCursor() {
      return "clicked at current cursor";
  }

  String typeText(String text) {
      return "typed: " + text;
  }

  /** Runs one computer toolset member; {@code action} is the tool_use block's name. */
  ToolResultBlockParam.Content handleComputerAction(String action, Map<String, JsonValue> input) {
      if (action.equals("screenshot")) {
          return captureScreenshot(); // the one member here that answers with an image block
      }
      String output = switch (action) {
          case "left_click" -> {
              JsonValue coordinate = input.get("coordinate"); // optional on the toolset
              if (coordinate == null) {
                  yield clickAtCursor();
              }
              List<JsonValue> point = (List<JsonValue>) coordinate.asArray().get();
              long x = ((Number) point.get(0).asNumber().get()).longValue();
              long y = ((Number) point.get(1).asNumber().get()).longValue();
              yield clickAt(x, y);
          }
          case "type" -> typeText(input.get("text").asStringOrThrow());
          // Handle other actions as needed
          default -> throw new UnsupportedOperationException("Unknown or unimplemented member: " + action);
      };
      return ToolResultBlockParam.Content.ofString(output);
  }
  ```

  ```php PHP
  // Stand-in for real PNG bytes; a real executor captures the screen
  const PLACEHOLDER_PNG = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';

  function captureScreenshot(): array
  {
      // screenshot answers with an image block rather than text, so return the result content list
      $image = [
          'type' => 'image',
          'source' => ['type' => 'base64', 'media_type' => 'image/png', 'data' => PLACEHOLDER_PNG],
      ];

      return [$image];
  }

  function clickAt(?array $coordinate): string
  {
      // left_click may omit coordinate, in which case the click lands where the cursor already is
      if ($coordinate === null) {
          return 'clicked at current cursor';
      }
      [$x, $y] = $coordinate;

      return "clicked at ({$x}, {$y})";
  }

  function typeText(string $text): string
  {
      return "typed: {$text}";
  }

  function handleComputerAction(string $name, array $input): string|array
  {
      return match ($name) {
          'screenshot' => captureScreenshot(),
          'left_click' => clickAt($input['coordinate'] ?? null),
          'type' => typeText($input['text']),
          // Handle other actions as needed
          default => throw new RuntimeException("Unknown or unimplemented member: {$name}"),
      };
  }
  ```

  ```ruby Ruby
  # Stand-in image data; a real executor captures the screen as a PNG.
  PLACEHOLDER_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="

  # screenshot answers with an image block rather than text
  def capture_screenshot
    [
      {
        type: "image",
        source: { type: "base64", media_type: "image/png", data: PLACEHOLDER_PNG }
      }
    ]
  end

  def click(coordinate = nil)
    return "clicked at current cursor" if coordinate.nil?

    x, y = coordinate
    "clicked at (#{x}, #{y})"
  end

  def type_text(text)
    "typed: #{text}"
  end

  def handle_computer_action(name, input)
    case name
    when "screenshot"
      capture_screenshot
    when "left_click"
      # coordinate is optional; without it, click where the cursor already is
      click(input[:coordinate])
    when "type"
      type_text(input[:text])
    # Handle other actions as needed
    else
      raise ArgumentError, "Unknown or unimplemented member: #{name}"
    end
  end
  ```
</CodeGroup>
Claude의 응답에서 도구 호출을 추출하고 실행하세요:
<CodeGroup exclude="shell">
  ```python Python
  NOT_EXECUTED = "Not executed: an earlier computer action in this turn failed."


  def process_tool_calls(response: Message) -> list[ToolResultBlockParam]:
      """
      Run the computer actions in Claude's response in order and answer each
      one. After the first failure the rest are skipped, because Claude planned
      them assuming the earlier actions succeeded.
      """
      tool_results: list[ToolResultBlockParam] = []
      failed = False
      for block in response.content:
          # Only the computer toolset is declared; route other tools here if you add them
          if block.type != "tool_use" or block.toolset_name != "computer":
              continue
          result: ToolResultBlockParam = {
              "type": "tool_result",
              "tool_use_id": block.id,
              "toolset_name": "computer",
          }
          if failed:
              result["content"] = NOT_EXECUTED
              result["is_error"] = True
          else:
              try:
                  # A string, or a list of content blocks such as the screenshot image
                  result["content"] = handle_computer_action(block.name, block.input)
              except Exception as err:
                  result["content"] = f"Error: {err}"
                  result["is_error"] = True
                  failed = True
          tool_results.append(result)
      return tool_results
  ```

  ```typescript TypeScript
  const HALT_TEXT =
    "Not executed: an earlier computer action in this turn failed.";

  function computerResult(
    toolUseId: string,
    content: string | Anthropic.ImageBlockParam[],
    isError?: boolean,
  ): Anthropic.ToolResultBlockParam {
    return {
      type: "tool_result",
      tool_use_id: toolUseId,
      toolset_name: "computer",
      content,
      is_error: isError,
    };
  }

  function processToolCalls(
    response: Anthropic.Message,
  ): Anthropic.ToolResultBlockParam[] {
    const toolResults: Anthropic.ToolResultBlockParam[] = [];
    let failed = false;
    for (const block of response.content) {
      if (block.type !== "tool_use") {
        continue;
      }
      if (block.toolset_name !== "computer") {
        // This example declares only the computer toolset; route other tools
        // here if you add them.
        continue;
      }
      if (failed) {
        // A batch stops at its first failure; answer later actions unexecuted
        toolResults.push(computerResult(block.id, HALT_TEXT, true));
        continue;
      }
      try {
        // A string, or the image block list that screenshot returns
        const result = handleComputerAction(block.name, block.input);
        toolResults.push(computerResult(block.id, result));
      } catch (error) {
        failed = true;
        const message = error instanceof Error ? error.message : String(error);
        toolResults.push(computerResult(block.id, `Error: ${message}`, true));
      }
    }
    return toolResults;
  }
  ```

  ```csharp C#
  const string HaltText = "Not executed: an earlier computer action in this turn failed.";

  List<ContentBlockParam> ProcessToolCalls(Message response)
  {
      List<ContentBlockParam> toolResults = [];
      var failed = false;
      foreach (var block in response.Content)
      {
          if (!block.TryPickToolUse(out var toolUse))
          {
              continue;
          }

          if (toolUse.ToolsetName != "computer")
          {
              // This example declares only the computer toolset; route other tools
              // here if you add them.
              continue;
          }

          if (failed)
          {
              // A batch stops at its first failure; answer later actions without running them
              toolResults.Add(
                  new ToolResultBlockParam(toolUse.ID)
                  {
                      Content = HaltText,
                      IsError = true,
                      ToolsetName = "computer",
                  }
              );
              continue;
          }

          try
          {
              // A string, or the image block list that screenshot returns
              var result = HandleComputerAction(toolUse.Name, toolUse.Input);
              toolResults.Add(
                  new ToolResultBlockParam(toolUse.ID) { Content = result, ToolsetName = "computer" }
              );
          }
          catch (Exception e)
          {
              failed = true;
              toolResults.Add(
                  new ToolResultBlockParam(toolUse.ID)
                  {
                      Content = $"Error: {e.Message}",
                      IsError = true,
                      ToolsetName = "computer",
                  }
              );
          }
      }
      return toolResults;
  }
  ```

  ```go Go
  const notExecuted = "Not executed: an earlier computer action in this turn failed."

  // computerToolResult builds the result for one computer action. Unlike an
  // ordinary tool result, it must echo the toolset name.
  func computerToolResult(toolUseID string, content []anthropic.ToolResultBlockParamContentUnion, isError bool) anthropic.ContentBlockParamUnion {
  	result := anthropic.ToolResultBlockParam{
  		ToolUseID:   toolUseID,
  		ToolsetName: anthropic.String("computer"),
  		Content:     content,
  	}
  	if isError {
  		result.IsError = anthropic.Bool(true)
  	}
  	return anthropic.ContentBlockParamUnion{OfToolResult: &result}
  }

  // processToolCalls runs the computer actions in Claude's response in order and
  // builds one tool_result per tool_use block. After the first failure it skips
  // the rest: Claude planned them assuming the earlier actions succeeded.
  func processToolCalls(response *anthropic.Message) []anthropic.ContentBlockParamUnion {
  	var toolResults []anthropic.ContentBlockParamUnion
  	failed := false
  	for _, block := range response.Content {
  		switch variant := block.AsAny().(type) {
  		case anthropic.ToolUseBlock:
  			// This example declares only the computer toolset; route other tools here if you add them.
  			if variant.ToolsetName != "computer" {
  				continue
  			}
  			if failed {
  				toolResults = append(toolResults, computerToolResult(variant.ID, textContent(notExecuted), true))
  				continue
  			}
  			var input map[string]any
  			var content []anthropic.ToolResultBlockParamContentUnion
  			err := json.Unmarshal(variant.Input, &input)
  			if err == nil {
  				// Text, or the image block that screenshot returns
  				content, err = handleComputerAction(variant.Name, input)
  			}
  			if err != nil {
  				failed = true
  				content = textContent("Error: " + err.Error())
  			}
  			toolResults = append(toolResults, computerToolResult(variant.ID, content, err != nil))
  		}
  	}
  	return toolResults
  }

  ```

  ```java Java
  /** The exact text the toolset contract prescribes for member calls skipped after a failure. */
  static final String HALT_TEXT = "Not executed: an earlier computer action in this turn failed.";

  /** Every result answering a computer toolset member echoes toolset_name. */
  ToolResultBlockParam.Builder computerResult(ToolUseBlock toolUse) {
      return ToolResultBlockParam.builder()
              .toolUseId(toolUse.id())
              .toolsetName("computer");
  }

  /**
   * Run the computer actions in Claude's response in order and build one
   * tool_result per tool_use block. After the first failure, skip the rest:
   * Claude planned them assuming the earlier actions succeeded.
   */
  List<ContentBlockParam> processToolCalls(Message response) {
      List<ContentBlockParam> toolResults = new ArrayList<>();
      boolean failed = false;
      for (ContentBlock block : response.content()) {
          // This example declares only the computer toolset; route other tools here if you add them.
          if (!block.isToolUse() || !block.asToolUse().toolsetName().equals(Optional.of("computer"))) {
              continue;
          }
          ToolUseBlock toolUse = block.asToolUse();
          ToolResultBlockParam result;
          if (failed) {
              result = computerResult(toolUse).content(HALT_TEXT).isError(true).build();
          } else {
              try {
                  Map<String, JsonValue> input =
                          (Map<String, JsonValue>) toolUse._input().asObject().get();
                  // A string, or the image block that screenshot returns
                  ToolResultBlockParam.Content output = handleComputerAction(toolUse.name(), input);
                  result = computerResult(toolUse).content(output).build();
              } catch (RuntimeException e) {
                  failed = true;
                  result = computerResult(toolUse).content("Error: " + e.getMessage()).isError(true).build();
              }
          }
          toolResults.add(ContentBlockParam.ofToolResult(result));
      }
      return toolResults;
  }
  ```

  ```php PHP
  const HALT_TEXT = 'Not executed: an earlier computer action in this turn failed.';

  function processToolCalls(Message $response): array
  {
      $toolResults = [];
      $failed = false;
      foreach ($response->content as $block) {
          // This example declares only the computer toolset; route other tools here if you add them.
          if (!($block instanceof \Anthropic\Messages\ToolUseBlock) || $block->toolsetName !== 'computer') {
              continue;
          }
          $result = ['type' => 'tool_result', 'tool_use_id' => $block->id, 'toolset_name' => 'computer'];
          if ($failed) {
              // A batch stops at its first failure; the remaining actions are answered without running
              $toolResults[] = [...$result, 'content' => HALT_TEXT, 'is_error' => true];
              continue;
          }
          try {
              // A string, or the image block list that screenshot returns
              $toolResults[] = [...$result, 'content' => handleComputerAction($block->name, $block->input)];
          } catch (Throwable $e) {
              $failed = true;
              $toolResults[] = [...$result, 'content' => 'Error: ' . $e->getMessage(), 'is_error' => true];
          }
      }

      return $toolResults;
  }
  ```

  ```ruby Ruby
  NOT_EXECUTED = "Not executed: an earlier computer action in this turn failed."

  # Run the computer actions in Claude's response in order and build one
  # tool_result per tool_use block. After the first failure, skip the rest:
  # Claude planned them assuming the earlier actions succeeded.
  def process_tool_calls(response)
    tool_results = []
    failed = false
    response.content.each do |block|
      # This example declares only the computer toolset; route other tools here
      # if you add them.
      next unless block.type == :tool_use && block.toolset_name == "computer"

      result = { type: "tool_result", tool_use_id: block.id, toolset_name: "computer" }
      if failed
        result.update(content: NOT_EXECUTED, is_error: true)
      else
        begin
          # A String, or the image content blocks that screenshot returns
          result[:content] = handle_computer_action(block.name, block.input)
        rescue => e
          result.update(content: "Error: #{e.message}", is_error: true)
          failed = true
        end
      end
      tool_results << result
    end
    tool_results
  end
  ```
</CodeGroup>
두 이전 단계를 결과를 돌려보내고 Claude가 구성원 도구 호출을 돌려주지 않을 때까지 반복하는 루프로 감싸세요. [에이전트 루프 이해하기](https://platform.claude.com/docs/en/agents-and-tools/tool-use/computer-use-tool#understanding-the-agentic-loop)가 각 언어의 이 루프를 보여줘요.

오류 처리

실패한 행동을 Claude에게 tool_use에 대한 tool_result로 is_error: true와 짧은 설명과 함께 보고하고, 다른 구성원 결과와 마찬가지로 "toolset_name": "computer"를 포함하세요. 실패한 행동이 배치 행동의 일부였다면, 실행하지 말고 배치의 나머지 블록에 거기 나온 정지 텍스트로 답하세요.

예를 들어 스크린샷 캡처가 실패하면:

{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
      "toolset_name": "computer",
      "content": "Error: Failed to capture screenshot. Display may be locked or unavailable.",
      "is_error": true
    }
  ]
}

디스플레이 경계 밖의 좌표와 실행에 실패한 행동에도 무슨 문제였는지 말하는 메시지와 함께 같은 형태를 쓰세요.

이미지 한도에 맞게 스크린샷 크기 조정

컴퓨터 사용 도구셋에 돌려주는 스크린샷과 확대 이미지는 이미 모델의 이미지 크기 한도 안에 맞아야 해요. 도구셋은 디스플레이 크기를 받지 않고 API가 대신 줄여주지 않으므로, 크기가 초과된 tool_result 이미지는 검증 오류로 거부돼요. Claude가 보는 이미지의 픽셀 공간으로 좌표를 돌려주므로, 사용한 배율 계수를 보관해 그 좌표를 화면에 다시 매핑할 수 있게 하세요.

한도는 모델마다 달라요. Claude Opus 4.7 이후 모델은 `computer_toolset_20260801`을 지원하는 모든 모델을 포함해 긴 변에 최대 2576픽셀과 총 4784 비주얼 토큰(`⌈width / 28⌉ × ⌈height / 28⌉`, 약 3.75 메가픽셀)을 받아요. 이전 모델은 긴 변에 최대 1568픽셀과 총 약 1.15 메가픽셀을 받아요(각 모델의 등급은 [해상도와 토큰 비용](https://platform.claude.com/docs/en/build-with-claude/vision#evaluate-image-size) 참고). 다음 예시는 이전 모델의 1568px / 1.15 MP 한도를 써요. 고해상도 등급 모델의 경우 픽셀 총계가 아니라 비주얼 토큰 한도에 맞춰 크기를 조정하세요. 예: [업로드 전 이미지 크기 조정](https://platform.claude.com/docs/en/build-with-claude/vision-coordinates#resize-your-image-before-uploading)의 크기 조정 헬퍼 사용.

화면이 한도보다 크면 돌려주기 전에 각 스크린샷의 크기를 조정하고 Claude가 돌려준 좌표를 원래 화면 공간으로 다시 키우세요. 도구셋이 디스플레이 크기를 받지 않으므로, 애플리케이션 코드의 크기 조정과 좌표 스케일링만 있으면 돼요:

```python Python import math

screen_width, screen_height = 1512, 982

def get_scale_factor(width, height): """Calculate scale factor to meet API constraints.""" long_edge = max(width, height) total_pixels = width * height

  long_edge_scale = 1568 / long_edge
  total_pixels_scale = math.sqrt(1_150_000 / total_pixels)

  return min(1.0, long_edge_scale, total_pixels_scale)

When capturing screenshot

scale = get_scale_factor(screen_width, screen_height) scaled_width = int(screen_width * scale) scaled_height = int(screen_height * scale)

Resize image to scaled dimensions before sending to Claude

screenshot = capture_and_resize(scaled_width, scaled_height)

When handling Claude's coordinates, scale them back up

def execute_click(x, y): screen_x = x / scale screen_y = y / scale perform_click(screen_x, screen_y)


```typescript TypeScript
const screenWidth = 1512;
const screenHeight = 982;
const MAX_LONG_EDGE = 1568;
const MAX_PIXELS = 1_150_000;

function getScaleFactor(width: number, height: number): number {
  const longEdge = Math.max(width, height);
  const totalPixels = width * height;

  const longEdgeScale = MAX_LONG_EDGE / longEdge;
  const totalPixelsScale = Math.sqrt(MAX_PIXELS / totalPixels);

  return Math.min(1.0, longEdgeScale, totalPixelsScale);
}

// When capturing screenshot
const scale = getScaleFactor(screenWidth, screenHeight);
const scaledWidth = Math.floor(screenWidth * scale);
const scaledHeight = Math.floor(screenHeight * scale);

// Resize image to scaled dimensions before sending to Claude
const screenshot = captureAndResize(scaledWidth, scaledHeight);

// When handling Claude's coordinates, scale them back up
function executeClick(x: number, y: number): void {
  const screenX = x / scale;
  const screenY = y / scale;
  performClick(screenX, screenY);
}
int screenWidth = 1512, screenHeight = 982;

double GetScaleFactor(int width, int height)
{
    // Calculate scale factor to meet API constraints.
    int longEdge = Math.Max(width, height);
    int totalPixels = width * height;

    double longEdgeScale = 1568.0 / longEdge;
    double totalPixelsScale = Math.Sqrt(1_150_000.0 / totalPixels);

    return Math.Min(1.0, Math.Min(longEdgeScale, totalPixelsScale));
}

// When capturing screenshot
double scale = GetScaleFactor(screenWidth, screenHeight);
int scaledWidth = (int)(screenWidth * scale);
int scaledHeight = (int)(screenHeight * scale);

// Resize image to scaled dimensions before sending to Claude
var screenshot = CaptureAndResize(scaledWidth, scaledHeight);

// When handling Claude's coordinates, scale them back up
void ExecuteClick(int x, int y)
{
    double screenX = x / scale;
    double screenY = y / scale;
    PerformClick(screenX, screenY);
}
func getScaleFactor(width, height int) float64 {
	longest := float64(max(width, height))
	area := float64(width * height)
	return min(1.0, 1568/longest, math.Sqrt(1_150_000/area))
}

// ...
	screenWidth, screenHeight := 1512, 982

	// When capturing screenshot
	scale := getScaleFactor(screenWidth, screenHeight)
	scaledWidth := int(float64(screenWidth) * scale)
	scaledHeight := int(float64(screenHeight) * scale)

	// Resize image to scaled dimensions before sending to Claude
	screenshot := captureAndResize(scaledWidth, scaledHeight)

	// When handling Claude's coordinates, scale them back up
	executeClick := func(x, y int) {
		performClick(float64(x)/scale, float64(y)/scale)
	}
static double getScaleFactor(int width, int height) {
    return Math.min(
        1.0,
        Math.min(
            1568.0 / Math.max(width, height),
            Math.sqrt(1_150_000.0 / (width * height))
        )
    );
}

void main() {
    int screenWidth = 1512, screenHeight = 982;

    // When capturing screenshot
    double scale = getScaleFactor(screenWidth, screenHeight);
    int scaledWidth = (int)(screenWidth * scale);
    int scaledHeight = (int)(screenHeight * scale);

    // Resize image to scaled dimensions before sending to Claude
    var screenshot = captureAndResize(scaledWidth, scaledHeight);

    // When handling Claude's coordinates, scale them back up
    BiConsumer<Integer, Integer> executeClick =
        (x, y) -> performClick(x / scale, y / scale);
// ...
}
function getScaleFactor(int $width, int $height): float
{
    return min(
        1.0,
        1568 / max($width, $height),
        sqrt(1_150_000 / ($width * $height)),
    );
}

$screenWidth = 1512;
$screenHeight = 982;

// When capturing screenshot
$scale = getScaleFactor($screenWidth, $screenHeight);
$scaledWidth = (int)($screenWidth * $scale);
$scaledHeight = (int)($screenHeight * $scale);

// Resize image to scaled dimensions before sending to Claude
$screenshot = captureAndResize($scaledWidth, $scaledHeight);

// When handling Claude's coordinates, scale them back up
$executeClick = fn(int $x, int $y) => performClick($x / $scale, $y / $scale);
def get_scale_factor(width, height)
  [1.0, 1568.0 / [width, height].max, Math.sqrt(1_150_000.0 / (width * height))].min
end

screen_width, screen_height = 1512, 982

# When capturing screenshot
scale = get_scale_factor(screen_width, screen_height)
scaled_width = (screen_width * scale).to_i
scaled_height = (screen_height * scale).to_i

# Resize image to scaled dimensions before sending to Claude
screenshot = capture_and_resize(scaled_width, scaled_height)

# When handling Claude's coordinates, scale them back up
execute_click = ->(x, y) { perform_click(x / scale, y / scale) }
**macOS Retina 디스플레이**는 디바이스 픽셀 비율 2로 스크린샷을 캡처하므로 이미지가 논리 화면 좌표의 두 배 해상도예요. 보내기 전에 스크린샷을 2배 줄이거나, 클릭을 발행하기 전에 Claude가 돌려준 좌표를 반으로 나누세요.

디스플레이 해상도를 선택하고 스크린샷을 돌려줄 때:

  • 일반 데스크톱 작업에는 1024x768 또는 1280x720, 웹 애플리케이션에는 1280x800 또는 1366x768을 사용하세요.
  • 성능 문제를 피하려면 1920x1080 이상의 해상도를 피하세요.
  • 스크린샷을 base64 PNG 또는 JPEG로 인코딩하고, 성능을 위해 큰 스크린샷은 압축을 고려하세요.
  • 타임스탬프나 디스플레이 상태 같은 관련 메타데이터를 포함하세요.
  • 고해상도를 쓰면 좌표가 정확히 스케일링되게 하세요.

스크린샷 히스토리 관리

긴 에이전트 루프는 스크린샷을 빠르게 쌓아요(각각 대략 1,000~1,800 입력 토큰). API의 요청 한도도 적용돼요. 단일 요청이 20개가 넘는 이미지를 담게 되면 그 안의 모든 이미지가 더 엄격한 변별 한도에 걸려요. 스크린샷 히스토리를 유지하는 루프는 수십 턴 안에 그 수에 도달해요. 그러니 각 스크린샷의 크기를 변별 2000px를 넘지 않게 조정하거나, 요청에 20개 이하로 유지하도록 오래된 스크린샷을 정리하세요.

컨텍스트를 제한하면서 프롬프트 캐싱의 효과를 유지하려면:

  • 시스템 프롬프트와 도구 정의 뒤에 cache_control 중단점 하나를 두고, 가장 최근 턴들 각각의 마지막 tool_result 블록에 최대 세 개를 더해 매 턴 앞으로 옮기세요. 배치 행동 안에서 여러 블록의 마커는 단일 중단점으로 작동하지만 각각이 여전히 네 개 한도에 포함되므로 턴당 하나를 쓰세요.
  • 오래된 스크린샷은 매 턴이 아니라 배치로 정리하세요. 매 턴 스크린샷을 버리면 매 턴 접두사가 바뀌고 캐시가 무효화돼요. 합리적 기본값은 마지막 세 스크린샷을 유지하고 25턴마다 정리하는 것이에요. 그러면 정리 이벤트 사이 접두사가 바이트 단위로 같아져요. 스크린샷이 어느 변이든 2000px를 넘으면 각 요청을 20개 이하 이미지로 유지하는 간격을 선택하세요.
  • Claude Fable 5.1과 Claude Opus 5.5에서는 클라이언트에서 정리하지 마세요. 이전 스크린샷을 제거하면 그 턴들을 여전히 담는 모든 요청에서 이후의 모든 생각 블록을 무효화해요. 대신 스크린샷을 변별 2000px 이하로 크기를 조정하고, 서버 측 도구 결과 정리로 컨텍스트에서 오래된 것을 제거하세요. 정리해야 한다면 그때부터 prefix_mismatch_behavior: "drop_block"을 유지하세요. 정리 후에는 정리된 스크린샷 이후에 생산된 생각 없이 Claude가 그 요청과 이후 모든 요청에서 계속해요.

클릭 문제 진단

클릭이 대상을 놓치면 보통 다음 중 하나가 원인이에요:

증상 가능한 원인 시도
클릭이 한 방향으로 일관되게 밀림 Claude의 좌표(여러분이 돌려주는 스크린샷의 픽셀 공간)가 스케일링 없이 다른 크기의 디스플레이에 적용됨 클릭 전에 각 좌표를 화면 크기 대비 스크린샷 크기의 비율로 스케일링하세요(이미지 한도에 맞게 스크린샷 크기 조정 참고). macOS Retina 디스플레이에서는 2x 디바이스 픽셀 비율을 고려하세요
클릭이 올바른 영역에 도달하지만 대상을 놓침 대상이 매우 작거나, 4K+ 소스 축소 중 디테일이 사라졌거나, 종횡비가 왜곡됨 zoom 구성원을 활성화된 채로 두고 구현해서 Claude가 영역을 전체 해상도로 검사하게 하세요. 낮은 DPI로 캡처하거나 관련 영역으로 크롭하세요. 크기 조정 시 종횡비를 유지하세요
Claude가 완전히 다른 요소를 클릭함 모호한 지시, 또는 시각적으로 유사한 요소가 가까이 있음 위치 기반 프롬프트("오른쪽 아래의 파란 Submit 버튼")를 사용하세요. 상호작용을 더 작은 단계로 나누세요
정확도가 일관되게 낮음 해상도가 너무 낮음 기준으로 1280x720을 시도해보세요
**모델 선택이 클릭 정밀도에 영향을 줘요.** 이전 `computer_20251124` 도구를 사용하는 모델 중 Claude Sonnet 4.6은 클릭이 Claude Opus 4.6보다 기계적으로 더 정밀하고, 스크린샷을 많이 축소해야 할 때 더 강건해요. Claude Opus 4.7은 그 차이를 좁혀요. 클릭 정밀도가 Sonnet 4.6과 거의 비슷하고, 더 높은 해상도 한도 덕분에 축소가 덜 필요해요.

구현 모범 사례 따르기

어떤 애플리케이션은 행동에 응답할 시간이 필요해요:
<CodeGroup exclude="shell">
  ```python Python
  def click_and_wait(x, y, wait_time=0.5):
      click_at(x, y)
      time.sleep(wait_time)  # Allow UI to update
  ```

  ```typescript TypeScript
  async function clickAndWait(x: number, y: number, waitMs = 500): Promise<void> {
    clickAt(x, y);
    await setTimeout(waitMs); // Allow UI to update
  }
  ```

  ```csharp C#
  static void ClickAndWait(int x, int y, double waitSeconds = 0.5)
  {
      ClickAt(x, y);
      Thread.Sleep(TimeSpan.FromSeconds(waitSeconds));  // Allow UI to update
  }
  ```

  ```go Go
  func clickAndWaitFor(x, y int, wait time.Duration) {
  	clickAt(x, y)
  	time.Sleep(wait) // Allow UI to update
  }

  func clickAndWait(x, y int) {
  	clickAndWaitFor(x, y, 500*time.Millisecond)
  }
  ```

  ```java Java
  void clickAndWait(int x, int y) throws InterruptedException {
      clickAndWait(x, y, 500);
  }

  void clickAndWait(int x, int y, long waitTimeMillis) throws InterruptedException {
      clickAt(x, y);
      Thread.sleep(waitTimeMillis);  // Allow UI to update
  }
  ```

  ```php PHP
  function clickAndWait(int $x, int $y, float $waitSeconds = 0.5): void
  {
      clickAt($x, $y);
      usleep((int) ($waitSeconds * 1_000_000));  // Allow UI to update
  }
  ```

  ```ruby Ruby
  def click_and_wait(x, y, wait_time: 0.5)
    click_at(x, y)
    sleep(wait_time) # Allow UI to update
  end
  ```
</CodeGroup>
요청된 행동이 안전하고 유효한지 확인하세요:
<CodeGroup exclude="shell">
  ```python Python
  display_width, display_height = 1024, 768


  def validate_action(action_type, params):
      if action_type == "left_click" and "coordinate" in params:
          x, y = params["coordinate"]
          if not (0 <= x < display_width and 0 <= y < display_height):
              return False, "Coordinates out of bounds"
      return True, None
  ```

  ```typescript TypeScript
  const displayWidth = 1024;
  const displayHeight = 768;

  interface ActionParams {
    coordinate?: [number, number];
  }

  function validateAction(actionType: string, params: ActionParams): [boolean, string | null] {
    if (actionType === "left_click" && params.coordinate) {
      const [x, y] = params.coordinate;
      if (!(x >= 0 && x < displayWidth && y >= 0 && y < displayHeight)) {
        return [false, "Coordinates out of bounds"];
      }
    }
    return [true, null];
  }
  ```

  ```csharp C#
  const int DisplayWidth = 1024;
  const int DisplayHeight = 768;
  // ...
  static (bool IsValid, string? Error) ValidateAction(string actionType, IReadOnlyDictionary<string, JsonElement> parameters)
  {
      if (actionType == "left_click" && parameters.TryGetValue("coordinate", out JsonElement coordinate))
      {
          int x = coordinate[0].GetInt32();
          int y = coordinate[1].GetInt32();
          if (x is < 0 or >= DisplayWidth || y is < 0 or >= DisplayHeight)
          {
              return (false, "Coordinates out of bounds");
          }
      }
      return (true, null);
  }
  ```

  ```go Go
  const (
  	displayWidth  = 1024
  	displayHeight = 768
  )

  func validateAction(actionType string, params map[string]any) (bool, string) {
  	raw, hasCoordinate := params["coordinate"]
  	if actionType == "left_click" && hasCoordinate {
  		coord, ok := raw.([]any)
  		if !ok || len(coord) != 2 {
  			return false, "Invalid coordinate"
  		}
  		x, y := int(coord[0].(float64)), int(coord[1].(float64))
  		if !(0 <= x && x < displayWidth && 0 <= y && y < displayHeight) {
  			return false, "Coordinates out of bounds"
  		}
  	}
  	return true, ""
  }
  ```

  ```java Java
  static final int DISPLAY_WIDTH = 1024;
  static final int DISPLAY_HEIGHT = 768;

  record Validation(boolean valid, String error) {}

  Validation validateAction(String actionType, Map<String, JsonValue> params) {
      if (actionType.equals("left_click") && params.containsKey("coordinate")) {
          List<JsonValue> coord = (List<JsonValue>) params.get("coordinate").asArray().get();
          long x = ((Number) coord.get(0).asNumber().get()).longValue();
          long y = ((Number) coord.get(1).asNumber().get()).longValue();
          if (!(0 <= x && x < DISPLAY_WIDTH && 0 <= y && y < DISPLAY_HEIGHT)) {
              return new Validation(false, "Coordinates out of bounds");
          }
      }
      return new Validation(true, null);
  }
  ```

  ```php PHP
  const DISPLAY_WIDTH = 1024;
  const DISPLAY_HEIGHT = 768;

  /** @return array{bool, ?string} */
  function validateAction(string $actionType, array $params): array
  {
      if ($actionType === 'left_click' && isset($params['coordinate'])) {
          [$x, $y] = $params['coordinate'];
          if (!(0 <= $x && $x < DISPLAY_WIDTH && 0 <= $y && $y < DISPLAY_HEIGHT)) {
              return [false, 'Coordinates out of bounds'];
          }
      }
      return [true, null];
  }
  ```

  ```ruby Ruby
  DISPLAY_WIDTH = 1024
  DISPLAY_HEIGHT = 768

  def validate_action(action_type, params)
    if action_type == "left_click" && params.key?(:coordinate)
      x, y = params[:coordinate]
      unless (0...DISPLAY_WIDTH).cover?(x) && (0...DISPLAY_HEIGHT).cover?(y)
        return [false, "Coordinates out of bounds"]
      end
    end
    [true, nil]
  end
  ```
</CodeGroup>
문제 해결을 위해 모든 행동의 로그를 유지하세요:
<CodeGroup exclude="shell">
  ```python Python
  import logging


  def log_action(action_type, params, result):
      logging.info(f"Action: {action_type}, Params: {params}, Result: {result}")
  ```

  ```typescript TypeScript
  function logAction(actionType: string, params: unknown, result: unknown): void {
    console.error(
      `Action: ${actionType}, Params: ${JSON.stringify(params)}, Result: ${JSON.stringify(
        result
      )}`
    );
  }
  ```

  ```csharp C#
  static void LogAction(string actionType, object? parameters, object? result)
  {
      Console.Error.WriteLine($"Action: {actionType}, Params: {parameters}, Result: {result}");
  }
  ```

  ```go Go
  func logAction(actionType string, params map[string]any, result any) {
  	log.Printf("Action: %s, Params: %v, Result: %v", actionType, params, result)
  }
  ```

  ```java Java
  import static java.lang.System.Logger.Level.INFO;

  static final System.Logger LOGGER = System.getLogger("computer-use");

  void logAction(String actionType, Object params, Object result) {
      LOGGER.log(INFO, "Action: {0}, Params: {1}, Result: {2}", actionType, params, result);
  }
  ```

  ```php PHP
  function logAction(string $actionType, array $params, mixed $result): void
  {
      error_log(sprintf(
          'Action: %s, Params: %s, Result: %s',
          $actionType,
          json_encode($params),
          json_encode($result),
      ));
  }
  ```

  ```ruby Ruby
  require "logger"

  LOGGER = Logger.new($stderr)

  def log_action(action_type, params, result)
    LOGGER.info("Action: #{action_type}, Params: #{params}, Result: #{result}")
  end
  ```
</CodeGroup>

computer_20251124에서 마이그레이션

computer_20251124에서 도구셋으로 업그레이드는 선택 사항이에요. 이전 도구 버전 아래 computer_20251124에 나열된 모델은 베타 헤더와 함께 계속 받아주므로 기존 통합은 바꾸기 전까지 계속 작동해요. Claude Opus 5.5는 Claude API와 Google Cloud에서 예외예요. 거기서는 도구셋만 받으므로 그 모델로 옮기기 전에 통합을 업그레이드하세요. Amazon Bedrock에서는 computer_20251124를 계속 받아요. 업그레이드하려면 다음을 함께 바꾸세요:

  1. 베타 헤더 제거. 요청에서 anthropic-beta: computer-use-2025-11-24를 내려주세요. SDK에서는 betas 파라미터를 제거하고 베타 네임스페이스가 아닌 표준 클라이언트로 Messages API를 호출하세요.
  2. tools 항목 변경. type을 computer_toolset_20260801로 설정하고 name, display_width_px, display_height_px, display_number, enable_zoom을 삭제하세요. 도구셋은 이 필드들을 각각 거부해요.
  3. 확대를 유지할지 선택. 도구셋에서 확대는 기본 활성화인 반면 enable_zoom은 기본 false예요. 환경이 확대를 구현하지 않으면 이전 동작을 유지하도록 "configs": {"zoom": {"enabled": false}}를 추가하세요. 그렇지 않으면 구현하세요(사용 가능한 행동 참고).
  4. 턴의 모든 블록 처리. 에이전트 루프를 업데이트해 첫 번째만 읽는 대신 응답의 모든 tool_use 블록을 순회하고, input.action 대신 블록의 name과 함께 toolset_name으로 디스패치하세요. 구성원 입력에 더는 action 필드가 없고, 나머지 필드는 동일해요.
  5. 블록을 순서대로 실행하고 정지 텍스트 사용. 블록을 순차 실행하고 첫 실패에서 멈추며, 배치 행동에 나온 대로 나머지 블록에 Not executed: an earlier computer action in this turn failed.로 답하세요. 루프가 아직 배치를 실행할 수 없으면 도구 파라미터가 Claude를 턴당 한 행동으로 제한하는 방법을 설명해요.
  6. 결과에 toolset_name 그대로 담기. 구성원 호출에 답하는 모든 tool_result에 "toolset_name": "computer"를 추가하세요. 결과는 text와 image 콘텐츠만 담을 수 있어요.
  7. key의 repeat 지원. key 구성원은 선택 repeat 수 1~100을 받아요. 인식하지 못하는 필드를 무시하는 핸들러는 키를 한 번 누를 것이므로 key 핸들러가 repeat을 받들게 하세요.
  8. 직접 스크린샷 크기 조정. 도구셋은 축소 대신 모델의 이미지 한도를 초과하는 스크린샷이나 확대 이미지를 거부해요. 이미지를 돌려주기 전에 크기를 조정하고 이미지 한도에 맞게 스크린샷 크기 조정에 나온 대로 좌표 스케일링을 유지하세요.
  9. 지원되지 않는 옵션 제거. 항목의 defer_loading을 configs로 옮기고 모든 활성 구성원에 같은 값을 주세요. 도구셋 항목에서 지원하지 않는 다른 옵션은 클라이언트 도구셋 아래에 나열되어 있어요.

변경 전의 tools 항목이며 anthropic-beta: computer-use-2025-11-24 헤더와 함께 보냅니다:

{
  "type": "computer_20251124",
  "name": "computer",
  "display_width_px": 1024,
  "display_height_px": 768,
  "display_number": 1
}

변경 후의 tools 항목이며 베타 헤더 없이 보냅니다. configs 객체는 enable_zoom을 설정하지 않는 이전 항목과 일치하도록 확대를 끄고, configs를 완전히 생략하면 기본값을 받아들여 Claude가 확대하게 해요:

{
  "type": "computer_toolset_20260801",
  "configs": {
    "zoom": { "enabled": false }
  }
}

다음 쌍은 변경 전후의 tool_use 블록을 보여줘요. 행동 이름이 input.action에서 name으로 옮겨지고 블록에 toolset_name이 추가돼요:

{
  "type": "tool_use",
  "id": "toolu_01A9r5kQm2LxWc7vT3nZ4bJs",
  "name": "computer",
  "input": { "action": "left_click", "coordinate": [500, 300] }
}
{
  "type": "tool_use",
  "id": "toolu_01A9r5kQm2LxWc7vT3nZ4bJs",
  "name": "left_click",
  "toolset_name": "computer",
  "input": { "coordinate": [500, 300] }
}

이전 도구 버전

컴퓨터 사용 도구의 두 이전 버전이 기존 통합, 도구셋을 지원하지 않는 모델, 그리고 도구셋이 현재 제공되지 않는 플랫폼을 위해 베타로 계속 제공돼요. 각각은 모든 요청에 베타 헤더가 필요하고, 파라미터는 베타 Messages API 레퍼런스에 문서화되어 있어요. SDK에서는 betas 파라미터로 헤더를 전달하고 베타 네임스페이스를 사용하세요. 같은 요청의 bash나 텍스트 편집기 도구가 아니라 컴퓨터 사용 도구만 헤더가 필요해요.

도구 버전 베타 헤더 사용처 파라미터
computer_20251124 computer-use-2025-11-24 Claude Fable 5.1, Claude Mythos 5.1, Claude Fable 5, Claude Mythos 5, Claude Opus 5, Claude Sonnet 5, Claude Opus 4.8, Claude Opus 4.7, Claude Opus 4.6, Claude Sonnet 4.6, Claude Opus 4.5. Amazon Bedrock에서는 Claude Opus 5.5도. API 레퍼런스
computer_20250124 computer-use-2025-01-24 Claude Sonnet 4.5, Claude Haiku 4.5, Claude Opus 4.1(은퇴, Bedrock과 Google Cloud 제외), Claude Sonnet 4(은퇴, Bedrock과 Google Cloud 제외), Claude Opus 4(은퇴, Google Cloud 제외) API 레퍼런스

제한 사항

  1. 지연: 현재 컴퓨터 사용 지연은 인간-AI 상호작용에 있어 일반적인 인간 주도 컴퓨터 행동보다 너무 느릴 수 있어요. 속도가 중요하지 않은 사용 사례(예: 백그라운드 정보 수집, 자동화된 소프트웨어 테스트)를 신뢰할 수 있는 환경에서 집중하세요.
  2. 컴퓨터 비전 정확도와 신뢰성: Claude는 행동을 생성하면서 특정 좌표를 출력할 때 실수하거나 환각할 수 있어요. Claude의 요약된 생각 출력이 모델의 추론을 이해하고 잠재적 문제를 식별하는 데 도움이 돼요. 생각 구성에 display: "summarized"를 설정하세요. 도구셋을 지원하는 모델은 기본적으로 생각 텍스트를 생략하기 때문이에요.
  3. 도구 선택 정확도와 신뢰성: Claude는 행동을 생성하면서 도구를 선택할 때 실수하거나 환각하거나 문제를 해결하려고 예상치 못한 행동을 할 수 있어요. 또한 틈새 애플리케이션이나 한 번에 여러 애플리케이션과 상호작용할 때 신뢰성이 낮을 수 있어요. 복잡한 작업을 요청할 때 모델을 조심히 프롬프트하세요.
  4. 스크롤 신뢰성: scroll 행동은 방향 제어(위, 아래, 왼쪽, 오른쪽)와 지정된 양을 지원해요. 스크롤이 효과가 없는 애플리케이션에서는 Page Down 같은 키보드 대안이 도움이 될 수 있어요.
  5. 스프레드시트 상호작용: 개별 셀을 선택하려면 세밀한 마우스 제어 행동(left_mouse_down, left_mouse_up)과 수정자 키 조합을 사용하세요. 복잡한 스프레드시트 작업은 여전히 여러 번 시도가 필요할 수 있어요.
  6. 소셜·커뮤니케이션 플랫폼에서의 계정 생성과 콘텐츠 생성: Claude는 웹사이트를 방문해도, 소셜 미디어 웹사이트와 플랫폼에서 계정을 만들고, 콘텐츠를 생성·공유하거나 사람으로 가장하는 능력은 제한적이에요.
  7. 취약성: 탈옥과 프롬프트 인젝션은 웹페이지나 이미지에 내장된 지시를 포함해 어떤 최첨단 AI 시스템에도 그렇듯 컴퓨터 사용에 영향을 줄 수 있어요. 보안 고려 사항의 예방 조치를 적용하세요.
  8. 부적절하거나 불법적인 행동: Anthropic의 서비스 약관에 따라 컴퓨터 사용을 어떤 법률이나 허용 가능한 사용 정책을 위반하는 데 사용하면 안 돼요.

항상 Claude의 컴퓨터 사용 행동과 로그를 주의 깊게 검토하고 검증하세요. 인간 감독 없이 완벽한 정밀도나 민감한 사용자 정보가 필요한 작업에 Claude를 사용하지 마세요.

데이터 보존

컴퓨터 사용은 클라이언트 측 도구예요. 세션에 관련된 모든 스크린샷, 마우스 행동, 키보드 입력, 파일은 여러분의 환경에서 캡처·저장되고 Anthropic이 아니에요. Anthropic은 API 호출의 일부로 스크린샷 이미지와 행동 요청을 실시간 처리해요. 그 API 요청의 보존은 API와 데이터 보존이 관리해요.

애플리케이션이 컴퓨터 사용 데이터를 어디에 어떻게 저장할지 제어하므로 컴퓨터 사용은 ZDR 적격이에요. 모든 기능의 ZDR 적격성은 API와 데이터 보존 참고.

가격

컴퓨터 사용은 표준 도구 사용 가격을 따라요. 컴퓨터 사용 도구를 쓸 때:

도구셋 정의 오버헤드: 기본 구성원으로 computer_toolset_20260801을 선언하면 요청에 약 4,500 입력 토큰이 추가돼요(Claude Fable 5, Claude Mythos 5, Claude Opus 5, Claude Opus 4.8에서는 약 4,520, Claude Sonnet 5에서는 약 4,590). 구성원 도구 정의와 도구 사용 시스템 프롬프트를 포함해요. configs로 zoom을 비활성화하면 그 중 약 410 토큰이 제거돼요. 요청의 정확한 수치는 응답 usage에 보고되고, 토큰 계산 엔드포인트로 미리 추정할 수 있어요.

이전 도구 버전: 다음 수치는 computer_20251124와 computer_20250124 도구 버전에 적용되고 computer_toolset_20260801에는 적용되지 않아요:

  • 시스템 프롬프트 오버헤드: 시스템 프롬프트에 추가되는 466~499 토큰
  • 도구 정의: 도구 정의당 약 735 입력 토큰(computer_20250124로 측정)

추가 토큰 소비:

  • 도구 결과로 돌려준 스크린샷과 확대 이미지. 이미지 입력으로 청구돼요(비전 가격 참고).
  • Claude에게 돌려준 도구 실행 결과.
bash나 텍스트 편집기 도구를 컴퓨터 사용과 함께 쓴다면 그 도구들은 각각의 페이지에 문서화된 대로 자체 토큰 비용이 있어요.

다음 단계

증상별 수정 진단 표로 가장 흔한 도구 사용 오류를 고치세요. 완전한 Docker 기반 구현으로 시작하세요 Claude를 외부 도구와 API에 연결하세요. 도구가 어디서 실행되는지, 언제 Claude가 호출하는지, 어떤 도구가 작업에 맞는지 보세요. 해상도, 생각 노력, 컨텍스트 관리에 대한 벤치마크된 권장 사항 브라우저 안에 머무는 작업을 위해, 여러분의 브라우저 환경에서 Claude가 웹페이지를 탐색·읽고·상호작용하게 하세요.

더 알아보기 (Learn more)