An educational project to understand how function calling and tool calling work in language models, from low-level implementations to modern native support.
This project explores two approaches for language models to "call functions":
- Pseudo Tool Calling: Simulating tool calling in models without native support
- Real Tool Calling: Using native support from modern models
- How tool calling works "under the hood"
- Differences between structured JSON Schema vs native tool calling
- Limitations of older models vs modern capabilities
- How to process and execute function calls manually
- Best practices for validation and error handling
Model: llama2 (without native tool calling support)
Method:
- β¨ JSON Schema to force structured output
- π― Detailed system prompt explaining expected format
- π§ Manual parsing of JSON result
- β‘ Manual function execution
# Approach: Structured Output
response = chat(
model='llama2',
messages=messages,
format=tools_schema, # JSON Schema
)
# Manual parsing
result = json.loads(response.message.content)Advantages:
- π You understand exactly how it works
- π οΈ Total control over the process
- π§ Works with any model
- π Educational for understanding fundamentals
Limitations:
- π Complex system prompts
- π Limited JSON Schema in older models
β οΈ No format guarantees- π More manual code
Model: llama3.2 (with native tool calling support)
Method:
- π Standard tool definitions (OpenAI format)
- π¬ Simple system prompt
- π€ Model decides when and how to use tools
- β‘ Native tool calls in response
# Approach: Native Tool Calling
response = chat(
model='llama3.2',
messages=messages,
tools=tools, # Native definitions
# No need for temperature=0 with native tool calling
)
# Native tool calls
if response.message.tool_calls:
# Process tool_calls directlyAdvantages:
- π― More reliable and accurate
- π Simpler prompts
- π§ Better integration
- π Advanced features
Limitations:
- π¦ Requires modern models
- ποΈ Less granular control
- π Depends on model implementation
Both approaches implement the same basic functions:
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + bThe scripts include tests covering:
"Multiply 2 by 3""Add 15 and 25"
"Multiply 4 by 5 and add 6 to 7""Calculate 8*9 and also 10+15""I need both 3*6 and 12+8""Find 7*8 and 20+25"
Note: Examples focus on independent operations, not sequential ones, to avoid dependency problems between results.
Both approaches follow the same general flow:
User Request β Model Processing β Tool Calls β Function Execution β Natural Response
"Multiply 2 by 3"
β llama2 + JSON Schema
β {"tool_calls": [{"name": "multiply", "arguments": {"a": 2, "b": 3}}]}
β Manual parsing + execution
β "The result of multiplying 2 by 3 is 6."
"Multiply 2 by 3"
β llama3.2 + tools definition
β response.message.tool_calls[0].function.name = "multiply"
β Native tool call processing + execution
β "The result of multiplying 2 by 3 is 6."
# Install Ollama
curl -fsSL https://ollama.ai/install.sh | sh
# Install required models
ollama pull llama2 # For pseudo tool calling
ollama pull llama3.2 # For real tool calling
# Install Python dependencies
pip install ollamapython3 pseudo_tool_calling.pypython3 real_tool_calling.pyBoth scripts run the same tests automatically and show the complete process.
# Pseudo (JSON Schema)
tools_schema = {
"type": "object",
"properties": {
"tool_calls": {
"type": "array",
"items": {...}
}
}
}
# Real (OpenAI format)
tools = [{
"type": "function",
"function": {
"name": "multiply",
"description": "Multiply two numbers",
"parameters": {...}
}
}]Pseudo Tool Calling:
- Manual validation of function names
- Defensive JSON parsing
- Handling of unrespected schemas
Real Tool Calling:
- Type conversion (strings β integers)
- Handling different argument formats
- Native tool_calls validation
Sequential Operations:
Neither approach handles well "What is 2*3 plus 4?" because it requires using the result of one function as input to another.
Workaround: Use independent operations like "Calculate 2*3 and 4+5".
Pseudo Tool Calling:
- π Educational projects
- π§ Maximum process control
- π¦ Models without native support
- π― Simple and controlled cases
Real Tool Calling:
- π Production applications
- π― Better user experience
- π¦ When you have modern models
- π§ Advanced features
- Fundamentals matter: Understanding pseudo tool calling helps better use the real one
- JSON Schema has limitations: Old models don't always respect advanced constraints
- Validation is key: Always validate and handle errors, regardless of approach
- Simplicity: Simple independent cases work better than complex operations
- Name: Juanje Ojeda
- Email: [email protected]
This project was developed with the assistance of artificial intelligence tools:
Tools used:
- Cursor: Code editor with AI capabilities
- Claude-4-Sonnet: Anthropic's language model
Division of responsibilities:
AI (Cursor + Claude-4-Sonnet):
- π§ Initial code prototyping
- π Generation of examples and test cases
- π Assistance in debugging and error resolution
- π Documentation and comments writing
- π‘ Technical implementation suggestions
Human (Juanje Ojeda):
- π― Specification of objectives and requirements
- π Critical review of code and documentation
- π¬ Iterative feedback and solution refinement
- π Definition of project's educational structure
- β Final validation of concepts and approaches
Collaboration philosophy: AI tools served as a highly capable technical assistant, while all design decisions, educational objectives, and project directions were defined and validated by the human.
This project is an educational exploration of function calling in LLMs, from fundamentals to modern implementations.