教程Claude Code系列第 12 / 21 篇

Claude API 的现成工具:bash、文本编辑器、记忆,以及电脑和浏览器操作

跑命令、改文件、跨对话记东西、操作桌面和浏览器,这几类工具 Anthropic 已经定义好,你只负责执行。本文在服务器上用 bash 加文本编辑器搭了一个最小的改代码智能体,并实测记忆工具;电脑和浏览器操作按文档整理。

发布 · 约 20 分钟读完 · 经人工审核()· 本站的内容怎么来的

本站已实测,测试日期:2026-10-13。

本页目录(11 节)
  1. 一、为什么不自己定义一个 run_command
  2. 二、bash 工具:一个一直开着的 shell
  3. 三、文本编辑器工具:看文件、改文件
  4. 四、合起来:一个最小的改代码智能体
  5. 五、记忆工具:跨对话记东西
  6. 六、电脑操作和浏览器操作
  7. 七、怎么搭配
  8. 八、通过中转站实测到的
  9. 和 Claude Code 的关系
  10. 花费
  11. 这次实测没有覆盖到的

这篇教程解决一个问题:想让 Claude 跑命令、改文件、记住上次聊过的事,或者操作一台电脑、一个浏览器,要不要自己从头定义工具?

不用。这几类操作 Anthropic 已经把工具的定义写好了,模型就是在这些定义上训练的。你要做的只有一件事:它发来调用,你来执行。

工具调用的基本来回(tool_use、tool_result、循环)见上一篇《Claude API 工具调用入门》,这里不重复。

实测环境
日期 2026-10-13
方式 Python:直接发 HTTP 请求,记忆工具用 anthropic SDK 1.13.0
模型 Sonnet 5.5
执行环境 一台 Linux 服务器上的专用普通用户,只在一个示例目录里操作
连接方式 通过酷站AI 中转站,不是直连官方

先说实测的结论:

  • 五个工具的请求通过这家中转站都被接受,定义占的 token 数和官方文档对得上。
  • bash 加文本编辑器搭的最小智能体,六次请求修好了一个失败的测试。
  • 记忆工具在两段互不相干的对话之间把约定传了过去。
  • 电脑操作和浏览器操作只确认了请求被接受、调用长什么样,没有搭执行环境,那一节按文档整理。

一、为什么不自己定义一个 run_command

自己定义一个“执行命令”的工具当然可以。官方文档给的理由是:这几个工具的定义是训练进模型里的,Claude 在成千上万条用它们成功完成任务的轨迹上优化过,所以调得更稳,出错以后也更会补救。

五个工具都是客户端工具:代码在你这边跑,Anthropic 的服务器什么都不执行。

工具 type name 定义占的输入 token
bash bash_20250124 必须是 bash 325
文本编辑器 text_editor_20250728 见下方 974
记忆 memory_20250818 必须是 memory 约 1,440(实测,含它附带的系统提示词)
电脑操作 computer_toolset_20260801 不写 约 4,500,含工具系统提示词
浏览器操作 browser_toolset_20260801 不写 约 6,600,含工具系统提示词

文本编辑器的 name 必须是 str_replace_based_edit_tool。

表里前两行和后两行的数字来自官方文档,本站实测的结果和它们一致(bash 和文本编辑器分毫不差)。记忆工具那一行文档没有给数字,是本站用“带和不带这个工具”的差值算出来的。除了这些,只要请求里有工具,还有一段 286 个 token 的工具系统提示词。

这些工具不用写 input_schema,参数是什么样由 type 决定,改不了;名字也是定死的。实测把 bash 工具的名字改成 shell,请求被拒绝:

HTTP 400 tools.0.bash_20250124.name: Input should be 'bash'

声明起来就是一行:

TOOLS = [{"type": "bash_20250124", "name": "bash"},
         {"type": "text_editor_20250728", "name": "str_replace_based_edit_tool"}]

二、bash 工具:一个一直开着的 shell

Claude 发来的调用只有两种:

输入 意思
{"command": "ls -la"} 跑这条命令
{"restart": true} 把 shell 重启

