Computer use tool


Claude 可以通过 computer use tool 与计算机环境交互,该工具提供截图功能和鼠标/键盘控制,用于自主桌面交互。在 WebArena(一个跨真实网站的自主网页导航基准测试)上,Claude 在单 agent 系统中取得了最先进的结果,展示了端到端完成多步骤浏览器任务的强大能力。

Note

Computer use 处于 beta 阶段,需要 beta header

  • "computer-use-2025-11-24" 用于 Claude Opus 4.7、Claude Opus 4.6、Claude Sonnet 4.6 和 Claude Opus 4.5
  • "computer-use-2025-01-24" 用于 Claude Sonnet 4.5、Claude Haiku 4.5、Claude Opus 4.1、Claude Sonnet 4(已弃用)和 Claude Opus 4(已弃用

请通过反馈表分享你对此功能的反馈。

Note

此功能符合零数据留存 (ZDR) 条件。当你的组织有 ZDR 安排时,通过此功能发送的数据在 API 响应返回后不会被存储。

概述

Computer use 是一项 beta 功能,使 Claude 能够与桌面环境交互。该工具提供:

  • 截图捕获: 查看屏幕上当前显示的内容
  • 鼠标控制: 点击、拖动和移动光标
  • 键盘输入: 输入文本和使用键盘快捷键
  • 桌面自动化: 与任何应用程序或界面交互

虽然 computer use 可以与 bash 和 text editor 等其他工具结合使用以实现更全面的自动化工作流,但 computer use 特指 computer use tool 查看和控制桌面环境的能力。

模型支持请参阅工具参考

安全注意事项

Computer use 是一项 beta 功能,具有与标准 API 功能不同的独特风险。当与互联网交互时,这些风险会加剧。

Warning

为最大限度降低风险,请考虑采取以下预防措施:

  1. 使用具有最小权限的专用虚拟机或容器,以防止直接系统攻击或事故。
  2. 避免让模型访问敏感数据(如账户登录信息),以防止信息泄露。
  3. 将互联网访问限制为域名允许列表,以减少暴露于恶意内容。
  4. 要求人工确认可能导致有意义的真实世界后果的决策以及任何需要明确同意的任务,例如接受 cookie、完成金融交易或同意服务条款。

在某些情况下,Claude 会遵循内容中发现的命令,即使它与用户的指令冲突。例如,网页上的 Claude 指令或图片中包含的指令可能会覆盖指令或导致 Claude 犯错。请采取预防措施将 Claude 与敏感数据和操作隔离,以避免与 prompt injection 相关的风险。

Anthropic 已训练模型抵抗这些 prompt injection,并添加了额外的防御层。如果你使用 computer use 工具,分类器会自动在你的 prompt 上运行以标记潜在的 prompt injection 实例。当这些分类器在截图中识别出潜在的 prompt injection 时,它们会自动引导模型在继续下一步操作之前请求用户确认。这种额外保护并不适合每个用例(例如,没有人在回路中的用例),因此如果你想选择退出并关闭它,请联系支持

即使有分类器防御层,这些预防措施仍然重要。

在你自己的产品中启用 computer use 之前,请告知最终用户相关风险并获得他们的同意。

Computer use 参考实现

通过包含 Web 界面、Docker 容器、示例工具实现和 agent 循环的 computer use 参考实现开始。

快速开始

以下是如何开始使用 computer use:

curl https://api.anthropic.com/v1/messages \
  -H "content-type: application/json" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: computer-use-2025-11-24" \
  -d '{
    "model": "claude-opus-4-7",
    "max_tokens": 1024,
    "tools": [
      {
        "type": "computer_20251124",
        "name": "computer",
        "display_width_px": 1024,
        "display_height_px": 768,
        "display_number": 1
      },
      {
        "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 beta:messages create --beta computer-use-2025-11-24 <<'YAML'
model: claude-opus-4-7
max_tokens: 1024
tools:
  - type: computer_20251124
    name: computer
    display_width_px: 1024
    display_height_px: 768
    display_number: 1
  - 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
import anthropic

client = anthropic.Anthropic()

response = client.beta.messages.create(
    model="claude-opus-4-7",  # or another compatible model
    max_tokens=1024,
    tools=[
        {
            "type": "computer_20251124",
            "name": "computer",
            "display_width_px": 1024,
            "display_height_px": 768,
            "display_number": 1,
        },
        {"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."}],
    betas=["computer-use-2025-11-24"],
)
print(response)
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const response = await client.beta.messages.create({
  model: "claude-opus-4-7",
  max_tokens: 1024,
  tools: [
    {
      type: "computer_20251124",
      name: "computer",
      display_width_px: 1024,
      display_height_px: 768,
      display_number: 1
    },
    {
      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." }],
  betas: ["computer-use-2025-11-24"]
});

console.log(response);
using Anthropic;
using Anthropic.Models.Beta.Messages;
using Messages = Anthropic.Models.Messages;

var client = new AnthropicClient();

var parameters = new MessageCreateParams
{
    Model = Messages::Model.ClaudeOpus4_7,
    MaxTokens = 1024,
    Tools = new BetaToolUnion[]
    {
        new BetaToolComputerUse20251124
        {
            DisplayWidthPx = 1024,
            DisplayHeightPx = 768,
            DisplayNumber = 1
        },
        new BetaToolTextEditor20250728(),
        new BetaToolBash20250124()
    },
    Messages =
    [
        new BetaMessageParam
        {
            Role = Role.User,
            Content = "Save a picture of a cat to my desktop."
        }
    ],
    Betas = ["computer-use-2025-11-24"]
};

var response = await client.Beta.Messages.Create(parameters);
Console.WriteLine(response);
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/anthropics/anthropic-sdk-go"
)

func main() {
	client := anthropic.NewClient()

	response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
		Model:     anthropic.ModelClaudeOpus4_7,
		MaxTokens: 1024,
		Tools: []anthropic.BetaToolUnionParam{
			{OfComputerUseTool20251124: &anthropic.BetaToolComputerUse20251124Param{
				DisplayWidthPx:  1024,
				DisplayHeightPx: 768,
				DisplayNumber:   anthropic.Int(1),
			}},
			{OfTextEditor20250728: &anthropic.BetaToolTextEditor20250728Param{}},
			{OfBashTool20250124: &anthropic.BetaToolBash20250124Param{}},
		},
		Messages: []anthropic.BetaMessageParam{
			anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Save a picture of a cat to my desktop.")),
		},
		Betas: []anthropic.AnthropicBeta{
			"computer-use-2025-11-24", // typed constant pending in the Go SDK
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(response)
}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaToolBash20250124;
import com.anthropic.models.beta.messages.BetaToolComputerUse20251124;
import com.anthropic.models.beta.messages.BetaToolTextEditor20250728;
import com.anthropic.models.beta.messages.MessageCreateParams;

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

    MessageCreateParams params = MessageCreateParams.builder()
        .model("claude-opus-4-7")
        .maxTokens(1024L)
        .addTool(BetaToolComputerUse20251124.builder()
            .displayWidthPx(1024L)
            .displayHeightPx(768L)
            .displayNumber(1L)
            .build())
        .addTool(BetaToolTextEditor20250728.builder().build())
        .addTool(BetaToolBash20250124.builder().build())
        .addUserMessage("Save a picture of a cat to my desktop.")
        .addBeta("computer-use-2025-11-24")
        .build();

    BetaMessage response = client.beta().messages().create(params);
    IO.println(response);
}
<?php

use Anthropic\Client;

$client = new Client(apiKey: getenv("ANTHROPIC_API_KEY"));

$response = $client->beta->messages->create(
    maxTokens: 1024,
    messages: [
        ['role' => 'user', 'content' => 'Save a picture of a cat to my desktop.'],
    ],
    model: 'claude-opus-4-7',
    tools: [
        [
            'type' => 'computer_20251124',
            'name' => 'computer',
            'display_width_px' => 1024,
            'display_height_px' => 768,
            'display_number' => 1,
        ],
        [
            'type' => 'text_editor_20250728',
            'name' => 'str_replace_based_edit_tool',
        ],
        [
            'type' => 'bash_20250124',
            'name' => 'bash',
        ],
    ],
    betas: ['computer-use-2025-11-24'],
);

echo $response;
require "anthropic"

client = Anthropic::Client.new

response = client.beta.messages.create(
  model: "claude-opus-4-7",
  max_tokens: 1024,
  tools: [
    {
      type: "computer_20251124",
      name: "computer",
      display_width_px: 1024,
      display_height_px: 768,
      display_number: 1
    },
    {
      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." }
  ],
  betas: ["computer-use-2025-11-24"]
)

puts response
Note

Beta header 仅对 computer use tool 需要。

前面的示例展示了三个工具一起使用,这需要 beta header,因为它包含了 computer use tool。


Computer use 的工作原理

  1. 为 Claude 提供 computer use tool 和用户提示

    • 将 computer use tool(以及可选的其他工具)添加到你的 API 请求中。
    • 包含需要桌面交互的用户提示,例如"Save a picture of a cat to my desktop."
  2. Claude 选择 computer use tool

    • Claude 评估 computer use tool 是否有助于用户的查询。
    • 如果是,Claude 构造格式正确的工具使用请求。
    • API 响应的 stop_reasontool_use,表示工具使用请求。
  3. 提取工具输入,在计算机上评估工具,并返回结果

    • 在你这边,从 Claude 的请求中提取工具名称和输入。
    • 在容器或虚拟机上使用该工具。
    • 使用包含 tool_result 内容块的新 user 消息继续对话。
  4. Claude 继续调用 computer use tool 直到完成任务

    • Claude 分析工具结果以确定是否需要更多工具使用或任务已完成。
    • 如果 Claude 确定需要另一个工具,它会用另一个 tool_use stop_reason 响应,你应该返回步骤 3。
    • 否则,它会为用户生成文本响应。

步骤 3 和 4 的重复(无需用户输入)称为"agent 循环"(即 Claude 用工具使用请求响应,你的应用程序用评估该请求的结果响应 Claude)。

计算环境

Computer use 需要一个沙箱化的计算环境,Claude 可以在其中安全地与应用程序和 Web 交互。该环境包括:

  1. 虚拟显示: 一个虚拟 X11 显示服务器(使用 Xvfb),渲染 Claude 将通过截图看到并通过鼠标/键盘操作控制的桌面界面。

  2. 桌面环境: 在 Linux 上运行的轻量级 UI,带有窗口管理器 (Mutter) 和面板 (Tint2),为 Claude 提供一致的图形界面进行交互。

  3. 应用程序: 预安装的 Linux 应用程序,如 Firefox、LibreOffice、文本编辑器和文件管理器,Claude 可以使用它们完成任务。

  4. 工具实现: 将 Claude 的抽象工具请求(如"移动鼠标"或"截图")转换为虚拟环境中实际操作的集成代码。

  5. Agent 循环: 处理 Claude 与环境之间通信的程序,将 Claude 的操作发送到环境并将结果(截图、命令输出)返回给 Claude。

当你使用 computer use 时,Claude 不会直接连接到这个环境。相反,你的应用程序:

  1. 接收 Claude 的工具使用请求
  2. 将它们转换为你的计算环境中的操作
  3. 捕获结果(如截图和命令输出)
  4. 将这些结果返回给 Claude

为了安全和隔离,参考实现在 Docker 容器内运行所有这些,并使用适当的端口映射来查看和与环境交互。


如何实现 computer use

从参考实现开始

有一个参考实现,包含了开始使用 computer use 所需的一切:

理解 agentic 循环

Computer use 的核心是"agent 循环":Claude 请求工具操作、你的应用程序运行它们并将结果返回给 Claude 的循环。以下是一个简化的示例:

Info

Agent 循环是一种有状态的多轮模式,不适用于一次性 shell 命令。请参阅 SDK 选项卡了解实现。

Info

Agent 循环是一种有状态的多轮模式,不适用于一次性 shell 命令。请参阅 SDK 选项卡了解实现。

def sampling_loop(model, messages, max_iterations=10):
    """
    运行 computer-use agent 循环,直到 Claude 停止请求工具
    或达到迭代限制。
    """
    for _ in range(max_iterations):
        response = client.beta.messages.create(
            model=model,
            max_tokens=4096,
            messages=messages,
            tools=TOOLS,
            betas=["computer-use-2025-11-24"],
        )

        # 将 Claude 的响应添加到对话历史
        messages.append({"role": "assistant", "content": response.content})

        # 运行 Claude 请求的任何工具并收集结果
        tool_results = process_tool_calls(response)
        if not tool_results:
            return messages  # 没有更多工具使用;任务完成

        # 将工具结果发送回 Claude 进行下一次迭代
        messages.append({"role": "user", "content": tool_results})

    return messages
async function samplingLoop(
  model: string,
  messages: Anthropic.Beta.BetaMessageParam[],
  maxIterations = 10,
): Promise<Anthropic.Beta.BetaMessageParam[]> {
  // 运行 computer-use agent 循环,直到 Claude 停止请求工具
  // 或达到迭代限制。
  for (let i = 0; i < maxIterations; i++) {
    const response = await client.beta.messages.create({
      model,
      max_tokens: 4096,
      messages,
      tools,
      betas: ["computer-use-2025-11-24"],
    });

    // 将 Claude 的响应添加到对话历史
    messages.push({ role: "assistant", content: response.content });

    // 运行 Claude 请求的任何工具并收集结果
    const toolResults = processToolCalls(response);
    if (toolResults.length === 0) {
      return messages; // 没有更多工具使用;任务完成
    }

    // 将工具结果发送回 Claude 进行下一次迭代
    messages.push({ role: "user", content: toolResults });
  }

  return messages;
}
async Task<List<BetaMessageParam>> SamplingLoop(
    Model model,
    List<BetaMessageParam> messages,
    int maxIterations = 10
)
{
    // 运行 computer-use agent 循环,直到 Claude 停止请求工具
    // 或达到迭代限制。
    for (var i = 0; i < maxIterations; i++)
    {
        var response = await client.Beta.Messages.Create(
            new MessageCreateParams
            {
                Model = model,
                MaxTokens = 4096,
                Messages = messages,
                Tools = tools,
                Betas = ["computer-use-2025-11-24"],
            }
        );

        // 将 Claude 的响应添加到对话历史
        messages.Add(
            new()
            {
                Role = Role.Assistant,
                Content = response
                    .Content.Select(block => new BetaContentBlockParam(block.Json))
                    .ToList(),
            }
        );

        // 运行 Claude 请求的任何工具并收集结果
        var toolResults = ProcessToolCalls(response);
        if (toolResults.Count == 0)
        {
            return messages; // 没有更多工具使用;任务完成
        }

        // 将工具结果发送回 Claude 进行下一次迭代
        messages.Add(new() { Role = Role.User, Content = toolResults });
    }

    return messages;
}
// samplingLoop 运行 computer-use agent 循环,直到 Claude 停止
// 请求工具或达到迭代限制。
func samplingLoop(ctx context.Context, model anthropic.Model, messages []anthropic.BetaMessageParam, maxIterations int) ([]anthropic.BetaMessageParam, error) {
	for range maxIterations {
		response, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
			Model:     model,
			MaxTokens: 4096,
			Messages:  messages,
			Tools:     tools,
			Betas:     []anthropic.AnthropicBeta{"computer-use-2025-11-24"},
		})
		if err != nil {
			return nil, err
		}

		// 将 Claude 的响应添加到对话历史
		messages = append(messages, response.ToParam())

		// 运行 Claude 请求的任何工具并收集结果
		toolResults := processToolCalls(response)
		if len(toolResults) == 0 {
			return messages, nil // 没有更多工具使用;任务完成
		}

		// 将工具结果发送回 Claude 进行下一次迭代
		messages = append(messages, anthropic.BetaMessageParam{
			Role:    anthropic.BetaMessageParamRoleUser,
			Content: toolResults,
		})
	}
	return messages, nil
}

/**
 * 运行 computer-use agent 循环,直到 Claude 停止请求工具
 * 或达到迭代限制。
 */
List<BetaMessageParam> samplingLoop(Model model, List<BetaMessageParam> messages, int maxIterations) {
    for (int i = 0; i < maxIterations; i++) {
        BetaMessage response = client.beta().messages().create(MessageCreateParams.builder()
                .model(model)
                .maxTokens(4096)
                .messages(messages)
                .addTool(COMPUTER_TOOL)
                .addBeta("computer-use-2025-11-24")
                .build());

        // 将 Claude 的响应添加到对话历史
        messages.add(BetaMessageParam.builder()
                .role(BetaMessageParam.Role.ASSISTANT)
                .contentOfBetaContentBlockParams(
                        response.content().stream().map(BetaContentBlock::toParam).toList())
                .build());

        // 运行 Claude 请求的任何工具并收集结果
        List<BetaContentBlockParam> toolResults = processToolCalls(response);
        if (toolResults.isEmpty()) {
            return messages; // 没有更多工具使用;任务完成
        }

        // 将工具结果发送回 Claude 进行下一次迭代
        messages.add(BetaMessageParam.builder()
                .role(BetaMessageParam.Role.USER)
                .contentOfBetaContentBlockParams(toolResults)
                .build());
    }
    return messages;
}
/**
 * 运行 computer-use agent 循环,直到 Claude 停止请求工具
 * 或达到迭代限制。
 */
function samplingLoop(string $model, array $messages, int $maxIterations = 10): array
{
    global $client, $tools;

    for ($i = 0; $i < $maxIterations; $i++) {
        $response = $client->beta->messages->create(
            model: $model,
            maxTokens: 4096,
            messages: $messages,
            tools: $tools,
            betas: ['computer-use-2025-11-24'],
        );

        // 将 Claude 的响应添加到对话历史
        $messages[] = BetaMessageParam::with(role: Role::ASSISTANT, content: $response->content);

        // 运行 Claude 请求的任何工具并收集结果
        $toolResults = processToolCalls($response);
        if ($toolResults === []) {
            return $messages; // 没有更多工具使用;任务完成
        }

        // 将工具结果发送回 Claude 进行下一次迭代
        $messages[] = BetaMessageParam::with(role: Role::USER, content: $toolResults);
    }

    return $messages;
}
# 运行 computer-use agent 循环,直到 Claude 停止请求工具
# 或达到迭代限制。
def sampling_loop(model, messages, max_iterations: 10)
  max_iterations.times do
    response = CLIENT.beta.messages.create(
      model: model,
      max_tokens: 4096,
      messages: messages,
      tools: TOOLS,
      betas: ["computer-use-2025-11-24"]
    )

    # 将 Claude 的响应添加到对话历史
    messages << {role: "assistant", content: response.content}

    # 运行 Claude 请求的任何工具并收集结果
    tool_results = process_tool_calls(response)
    return messages if tool_results.empty? # 没有更多工具使用;任务完成

    # 将工具结果发送回 Claude 进行下一次迭代
    messages << {role: "user", content: tool_results}
  end

  messages
end

循环持续进行,直到 Claude 响应时不再请求任何工具(任务完成)或达到最大迭代限制。此安全措施可防止可能导致意外 API 成本的潜在无限循环。

在阅读本文档其余部分之前,请先尝试参考实现。

通过 prompting 优化模型性能

以下是获得最佳质量输出的一些技巧:

  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. 如果你需要模型登录,请在提示中在 XML 标签内提供用户名和密码,如 <robot_credentials>。在需要登录的应用程序中使用 computer use 会增加由于 prompt injection 导致不良结果的风险。在向模型提供登录凭据之前,请查看缓解越狱和 prompt injection
  6. 在构造用户轮次的 content 数组时,将指令文本放在截图图像之前。在处理图像之前提供目标描述可以提高点击准确性。
Tip

如果你反复遇到一组明确的问题或提前知道 Claude 需要完成的任务,请使用系统提示为 Claude 提供关于如何成功完成任务的明确提示或指令。

Tip

对于跨多个会话的 agent,请在每个会话开始时运行端到端验证,而不仅仅是在实现之后。基于浏览器的检查可以捕获仅代码级审查会遗漏的来自先前会话的回归。详情请参阅长时间运行 agent 的有效测试工具

系统提示

当通过 Claude API 请求 Anthropic-schema 工具之一时,会生成 computer use 特定的系统提示。它类似于工具使用系统提示,但以以下内容开头:

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_prompt 字段仍然被尊重并用于构造组合系统提示。

可用操作

Computer use tool 支持以下操作:

基本操作(所有版本)

  • screenshot: 捕获当前显示
  • left_click: 在坐标 [x, y] 处点击
  • type: 输入文本字符串
  • key: 按键或组合键(例如 "ctrl+s")
  • mouse_move: 将光标移动到坐标

增强操作 (computer_20250124) 在所有支持 computer use 的模型上可用:

  • scroll: 在任意方向滚动,可控制滚动量
  • left_click_drag: 在坐标之间点击并拖动
  • right_clickmiddle_click: 额外的鼠标按钮
  • double_clicktriple_click: 多次点击
  • left_mouse_downleft_mouse_up: 细粒度点击控制
  • hold_key: 按住指定持续时间(以秒为单位)的键
  • wait: 操作之间的暂停

增强操作 (computer_20251124) 在 Claude Opus 4.7、Claude Opus 4.6、Claude Sonnet 4.6 和 Claude Opus 4.5 中可用:

  • computer_20250124 的所有操作
  • zoom: 以全分辨率查看屏幕的特定区域。需要在工具定义中设置 enable_zoom: true。接受 region 参数,坐标 [x1, y1, x2, y2] 定义要检查区域的左上角和右下角。

示例操作

截图:

{
  "action": "screenshot"
}

在位置点击:

{
  "action": "left_click",
  "coordinate": [500, 300]
}

输入文本:

{
  "action": "type",
  "text": "Hello, world!"
}

向下滚动:

{
  "action": "scroll",
  "coordinate": [500, 400],
  "scroll_direction": "down",
  "scroll_amount": 3
}

缩放查看区域详情(Opus 4.7、Opus 4.6、Sonnet 4.6 和 Opus 4.5):

{
  "action": "zoom",
  "region": [100, 200, 400, 350]
}

带点击和滚动操作的修饰键

要在执行点击或滚动操作时按住修饰键(如 Shift、Ctrl 或 Alt),请在这些操作上使用 text 参数。这与 hold_key 不同,后者在不执行其他操作的情况下按住键一段时间。

Shift+点击(例如,选择一系列项目):

{
  "action": "left_click",
  "coordinate": [500, 300],
  "text": "shift"
}

Ctrl+点击(例如,在 Windows/Linux 上多选):

{
  "action": "left_click",
  "coordinate": [500, 300],
  "text": "ctrl"
}

Cmd+点击(例如,在 macOS 上多选):

{
  "action": "left_click",
  "coordinate": [500, 300],
  "text": "super"
}

Shift+滚动(例如,水平滚动):

{
  "action": "scroll",
  "coordinate": [500, 400],
  "scroll_direction": "down",
  "scroll_amount": 3,
  "text": "shift"
}

点击/滚动操作中的 text 参数接受修饰键,如 shiftctrlaltsuper(用于 Command/Windows 键)。

工具参数

参数必需描述
type工具版本 (computer_20251124computer_20250124)
name必须是 "computer"
display_width_px显示宽度(像素)
display_height_px显示高度(像素)
display_numberX11 环境的显示编号
enable_zoom启用缩放操作(仅限 computer_20251124)。设置为 true 以允许 Claude 缩放到特定屏幕区域。默认值:false
Note

重要: 你的应用程序必须显式运行 computer use tool;Claude 无法直接运行它。你负责根据 Claude 的请求实现截图捕获、鼠标移动、键盘输入和其他操作。

结合扩展思考

有关将 computer use 与扩展思考结合使用的信息,请参阅扩展思考

Tip

对于 computer use,内部基准测试建议以下 effort 设置:

  • Claude Opus 4.7: 使用 high 作为默认值;对于高吞吐量或成本敏感的工作负载使用 low
  • Claude Sonnet 4.6 和 Claude Opus 4.6: 使用 medium 作为默认值(最佳准确性与成本比)。避免使用 max,它会增加 token 成本而不会提高 UI 任务的准确性。在这些模型上,low 使用的输出 token 比完全禁用思考更少(更少的错误意味着更少的重试),使其成为成本敏感循环的有力选择。

使用其他工具增强 computer use

要在 computer use 旁边添加其他工具,请将它们包含在同一个 tools 数组中。快速开始部分展示了这种模式,使用了 bash tooltext editor tool。你可以用同样的方式添加自己的自定义工具定义

构建自定义 computer use 环境

参考实现旨在帮助你开始使用 computer use。它包含了让 Claude 使用计算机所需的所有组件。但是,你可以根据需要构建自己的 computer use 环境。你需要:

  • 一个适合与 Claude 一起使用的虚拟化或容器化环境
  • 至少一个 Anthropic-schema computer use 工具的实现
  • 一个与 Claude API 交互并使用你的工具实现运行 tool_use 结果的 agent 循环
  • 一个允许用户输入启动 agent 循环的 API 或 UI

实现 computer use tool

Computer use tool 作为无 schema 工具实现。使用此工具时,你不需要像其他工具那样提供输入 schema;schema 内置于 Claude 的模型中,无法修改。

  1. 设置你的计算环境

    创建虚拟显示或连接到 Claude 将与之交互的现有显示。这通常涉及设置 Xvfb(X Virtual Framebuffer)或类似技术。

  2. 实现操作处理程序

    创建函数来处理 Claude 可能请求的每种操作类型:

    Info

    这是应用程序端辅助代码,没有 API 请求。请参阅 SDK 选项卡了解模式。

    Info

    这是应用程序端辅助代码,没有 API 请求。请参阅 SDK 选项卡了解模式。

    def capture_screenshot():
        return "<screenshot data>"
    
    
    def click_at(x, y):
        return f"clicked at ({x}, {y})"
    
    
    def type_text(text):
        return f"typed: {text}"
    
    
    def handle_computer_action(action_type, params):
        if action_type == "screenshot":
            return capture_screenshot()
        elif action_type == "left_click":
            x, y = params["coordinate"]
            return click_at(x, y)
        elif action_type == "type":
            return type_text(params["text"])
        # 根据需要处理其他操作
        return f"unhandled action: {action_type}"
    
    function captureScreenshot(): string {
      return "<screenshot data>";
    }
    
    function clickAt(x: number, y: number): string {
      return `clicked at (${x}, ${y})`;
    }
    
    function typeText(text: string): string {
      return `typed: ${text}`;
    }
    
    function handleComputerAction(
      actionType: string,
      params: Record<string, unknown>,
    ): string {
      if (actionType === "screenshot") {
        return captureScreenshot();
      } else if (actionType === "left_click") {
        const [x, y] = params.coordinate as [number, number];
        return clickAt(x, y);
      } else if (actionType === "type") {
        return typeText(params.text as string);
      }
      // 根据需要处理其他操作
      return `unhandled action: ${actionType}`;
    }
    
    string CaptureScreenshot() => "<screenshot data>";
    
    string ClickAt(int x, int y) => {{CONTENT}}quot;clicked at ({x}, {y})";
    
    string TypeText(string text) => {{CONTENT}}quot;typed: {text}";
    
    string HandleComputerAction(string actionType, IReadOnlyDictionary<string, JsonElement> input) =>
        actionType switch
        {
            "screenshot" => CaptureScreenshot(),
            "left_click" => ClickAt(
                input["coordinate"][0].GetInt32(),
                input["coordinate"][1].GetInt32()
            ),
            "type" => TypeText(input["text"].GetString()!),
            // 根据需要处理其他操作
            _ => {{CONTENT}}quot;unhandled action: {actionType}",
        };
    
    func captureScreenshot() string {
    	return "<screenshot data>"
    }
    
    func clickAt(x, y int) string {
    	return fmt.Sprintf("clicked at (%d, %d)", x, y)
    }
    
    func typeText(text string) string {
    	return fmt.Sprintf("typed: %s", text)
    }
    
    func handleComputerAction(actionType string, params map[string]any) string {
    	switch actionType {
    	case "screenshot":
    		return captureScreenshot()
    	case "left_click":
    		coord := params["coordinate"].([]any)
    		return clickAt(int(coord[0].(float64)), int(coord[1].(float64)))
    	case "type":
    		return typeText(params["text"].(string))
    	// 根据需要处理其他操作
    	default:
    		return fmt.Sprintf("unhandled action: %s", actionType)
    	}
    }
    
    
    String captureScreenshot() {
        return "<screenshot data>";
    }
    
    String clickAt(long x, long y) {
        return "clicked at (" + x + ", " + y + ")";
    }
    
    String typeText(String text) {
        return "typed: " + text;
    }
    
    String handleComputerAction(String actionType, Map<String, JsonValue> params) {
        return switch (actionType) {
            case "screenshot" -> captureScreenshot();
            case "left_click" -> {
                List<JsonValue> coordinate = (List<JsonValue>) params.get("coordinate").asArray().get();
                long x = ((Number) coordinate.get(0).asNumber().get()).longValue();
                long y = ((Number) coordinate.get(1).asNumber().get()).longValue();
                yield clickAt(x, y);
            }
            case "type" -> typeText(params.get("text").asStringOrThrow());
            // 根据需要处理其他操作
            default -> "unhandled action: " + actionType;
        };
    }
    
    function captureScreenshot(): string
    {
        return '<screenshot data>';
    }
    
    function clickAt(int $x, int $y): string
    {
        return "clicked at ({$x}, {$y})";
    }
    
    function typeText(string $text): string
    {
        return "typed: {$text}";
    }
    
    function handleComputerAction(string $actionType, array $params): string
    {
        return match ($actionType) {
            'screenshot' => captureScreenshot(),
            'left_click' => clickAt(...$params['coordinate']),
            'type' => typeText($params['text']),
            // 根据需要处理其他操作
            default => "unhandled action: {$actionType}",
        };
    }
    
    def capture_screenshot
      "<screenshot data>"
    end
    
    def click_at(x, y)
      "clicked at (#{x}, #{y})"
    end
    
    def type_text(text)
      "typed: #{text}"
    end
    
    def handle_computer_action(action_type, params)
      case action_type
      when "screenshot"
        capture_screenshot
      when "left_click"
        x, y = params[:coordinate]
        click_at(x, y)
      when "type"
        type_text(params[:text])
      # 根据需要处理其他操作
      else
        "unhandled action: #{action_type}"
      end
    end
    
  3. 处理 Claude 的工具调用

    从 Claude 的响应中提取并运行工具调用:

    Info

    这是应用程序端辅助代码,没有 API 请求。请参阅 SDK 选项卡了解模式。

    Info

    这是应用程序端辅助代码,没有 API 请求。请参阅 SDK 选项卡了解模式。

    def process_tool_calls(response):
        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                action = block.input["action"]
                result = handle_computer_action(action, block.input)
                tool_results.append(
                    {
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": result,
                    }
                )
        return tool_results
    
    function processToolCalls(
      response: Anthropic.Beta.BetaMessage,
    ): Anthropic.Beta.BetaToolResultBlockParam[] {
      const toolResults: Anthropic.Beta.BetaToolResultBlockParam[] = [];
      for (const block of response.content) {
        if (block.type === "tool_use") {
          const input = block.input as Record<string, unknown>;
          const action = input.action as string;
          const result = handleComputerAction(action, input);
          toolResults.push({
            type: "tool_result",
            tool_use_id: block.id,
            content: result,
          });
        }
      }
      return toolResults;
    }
    
    List<BetaContentBlockParam> ProcessToolCalls(BetaMessage response)
    {
        List<BetaContentBlockParam> toolResults = [];
        foreach (var block in response.Content)
        {
            if (block.TryPickToolUse(out var toolUse))
            {
                var action = toolUse.Input["action"].GetString()!;
                var result = HandleComputerAction(action, toolUse.Input);
                toolResults.Add(new BetaToolResultBlockParam(toolUse.ID) { Content = result });
            }
        }
        return toolResults;
    }
    
    func processToolCalls(response *anthropic.BetaMessage) []anthropic.BetaContentBlockParamUnion {
    	var toolResults []anthropic.BetaContentBlockParamUnion
    	for _, block := range response.Content {
    		switch variant := block.AsAny().(type) {
    		case anthropic.BetaToolUseBlock:
    			input := variant.Input.(map[string]any)
    			action := input["action"].(string)
    			result := handleComputerAction(action, input)
    			toolResults = append(toolResults, anthropic.NewBetaToolResultBlock(variant.ID, result, false))
    		}
    	}
    	return toolResults
    }
    
    
    List<BetaContentBlockParam> processToolCalls(BetaMessage response) {
        List<BetaContentBlockParam> toolResults = new ArrayList<>();
        for (BetaContentBlock block : response.content()) {
            if (block.isToolUse()) {
                BetaToolUseBlock toolUse = block.asToolUse();
                Map<String, JsonValue> input =
                        (Map<String, JsonValue>) toolUse._input().asObject().get();
                String action = input.get("action").asStringOrThrow();
                String result = handleComputerAction(action, input);
                toolResults.add(BetaContentBlockParam.ofToolResult(
                        BetaToolResultBlockParam.builder()
                                .toolUseId(toolUse.id())
                                .content(result)
                                .build()));
            }
        }
        return toolResults;
    }
    
    function processToolCalls(BetaMessage $response): array
    {
        $toolResults = [];
        foreach ($response->content as $block) {
            if ($block instanceof BetaToolUseBlock) {
                $action = $block->input['action'];
                $result = handleComputerAction($action, $block->input);
                $toolResults[] = BetaToolResultBlockParam::with(
                    toolUseID: $block->id,
                    content: $result,
                );
            }
        }
        return $toolResults;
    }
    
    def process_tool_calls(response)
      tool_results = []
      response.content.each do |block|
        next unless block.type == :tool_use
    
        action = block.input[:action]
        result = handle_computer_action(action, block.input)
        tool_results << {
          type: "tool_result",
          tool_use_id: block.id,
          content: result
        }
      end
      tool_results
    end
    
  4. 实现 agent 循环

    创建一个循环,持续到 Claude 完成任务:

    Info

    Agent 循环是一种有状态的多轮模式,不适用于一次性 shell 命令。请参阅 SDK 选项卡了解实现。

    Info

    Agent 循环是一种有状态的多轮模式,不适用于一次性 shell 命令。请参阅 SDK 选项卡了解实现。

    def sampling_loop(model, messages, max_iterations=10):
        """
        运行 computer-use agent 循环,直到 Claude 停止请求工具
        或达到迭代限制。
        """
        for _ in range(max_iterations):
            response = client.beta.messages.create(
                model=model,
                max_tokens=4096,
                messages=messages,
                tools=TOOLS,
                betas=["computer-use-2025-11-24"],
            )
    
            # 将 Claude 的响应添加到对话历史
            messages.append({"role": "assistant", "content": response.content})
    
            # 运行 Claude 请求的任何工具并收集结果
            tool_results = process_tool_calls(response)
            if not tool_results:
                return messages  # 没有更多工具使用;任务完成
    
            # 将工具结果发送回 Claude 进行下一次迭代
            messages.append({"role": "user", "content": tool_results})
    
        return messages
    
    async function samplingLoop(
      model: string,
      messages: Anthropic.Beta.BetaMessageParam[],
      maxIterations = 10,
    ): Promise<Anthropic.Beta.BetaMessageParam[]> {
      // 运行 computer-use agent 循环,直到 Claude 停止请求工具
      // 或达到迭代限制。
      for (let i = 0; i < maxIterations; i++) {
        const response = await client.beta.messages.create({
          model,
          max_tokens: 4096,
          messages,
          tools,
          betas: ["computer-use-2025-11-24"],
        });
    
        // 将 Claude 的响应添加到对话历史
        messages.push({ role: "assistant", content: response.content });
    
        // 运行 Claude 请求的任何工具并收集结果
        const toolResults = processToolCalls(response);
        if (toolResults.length === 0) {
          return messages; // 没有更多工具使用;任务完成
        }
    
        // 将工具结果发送回 Claude 进行下一次迭代
        messages.push({ role: "user", content: toolResults });
      }
    
      return messages;
    }
    
    async Task<List<BetaMessageParam>> SamplingLoop(
        Model model,
        List<BetaMessageParam> messages,
        int maxIterations = 10
    )
    {
        // 运行 computer-use agent 循环,直到 Claude 停止请求工具
        // 或达到迭代限制。
        for (var i = 0; i < maxIterations; i++)
        {
            var response = await client.Beta.Messages.Create(
                new MessageCreateParams
                {
                    Model = model,
                    MaxTokens = 4096,
                    Messages = messages,
                    Tools = tools,
                    Betas = ["computer-use-2025-11-24"],
                }
            );
    
            // 将 Claude 的响应添加到对话历史
            messages.Add(
                new()
                {
                    Role = Role.Assistant,
                    Content = response
                        .Content.Select(block => new BetaContentBlockParam(block.Json))
                        .ToList(),
                }
            );
    
            // 运行 Claude 请求的任何工具并收集结果
            var toolResults = ProcessToolCalls(response);
            if (toolResults.Count == 0)
            {
                return messages; // 没有更多工具使用;任务完成
            }
    
            // 将工具结果发送回 Claude 进行下一次迭代
            messages.Add(new() { Role = Role.User, Content = toolResults });
        }
    
        return messages;
    }
    
    // samplingLoop 运行 computer-use agent 循环,直到 Claude 停止
    // 请求工具或达到迭代限制。
    func samplingLoop(ctx context.Context, model anthropic.Model, messages []anthropic.BetaMessageParam, maxIterations int) ([]anthropic.BetaMessageParam, error) {
    	for range maxIterations {
    		response, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
    			Model:     model,
    			MaxTokens: 4096,
    			Messages:  messages,
    			Tools:     tools,
    			Betas:     []anthropic.AnthropicBeta{"computer-use-2025-11-24"},
    		})
    		if err != nil {
    			return nil, err
    		}
    
    		// 将 Claude 的响应添加到对话历史
    		messages = append(messages, response.ToParam())
    
    		// 运行 Claude 请求的任何工具并收集结果
    		toolResults := processToolCalls(response)
    		if len(toolResults) == 0 {
    			return messages, nil // 没有更多工具使用;任务完成
    		}
    
    		// 将工具结果发送回 Claude 进行下一次迭代
    		messages = append(messages, anthropic.BetaMessageParam{
    			Role:    anthropic.BetaMessageParamRoleUser,
    			Content: toolResults,
    		})
    	}
    	return messages, nil
    }
    
    
    /**
     * 运行 computer-use agent 循环,直到 Claude 停止请求工具
     * 或达到迭代限制。
     */
    List<BetaMessageParam> samplingLoop(Model model, List<BetaMessageParam> messages, int maxIterations) {
        for (int i = 0; i < maxIterations; i++) {
            BetaMessage response = client.beta().messages().create(MessageCreateParams.builder()
                    .model(model)
                    .maxTokens(4096)
                    .messages(messages)
                    .addTool(COMPUTER_TOOL)
                    .addBeta("computer-use-2025-11-24")
                    .build());
    
            // 将 Claude 的响应添加到对话历史
            messages.add(BetaMessageParam.builder()
                    .role(BetaMessageParam.Role.ASSISTANT)
                    .contentOfBetaContentBlockParams(
                            response.content().stream().map(BetaContentBlock::toParam).toList())
                    .build());
    
            // 运行 Claude 请求的任何工具并收集结果
            List<BetaContentBlockParam> toolResults = processToolCalls(response);
            if (toolResults.isEmpty()) {
                return messages; // 没有更多工具使用;任务完成
            }
    
            // 将工具结果发送回 Claude 进行下一次迭代
            messages.add(BetaMessageParam.builder()
                    .role(BetaMessageParam.Role.USER)
                    .contentOfBetaContentBlockParams(toolResults)
                    .build());
        }
        return messages;
    }
    
    /**
     * 运行 computer-use agent 循环,直到 Claude 停止请求工具
     * 或达到迭代限制。
     */
    function samplingLoop(string $model, array $messages, int $maxIterations = 10): array
    {
        global $client, $tools;
    
        for ($i = 0; $i < $maxIterations; $i++) {
            $response = $client->beta->messages->create(
                model: $model,
                maxTokens: 4096,
                messages: $messages,
                tools: $tools,
                betas: ['computer-use-2025-11-24'],
            );
    
            // 将 Claude 的响应添加到对话历史
            $messages[] = BetaMessageParam::with(role: Role::ASSISTANT, content: $response->content);
    
            // 运行 Claude 请求的任何工具并收集结果
            $toolResults = processToolCalls($response);
            if ($toolResults === []) {
                return $messages; // 没有更多工具使用;任务完成
            }
    
            // 将工具结果发送回 Claude 进行下一次迭代
            $messages[] = BetaMessageParam::with(role: Role::USER, content: $toolResults);
        }
    
        return $messages;
    }
    
    # 运行 computer-use agent 循环,直到 Claude 停止请求工具
    # 或达到迭代限制。
    def sampling_loop(model, messages, max_iterations: 10)
      max_iterations.times do
        response = CLIENT.beta.messages.create(
          model: model,
          max_tokens: 4096,
          messages: messages,
          tools: TOOLS,
          betas: ["computer-use-2025-11-24"]
        )
    
        # 将 Claude 的响应添加到对话历史
        messages << {role: "assistant", content: response.content}
    
        # 运行 Claude 请求的任何工具并收集结果
        tool_results = process_tool_calls(response)
        return messages if tool_results.empty? # 没有更多工具使用;任务完成
    
        # 将工具结果发送回 Claude 进行下一次迭代
        messages << {role: "user", content: tool_results}
      end
    
      messages
    end
    

处理错误

实现 computer use tool 时,可能会发生各种错误。以下是处理方法:

截图捕获失败

如果截图捕获失败,请返回适当的错误消息:

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

无效坐标

如果 Claude 提供的坐标超出显示边界:

{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
      "content": "Error: Coordinates (1200, 900) are outside display bounds (1024x768).",
      "is_error": true
    }
  ]
}

