For my talk at PyData London, I was trying to explain what a tool calling agent is, and created the simplest version I could think of. It’s an agent that has two functions, sum and divide, and I asked it to calculate the average of a sequence of numbers.
Obviously, any modern language model can do this, but it’s still an interesting excersise to understand how agents work. I’ve worked with Hugging Face’s smolagents a lot recently, but I wanted to compare it to LlamaIndex and LangChain, two of the most popular frameworks.
In the outset, I thought the libraries would all have a different approach. Last time I tried LanngChain, for example, it was unwieldy and the imports had weird names from seemingly unrelated libraries. But to my surprise, the three frameworks had a very similar approach to building this simple agent. In the outset, I was going to compare what library made it easiest to get started, but in the end, they were all very similar.
smolagents
I love the smolagents library from Hugging Face. Because it’s such a small library, it makes it easy to understand exactly what is going on. I also highly recommend their Agent’s Course for anyone getting into building agents.
Smolagents supports both function decorators like @tool and defining tools as a class. The example below only shows the former.
from smolagents import ToolCallingAgent, LiteLLMModel, tool@tooldef sum_numbers(numbers: list[float]) ->float:""" PLEASE Calculate the sum of a list of numbers. Args: numbers: A list of numbers to sum """returnsum(numbers)@tooldef divide_sum(total: float, length: int) ->float:""" Divide a sum by a length to get the average. Args: total: The sum/total to divide length: The number to divide by (typically the count of numbers) """return total / lengthmodel = LiteLLMModel(model_id="gpt-4o-mini")agent = ToolCallingAgent(tools=[sum_numbers, divide_sum], model=model)agent.run("What is the average of 10, 20, 30, 40, and 50?")
╭──────────────────────────────────────────────────── New run ────────────────────────────────────────────────────╮│││What is the average of 10, 20, 30, 40, and 50?│││╰─ LiteLLMModel - gpt-4o-mini ────────────────────────────────────────────────────────────────────────────────────╯
LlamaIndex was one of the first AI assistant frameworks I came across, although I opted for LangChain when I was exploring searching through PDFs with embeddings, the corner stone of RAG. I can’t say I’m a fan of this code:
from llama_index.core.tools import FunctionTool
sum_tool = FunctionTool.from_defaults(fn=sum_numbers)
But maybe LlamaIndex also has decorators, I haven’t looked that closely. And this is no cardinal sin if LlamaIndex is otherwise strong.
from llama_index.core.agent.workflow import ReActAgentfrom llama_index.core.tools import FunctionToolfrom llama_index.llms.openai import OpenAIdef sum_numbers(numbers: list[float]) ->float:""" Calculate the sum of a list of numbers. Args: numbers: A list of numbers to sum """returnsum(numbers)def divide_sum(total: float, length: int) ->float:""" Divide a sum by a length to get the average. Args: total: The sum/total to divide length: The number to divide by (typically the count of numbers) """return total / length# Create FunctionTools from the functionssum_tool = FunctionTool.from_defaults(fn=sum_numbers)divide_tool = FunctionTool.from_defaults(fn=divide_sum)# Create LLM and agentllm = OpenAI(model="gpt-4o-mini")agent = ReActAgent(tools=[sum_tool, divide_tool], llm=llm, verbose=True)# Run the agent and show intermediate stepsfrom llama_index.core.agent.workflow import AgentStreamhandler = agent.run("What is the average of 10, 20, 30, 40, and 50?")asyncfor ev in handler.stream_events():ifisinstance(ev, AgentStream):print(f"{ev.delta}", end="", flush=True)response =await handlerprint(f"\n\nFinal response: {response}")
Thought: The current language of the user is: English. I need to use tools to calculate the average of the numbers provided.
Action: sum_numbers
Action Input: {"numbers":[10,20,30,40,50]}Thought: I have the sum of the numbers, which is 150. Now I need to divide this sum by the count of the numbers to find the average.
Action: divide_sum
Action Input: {'total': 150, 'length': 5}Thought: I can answer without using any more tools. I'll use the user's language to answer.
Answer: The average of 10, 20, 30, 40, and 50 is 30.0.
Final response: The average of 10, 20, 30, 40, and 50 is 30.0.
LangChain
Finally, I tried LangChain, which seems to have the most adoption in the industry. Their approach is similar to the others, using @tool decoratros like smolagents.
from langchain_core.tools import toolfrom langchain_openai import ChatOpenAIfrom langchain.agents import create_agent@tooldef sum_numbers(numbers: list[float]) ->float:""" Calculate the sum of a list of numbers. Args: numbers: A list of numbers to sum """returnsum(numbers)@tooldef divide_sum(total: float, length: int) ->float:""" Divide a sum by a length to get the average. Args: total: The sum/total to divide length: The number to divide by (typically the count of numbers) """return total / length# Create LLM and agentmodel = ChatOpenAI(model="gpt-4o-mini")agent = create_agent(model, [sum_numbers, divide_sum])# Run the agent with streaming to see intermediate stepsfor chunk in agent.stream( {"messages": [("user", "What is the average of 10, 20, 30, 40, and 50?")]}, stream_mode="values"): chunk["messages"][-1].pretty_print()
================================ Human Message =================================
What is the average of 10, 20, 30, 40, and 50?
================================== Ai Message ==================================
Tool Calls:
sum_numbers (call_oq9YUUKfb2GrNINTJ9H8kc2z)
Call ID: call_oq9YUUKfb2GrNINTJ9H8kc2z
Args:
numbers: [10, 20, 30, 40, 50]
================================= Tool Message =================================
Name: sum_numbers
150.0
================================== Ai Message ==================================
Tool Calls:
divide_sum (call_2Df47jyASnaXh6apmKovGbhJ)
Call ID: call_2Df47jyASnaXh6apmKovGbhJ
Args:
total: 150
length: 5
================================= Tool Message =================================
Name: divide_sum
30.0
================================== Ai Message ==================================
The average of 10, 20, 30, 40, and 50 is 30.0.
Running the examples on your machine
To run the examples on your machine, install uv and run the following commands in your terminal:
uv run https://raw.githubusercontent.com/geirfreysson/ai-experiments/main/posts/2025-11-09-the-ai-agent-version-of-hello-world-in-different-frameworks/two_tool_agent_llamaindex_script.py
uv run https://raw.githubusercontent.com/geirfreysson/ai-experiments/main/posts/2025-11-09-the-ai-agent-version-of-hello-world-in-different-frameworks/two_tool_agent_langgraph.py
uv run https://raw.githubusercontent.com/geirfreysson/ai-experiments/main/posts/2025-11-09-the-ai-agent-version-of-hello-world-in-different-frameworks/two_tool_agent.py
Conclusion
I need to explore these frameworks further to form an opinion of them. I thought I was going to walk away saying smolagents is the simplest one to get started with, but the other libraries approach is very similar. Smolagents might still have the smallest codebase, which makes it a better framework for learning about agents, but I also need to explore that further.
If you want to explore further, the above is also available as standalone scripts, which you can run with uv, so you don’t need to install any libraries.