接口本身不保存任何状态。“会话”是你的程序维持的:开一个 bash 进程一直不关,每条命令都在里面跑,这样上一条命令 cd 到哪里、设了什么环境变量、建了什么文件,下一条都还在。

你要负责的事,官方文档列得很细,这里合成一张表:

事情 做法
怎么知道一条命令跑完了 每条命令后面跟着打印一行独一无二的标记,读到标记就算结束
错误输出 把 stderr 并进 stdout,报错会出现在它发生的位置
命令卡住 每条命令设时限。超时就杀掉 shell 和它起的所有进程,重启会话,回一个出错的结果
输出太长 接口不会替你截断,过长的请求会直接被拒。自己截断再返回
限制能跑什么 用允许清单,不用禁止清单。禁止清单总有想不到的命令
真正的安全边界 整个会话放进容器或虚拟机,用权限最小的用户
审计 每条命令执行前记一笔,结束后记输出
密钥 输出返回给 Claude 之前,把里面的密钥抹掉

它做不了的事:交互式的命令(vim、less、要输密码的)、图形界面;命令的输出要等你下一次请求才到模型那里,没有实时流。

下面是本站实测用的会话类。有一处要特别留意:shell 的环境变量是另起的一份干净的。跑测试的进程自己的环境变量里有 API key,如果让 shell 继承过去,模型一条 env 就能把它打出来。

class BashSession:
    """一个一直开着的 bash 进程。环境变量是另起的一份干净的,API key 不在里面。"""

    def __init__(self):
        self.start()

    def start(self):
        env = {"PATH": "/usr/bin:/bin", "HOME": str(WORK), "LANG": "C.UTF-8"}
        self.p = subprocess.Popen(["/bin/bash", "--noprofile", "--norc"], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                                  stderr=subprocess.STDOUT, cwd=WORK, env=env, text=True, start_new_session=True)

    def restart(self):
        try:
            os.killpg(self.p.pid, 9)
        except ProcessLookupError:
            pass
        self.start()

    def run(self, command, timeout=20):
        mark = f"__END_{uuid.uuid4().hex}__"
        self.p.stdin.write(f"{command}\necho {mark} $?\n")
        self.p.stdin.flush()
        out, deadline = [], time.monotonic() + timeout
        while True:
            left = deadline - time.monotonic()
            if left <= 0 or not select.select([self.p.stdout], [], [], left)[0]:
                self.restart()
                raise TimeoutError(f"命令超过 {timeout} 秒没有结束,已经终止并重启了会话;工作目录回到了起点。")
            line = self.p.stdout.readline()
            if line.startswith(mark):
                code = line.split()[-1]
                text = "".join(out)
                if len(text) > 4000:
                    text = text[:4000] + f"\n…(输出共 {len(text)} 字符,只显示前 4000)"
                return text + (f"\n[退出码 {code}]" if code != "0" else "")
            out.append(line)

再加一道检查。它只是防呆的绊线,拦得住明显的越界,拦不住有心的绕过(python3 -c 里什么都能写):

ALLOWED = {"ls", "cat", "pwd", "python3", "grep", "head", "tail", "wc", "echo", "find", "cd", "diff", "git"}


def check_command(command):
    """只是防呆的绊线,不是安全边界:真正的隔离要靠容器或虚拟机。"""
    for bad in ("..", "~", "$(", "`", "/home", "/etc", "/root", ".claude", "sudo"):
        if bad in command:
            raise PermissionError(f"命令里有不允许的内容 {bad!r}。只能在工作目录里操作。")
    for seg in command.replace("&&", "\n").replace(";", "\n").replace("|", "\n").splitlines():
        words = seg.split()
        if words and words[0] not in ALLOWED:
            raise PermissionError(f"不允许运行 {words[0]}。允许的命令:{', '.join(sorted(ALLOWED))}。")

报错信息里把允许的命令列了出来。后面的实测里能看到这句话起的作用。

三、文本编辑器工具:看文件、改文件

Claude 通过 command 字段发来四种操作:

