Generative AI

Coding Implementation to Build Multi-Agent AI Systems with SmolAgents Using Coding, Tool Calling, and Dynamic Orchestration

In this tutorial, we build an advanced, production-ready agent system using SmolAgents and show how modern, lightweight AI agents can think, code, handle dynamic tools, and interact across multiple agents. We start by installing dependencies and configuring the powerful yet efficient LLM backend, then gradually design custom tools, including statistical utilities, memory storage, and web search capabilities. We explore both the CodeAgent and ToolCallingAgent paradigms, understand how tools are managed dynamically through the agent.tools dictionary, and implement multi-agent orchestration.

import subprocess, sys


def pip(*args):
   subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", *args])


pip("smolagents[all]", "duckduckgo-search", "wikipedia", "rich")


import os, math, textwrap
from rich.console import Console
from rich.panel   import Panel
from rich.table   import Table
from rich         import print as rprint


console = Console()


def section(title: str, color: str = "bold cyan"):
   console.rule(f"[{color}]{title}[/{color}]")


def show(label: str, value):
   console.print(Panel(str(value), title=f"[bold yellow]{label}[/bold yellow]", expand=False))


import getpass


OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
if not OPENAI_API_KEY:
   OPENAI_API_KEY = getpass.getpass("🔑 Enter your OpenAI API key: ")
   os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY


console.print("[green]✓ OpenAI API key loaded.[/green]")


section("SECTION 1 · SmolAgents Architecture")


console.print(Panel("""
SmolAgents (HuggingFace) is a minimalist agent framework.
Current stable release: 1.24.0   |   Using: OpenAI gpt-4o-mini


CORE ABSTRACTIONS
 Tool
 agent.tools (dict)
 ToolCollection
 LiteLLMModel
 CodeAgent
 ToolCallingAgent


MULTI-AGENT  (v1.8+ API)
 Pass sub-agents directly via  managed_agents=[sub_agent]
 Sub-agents need  name=  and  description=  set at init.
 ManagedAgent wrapper class was removed in v1.8.0.


EXECUTION LOOP (CodeAgent)
 Task ──► LLM writes Python ──► sandbox executes it
      ◄── observation (tool output / exception) ◄──
 Repeats up to max_steps, then calls final_answer(...)
""", title="[bold green]Architecture[/bold green]"))

We install all the necessary dependencies and set up the working environment. We're ready to load a secure API key and launch rich console resources for structured output formatting. We also present an overview of SmolAgents architecture to establish a solid conceptual foundation before building agents.

section("SECTION 2 · Building Custom Tools")


from smolagents import Tool, tool


@tool
def celsius_to_fahrenheit(celsius: float) -> str:
   return f"{celsius}°C = {celsius * 9/5 + 32:.2f}°F"


class PrimeTool(Tool):


   name        = "prime_checker"
   description = (
       "If composite, returns the smallest prime factor."
   )
   inputs = {
       "n": {"type": "integer", "description": "Positive integer to test."}
   }
   output_type = "string"


   def forward(self, n: int) -> str:
       if n < 2:
           return f"{n} is not prime (must be >= 2)."
       for i in range(2, int(math.isqrt(n)) + 1):
           if n % i == 0:
               return f"{n} is NOT prime. Smallest factor: {i}."
       return f"{n} IS prime!"


class MemoTool(Tool):


   name        = "memory_store"
   description = (
       "Stores or retrieves key-value pairs. "
       "action='set' stores key+value; "
       "action='get' retrieves by key; "
       "action='list' shows all keys."
   )
   inputs = {
       "action": {"type": "string", "description": "set | get | list"},
       "key":    {"type": "string", "description": "Memory key (skip for list)", "nullable": True},
       "value":  {"type": "string", "description": "Value to store (set only)",  "nullable": True},
   }
   output_type = "string"


   def __init__(self, *args, **kwargs):
       super().__init__(*args, **kwargs)
       self._store: dict[str, str] = {}


   def forward(self, action: str, key: str = None, value: str = None) -> str:
       if action == "set":
           self._store[key] = value
           return f"Stored '{key}' = '{value}'"
       elif action == "get":
           return self._store.get(key, f"Key '{key}' not found.")
       elif action == "list":
           return "Keys: " + ", ".join(self._store.keys()) if self._store else "Memory empty."
       return "Unknown action. Use: set | get | list"

We define custom tools using both decorator-based and class-based methods to provide flexibility in creating tools. We use mathematical reasoning and a good memory tool to enable continuous interaction in all steps of the agent. We build tools with clear schemas so agents can interpret and call them correctly.

class DuckDuckGoTool(Tool):


   name        = "web_search"
   description = "Performs a web search and returns top results as plain text."
   inputs = {
       "query":       {"type": "string",  "description": "The search query."},
       "max_results": {"type": "integer", "description": "Results to return (1-10).", "nullable": True},
   }
   output_type = "string"


   def forward(self, query: str, max_results: int = 3) -> str:
       try:
           from duckduckgo_search import DDGS
           with DDGS() as ddgs:
               results = [
                   f"* {r['title']}n  {r['href']}n  {r['body'][:200]}"
                   for r in ddgs.text(query, max_results=max_results)
               ]
           return "nn".join(results) if results else "No results found."
       except Exception as e:
           return f"Search failed: {e}"


@tool
def factorial(n: int) -> str:
   return f"{n}! = {math.factorial(n)}"