操作执行失败

如果操作运行失败:

{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
      "content": "Error: Failed to perform click action. The application may be unresponsive.",
      "is_error": true
    }
  ]
}

处理高分辨率的坐标缩放

Note

Claude Opus 4.7 支持长边最多 2576 像素,其坐标与图像像素 1:1 对应(无需缩放因子转换)。以下 1568 像素的指导适用于早期模型。

API 将图像限制为最长边最多 1568 像素,总共约 1.15 兆像素(详见图像调整大小)。例如,1512x982 的屏幕会被下采样到大约 1330x864。Claude 分析这个较小的图像并在该空间中返回坐标,但你的工具在原始屏幕空间中执行点击。

除非你处理坐标转换,否则这会导致 Claude 的点击坐标错过目标。

为了解决这个问题,请自行调整截图大小并将 Claude 的坐标放大回来:

Info

坐标缩放和截图调整大小发生在你的应用程序代码中,而不是 API 请求中。请参阅 SDK 选项卡了解辅助模式。

Info

坐标缩放和截图调整大小发生在你的应用程序代码中,而不是 API 请求中。请参阅 SDK 选项卡了解辅助模式。

screen_width, screen_height = 1512, 982


def capture_and_resize(w, h): ...
def perform_click(x, y): ...


import math