command 其他参数 做什么
view path,可选 view_range 看文件内容,或列出目录。view_range 是 [起始行, 结束行],从 1 数起,结束行写 -1 表示到文件末尾
str_replace path、old_str、new_str 把文件里的一段原文换成新的。原文必须一字不差,包括空格和缩进
create path、file_text 新建文件
insert path、insert_line、insert_text 在第几行之后插入,0 表示文件开头

早期版本里的 undo_edit(撤销)在给 Claude 4 及之后模型用的版本里去掉了。工具定义上可以加一个可选的 max_characters,表示看大文件时截到多少字符;截断这件事仍然要你的代码来做。

实现时的几个要点,都来自官方文档:

  • view 的结果带上行号。 不带也能用,但模型要靠行号来用 view_range 和 insert_line。
  • str_replace 必须恰好匹配一处。 匹配到零处或多处都要回一个出错的结果,并说清楚是哪种情况。
  • 校验路径,不许跳出工作目录。
  • 改之前先备份。

本站实测用的处理函数:

def safe_path(p):
    full = (WORK / p).resolve() if not os.path.isabs(p) else Path(p).resolve()
    if full != WORK and WORK not in full.parents:
        raise PermissionError(f"路径 {p} 在工作目录之外,不能访问。")
    return full


def editor(command, path, **kw):
    f = safe_path(path)
    if command == "view":
        if f.is_dir():
            return "\n".join(sorted(str(x.relative_to(WORK)) + ("/" if x.is_dir() else "") for x in f.iterdir() if not x.name.startswith(".")))
        if not f.exists():
            raise FileNotFoundError(f"文件 {path} 不存在。")
        lines = f.read_text().splitlines()
        a, b = kw.get("view_range") or [1, -1]
        b = len(lines) if b == -1 else b
        return "\n".join(f"{i}: {line}" for i, line in enumerate(lines[a - 1:b], start=a))
    if command == "create":
        f.write_text(kw["file_text"])
        return f"已创建 {path}"
    if not f.exists():
        raise FileNotFoundError(f"文件 {path} 不存在。")
    text = f.read_text()
    shutil.copy(f, f.with_suffix(f.suffix + ".bak"))          # 改之前先备份
    if command == "str_replace":
        n = text.count(kw["old_str"])
        if n != 1:
            raise ValueError(f"old_str 在文件里出现了 {n} 次,必须恰好出现 1 次。" + ("请带上更多上下文让它唯一。" if n > 1 else "请先 view 确认原文,空格和缩进要完全一致。"))
        f.write_text(text.replace(kw["old_str"], kw.get("new_str", "")))
        return f"已修改 {path}"
    if command == "insert":
        lines = text.splitlines(keepends=True)
        lines.insert(kw["insert_line"], kw["insert_text"] + ("" if kw["insert_text"].endswith("\n") else "\n"))
        f.write_text("".join(lines))
        return f"已在 {path} 第 {kw['insert_line']} 行后插入"
    raise ValueError(f"不认识的命令 {command}")

四、合起来:一个最小的改代码智能体

把这两个工具接进上一篇的那个循环,就是一个能自己改代码、跑测试的智能体。循环本身没有变:stop_reason 是 tool_use 就执行,按工具名分发到 BashSession.run 或 editor,抛异常就回 is_error。

测试用的项目只有两个文件。pricing.py 里有一个 bug,coupon 本该是折扣比例,代码却当成金额减掉了:

def cart_total(items, coupon=None):
    """items 是 (单价, 数量) 的列表;coupon 是 0 到 1 之间的折扣比例,例如 0.1 表示打九折。"""
    total = 0
    for price, quantity in items:
        total += line_total(price, quantity)
    if coupon:
        total = total - coupon
    return round(total, 2)

三个测试里有一个因此不过。给它的任务是一句话:“当前目录是一个小 Python 项目,有一个测试没过。找出原因,修好代码(不要改测试),再跑一遍测试确认全部通过。”

实际的过程(命令的输出有删节):

第 1 次请求 → stop_reason=tool_use
  bash {"command": "ls -R . | head -50; python -m pytest -x -q 2>&1 | tail -30"}
      → 出错:不允许运行 python。允许的命令:cat, cd, diff, echo, find, git, grep, head, ls, pwd, python3, tail, wc。
