Build a Simple AI Agent with Hugging Face smolagents: Tools, Code and Local Models

Close-up of a knight chess piece from a Centurion chess set Photo: Bubba73 via Wikimedia Commons (CC BY-SA 3.0 Unported)

An “AI agent” sounds grandiose, but the working definition is simple: a language model in a loop, allowed to call tools, observe the results, and decide what to do next. Hugging Face’s smolagents is the most approachable open-source framework for building one — a few hundred lines of well-designed code instead of a heavyweight platform. In this guide you’ll build a research agent that searches the web, runs Python, and answers a question no single model call could.

This is for developers who’ve played with chat APIs and want to see what changes when the model can act. You don’t need a GPU — we’ll use a hosted open model.

Prerequisites

  • Python: 3.10 or newer.
  • A Hugging Face account and token: free at huggingface.co. The agent calls a model through Hugging Face’s inference API, so set HF_TOKEN in your environment.
  • Network access: for the model API and the web search tool.

Step 1: Install smolagents

pip install "smolagents[toolkit]"
export HF_TOKEN="hf_your_token_here"

The [toolkit] extra bundles the default tools, including web search. Verify the install:

python -c "import smolagents; print(smolagents.__version__)"

Step 2: Build your first agent

Create agent.py. A CodeAgent lets the model solve problems by writing Python code — more reliable than asking it to emit JSON tool calls:

from smolagents import CodeAgent, InferenceClientModel, DuckDuckGoSearchTool

model = InferenceClientModel(model_id="Qwen/Qwen2.5-72B-Instruct")
agent = CodeAgent(
    tools=[DuckDuckGoSearchTool()],
    model=model,
    max_steps=8,
)

result = agent.run(
    "What was the closing price of NVIDIA stock last Friday, "
    "and what was it exactly one year before that date? "
    "Report both numbers and the percentage change."
)
print(result)

Run it with python agent.py. What happens: the model reasons about the task, writes code calling the search tool, reads the results, does the date arithmetic in Python, and returns a final answer. The stream_outputs=True argument (add it to CodeAgent) prints each step live, which is the best way to understand — and debug — agent behaviour.

smolagents also ships a ToolCallingAgent, which has the model emit structured tool calls instead of writing code. CodeAgent is the better default: writing code lets the agent do arithmetic, string manipulation, and control flow in one step, where a tool-calling agent would need several round trips. ToolCallingAgent shines only with models specifically fine-tuned for function calling. Either way, the loop is the same — think, act, observe, repeat — and everything you learn about tool design transfers between them.

Step 3: Give it a custom tool

Tools are just Python functions with type hints and a docstring, wrapped in the @tool decorator. The docstring is the model’s instruction manual for the tool, so write it carefully:

from smolagents import tool

@tool
def compound_interest(principal: float, rate: float, years: int) -> float:
    """Compute compound interest.

    Args:
        principal: starting amount in dollars.
        rate: annual interest rate as a decimal (0.05 = 5%).
        years: number of years to compound.
    """
    return principal * (1 + rate) ** years

agent = CodeAgent(tools=[DuckDuckGoSearchTool(), compound_interest], model=model)
print(agent.run("If I invest $2000 at 6% annual interest, how much do I have after 10 years?"))

This is the whole extensibility story: any function becomes a capability. Database lookups, API calls, file operations — wrap them, describe them, hand them to the agent. For a meatier example, our RAG tutorial builds a document-search function that would make an excellent agent tool.

Step 4: Run the model locally (optional)

Don’t want API calls? Point the agent at a local model instead. With Ollama serving an OpenAI-compatible endpoint:

from smolagents import OpenAIModel

model = OpenAIModel(
    model_id="qwen2.5:7b",
    api_base="http://localhost:11434/v1/",
    api_key="ollama",   # any non-empty string works locally
)
agent = CodeAgent(tools=[DuckDuckGoSearchTool()], model=model)

Be honest about the trade-off: small local models are weaker at following the agent’s code-writing format. A 7B model will fumble multi-step tasks that a 70B model sails through. For learning the framework, local is fine; for real work, use the biggest model you can afford.

Step 5: Watch it think, then set boundaries

Two settings separate a demo from something you’d actually run. First, max_steps caps the loop so a confused agent can’t burn money forever — 8–12 is sane. Second, be deliberate about which tools get destructive powers: an agent with a shell tool and no supervision is a liability. smolagents executes code in a restricted environment, but review what your custom tools can touch before handing them over.

Debugging: read the trace, not just the answer

The difference between a demo agent and a reliable one is observability. Run with stream_outputs=True and read the full trace: what the model planned, what code it wrote, what each tool returned, where it went wrong. Most agent failures are boring — a search returned an ad-filled page, the model misread a date, a tool’s docstring was ambiguous. Fix the tool or the task description; don’t blame the model first.

Keep a log of traces for your real tasks. When the agent fails, you want the exact sequence of steps, not just the final wrong answer. A simple pattern: wrap agent.run() in a function that writes the task, the trace (captured via the agent’s logger), and the result to a file. Over a week of real use, those logs teach you more about your agent’s failure modes than any amount of theorizing — and they show you exactly which tool needs a better docstring.

Where things go wrong

“Model returned no code” / format errors. The model isn’t following the agent’s expected output format. Use a stronger model, or simplify the task. This is the most common failure with small local models.

401 errors from the inference API. Your HF_TOKEN is missing or invalid. Also note that some gated models require accepting their license on the Hub before the API will serve them.

The agent loops or repeats searches. Raise max_steps slightly, but more importantly make the task concrete — vague tasks (“research AI”) produce vague wandering. Specific questions get specific tool use.

Search tool returns junk. DuckDuckGoSearchTool is a free, rate-limited search. For serious research tasks, swap in a proper search API (Tavily, Brave) wrapped with @tool.

Wrap-up

You’ve built an agent that reasons, searches, and computes — the core loop behind far more elaborate systems. The framework stays out of your way deliberately: models, tools, and a loop. From here, the interesting work is all in tool design — the better your tools, the smarter your agent looks. Try giving it the document-search pipeline from our RAG guide and asking questions over your own files.

Featured image: Bubba73 via Wikimedia Commons (CC BY-SA 3.0 Unported)

Leave a Reply

Your email address will not be published. Required fields are marked *