def get_scale_factor(width, height):
    """计算缩放因子以满足 API 约束。"""
    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)


# 截图时
scale = get_scale_factor(screen_width, screen_height)
scaled_width = int(screen_width * scale)
scaled_height = int(screen_height * scale)

# 在发送给 Claude 之前将图像调整为缩放后的尺寸
screenshot = capture_and_resize(scaled_width, scaled_height)


# 处理 Claude 的坐标时,将它们放大回来
def execute_click(x, y):
    screen_x = x / scale
    screen_y = y / scale
    perform_click(screen_x, screen_y)


print(f"scale={scale:.6f} scaled={scaled_width}x{scaled_height}")
const screenWidth = 1512;
const screenHeight = 982;
function captureAndResize(w: number, h: number): string {
  return "";
}
function performClick(x: number, y: number): void {}
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);
}

// 截图时
const scale = getScaleFactor(screenWidth, screenHeight);
const scaledWidth = Math.floor(screenWidth * scale);
const scaledHeight = Math.floor(screenHeight * scale);

// 在发送给 Claude 之前将图像调整为缩放后的尺寸
const screenshot = captureAndResize(scaledWidth, scaledHeight);

// 处理 Claude 的坐标时,将它们放大回来
function executeClick(x: number, y: number): void {
  const screenX = x / scale;
  const screenY = y / scale;
  performClick(screenX, screenY);
}

