Hugging Face’s smolagents framework flips this design on its head. Built in approximately 1,000 lines of clean Python code, smolagents centers on a bold premise: language models perform significantly better when they generate and execute standard Python code instead of raw JSON parameters.
This blueprint covers how to build secure, high-performance, code-executing agents with smolagents, complete with dynamic tool building, sandboxed runtime security, and custom model routing.
Why Code Agents Outperform JSON Tool Calling
Standard tool calling asks a model to output a structured JSON schema:
{
"name": "search_docs",
"arguments": { "query": "latency metrics" }
}
If the agent needs to loop over five items, filter out non-zero results, and calculate an average, a JSON-based agent must execute five distinct LLM turns.
smolagents uses CodeAgent, allowing the model to express actions directly as executable Python snippets:
results = [search_docs(item) for item in items]
filtered = [r for r in results if r["latency"] > 0]
avg_latency = sum(r["latency"] for r in filtered) / len(filtered)
print(avg_latency)
Key Architectural Advantages
- Native Control Flow: The model handles conditionals, loops, error handling, and variable storage within a single execution block.
- State Management: Intermediary object instances (like pandas DataFrames, Pillow images, or custom class instances) stay in memory between steps without getting converted to strings.
- Reduced Token Footprint: Combining multiple tool interactions into a single code block dramatically reduces round-trip API calls and context consumption.
Step-by-Step Blueprint: Constructing Your First Code-Executing Agent
Building an production-ready agent with smolagents requires three primary components: an LLM engine, tools, and an execution runtime.
Step 1: Environment Setup and Library Installation
First, install smolagents alongside your preferred model runner backend.
pip install smolagents duckduckgo-search requests
Step 2: Defining Custom Tools with Python Decorators
smolagents makes custom tool creation frictionless. You convert standard Python functions into agent tools using the @tool decorator. The library uses function type hints and docstrings to generate descriptions for the agent.
from typing import Optional
import requests
from smolagents import tool
@tool
def fetch_system_status(service_name: str, region: Optional[str] = "us-east-1") -> str:
"""
Queries internal health endpoints to retrieve service metrics.
Args:
service_name: The name of the target microservice (e.g., 'auth-service').
region: The AWS cloud region where the service is deployed. Defaults to 'us-east-1'.
"""
# Simulate an internal API call
api_url = f"https://api.internal.techelite.org/health/{service_name}?region={region}"
# Imports must live inside the function for clean modular isolation
import json
return json.dumps({
"service": service_name,
"region": region,
"status": "healthy",
"cpu_utilization": 42.5,
"memory_usage_mb": 1024
})
Step 3: Instantiating the Model and Agent
Select an LLM model provider using LiteLLMModel (for OpenAI, Anthropic, or local Ollama instances) or InferenceClientModel for Hugging Face Hub inference.
from smolagents import CodeAgent, DuckDuckGoSearchTool, LiteLLMModel
# Initialize the model engine via LiteLLM
model = LiteLLMModel(model_id="gpt-4o-mini")
# Assemble the CodeAgent with built-in and custom tools
agent = CodeAgent(
tools=[DuckDuckGoSearchTool(), fetch_system_status],
model=model,
additional_authorized_imports=["datetime", "json", "math"]
)
# Run the agent with a complex query
response = agent.run(
"Check the system status for 'auth-service'. "
"If CPU utilization is under 50%, search DuckDuckGo for recent "
"deployment best practices for light workloads."
)
print(response)
Hardening Security: Sandboxing and Import Controls
Allowing an LLM to generate and run arbitrary code on production infrastructure demands strict isolation. smolagents implements multi-tiered security features to prevent unchecked execution.
┌──────────────────────────────────────────────────────────┐
│ LLM (Code Generation) │
└─────────────────────────────┬────────────────────────────┘
│ Generated Python Code
▼
┌──────────────────────────────────────────────────────────┐
│ Smolagents AST Parser │
│ • Blocks syntax-level threats │
│ • Restricts unauthorized module imports │
└─────────────────────────────┬────────────────────────────┘
│ Sanitized Execution Tree
▼
┌──────────────────────────────────────────────────────────┐
│ Sandboxed Runtime Isolation │
│ • Local Restricted Environment │
│ • External Containers (Docker / E2B Sandbox API) │
└──────────────────────────────────────────────────────────┘
Import Whitelisting
By default, smolagents blocks arbitrary standard library and third-party imports. If the generated code attempts to call import os or import subprocess without authorization, the parser halts execution and alerts the agent to rethink its approach. Explicitly grant authorization using additional_authorized_imports:
agent = CodeAgent(
tools=[fetch_system_status],
model=model,
# Restrict permissions strictly to safe, required packages
additional_authorized_imports=["math", "datetime", "re"]
)
Isolated Sandbox Environments
For enterprise production deployments handling untrusted user input, execution should move entirely off the application host machine. Configure remote sandboxing backends using E2B, Docker, or Blaxel:
# Executing code inside isolated E2B micro-VM containers
agent = CodeAgent(
tools=[fetch_system_status],
model=model,
executor_type="e2b" # Routes code execution to a remote E2B sandbox container
)
Pro-Tip: Fixing the “Import State Leak” Pitfall
A common trap for developers migrating to smolagents involves defining dependencies at the root level of custom tool modules rather than inside the decorated function.
# ❌ INCORRECT: Root-level import breaks tool portability
import requests
from smolagents import tool
@tool
def bad_weather_tool(location: str) -> str:
"""Fetches current weather for a location."""
res = requests.get(f"https://wttr.in/{location}?format=3")
return res.text
Why this fails: When smolagents pushes custom tools across remote sandboxes (such as E2B or Hugging Face Spaces), root-level imports fail to serialize properly.
The Fix: Always write self-contained tools with imports placed strictly inside the function body.
# ✅ CORRECT: Fully self-contained tool design
from smolagents import tool
@tool
def good_weather_tool(location: str) -> str:
"""Fetches current weather for a location.
Args:
location: City or region name.
"""
import requests # Scoped import enables seamless remote sandbox execution
res = requests.get(f"https://wttr.in/{location}?format=3")
return res.text



