Claude API 的现成工具:bash、文本编辑器、记忆,以及电脑和浏览器操作
跑命令、改文件、跨对话记东西、操作桌面和浏览器,这几类工具 Anthropic 已经定义好,你只负责执行。本文在服务器上用 bash 加文本编辑器搭了一个最小的改代码智能体,并实测记忆工具;电脑和浏览器操作按文档整理。
本站已实测,测试日期:2026-10-13。
本页目录(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 一般不会往记忆里写敏感信息,要更有把握就在写文件之前自己过滤。
- 大小和过期。 给单个文件设大小上限,定期清掉很久没访问的文件。
和压缩、上下文编辑配合
长时间运行的智能体,官方建议几样一起用:压缩或上下文编辑负责把当前上下文控制住,记忆负责保存那些不能被摘要掉的信息。两者的用法见《压缩和上下文编辑》。
跨多次会话做一个软件项目时,文档给了一个固定的做法:
- 第一次会话先建记忆文件,再干活:一份进度记录(做了什么、接下来做什么)、一份功能清单、项目启动脚本在哪里。
- 之后每次会话先读这些文件,不用重新摸索代码。
- 每次会话结束前更新进度记录。
原则是一次只做一个功能,并且只有端到端验证通过了才标记完成,不是代码写完就标。
六、电脑操作和浏览器操作
这两个工具本站没有搭执行环境,只各发了一次请求,确认请求被接受,看了调用的样子。下面的内容除了标明“实测”的,都是按文档整理的。
它们是“工具集”
和前面三个工具不同,这两个是工具集: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 打开。
安全
这两个工具的风险比普通接口大,因为模型在读它控制不了的内容:网页上、图片里的文字都可能是写给它看的指令。文档要求的做法:
- 放在专用的虚拟机或容器里,权限最小,浏览器用不带任何登录状态的新配置。
- 不给它账号密码这类敏感数据。
- 能访问的网站用允许清单限制,在网络层执行。
- 有实际后果的动作(付款、改账号、发消息、接受条款)让人确认,而且要在每一个调用执行之前确认,因为一次回复里可能有好几个动作。
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 自带的本地实现;自己实现存储、和上下文编辑或压缩配合,没有试。 - 执行环境没有放进容器。 本站用的是服务器上的一个专用普通用户加一道命令检查,文中说过这不是安全边界。
- 每个实验只跑了一次,模型每次走的路不一定一样。
- 只测了一家中转站、一天。