第 2 次请求 → stop_reason=tool_use
  bash: ls -R . | head -50; python3 -m pytest -x -q 2>&1 | tail -30
      → .:
        pricing.py
        test_pricing.py
        /usr/bin/python3: No module named pytest
第 3 次请求 → stop_reason=tool_use
  bash: cat pricing.py test_pricing.py
      → (两个文件的内容)
第 4 次请求 → stop_reason=tool_use
  Claude:Bug:coupon 是被当作减去的金额,而不是折扣比例。
  bash {"command": "sed -i 's/total = total - coupon/total = total * (1 - coupon)/' pricing.py && python3 -m unittest -v 2>&1 | tail -10"}
      → 出错:不允许运行 sed。允许的命令:cat, cd, diff, echo, find, git, grep, head, ls, pwd, python3, tail, wc。
第 5 次请求 → stop_reason=tool_use
  editor: {"command": "str_replace", "new_str": "total * (1 - coupon)", "old_str": "total - coupon", "path": "pricing.py"}
      → 已修改 pricing.py
  bash: python3 -m unittest -v 2>&1 | tail -10
      → test_cart_total_with_coupon ... ok
        test_cart_total_without_coupon ... ok
        test_line_total ... ok
        Ran 3 tests in 0.000s
        OK
第 6 次请求 → stop_reason=end_turn
  Claude:已修好,3 个测试全部通过。
          原因:`pricing.py` 的 `cart_total` 把 `coupon` 当成要减去的金额……
          测试没有改动。环境里没有 pytest,我用 `python3 -m unittest` 跑的测试。
合计:6 次请求,输入 13898,输出 746

改完以后和原文件比,只动了一行:

14c14
<         total = total - coupon
---
>         total = total * (1 - coupon)

这一趟里有几件事值得看:

  • 它被拦了两次,两次都自己换了路。 python 不在允许清单里,它换成 python3;sed 不在,它改用文本编辑器工具。原因是报错里写了“允许的命令有哪些”。如果只回一句“不允许”,它只能一个一个试。
  • 它想用 sed 改文件。 同时给了 bash 和文本编辑器,模型不一定用你期望的那个。想让改动都走文本编辑器(好做备份和审计),就像这里一样不放行 sed,或者在系统提示词里说明。
  • pytest 没装,它自己换成了 unittest,并且在最后的回答里交代了这一点。
  • 第 5 次回复里有两个调用:先改文件,再跑测试。这两个有先后依赖,要按它们出现的顺序执行,不能并发。
  • 备份起了作用:工作目录里多了一个 pricing.py.bak。

输入 13,898 个 token 里,大头是每次请求都重发一遍的工具定义和历史。这类循环应该配上提示词缓存,做法见《提示词缓存》。

五、记忆工具:跨对话记东西

记忆工具让 Claude 在一个目录里建文件、读文件、改文件,这个目录在对话之间一直保留。和前两个工具一样,文件操作是你执行的:Claude 发来“看 /memories 目录”“在某个路径建一个文件”这样的调用,你的代码把它们落到你自己的存储上。/memories 只是一个前缀,背后可以是磁盘上的一个目录,也可以是数据库。

带上这个工具以后,接口会自动往系统提示词里加一段话,大意是:做任何事之前先看一眼记忆目录;干活的过程中把进度记进去;假定上下文随时可能被清空,没记下来的就丢了。这段不用你自己写。

它发来的命令有六种:

command 做什么
view 看目录(列两层)或看文件(带行号)
create 新建文件
str_replace 替换文件里的一段
insert 在某一行后插入
delete 删除文件或目录
rename 改名或移动

每种命令成功和失败时该回什么文字,文档给了参考写法。Python 和 TypeScript 的 SDK 自带一个存到本地磁盘的现成实现;想存到数据库或云存储,Python 里继承 BetaAbstractMemoryTool,自己实现这六个方法。

下面这段代码在服务器上原样跑过。它用 SDK 自带的本地实现,配上一篇讲过的 Tool Runner,连续开两段互不相干的对话:

import json
import anthropic
from anthropic.tools import BetaLocalFilesystemMemoryTool

client = anthropic.Anthropic()