show("celsius_to_fahrenheit(100)", celsius_to_fahrenheit(100))
show("PrimeTool — 97",             PrimeTool().forward(97))
show("PrimeTool — 100",            PrimeTool().forward(100))
m = MemoTool()
m.forward("set", "author", "Ada Lovelace")
show("MemoTool get 'author'",      m.forward("get", "author"))


section("SECTION 3 · Managing Tools  (agent.tools dict)")


console.print(Panel("""
The Toolbox class was removed in v1.x.
Tools live in  agent.tools, a plain Python dict keyed by tool name.
""", title="[bold green]Tools Dict[/bold green]"))


section("SECTION 4 · LLM Engines")


console.print(Panel("""
SmolAgents supports multiple LLM backends via  LiteLLMModel.
We use  gpt-4o-mini.
""", title="[bold green]Engine Options[/bold green]"))


from smolagents import LiteLLMModel


MODEL_ID = "openai/gpt-4o-mini"
engine   = LiteLLMModel(model_id=MODEL_ID, api_key=OPENAI_API_KEY)
console.print(f"[green]Engine ready:[/green] {MODEL_ID}")

We extend the system with a web search tool and a factorial service to increase the agent's capabilities. We independently test tools to ensure correctness before integrating them into agents. We also implement the LLM engine using LiteLLMModel, preparing the underlying logic for implementation.

section("SECTION 5 · CodeAgent")


from smolagents import CodeAgent


code_agent = CodeAgent(
   tools           = [celsius_to_fahrenheit, PrimeTool(), MemoTool(), DuckDuckGoTool()],
   model           = engine,
   max_steps       = 6,
   verbosity_level = 1,
)


console.print("n[bold]Initial agent.tools keys:[/bold]", list(code_agent.tools.keys()))
code_agent.tools["factorial"] = factorial
console.print("[dim]After adding factorial:[/dim]", list(code_agent.tools.keys()))


console.print("n[bold yellow]Task 1:[/bold yellow]")
result1 = code_agent.run(
   "Convert boiling point (100C) and body temperature (37C) to Fahrenheit. "
   "Which is higher and by how much?"
)
show("CodeAgent — Task 1", result1)


console.print("n[bold yellow]Task 2:[/bold yellow]")
result2 = code_agent.run("What is 17 times 19? Is that result prime? Also check 7919.")
show("CodeAgent — Task 2", result2)


console.print("n[bold yellow]Task 3:[/bold yellow]")
result3 = code_agent.run("Compute 10! using the factorial tool.")
show("CodeAgent — Task 3", result3)

We're building a CodeAgent that can dynamically write and use Python to solve multi-step problems. We demonstrate runtime tool injection by adding a new tool without rebuilding the agent. We then perform complex logic operations further to verify convergence, arithmetic, and device connectivity.

section("SECTION 6 · ToolCallingAgent (ReAct)")


from smolagents import ToolCallingAgent


react_agent = ToolCallingAgent(
   tools           = [celsius_to_fahrenheit, PrimeTool(), MemoTool()],
   model           = engine,
   max_steps       = 5,
   verbosity_level = 1,
)


console.print("n[bold yellow]Task 4:[/bold yellow]")
result4 = react_agent.run(
   "Then retrieve both facts and summarise them."
)
show("ToolCallingAgent — Task 4", result4)


section("SECTION 7 · Multi-Agent Orchestration  (v1.8+ API)")


math_agent = CodeAgent(
   tools           = [PrimeTool()],
   model           = engine,
   max_steps       = 4,
   name            = "math_specialist",
   description     = "Handles mathematical questions and primality checks.",
   verbosity_level = 0,
)


research_agent = ToolCallingAgent(
   tools           = [DuckDuckGoTool(), MemoTool()],
   model           = engine,
   max_steps       = 4,
   name            = "research_specialist",
   description     = "Searches the web and stores or retrieves facts from memory.",
   verbosity_level = 0,
)


manager_agent = CodeAgent(
   tools           = [],
   model           = engine,
   managed_agents  = [math_agent, research_agent],
   max_steps       = 8,
   verbosity_level = 1,
)


console.print("n[bold yellow]Task 5:[/bold yellow]")
result5 = manager_agent.run(
   "Find out what year Python was first released (use research_specialist), "
   "then check whether that year is a prime number (use math_specialist)."
)
show("Manager Agent — Task 5", result5)

We created a ToolCallingAgent to demonstrate ReAct-style structured reasoning for managed tool requests. We then implement a multi-agent orchestration system where specialized agents interact under a manager agent. We demonstrate delegation, collaboration, and multi-agent reasoning to solve complex tasks efficiently.

In conclusion, we have built a fully functional multi-agent system that can reason, search, compute, store memory, and delegate tasks between specialized agents. We showed how SmolAgents allows flexible tool integration, runtime scalability, and streamlined interactions without unnecessary architectural complexity. We showed how CodeAgent implements the real Python concept of advanced integration, while ToolCallingAgent ensures structured, readable logic loops. Finally, we implemented a manager agent that coordinates specialized sub-agents, proving how scalable orchestration can be achieved with minimal overhead.


Check out Full Implementation Code and notebook. Also, feel free to follow us Twitter and don't forget to join our 130k+ ML SubReddit and Subscribe to Our newspaper. Wait! are you on telegram? now you can join us on telegram too.

Need to work with us on developing your GitHub Repo OR Hug Face Page OR Product Release OR Webinar etc.? contact us


Source link

Related Articles

Leave a Reply

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

Back to top button