console.log(`scale=${scale.toFixed(6)} scaled=${scaledWidth}x${scaledHeight}`);
int screenWidth = 1512, screenHeight = 982;

object? CaptureAndResize(int w, int h) => null;
void PerformClick(double x, double y) { }

double GetScaleFactor(int width, int height)
{
    // 计算缩放因子以满足 API 约束。
    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));
}

// 截图时
double scale = GetScaleFactor(screenWidth, screenHeight);
int scaledWidth = (int)(screenWidth * scale);
int scaledHeight = (int)(screenHeight * scale);

// 在发送给 Claude 之前将图像调整为缩放后的尺寸
var screenshot = CaptureAndResize(scaledWidth, scaledHeight);

// 处理 Claude 的坐标时,将它们放大回来
void ExecuteClick(int x, int y)
{
    double screenX = x / scale;
    double screenY = y / scale;
    PerformClick(screenX, screenY);
}

Console.WriteLine({{CONTENT}}quot;scale={scale:F6} scaled={scaledWidth}x{scaledHeight}");
package main

import (
	"fmt"
	"math"
)

func captureAndResize(w, h int) any { return nil }
func performClick(x, y float64)     {}

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))
}

func main() {
	screenWidth, screenHeight := 1512, 982

	// 截图时
	scale := getScaleFactor(screenWidth, screenHeight)
	scaledWidth := int(float64(screenWidth) * scale)
	scaledHeight := int(float64(screenHeight) * scale)

	// 在发送给 Claude 之前将图像调整为缩放后的尺寸
	screenshot := captureAndResize(scaledWidth, scaledHeight)

	// 处理 Claude 的坐标时,将它们放大回来
	executeClick := func(x, y int) {
		performClick(float64(x)/scale, float64(y)/scale)
	}

	_, _ = screenshot, executeClick
	fmt.Printf("scale=%.6f scaled=%dx%d\n", scale, scaledWidth, scaledHeight)
}
import java.util.function.BiConsumer;

