Skip to content

Tool Calling

Several local models support OpenAI-compatible tool calling, allowing the model to invoke functions you define.

Loading model list …

The model can’t check the weather on its own — but with a tool, it can call a real API. This example uses Open-Meteo (free, no API key needed):

import json
import requests
from openai import OpenAI
client = OpenAI(
base_url="https://maki.uni-mannheim.de/v1",
api_key="your-api-key",
)
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name, e.g. 'Mannheim'",
}
},
"required": ["city"],
},
},
}
]
def get_weather(city: str) -> str:
"""Call the Open-Meteo API to get current weather."""
geo = requests.get(
"https://geocoding-api.open-meteo.com/v1/search",
params={"name": city, "count": 1},
).json()
loc = geo["results"][0]
weather = requests.get(
"https://api.open-meteo.com/v1/forecast",
params={
"latitude": loc["latitude"],
"longitude": loc["longitude"],
"current": "temperature_2m,wind_speed_10m",
},
).json()["current"]
return json.dumps({
"city": loc["name"],
"temperature_c": weather["temperature_2m"],
"wind_speed_kmh": weather["wind_speed_10m"],
})
messages = [
{"role": "user", "content": "What's the weather like in Mannheim?"}
]
# Step 1: The model decides it needs the weather tool
response = client.chat.completions.create(
model="gemma4-26b",
messages=messages,
tools=tools,
)
message = response.choices[0].message
# Step 2: Execute the tool call and send the result back
if message.tool_calls:
call = message.tool_calls[0]
args = json.loads(call.function.arguments)
# Call the real API
result = get_weather(args["city"])
# Send tool result back to the model
messages.append(message)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": result,
})
# Step 3: The model uses the result to answer
final = client.chat.completions.create(
model="gemma4-26b",
messages=messages,
tools=tools,
)
print(final.choices[0].message.content)
# "It's currently 18.3 °C in Mannheim with a wind speed of 12.5 km/h."

PydanticAI handles the tool-calling loop for you — you just decorate a function and the framework takes care of the rest:

import json
import requests
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.openai import OpenAIProvider
model = OpenAIChatModel(
"gemma4-26b",
provider=OpenAIProvider(
base_url="https://maki.uni-mannheim.de/v1",
api_key="your-api-key",
),
)
agent = Agent(model)
@agent.tool_plain
def get_weather(city: str) -> str:
"""Get the current weather for a city.
Args:
city: City name, e.g. 'Mannheim'.
"""
geo = requests.get(
"https://geocoding-api.open-meteo.com/v1/search",
params={"name": city, "count": 1},
).json()
loc = geo["results"][0]
weather = requests.get(
"https://api.open-meteo.com/v1/forecast",
params={
"latitude": loc["latitude"],
"longitude": loc["longitude"],
"current": "temperature_2m,wind_speed_10m",
},
).json()["current"]
return json.dumps({
"city": loc["name"],
"temperature_c": weather["temperature_2m"],
"wind_speed_kmh": weather["wind_speed_10m"],
})
result = agent.run_sync("What's the weather like in Mannheim?")
print(result.output)
# "It's currently 18.3 °C in Mannheim with a wind speed of 12.5 km/h."