def session(prompt):
    memory = BetaLocalFilesystemMemoryTool(base_path="./memstore2")   # 文件落在 ./memstore2/memories/
    runner = client.beta.messages.tool_runner(
        model="claude-sonnet-5-5", max_tokens=2048, max_iterations=8,
        tools=[memory], messages=[{"role": "user", "content": prompt}])
    for message in runner:
        for block in message.content:
            if block.type == "tool_use":
                print("memory 调用:", json.dumps(block.input, ensure_ascii=False))
            elif block.type == "text" and block.text.strip():
                print("Claude:", block.text.strip())
        results = runner.generate_tool_call_response()        # 这一轮工具执行的结果
        for r in (results or {}).get("content", []):
            print("    →", r["content"])


session("我们这个项目的约定:包管理器用 pnpm,不用 npm;提交前要跑 pnpm lint 和 "
        "pnpm test --run;提交信息用英文、祈使句。先记下来,以后都按这个来。")
print("=" * 20)
session("我改完代码了,提交前该跑哪些命令?给我一条能直接复制的。")

第一段对话里,它先看目录(空的),再建了一个文件:

memory 调用: {"command": "view", "path": "/memories"}
    → Here're the files and directories up to 2 levels deep in /memories, excluding hidden items:
4K	/memories
Claude: Memory 是空的,我来新建一个文件。
memory 调用: {"command": "create", "file_text": "# 项目约定(用户明确要求,以后都按此执行)\n\n- 包管理器:使用 pnpm,不要用 npm\n- 提交前必须运行:`pnpm lint` 和 `pnpm test --run`\n- 提交信息:英文、祈使句(例如 \"Add login validation\")\n", "path": "/memories/project_conventions.md"}
    → File created successfully at: /memories/project_conventions.md
Claude: 已记下,以后都按这个来:
(以下略)

第二段对话的 messages 是全新的,里面只有那一句提问。它照样先看目录,读到了上一段留下的文件:

memory 调用: {"command": "view", "path": "/memories"}
    → Here're the files and directories up to 2 levels deep in /memories, excluding hidden items:
4K	/memories
240B	/memories/project_conventions.md
memory 调用: {"command": "view", "path": "/memories/project_conventions.md"}
    → Here's the content of /memories/project_conventions.md with line numbers:
     1	# 项目约定(用户明确要求,以后都按此执行)
     2	
     3	- 包管理器:使用 pnpm,不要用 npm
     4	- 提交前必须运行:`pnpm lint` 和 `pnpm test --run`
     5	- 提交信息:英文、祈使句(例如 "Add login validation")
     6	
Claude: 提交前运行下面这一条,lint 失败时会自动停止,不会继续跑测试:

pnpm lint && pnpm test --run

两个命令都通过后再提交。提交信息请用英文祈使句,例如 `Add login validation`。

代价:两段对话各发了三次请求。本站另外量过一次同样的两段对话,输入合计分别是 5,946 和 5,834 个 token。工具定义和它附带的那段系统提示词合计约 1,440 个 token,每次请求都带着;而且每段对话都固定以“看一眼目录”开头,多一个来回。记的东西不多、每次都用得上的话,直接写进系统提示词更省。记忆工具适合内容多、每次只用得上一小部分,或者要让模型自己往里写的场合。

安全上要你自己把关的

  • 路径越界。 像 /memories/../../etc/hostname 这样的路径能跳出记忆目录。你的处理函数必须校验每一条命令里的每一个路径:以 /memories 开头、解析成规范路径后仍在目录之内。本站直接把这个路径交给 SDK 自带的实现,它拒绝了,报的是 would escape /memories directory。
  • 不要指望模型替你挡。 本站也试过让模型去看这个路径,这一次请求被判成了拒答(stop_reason 是 refusal,类别 cyber),没有发出工具调用。但这是模型一侧的行为,不能当成你这一侧可以不校验的理由。
  • 敏感信息。 Claude 一般不会往记忆里写敏感信息,要更有把握就在写文件之前自己过滤。
  • 大小和过期。 给单个文件设大小上限,定期清掉很久没访问的文件。