static Object captureAndResize(int w, int h) { return null; }
static void performClick(double x, double y) {}

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;

    // 截图时
    double scale = getScaleFactor(screenWidth, screenHeight);
    int scaledWidth = (int)(screenWidth * scale);
    int scaledHeight = (int)(screenHeight * scale);

    // 在发送给 Claude 之前将图像调整为缩放后的尺寸
    var screenshot = captureAndResize(scaledWidth, scaledHeight);

    // 处理 Claude 的坐标时,将它们放大回来
    BiConsumer<Integer, Integer> executeClick =
        (x, y) -> performClick(x / scale, y / scale);

    IO.println("scale=%.6f scaled=%dx%d".formatted(scale, scaledWidth, scaledHeight));
}
<?php

function captureAndResize(int $w, int $h): mixed { return null; }
function performClick(float $x, float $y): void {}

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;

// 截图时
$scale = getScaleFactor($screenWidth, $screenHeight);
$scaledWidth = (int)($screenWidth * $scale);
$scaledHeight = (int)($screenHeight * $scale);

// 在发送给 Claude 之前将图像调整为缩放后的尺寸
$screenshot = captureAndResize($scaledWidth, $scaledHeight);

// 处理 Claude 的坐标时,将它们放大回来
$executeClick = fn(int $x, int $y) => performClick($x / $scale, $y / $scale);

