Function Calling - a simple PHP example
Borys Zielonka • April 1, 2025
programming php ai llmWhat is function calling and how it works?
Function calling lets LLM's talk directly to your code instead of just generating text. When someone says "play song Happy", the AI figures out which function to call and gives you the right parameters to make it happen.
In other words it is giving the LLM a menu of functions it can use. When a user asks something, the AI picks the right function and tells our app exactly what parameters to use.
The flow is simple:
1. Define functions - play_song, stop_song
2. User asks something - "Play {song_name}"
3. LLM decides - "I need to call play_song function"
4. AI gives you structured data - {"song": "{song_name}"}
5. Our app executes the function - the music starts :)
Why Use Function Calling?
Good Stuff:
- AI gets real-time data instead of making stuff up
- No more parsing messy text responses
- Direct integration with your APIs
- Way more accurate results
Not-So-Good:
- Uses more tokens = costs more money
- More functions definitions = more tokens during each interference
- More complex error handling
PHP function calling example
Let's build a simple music control system.
Setup
mkdir ai-music-player
cd ai-music-player
composer init --name="demo/ai-music-player" --no-interaction
composer require guzzlehttp/guzzle vlucas/phpdotenv
Create .env:
OPENAI_API_KEY=your_key_here
Code
<?php
require_once 'vendor/autoload.php';
use GuzzleHttp\Client;
use Dotenv\Dotenv;
$dotenv = Dotenv::createImmutable(__DIR__);
$dotenv->load();
class MusicPlayer
{
private Client $client;
private string $apiKey;
private array $currentSong = [];
public function __construct()
{
$this->client = new Client();
$this->apiKey = $_ENV['OPENAI_API_KEY'];
}
public function playSong(string $songName = 'Never Gonna Give You Up'): array
{
$this->currentSong = [
'song_name' => $songName,
'status' => 'playing'
];
return $this->currentSong;
}
public function stopSong(): array
{
$this->currentSong['status'] = 'stopped';
return ['message' => 'Music stopped', 'status' => 'stopped'];
}
public function getFunctions(): array
{
return [
[
'type' => 'function',
'function' => [
'name' => 'play_song',
'description' => 'Play music by song_name',
'parameters' => [
'type' => 'object',
'properties' => [
'song_name' => [
'type' => 'string',
'description' => 'Specific song name'
]
]
]
]
],
[
'type' => 'function',
'function' => [
'name' => 'stop_song',
'description' => 'Stop currently playing music',
'parameters' => [
'type' => 'object',
'properties' => (object)[]
]
]
],
];
}
public function handleMessage(string $userMessage): string
{
echo "User: {$userMessage}\n";
// Ask OpenAI what to do
$response = $this->client->post('https://api.openai.com/v1/chat/completions', [
'headers' => [
'Authorization' => 'Bearer ' . $this->apiKey,
'Content-Type' => 'application/json',
],
'json' => [
'model' => 'gpt-4',
'messages' => [['role' => 'user', 'content' => $userMessage]],
'tools' => $this->getFunctions(),
'tool_choice' => 'auto'
]
]);
$data = json_decode($response->getBody()->getContents(), true);
$message = $data['choices'][0]['message'];
if (isset($message['tool_calls'])) {
$toolCall = $message['tool_calls'][0];
$functionName = $toolCall['function']['name'];
$args = json_decode($toolCall['function']['arguments'], true) ?? [];
echo "AI calling: {$functionName} with " . json_encode($args) . "\n";
$result = match ($functionName) {
'play_song' => $this->playSong($args['song_name'] ?? null),
'stop_song' => $this->stopSong(),
default => ['error' => 'Unknown function']
};
echo "Function result: " . json_encode($result) . "\n";
// Get final response from AI
$finalResponse = $this->client->post('https://api.openai.com/v1/chat/completions', [
'headers' => [
'Authorization' => 'Bearer ' . $this->apiKey,
'Content-Type' => 'application/json',
],
'json' => [
'model' => 'gpt-4',
'messages' => [
['role' => 'user', 'content' => $userMessage],
$message,
[
'role' => 'tool',
'tool_call_id' => $toolCall['id'],
'content' => json_encode($result)
]
]
]
]);
$finalData = json_decode($finalResponse->getBody()->getContents(), true);
return $finalData['choices'][0]['message']['content'];
}
return $message['content'] ?? 'Sorry, no idea what to do';
}
}
// Test it out
try {
$player = new MusicPlayer();
echo $player->handleMessage("Play this song about submarine by The Beatles") . "\n\n";
echo $player->handleMessage("Stop the music") . "\n\n";
} catch (Exception $e) {
echo "Oops: " . $e->getMessage() . "\n";
}
Run
php index.php
What's Happening Here
- User says: "Play this song about submarine by The Beatles"
- LLM: "This needs the play_song function"
- LLM responds:
play_song({"song": "Yellow Submarine"}) - Your code: Calls
playSong('Yellow Submarine')method - Function returns: Song data
Conclusion
Function calling turns your AI from a text generator into something that actually does stuff. Sure, it costs more tokens and adds complexity, but the results are way more useful. MCP servers use this concept a lot, don't they?