和压缩、上下文编辑配合

长时间运行的智能体,官方建议几样一起用:压缩或上下文编辑负责把当前上下文控制住,记忆负责保存那些不能被摘要掉的信息。两者的用法见《压缩和上下文编辑》。

跨多次会话做一个软件项目时,文档给了一个固定的做法:

  1. 第一次会话先建记忆文件,再干活:一份进度记录(做了什么、接下来做什么)、一份功能清单、项目启动脚本在哪里。
  2. 之后每次会话先读这些文件,不用重新摸索代码。
  3. 每次会话结束前更新进度记录。

原则是一次只做一个功能,并且只有端到端验证通过了才标记完成,不是代码写完就标。

六、电脑操作和浏览器操作

这两个工具本站没有搭执行环境,只各发了一次请求,确认请求被接受,看了调用的样子。下面的内容除了标明“实测”的,都是按文档整理的。

它们是“工具集”

和前面三个工具不同,这两个是工具集:tools 里写一项,Claude 就得到一组成员工具。

tools = [{"type": "computer_toolset_20260801"}]     # 17 个成员:截图、点击、输入、滚动、缩放……
tools = [{"type": "browser_toolset_20260801"}]      # 默认 27 个成员,另有 4 个要手动开

实测各发一次请求,拿到的调用是这样的:

{"type": "tool_use", "id": "toolu_01Fsjp…", "name": "screenshot", "input": {},
 "toolset_name": "computer", "caller": {"type": "direct"}}

{"type": "tool_use", "id": "toolu_01QQiG…", "name": "navigate",
 "input": {"url": "https://example.com"}, "toolset_name": "browser", "caller": {"type": "direct"}}

和普通工具调用相比多了一个 toolset_name。浏览器那一次,同一条回复里还跟着一个 list_tabs 调用,这就是下面说的批量动作。围绕 toolset_name 有几条硬规则:

  • 按 toolset_name 和 name 两个字段一起分发。 你自己的工具可以和成员重名,两个工具集之间也有重名的(比如都有 screenshot)。
  • 回结果时要把 toolset_name 原样带上,不带会被拒绝。
  • 一次回复里的多个调用要按顺序执行,遇到第一个失败就停。 这叫批量动作:点一下、输入、再截图,后面的依赖前面的。失败的那个回出错信息;它后面没执行的每一个,也都要回一个出错的结果。电脑操作规定的文字是 Not executed: an earlier computer action in this turn failed.,浏览器操作有它自己的一句。
  • 截图要你自己缩到模型的图片尺寸上限以内,接口不会替你缩,超了直接拒绝。坐标一律以你返回的截图的像素为准。
  • 不想提供的成员,用 configs 关掉,例如 {"zoom": {"enabled": false}}。
  • 工具集不接受 strict 和 input_examples,也不能用 tool_choice 指定某个成员。

支持的模型:Fable 5.1、Mythos 5.1、Fable 5、Mythos 5、Opus 5.5、Opus 5、Sonnet 5.5、Sonnet 5、Opus 4.8、Haiku 5.5。在官方接口上,5.5 这一代只接受工具集这种新写法。实测用旧的 computer_20251124 请求 Sonnet 5.5,返回 400:

tools.0.type: this model requires computer_toolset_20260801 on the Claude API;
update the client tool loop to preserve toolset_name

两个怎么选

电脑操作 浏览器操作
操作的对象 一整个桌面 你的程序控制的一个浏览器
模型怎么“看” 只靠截图 截图,加上页面结构:read_page 返回带编号的元素,find 按描述找元素,get_page_text 取正文
模型怎么“动” 按像素坐标点击、输入 按坐标,或者按元素编号(ref_2 这样的引用)
你要搭的环境 虚拟显示器、桌面、应用,一般放在容器里 一个浏览器和驱动它的程序
适合 没有接口的老软件、跨多个桌面应用的流程 整件事都在网页里:填表、读页面、跨标签页

文档的建议是:事情全在网页里就用浏览器操作,按元素编号操作比按坐标稳,读页面结构也常常比截图省 token。电脑操作最通用,也最慢。有更窄的工具能做的事,就不要用它。