printf("scale=%.6f scaled=%dx%d\n", $scale, $scaledWidth, $scaledHeight);
def capture_and_resize(w, h) = nil
def perform_click(x, y) = nil

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

# 截图时
scale = get_scale_factor(screen_width, screen_height)
scaled_width = (screen_width * scale).to_i
scaled_height = (screen_height * scale).to_i

# 在发送给 Claude 之前将图像调整为缩放后的尺寸
screenshot = capture_and_resize(scaled_width, scaled_height)

# 处理 Claude 的坐标时,将它们放大回来
execute_click = ->(x, y) { perform_click(x / scale, y / scale) }

puts format("scale=%.6f scaled=%dx%d", scale, scaled_width, scaled_height)
Note

macOS Retina 显示屏以 2 倍的设备像素比捕获截图,因此图像分辨率是逻辑屏幕坐标的两倍。在发送之前将截图缩小 2 倍,或者在发出点击之前将 Claude 返回的坐标减半。

诊断点击问题

如果点击错过目标,原因通常是以下之一:

症状可能原因尝试
点击始终在一个方向偏移display_width_px/display_height_px 与实际发送的图像尺寸不匹配,或图像超出 API 限制并被静默缩小确保显示尺寸与调整后的截图完全匹配;预缩小以适应 API 限制
点击落在正确区域但错过目标目标非常小,缩小 4K+ 源时细节丢失,或宽高比失真设置 enable_zoom: true;以较低 DPI 捕获或裁剪到相关区域;调整大小时保持宽高比
Claude 点击了完全错误的元素指令不明确,或附近有视觉相似的元素使用位置提示("右下角的蓝色 Submit 按钮");将交互分解为更小的步骤
准确性始终较差截图发送超出 API 限制,或分辨率太低预缩小以适应限制;尝试 1280x720 作为基线
Tip

