0
# Builtin Tool Classes
1
2
Builtin tool classes provide abstract base classes for implementing built-in tool types with custom behavior. Unlike function tools that wrap Python functions, builtin tools define their own tool schema and execution logic, enabling integration with special tool types like computer use, bash, or memory.
3
4
## Capabilities
5
6
### Synchronous Builtin Tool
7
8
Abstract base class for implementing synchronous built-in tools with custom schemas and behavior.
9
10
```python { .api }
11
class BetaBuiltinFunctionTool(ABC):
12
"""
13
Abstract base class for synchronous built-in tools.
14
15
Subclass this to create custom implementations of built-in tool types
16
that require special handling beyond simple function wrapping.
17
"""
18
19
@abstractmethod
20
def to_dict(self) -> BetaToolUnionParam:
21
"""
22
Generate the tool definition for the API.
23
24
Must return a tool parameter dictionary that matches one of the
25
built-in tool schemas (e.g., computer_20250124, bash_20250124,
26
text_editor_20250728, memory_20250818).
27
28
Returns:
29
Tool parameter dictionary with type, name, and any tool-specific fields
30
"""
31
32
@abstractmethod
33
def call(self, input: object) -> str | Iterable[BetaContent]:
34
"""
35
Execute the tool with the given input.
36
37
Args:
38
input: Tool input parameters from Claude (format depends on tool type)
39
40
Returns:
41
Tool execution result as string or content blocks
42
"""
43
44
@property
45
def name(self) -> str:
46
"""
47
Get the tool name from the tool definition.
48
49
Returns:
50
The tool's name as it appears in the API
51
"""
52
```
53
54
#### Usage Examples
55
56
**Custom computer use tool:**
57
58
```python
59
from anthropic.lib.tools import BetaBuiltinFunctionTool
60
from anthropic.types.beta import BetaToolComputerUse20250124Param
61
62
class MyComputerTool(BetaBuiltinFunctionTool):
63
"""Custom computer use implementation."""
64
65
def to_dict(self) -> BetaToolComputerUse20250124Param:
66
return {
67
"type": "computer_20250124",
68
"name": "computer",
69
"display_width_px": 1920,
70
"display_height_px": 1080,
71
"display_number": 1,
72
}
73
74
def call(self, input: object) -> str:
75
"""Execute computer action."""
76
# input will be a dict with action, text, coordinate, etc.
77
action = input.get("action")
78
79
if action == "screenshot":
80
# Take and return screenshot
81
return self._capture_screenshot()
82
elif action == "mouse_move":
83
# Move mouse to coordinate
84
x, y = input["coordinate"]
85
return f"Moved mouse to ({x}, {y})"
86
elif action == "type":
87
# Type text
88
text = input["text"]
89
return f"Typed: {text}"
90
else:
91
return f"Unknown action: {action}"
92
93
def _capture_screenshot(self) -> str:
94
# Your screenshot logic
95
return "data:image/png;base64,..."
96
97
# Use the custom tool
98
client = Anthropic()
99
computer_tool = MyComputerTool()
100
101
message = client.beta.messages.create(
102
model="claude-3-5-sonnet-20241022",
103
max_tokens=1024,
104
tools=[computer_tool.to_dict()],
105
messages=[{"role": "user", "content": "Take a screenshot"}],
106
betas=["computer-use-2025-01-24"]
107
)
108
```
109
110
**Custom bash tool:**
111
112
```python
113
import subprocess
114
from anthropic.types.beta import BetaToolBash20250124Param
115
116
class SafeBashTool(BetaBuiltinFunctionTool):
117
"""Bash tool with safety restrictions."""
118
119
def __init__(self, allowed_commands: set[str]):
120
self.allowed_commands = allowed_commands
121
122
def to_dict(self) -> BetaToolBash20250124Param:
123
return {
124
"type": "bash_20250124",
125
"name": "bash"
126
}
127
128
def call(self, input: object) -> str:
129
"""Execute bash command if allowed."""
130
command = input.get("command", "")
131
132
# Check if command is in allowed list
133
cmd_name = command.split()[0] if command else ""
134
if cmd_name not in self.allowed_commands:
135
return f"Error: Command '{cmd_name}' not allowed"
136
137
try:
138
# Execute command with timeout
139
result = subprocess.run(
140
command,
141
shell=True,
142
capture_output=True,
143
text=True,
144
timeout=5
145
)
146
return result.stdout or result.stderr
147
except subprocess.TimeoutExpired:
148
return "Error: Command timed out"
149
except Exception as e:
150
return f"Error: {str(e)}"
151
152
# Use with restricted commands
153
bash_tool = SafeBashTool(allowed_commands={"ls", "pwd", "echo", "cat"})
154
155
runner = client.beta.messages.tool_runner(
156
model="claude-3-5-sonnet-20241022",
157
max_tokens=1024,
158
tools=[bash_tool],
159
messages=[{"role": "user", "content": "List files in current directory"}],
160
betas=["bash-2025-01-24"]
161
)
162
```
163
164
**Custom text editor tool:**
165
166
```python
167
from anthropic.types.beta import BetaToolTextEditor20250728Param
168
169
class InMemoryEditorTool(BetaBuiltinFunctionTool):
170
"""Text editor that stores files in memory."""
171
172
def __init__(self):
173
self.files: dict[str, list[str]] = {}
174
175
def to_dict(self) -> BetaToolTextEditor20250728Param:
176
return {
177
"type": "text_editor_20250728",
178
"name": "str_replace_editor"
179
}
180
181
def call(self, input: object) -> str:
182
"""Execute editor command."""
183
command = input.get("command")
184
path = input.get("path", "")
185
186
if command == "view":
187
if path not in self.files:
188
return f"Error: File {path} not found"
189
return "\n".join(self.files[path])
190
191
elif command == "create":
192
content = input.get("file_text", "")
193
self.files[path] = content.split("\n")
194
return f"Created {path} with {len(self.files[path])} lines"
195
196
elif command == "str_replace":
197
if path not in self.files:
198
return f"Error: File {path} not found"
199
old_str = input.get("old_str", "")
200
new_str = input.get("new_str", "")
201
content = "\n".join(self.files[path])
202
if old_str not in content:
203
return f"Error: String not found: {old_str}"
204
content = content.replace(old_str, new_str, 1)
205
self.files[path] = content.split("\n")
206
return f"Replaced text in {path}"
207
208
return f"Unknown command: {command}"
209
210
# Use the in-memory editor
211
editor_tool = InMemoryEditorTool()
212
```
213
214
**Logging tool wrapper:**
215
216
```python
217
class LoggingToolWrapper(BetaBuiltinFunctionTool):
218
"""Wraps another tool and logs all calls."""
219
220
def __init__(self, wrapped_tool: BetaBuiltinFunctionTool):
221
self.wrapped_tool = wrapped_tool
222
self.call_log = []
223
224
def to_dict(self):
225
return self.wrapped_tool.to_dict()
226
227
def call(self, input: object) -> str | Iterable[BetaContent]:
228
# Log the call
229
self.call_log.append({
230
"timestamp": datetime.now(),
231
"tool": self.wrapped_tool.name,
232
"input": input
233
})
234
235
# Execute wrapped tool
236
try:
237
result = self.wrapped_tool.call(input)
238
self.call_log[-1]["result"] = result
239
self.call_log[-1]["success"] = True
240
return result
241
except Exception as e:
242
self.call_log[-1]["error"] = str(e)
243
self.call_log[-1]["success"] = False
244
raise
245
246
def get_log(self) -> list[dict]:
247
"""Get the call log."""
248
return self.call_log
249
250
# Wrap any tool with logging
251
original_tool = MyComputerTool()
252
logged_tool = LoggingToolWrapper(original_tool)
253
254
# Use and inspect log
255
runner = client.beta.messages.tool_runner(
256
model="claude-3-5-sonnet-20241022",
257
max_tokens=1024,
258
tools=[logged_tool],
259
messages=[{"role": "user", "content": "Take a screenshot"}]
260
)
261
result = runner.until_done()
262
263
# Check what was called
264
for call in logged_tool.get_log():
265
print(f"{call['timestamp']}: {call['tool']} - {call['success']}")
266
```
267
268
### Asynchronous Builtin Tool
269
270
Abstract base class for implementing asynchronous built-in tools with custom schemas and behavior.
271
272
```python { .api }
273
class BetaAsyncBuiltinFunctionTool(ABC):
274
"""
275
Abstract base class for asynchronous built-in tools.
276
277
Identical to BetaBuiltinFunctionTool but with async execution support.
278
"""
279
280
@abstractmethod
281
def to_dict(self) -> BetaToolUnionParam:
282
"""
283
Generate the tool definition for the API.
284
285
Returns:
286
Tool parameter dictionary with type, name, and any tool-specific fields
287
"""
288
289
@abstractmethod
290
async def call(self, input: object) -> str | Iterable[BetaContent]:
291
"""
292
Execute the tool asynchronously with the given input.
293
294
Args:
295
input: Tool input parameters from Claude
296
297
Returns:
298
Tool execution result as string or content blocks
299
"""
300
301
@property
302
def name(self) -> str:
303
"""
304
Get the tool name from the tool definition.
305
306
Returns:
307
The tool's name as it appears in the API
308
"""
309
```
310
311
#### Usage Examples
312
313
**Async computer use tool:**
314
315
```python
316
class AsyncComputerTool(BetaAsyncBuiltinFunctionTool):
317
"""Async computer use implementation."""
318
319
def to_dict(self) -> BetaToolComputerUse20250124Param:
320
return {
321
"type": "computer_20250124",
322
"name": "computer",
323
"display_width_px": 1920,
324
"display_height_px": 1080,
325
}
326
327
async def call(self, input: object) -> str:
328
"""Execute computer action asynchronously."""
329
action = input.get("action")
330
331
if action == "screenshot":
332
# Async screenshot capture
333
screenshot = await self._async_capture_screenshot()
334
return screenshot
335
elif action == "type":
336
# Async typing
337
text = input["text"]
338
await asyncio.sleep(len(text) * 0.01) # Simulate typing delay
339
return f"Typed: {text}"
340
341
return f"Action complete: {action}"
342
343
async def _async_capture_screenshot(self) -> str:
344
# Async screenshot logic
345
await asyncio.sleep(0.1)
346
return "data:image/png;base64,..."
347
```
348
349
**Async bash tool with process management:**
350
351
```python
352
class AsyncBashTool(BetaAsyncBuiltinFunctionTool):
353
"""Async bash execution with proper process handling."""
354
355
def to_dict(self) -> BetaToolBash20250124Param:
356
return {"type": "bash_20250124", "name": "bash"}
357
358
async def call(self, input: object) -> str:
359
"""Execute bash command asynchronously."""
360
command = input.get("command", "")
361
362
try:
363
# Use async subprocess
364
process = await asyncio.create_subprocess_shell(
365
command,
366
stdout=asyncio.subprocess.PIPE,
367
stderr=asyncio.subprocess.PIPE
368
)
369
370
# Wait with timeout
371
stdout, stderr = await asyncio.wait_for(
372
process.communicate(),
373
timeout=10
374
)
375
376
return stdout.decode() or stderr.decode()
377
378
except asyncio.TimeoutError:
379
return "Error: Command timed out"
380
except Exception as e:
381
return f"Error: {str(e)}"
382
```
383
384
**Async API-backed tool:**
385
386
```python
387
import httpx
388
389
class AsyncWebSearchTool(BetaAsyncBuiltinFunctionTool):
390
"""Web search using external API."""
391
392
def to_dict(self):
393
return {
394
"type": "custom_search",
395
"name": "web_search",
396
"description": "Search the web for information"
397
}
398
399
async def call(self, input: object) -> str:
400
"""Perform web search."""
401
query = input.get("query", "")
402
403
async with httpx.AsyncClient() as client:
404
response = await client.get(
405
"https://api.search.com/search",
406
params={"q": query}
407
)
408
results = response.json()
409
410
# Format results
411
formatted = []
412
for result in results["results"][:5]:
413
formatted.append(f"- {result['title']}: {result['url']}")
414
415
return "\n".join(formatted)
416
417
# Use with async client
418
client = AsyncAnthropic()
419
search_tool = AsyncWebSearchTool()
420
421
runner = client.beta.messages.tool_runner(
422
model="claude-3-5-sonnet-20241022",
423
max_tokens=1024,
424
tools=[search_tool],
425
messages=[{"role": "user", "content": "Search for Python tutorials"}]
426
)
427
final_message = await runner.until_done()
428
```
429
430
## Types
431
432
### Built-in Tool Union Type
433
434
```python { .api }
435
BetaToolUnionParam = (
436
ToolParam
437
| BetaToolBash20250124Param
438
| BetaToolTextEditor20250728Param
439
| BetaToolComputerUse20250124Param
440
| BetaWebSearchTool20250305Param
441
| BetaMemoryTool20250818Param
442
# ... and other built-in tool types
443
)
444
```
445
446
Union of all possible tool parameter types that can be used with the API.
447