浏览器操作有四个成员默认关闭:在页面里执行 JavaScript、上传文件、读控制台、读网络请求。前两个会扩大被恶意页面利用的范围,后两个可能把页面里的密钥带进上下文。确实需要再用 configs 打开。

安全

这两个工具的风险比普通接口大,因为模型在读它控制不了的内容:网页上、图片里的文字都可能是写给它看的指令。文档要求的做法:

  1. 放在专用的虚拟机或容器里,权限最小,浏览器用不带任何登录状态的新配置。
  2. 不给它账号密码这类敏感数据。
  3. 能访问的网站用允许清单限制,在网络层执行。
  4. 有实际后果的动作(付款、改账号、发消息、接受条款)让人确认,而且要在每一个调用执行之前确认,因为一次回复里可能有好几个动作。

Anthropic 另外加了一层防护:用了这两个工具时,会有分类器自动扫描工具返回的内容(截图、页面文字),发现疑似注入就引导模型先确认指令是不是你发的。文档明确说,有这一层,上面几条仍然要做。

把这类功能做进你自己的产品时,要告知最终用户相关风险并取得同意。

七、怎么搭配

官方文档给了几种常见的组合:

场景 工具 说明
写代码的智能体 文本编辑器 + bash 看代码、改、跑测试,循环往复。本文第四节就是这个
长时间运行的智能体 记忆 + 其他任何工具 记忆不改变别的工具的行为,只是多一个记东西的地方
网页里的任务 浏览器操作
任意图形界面 电脑操作 别的工具都够不着时再用
查资料再分析 联网搜索 + 代码执行 这两个是服务器端工具,见下一篇

八、通过中转站实测到的

项目 结果
五个工具的请求 都被接受,都返回了正常的 tool_use
定义占的 token bash 325、文本编辑器 974,和文档一致;电脑操作约 4,530、浏览器操作约 6,600(含工具系统提示词),和文档的“约 4,520”“约 6,610”对得上
bash 工具改名 400,报错是官方格式
旧版电脑操作工具 + Sonnet 5.5 400,提示改用工具集
记忆工具的多轮循环 正常

这一组没有发现和官方文档不一致的地方。

和 Claude Code 的关系

Claude Code 读写文件、跑命令,用的是它自己内置的一套工具(Read、Edit、Bash 等),思路和这里相同:一个持续的 shell、按原文替换的编辑方式、失败时给出能照着改的报错。第四节那个六次请求的小循环,就是 Claude Code 干的事的最小形态。它多出来的东西(权限确认、沙箱、上下文压缩、子智能体),正是这篇里反复说“要你自己负责”的那些部分。

所以多数情况下不必自己搭:要一个现成的编程智能体,用 Claude Code 或 Agent SDK。执行环境必须由你自己掌握时,比如要把它嵌进你们自己的 CI 或自己的沙箱,才值得用这几个工具从头写。

花费

这一篇一共发了 29 次请求。最大的一笔是改代码的那个循环(输入合计约 1.4 万 token),其次是带着电脑和浏览器工具集的两次请求(定义就有四千多和六千多 token)。按官方标价粗算约 0.15 美元。

这次实测没有覆盖到的

  • 官方接口没有测。
  • 电脑操作和浏览器操作没有搭执行环境。 批量动作、截图缩放、browser_state、成员开关、SDK 里的辅助类,全部是按文档整理的。
  • bash 的超时重启、restart 调用、长输出截断,代码里写了,这次任务没有触发。
  • 文本编辑器的 insert、create、view_range、max_characters 这次任务没有用到。
  • 记忆工具只测了 view 和 create,用的是 SDK 自带的本地实现;自己实现存储、和上下文编辑或压缩配合,没有试。
  • 执行环境没有放进容器。 本站用的是服务器上的一个专用普通用户加一道命令检查,文中说过这不是安全边界。
  • 每个实验只跑了一次,模型每次走的路不一定一样。
  • 只测了一家中转站、一天。

来源

通过 API 中转站使用这些模型?到 Veridrop 免费检测中转站给你的是不是它声称的模型,或者看看中转站排行榜前 100 名。

相关文章