模型选择影响点击精度。 Claude Sonnet 4.6 在点击方面比 Claude Opus 4.6 更加机械精确,并且在截图需要大量缩小时更加稳健。Claude Opus 4.7 缩小了这一差距:其点击精度与 Sonnet 4.6 大致相当,其更高的分辨率限制意味着需要更少的缩小。

遵循实现最佳实践

使用适当的显示分辨率

设置与你的用例匹配的显示尺寸,同时保持在推荐限制内:

  • 对于一般桌面任务:1024x768 或 1280x720
  • 对于 Web 应用程序:1280x800 或 1366x768
  • 避免超过 1920x1080 的分辨率以防止性能问题

实现适当的截图处理

将截图返回给 Claude 时:

  • 将截图编码为 base64 PNG 或 JPEG
  • 考虑压缩大截图以提高性能
  • 包含相关元数据,如时间戳或显示状态
  • 如果使用更高的分辨率,确保坐标准确缩放

为 prompt caching 管理截图历史

长时间运行的 agent 循环会快速积累截图(每张大约 1,000-1,800 个输入 token)。为了保持 Prompt caching 有效同时限制上下文:

  • 在系统提示和工具定义之后放置一个 cache_control 断点,在最近的 tool_result 块上最多再放置三个,每轮推进它们。
  • 每轮批量修剪旧截图,而不是每轮一个。每轮丢弃截图会每轮更改前缀并使缓存失效。合理的默认值是保留最后三张截图,每 25 轮修剪一次,这样前缀在修剪事件之间保持字节相同。

添加操作延迟

某些应用程序需要时间来响应操作:

Info

这是应用程序端辅助代码,没有 API 请求。请参阅 SDK 选项卡了解模式。

Info

这是应用程序端辅助代码,没有 API 请求。请参阅 SDK 选项卡了解模式。

import time


def click_at(x, y): ...
def click_and_wait(x, y, wait_time=0.5):
    click_at(x, y)
    time.sleep(wait_time)  # 允许 UI 更新


print("ok")
import { setTimeout } from "node:timers/promises";

function clickAt(x: number, y: number): void {}

async function clickAndWait(x: number, y: number, waitMs = 500): Promise<void> {
  clickAt(x, y);
  await setTimeout(waitMs); // 允许 UI 更新
}

await clickAndWait(100, 200);
console.log("ok");
ClickAndWait(100, 200);
Console.WriteLine("ok");

static void ClickAt(int x, int y) { }

static void ClickAndWait(int x, int y, double waitSeconds = 0.5)
{
    ClickAt(x, y);
    Thread.Sleep(TimeSpan.FromSeconds(waitSeconds));  // 允许 UI 更新
}
package main

import (
	"fmt"
	"time"
)

func clickAt(x, y int) {}

func clickAndWaitFor(x, y int, wait time.Duration) {
	clickAt(x, y)
	time.Sleep(wait) // 允许 UI 更新
}

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

