A language model without tools answers questions. A language model with tools gets things done. Tool use โ also called function calling โ is the mechanism that lets an AI agent write files, run code, query databases, search the web, and call any API. It's the single capability that separates chatbots from agents.
How Function Calling Works
When you register tools with an LLM, you send a JSON schema describing each tool's name, description, and parameters. The model reads this schema and decides when to call a tool based on the task.
# Define tools for the model
tools = [
{
"name": "run_code",
"description": "Execute Python code and return stdout/stderr",
"input_schema": {
"type": "object",
"properties": {
"code": {"type": "string", "description": "Python code to execute"},
"timeout": {"type": "integer", "description": "Max execution seconds", "default": 30}
},
"required": ["code"]
}
},
{
"name": "web_search",
"description": "Search the web and return top results",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string"},
"num_results": {"type": "integer", "default": 5}
},
"required": ["query"]
}
}
]
# Call the model with tools
response = anthropic_client.messages.create(
model="claude-opus-4",
tools=tools,
messages=[{"role": "user", "content": task}],
max_tokens=4096
)
If the model decides to call a tool, it returns a tool_use block instead of text. Your code executes the tool and sends the result back.
# Handle tool call
if response.stop_reason == "tool_use":
tool_call = next(b for b in response.content if b.type == "tool_use")
# Dispatch to actual tool function
result = dispatch_tool(tool_call.name, tool_call.input)
# Send result back to model
messages.append({"role": "assistant", "content": response.content})
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_call.id,
"content": str(result)
}]
})
# Continue conversation
response = anthropic_client.messages.create(
model="claude-opus-4",
tools=tools,
messages=messages,
max_tokens=4096
)
The 6 Essential Agent Tools
โ๏ธ 1. Code Executor
Runs Python (or any language) in a sandboxed subprocess. Essential for data analysis, file manipulation, and computation. Always enforce a timeout and capture stderr separately.
๐ 2. Web Search
Queries Brave, Serper, or Google Custom Search. Gives the agent access to real-time information beyond its training cutoff. Return URL + snippet, not raw HTML.
๐ 3. File System
Read, write, and list files within a scoped workspace directory. The most dangerous tool โ always constrain to an allowed path prefix (e.g., /workspace/).
๐ 4. Git / GitHub
Clone, commit, push, and open PRs. This is what makes coding agents actually useful โ they can ship changes to real repos, not just generate code in a chat window.
๐ก 5. HTTP / API Caller
Makes arbitrary REST API calls. Used for Slack notifications, database inserts, webhook triggers, and any external integration. Whitelist allowed domains in production.
๐ง 6. Memory (ChromaDB)
Store and retrieve long-term memories. The agent uses this to remember past decisions, user preferences, and code patterns across sessions. See our memory guide.
Safe Tool Wrapper Pattern
Never expose raw tools to the model. Wrap every tool with validation, logging, and error handling.
import subprocess, os, re
from pathlib import Path
WORKSPACE = Path("/workspace/agent_001")
def safe_run_code(code: str, timeout: int = 30) -> dict:
"""Execute code safely with timeout and output capture."""
# Block dangerous imports
forbidden = ["os.system", "subprocess.call", "shutil.rmtree", "__import__"]
for f in forbidden:
if f in code:
return {"error": f"Forbidden operation: {f}", "stdout": "", "stderr": ""}
try:
result = subprocess.run(
["python3", "-c", code],
capture_output=True, text=True,
timeout=timeout,
cwd=str(WORKSPACE)
)
return {
"stdout": result.stdout[:4000], # Limit output size
"stderr": result.stderr[:1000],
"returncode": result.returncode
}
except subprocess.TimeoutExpired:
return {"error": f"Code timed out after {timeout}s", "stdout": "", "stderr": ""}
except Exception as e:
return {"error": str(e), "stdout": "", "stderr": ""}
def safe_file_write(path: str, content: str) -> dict:
"""Write file within allowed workspace only."""
full_path = (WORKSPACE / path).resolve()
if not str(full_path).startswith(str(WORKSPACE)):
return {"error": "Path traversal attempt blocked"}
full_path.parent.mkdir(parents=True, exist_ok=True)
full_path.write_text(content)
return {"success": True, "path": str(full_path)}
โ ๏ธ Security critical
Always sandbox code execution, whitelist file paths, restrict network calls to approved domains, and log every tool invocation. Never let an agent call arbitrary shell commands or access credentials outside its designated scope.
Parallel Tool Calls
Modern models (Claude Opus 4, GPT-5) support parallel tool calls โ requesting multiple tools in a single response. Always handle this in your dispatch loop.
async def dispatch_all_tools(tool_calls: list) -> list:
"""Execute multiple tool calls in parallel."""
tasks = [dispatch_tool_async(tc.name, tc.input) for tc in tool_calls]
results = await asyncio.gather(*tasks, return_exceptions=True)
return [
{
"type": "tool_result",
"tool_use_id": tc.id,
"content": str(result) if not isinstance(result, Exception) else f"Error: {result}"
}
for tc, result in zip(tool_calls, results)
]
All 6 tools, pre-configured and safe
Every MoltBot agent ships with sandboxed code execution, web search, GitHub integration, and ChromaDB memory out of the box. No setup required.
Start Free Trial โ