Building a Minimal Weather Agent with Tool Calling on the Aggregator
Learn how to build a minimal weather agent using tool calling on an LLM API aggregator. This tutorial covers declaring a JSON schema for a weather tool, letting the model emit tool calls, executing the tool locally, and feeding results back in a second request. It uses a conceptual approach with placeholder code and emphasizes key steps without specific model details or pricing.
Introduction to Tool Calling
Tool calling (also known as function calling) lets a language model request external actions, such as fetching weather data. The model doesn't execute tools itself; it emits a structured request that your code handles. This pattern is essential for building agents that interact with the real world.
In this tutorial, we'll create a minimal weather agent that:
- Declares a weather tool using a JSON schema.
- Sends a user query to the model.
- Receives a tool call request from the model.
- Executes the tool locally (e.g., calls a weather API).
- Sends the tool result back to the model for a final response.
We'll use a generic aggregator API that supports multiple models. The concepts apply regardless of the specific provider.
Prerequisites
- An API key from the aggregator (top up with USDC on Base, no KYC).
- Basic knowledge of HTTP requests and JSON.
- A local environment to run code (Python or Node.js examples below).
Step 1: Define the Weather Tool Schema
First, describe the weather tool in JSON Schema format. This tells the model what the tool does and what parameters it accepts.
{
"name": "get_weather",
"description": "Get current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and country, e.g., London, UK"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["location"]
}
}
Keep the schema simple. The model uses this to decide when and how to call the tool.
Step 2: Send the Initial Request
Include the tool schema in your API request. The model will either answer directly or emit a tool call.
import requests
API_KEY = "your_api_key"
URL = "https://api.aggregator.com/v1/chat/completions"
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
payload = {
"model": "claude-3-haiku", # Replace with any supported model
"messages": [
{"role": "user", "content": "What's the weather in Paris?"}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["location"]
}
}
}
],
"tool_choice": "auto"
}
response = requests.post(URL, headers=headers, json=payload)
print(response.json())
The response may contain a tool_calls array in the assistant message.
Step 3: Parse the Tool Call
The model returns a message with tool_calls. Extract the function name and arguments.
response_data = response.json()
message = response_data["choices"][0]["message"]
if "tool_calls" in message:
tool_call = message["tool_calls"][0]
function_name = tool_call["function"]["name"]
arguments = json.loads(tool_call["function"]["arguments"])
print(f"Model wants to call {function_name} with {arguments}")
else:
print("No tool call. Model responded directly:", message["content"])
Step 4: Execute the Tool Locally
Implement the weather tool. For demonstration, we'll mock a weather API call.
def get_weather(location: str, unit: str = "celsius") -> dict:
# In production, call a real weather API here.
# This is a mock response.
return {
"location": location,
"temperature": 22,
"unit": unit,
"condition": "Partly cloudy"
}
# Execute based on the tool call
if function_name == "get_weather":
result = get_weather(**arguments)
print("Tool result:", result)
Step 5: Send the Tool Result Back
Append the assistant's tool call message and a new message with the tool result to the conversation. Then send a second request.
messages = [
{"role": "user", "content": "What's the weather in Paris?"},
message, # assistant message with tool_calls
{
"role": "tool",
"tool_call_id": tool_call["id"],
"content": json.dumps(result)
}
]
payload["messages"] = messages
response2 = requests.post(URL, headers=headers, json=payload)
final_message = response2.json()["choices"][0]["message"]["content"]
print("Final answer:", final_message)
The model now sees the tool output and generates a natural language response.
Step 6: Putting It All Together
Here's the complete flow in a single script:
import requests, json
API_KEY = "your_api_key"
URL = "https://api.aggregator.com/v1/chat/completions"
headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
def get_weather(location, unit="celsius"):
return {"location": location, "temperature": 22, "unit": unit, "condition": "Partly cloudy"}
# Initial request
payload = {
"model": "claude-3-haiku",
"messages": [{"role": "user", "content": "What's the weather in Paris?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["location"]
}
}
}],
"tool_choice": "auto"
}
resp = requests.post(URL, headers=headers, json=payload).json()
msg = resp["choices"][0]["message"]
if "tool_calls" in msg:
tool_call = msg["tool_calls"][0]
args = json.loads(tool_call["function"]["arguments"])
result = get_weather(**args)
payload["messages"].append(msg)
payload["messages"].append({
"role": "tool",
"tool_call_id": tool_call["id"],
"content": json.dumps(result)
})
resp2 = requests.post(URL, headers=headers, json=payload).json()
print(resp2["choices"][0]["message"]["content"])
else:
print(msg["content"])
Best Practices
- Validate arguments: Always check that the model's arguments match your schema before execution.
- Handle errors: If the tool fails, return an error message in the tool result so the model can respond appropriately.
- Iterate: The model can request multiple tool calls in sequence. Loop until no more tool calls are emitted.
- Keep schemas simple: Overly complex schemas confuse models.
Using the Aggregator
The aggregator provides a unified API for many models. You can switch between Claude, GPT, DeepSeek, Qwen, GLM, Kimi, and others by changing the model field. Billing is straightforward: you pay official price × 1.3, and key contributors are credited official price × 1.1 (premium × 1.2) in USDC. No KYC is required; top up with USDC on Base.
Conclusion
You've built a minimal weather agent using tool calling. The pattern extends to any external API: define the schema, let the model request, execute locally, and return results. With the aggregator, you can experiment across multiple models using a single API key and transparent pricing.