๐Ÿ“… April 14, 2026โฑ 8 min readโœ๏ธ MoltBot Engineering
ToolsFunction CallingProduction

AI Agent Tool Use: How Agents Execute Code, Search the Web & Call APIs

Tool use transforms LLMs from text generators into autonomous agents that act in the world. Here's how function calling works under the hood โ€” and the 6 tools every production agent needs.

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 โ†’