PHP Requires Chilkat v11.4.0+
Chilkat2 PHP
Streaming AI with Manual AI Tool Function Calling
See more AI Examples
Demonstrates how to get AI responses in streaming mode, including manual tool function calls.Chilkat PHP Downloads
<?php
use Chilkat2\Ai;
use Chilkat2\JsonObject;
use Chilkat2\StringBuilder;
// Create the following JSON to define tool functions available for the AI to use.
// Note: You'll use the following JSON format regardless of the AI provider, whether
// it be ChatGPT, Gemini, Claude, Grok, etc. Chilkat automatically converts to the required
// format needed for a given AI provider.
// In this example, the application is providing a single function the AI may choose to call.
// {
// "tools": [
// {
// "name": "get_horoscope",
// "description": "Get today's horoscope for an astrological sign.",
// "parameters": {
// "properties": {
// "sign": {
// "type": "string",
// "description": "An astrological sign like Taurus or Aquarius"
// }
// }
// }
// }
// ]
// }
$jsonTools = new JsonObject();
$toolIdx = 0;
$jsonTools->I = $toolIdx;
$jsonTools->UpdateString('tools[i].name', 'get_horoscope');
$jsonTools->UpdateString('tools[i].description', 'Get today\'s horoscope for an astrological sign.');
$jsonTools->UpdateString('tools[i].parameters.properties.sign.type', 'string');
$jsonTools->UpdateString('tools[i].parameters.properties.sign.description', 'An astrological sign like Taurus or Aquarius');
// More tools can be added as desired..
$jsonTools->EmitCompact = false;
echo $jsonTools->Emit(), "\n";
$ai = new Ai();
// Register the tools that will be made available to the AI.
$ai->RegisterManualTools($jsonTools);
// The provider can be "openai", "google", "claude", "grok", "mistral", "custom", etc.
$ai->Provider = 'openai';
// Use your provider's API key.
$ai->ApiKey = 'MY_API_KEY';
// Choose a model.
$ai->Model = 'gpt-5.6-luna';
// Tool function calling must always occur within a conversation.
$conversation_name = 'convo_astrology';
$sysMessage = 'You are a helpful astrologer';
$devMessage = 'Respond only with markdown.';
$ai->NewConvo($conversation_name, $sysMessage, $devMessage);
// Provide inputs
$ai->InputAddText('What is my horoscope? I am an Aquarius.');
// Get the response in streaming mode.
$ai->Streaming = true;
// In streaming mode, if we receive an AI event that is a request for tool use,
// we'll need to make the call to the JavaScript and then continue with a followup Ask,
// until the final response is received.
$sbEventName = new StringBuilder();
$sbDelta = new StringBuilder();
$sbFullResponse = new StringBuilder();
// When PollAi returns with an event, it's highly unlikely the
// call to NextAiEvent does not immediately return. Setting a max
// timeout is just a precaution..
$maxWaitMs = 5000;
$jsonFn = new JsonObject();
$finished = false;
$numAsks = 0;
// Set a max # of followup Asks to prevent any unexpected infinite looping.
while (!$finished && ($numAsks < 10)) {
// Send the request to the AI model.
try {
$ai->Ask('text');
} catch (\Chilkat2\Exception $e) {
echo $e->getMessage(), "\n";
exit;
}
$madeFunctionCalls = false;
$streamingDone = false;
while (!$streamingDone) {
$result = $ai->PollAi(false);
if ($result < 0) {
echo $ai->LastErrorText, "\n";
echo "Failed.\n";
exit;
}
if ($result > 0) {
// We have an event..
try {
$ai->NextAiEvent($maxWaitMs, $sbEventName, $sbDelta);
} catch (\Chilkat2\Exception $e) {
echo $e->getMessage(), "\n";
exit;
}
// Is this an event where the AI is requesting a function call?
if ($sbEventName->ContentsEqual('function_call', true)) {
$jsonFn->LoadSb($sbDelta);
// Note: Chilkat will convert responses from all AI providers to this format:
// {
// "function_call": [
// {
// "name": "get_horoscope",
// "call_id": "call_RYmeysYQFocFc7Z2ofkv61dW",
// "arguments": "{\"sign\":\"Aquarius\"}",
// "args": {
// "sign": "Aquarius"
// }
// }
// ]
// }
$numFnCalls = $jsonFn->SizeOfArray('function_call');
$fn_idx = 0;
while ($fn_idx < $numFnCalls) {
$jsonFn->I = $fn_idx;
$sbFnName = new StringBuilder();
$jsonFn->StringOfSb('function_call[i].name', $sbFnName);
$callId = $jsonFn->StringOf('function_call[i].call_id');
if ($sbFnName->ContentsEqual('get_horoscope', true)) {
// The get_horoscope function (as defined above) has one argument named "sign".
$zodiac_sign = $jsonFn->StringOf('function_call[i].args.sign');
echo 'zodiac_sign = ', $zodiac_sign, "\n";
// Insert application code here to call your app's get_horoscope function, passing the zodiac_sign to it..
// For this example, we'll pretend the app's get_horoscope function returned the following:
$applicationFnCallResult = 'Aquarius: Next Tuesday you will befriend a baby otter.';
// Provide the tool call result as an input for the followup Ask.
$ai->InputAddFnResult($callId, $applicationFnCallResult);
$madeFunctionCalls = true;
}
// Your application would add code to check for and handle each possible function call.
$fn_idx = $fn_idx + 1;
}
} else {
if (!$sbEventName->ContentsEqual('empty', true)) {
$sbFullResponse->AppendSb($sbDelta);
if ($sbEventName->ContentsEqual('null_terminator', true)) {
$streamingDone = true;
}
}
}
} else {
// No event arrived, so wait a short time rather than spin in a loop..
$ai->SleepMs(100);
}
}
if (!$madeFunctionCalls) {
$finished = true;
}
$numAsks = $numAsks + 1;
}
echo "Full Response:\n";
echo $sbFullResponse->GetAsString(), "\n";