Calling Real APIs from a PHP Agent - Live Weather in Four Functions
An agent that only talks is a toy. The moment you give it a tool that hits a real external API, it becomes useful - it can answer with current, real data instead of whatever it memorized in training.
This example gives the agent a FunctionTool that calls wttr.in (a free, no-key weather API) and returns live conditions.
The tool is just a PHP function that does HTTP
Start with the tool's shape: a name, a description the model reads to decide when to use it, and a single required city argument.
use NanoAgent\Agent;
use NanoAgent\Tools\FunctionTool;
$weatherTool = new FunctionTool(
name: 'fetch_live_weather',
description: 'Retrieves current weather for any city using a real weather API.',
parameters: [
'type' => 'object',
'properties' => [
'city' => ['type' => 'string', 'description' => 'The city name']
],
'required' => ['city']
],
The callable is where the real HTTP call happens - a plain cURL request to wttr.in's JSON endpoint, with a timeout and a user agent so the request doesn't hang or get rejected outright.
callable: function (array $args) {
$url = "https://wttr.in/" . urlencode($args['city']) . "?format=j1";
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_USERAGENT => 'NanoAgent/1.0',
]);
$response = curl_exec($ch);
if (curl_errno($ch)) {
$err = curl_error($ch);
curl_close($ch);
return "Network error while reaching weather API: $err";
}
curl_close($ch);
Then decode the response and hand back a plain sentence - not raw JSON - so the model has something natural to fold into its answer.
$data = json_decode($response, true);
$current = $data['current_condition'][0] ?? null;
if (!$current) return "Could not find weather data for '{$args['city']}'.";
return "Current weather in {$args['city']}: "
. $current['weatherDesc'][0]['value']
. ", {$current['temp_C']}°C.";
}
);
The agent decides *when* to call it
$city = $_GET['city'] ?? 'London';
$agent = new Agent(
llm: $llmConfig,
systemPrompt: "You are a meteorological assistant. You can retrieve real-time "
. "weather information for any city.",
tools: [$weatherTool]
);
$response = $agent->chat("What is the real-time weather in $city right now?");
echo $response;
The user never mentions fetchliveweather. The model reads the tool's description, decides it's the right tool, fills in city, and the agent runs the function and folds the result into the answer. You wrote the HTTP; the model wrote the decision to use it.
Make the tool robust - the details that matter
A real-world tool lives or dies on error handling. Notice the patterns above:
CURLOPT_TIMEOUT) so a slow API can't hang your request.$data['current_condition'][0].Generalize it
Swap wttr.in for anything that speaks HTTP:
Rule of thumb: the tool's description is what the model reads, so write it like you're telling a smart intern what the tool does and when to use it. The parameters schema is the contract; the callable is your normal PHP (cURL, PDO, anything).
Ready to ship an agent?
composer require hammrouni/nanoagent
MIT, zero dependencies. Explore the full series - each article teaches one pattern.
Get each new article by email - one per publish day + the 12-part cheatsheet when it drops.