func main() {
	fmt.Println("ok")
}
void clickAt(int x, int y) {}

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);  // 允许 UI 更新
}

void main() {
    IO.println("ok");
}
<?php

function clickAt(int $x, int $y): void {}

function clickAndWait(int $x, int $y, float $waitSeconds = 0.5): void
{
    clickAt($x, $y);
    usleep((int) ($waitSeconds * 1_000_000));  // 允许 UI 更新
}

echo "ok\n";
def click_at(x, y) = nil

def click_and_wait(x, y, wait_time: 0.5)
  click_at(x, y)
  sleep(wait_time) # 允许 UI 更新
end

puts "ok"

在运行前验证操作

检查请求的操作是否安全有效:

Info

这是应用程序端辅助代码,没有 API 请求。请参阅 SDK 选项卡了解模式。

Info

这是应用程序端辅助代码,没有 API 请求。请参阅 SDK 选项卡了解模式。

display_width, display_height = 1024, 768


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


print(validate_action("left_click", {"coordinate": (2000, 100)}))
const displayWidth = 1024;
const displayHeight = 768;

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

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

console.log(validateAction("left_click", { coordinate: [2000, 100] }));
using System.Text.Json;

const int DisplayWidth = 1024;
const int DisplayHeight = 768;

Console.WriteLine(ValidateAction("left_click", new Dictionary<string, JsonElement> { ["coordinate"] = JsonSerializer.SerializeToElement(new[] { 2000, 100 }) }));

static (bool IsValid, string? Error) ValidateAction(string actionType, IReadOnlyDictionary<string, JsonElement> parameters)
{
    if (actionType == "left_click")
    {
        int x = parameters["coordinate"][0].GetInt32();
        int y = parameters["coordinate"][1].GetInt32();
        if (x is < 0 or >= DisplayWidth || y is < 0 or >= DisplayHeight)
        {
            return (false, "Coordinates out of bounds");
        }
    }
    return (true, null);
}
package main

import "fmt"

const (
	displayWidth  = 1024
	displayHeight = 768
)

func validateAction(actionType string, params map[string]any) (bool, string) {
	if actionType == "left_click" {
		coord, ok := params["coordinate"].([]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, ""
}

func main() {
	ok, msg := validateAction("left_click", map[string]any{"coordinate": []any{2000.0, 100.0}})
	fmt.Println(ok, msg)
}
import com.anthropic.core.JsonValue;

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")) {
        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);
}

void main() {
    IO.println(validateAction("left_click", Map.of("coordinate", JsonValue.from(List.of(2000, 100)))));
}
<?php

const DISPLAY_WIDTH = 1024;
const DISPLAY_HEIGHT = 768;

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

[$valid, $error] = validateAction('left_click', ['coordinate' => [2000, 100]]);
echo ($valid ? 'true' : 'false') . ' ' . $error . "\n";
DISPLAY_WIDTH = 1024
DISPLAY_HEIGHT = 768

def validate_action(action_type, params)
  if action_type == "left_click"
    x, y = params.fetch(:coordinate, [0, 0])
    unless (0...DISPLAY_WIDTH).cover?(x) && (0...DISPLAY_HEIGHT).cover?(y)
      return [false, "Coordinates out of bounds"]
    end
  end
  [true, nil]
end

p validate_action("left_click", {coordinate: [2000, 100]})

记录操作用于调试

保留所有操作的日志以进行故障排除:

Info

这是应用程序端辅助代码,没有 API 请求。请参阅 SDK 选项卡了解模式。

Info

这是应用程序端辅助代码,没有 API 请求。请参阅 SDK 选项卡了解模式。

import logging


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


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

console.log("ok");
LogAction("screenshot", null, "<image data>");
Console.WriteLine("ok");

static void LogAction(string actionType, object? parameters, object? result)
{
    Console.Error.WriteLine({{CONTENT}}quot;Action: {actionType}, Params: {parameters}, Result: {result}");
}
package main

import (
	"fmt"
	"log"
)

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

func main() {
	fmt.Println("ok")
}
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);
}

void main() {
    IO.println("ok");
}
<?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),
    ));
}

echo "ok\n";
require "logger"

LOGGER = Logger.new($stderr)

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

puts "ok"

了解 computer use 的局限性

Computer use 功能处于 beta 阶段。虽然 Claude 的能力是最先进的,但开发者应该了解其局限性:

  1. 延迟: 当前 human-AI 交互的 computer use 延迟与常规人类指导的计算机操作相比可能太慢。专注于速度不关键的用例(例如,后台信息收集、自动化软件测试)在受信任环境中。
  2. 计算机视觉准确性和可靠性: Claude 在生成操作时输出特定坐标时可能会犯错或产生幻觉。扩展思考可以帮助你理解模型的推理并识别潜在问题。
  3. 工具选择准确性和可靠性: Claude 在生成操作时选择工具时可能会犯错或产生幻觉,或采取意外操作来解决问题。此外,在与小众应用程序或同时与多个应用程序交互时,可靠性可能较低。在请求复杂任务时请仔细提示模型。
  4. 滚动可靠性: 滚动操作支持方向控制(上、下、左、右)和指定的滚动量。在滚动不生效的应用程序中,键盘替代方案(如 Page Down)可能会有所帮助。
  5. 电子表格交互: 使用细粒度鼠标控制操作(left_mouse_downleft_mouse_up)和修饰键组合来选择单个单元格。复杂的电子表格操作可能仍需要多次尝试。
  6. 社交和通信平台上的账户创建和内容生成: 虽然 Claude 会访问网站,但 Claude 在社交媒体网站和平台上创建账户或生成和共享内容或以其他方式参与冒充人类的能力是有限的。此能力将来可能会更新。
  7. 漏洞: 越狱或 prompt injection 等漏洞可能在前沿 AI 系统中持续存在,包括 beta computer use API。在某些情况下,Claude 会遵循内容中发现的命令,有时甚至与用户的指令冲突。例如,网页上的 Claude 指令或图片中包含的指令可能会覆盖指令或导致 Claude 犯错。请考虑以下事项: a. 将 computer use 限制在受信任的环境中,如具有最小权限的虚拟机或容器 b. 避免在没有严格监督的情况下让 computer use 访问敏感账户或数据 c. 在你的应用程序中启用或请求 computer use 功能所需的权限之前,告知最终用户相关风险并获得他们的同意
  8. 不当或非法操作: 根据 Anthropic 的服务条款,你不得使用 computer use 违反任何法律或可接受使用政策。

请始终仔细审查和验证 Claude 的 computer use 操作和日志。不要在没有人工监督的情况下将 Claude 用于需要完美精度或敏感用户信息的任务。

数据留存

Computer use 是客户端工具。所有截图、鼠标操作、键盘输入以及会话中涉及的任何文件都在你的环境中捕获和存储,而不是由 Anthropic 存储。Anthropic 在 API 调用过程中实时处理截图图像和操作请求,但在响应返回后不会保留它们。

因为你的应用程序控制 computer use 数据的存储位置和方式,所以 computer use 符合 ZDR 条件。有关所有功能的 ZDR 资格,请参阅 API 和数据留存

定价

Computer use 遵循标准的工具使用定价。使用 computer use tool 时:

系统提示开销:computer use beta 在系统提示中增加 466-499 个 token

Computer use tool token 使用量

模型每个工具定义的输入 token
Claude 4.x 模型735 个 token

额外 token 消耗

  • 截图图像(参见视觉定价
  • 返回给 Claude 的工具执行结果
Note

如果你还同时使用 bash 或 text editor 工具以及 computer use,这些工具有自己的 token 成本,如各自页面中所述。

下一步