Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

176 Commits

Repository files navigation

OpenAI APIs for XP

Build status on GitHubXP Framework ModuleBSD LicenceRequires PHP 7.4+Supports PHP 8.0+Latest Stable Version

This library implements OpenAI APIs with a low-level abstraction approach, supporting their REST and realtime APIs, request and response streaming, function calling and TikToken encoding.

Quick start

Using the REST API, see https://platform.openai.com/docs/api-reference/making-requests

usecom\openai\rest\OpenAIEndpoint;
useutil\cmd\Console;
$ai= newOpenAIEndpoint('https://'.getenv('OPENAI_API_KEY').'@api.openai.com/v1');
Console::writeLine($ai->api('/responses')->invoke([
'model' => 'gpt-5.2',
'input' => $prompt,
]));

Streaming

The REST API can use server-sent events to stream responses, see https://platform.openai.com/docs/api-reference/streaming

usecom\openai\rest\OpenAIEndpoint;
useutil\cmd\Console;
$ai= newOpenAIEndpoint('https://'.getenv('OPENAI_API_KEY').'@api.openai.com/v1');
$events= $ai->api('/responses')->stream([
'model' => 'gpt-5.2',
'input' => $prompt,
]);
foreach ($eventsas$type => $value) {
Console::write('<', $type, '> ', $value);
}
Console::writeLine();

To access the result object, check for the response.completed event type and use its value. It contains the outuputs as well as model, filter results and usage information.

TikToken

Encodes text to tokens. Download the vocabularies cl100k_base (used for GPT-3.5 and GPT-4.0) and o200k_base (used for Omni and O1) first!

usecom\openai\{Encoding, TikTokenFilesIn};
$source= newTikTokenFilesIn('.');
// By name => [9906, 4435, 0]$tokens= Encoding::named('cl100k_base')->load($source)->encode('Hello World!');
// By model => [13225, 5922, 0]$tokens= Encoding::for('omni')->load($source)->encode('Hello World!');

Instead of encode(), you can use count() to count the number of tokens.

Embeddings

To create an embedding for a given text, use https://platform.openai.com/docs/guides/embeddings/what-are-embeddings

usecom\openai\rest\OpenAIEndpoint;
useutil\cmd\Console;
$ai= newOpenAIEndpoint('https://'.getenv('OPENAI_API_KEY').'@api.openai.com/v1');
Console::writeLine($ai->api('/embeddings')->invoke([
'input' => $text,
'model' => 'text-embedding-3-small'],
));

Text to speech

To stream generate audio, use the API's transmit() method, which sends the given payload and returns the response. See https://platform.openai.com/docs/guides/text-to-speech/overview

usecom\openai\rest\OpenAIEndpoint;
useutil\cmd\Console;
$ai= newOpenAIEndpoint('https://'.getenv('OPENAI_API_KEY').'@api.openai.com/v1');
$payload= [
'input' => $input,
'voice' => 'alloy', // or: echo, fable, onyx, nova, shimmer'model' => 'tts-1',
];
$stream= $ai->api('/audio/speech')->transmit($payload)->stream();
while ($stream->available()) {
Console::write($stream->read());
}

Speech to text

To convert audio into text, upload files via the API's open() method, which returns an Upload instance. See https://platform.openai.com/docs/guides/speech-to-text/overview

usecom\openai\rest\OpenAIEndpoint;
useio\File;
useutil\cmd\Console;
$ai= newOpenAIEndpoint('https://'.getenv('OPENAI_API_KEY').'@api.openai.com/v1');
$file= newFile($argv[1]);
$response= $ai->api('/audio/transcriptions')
->open(['model' => 'whisper-1'])
->transfer('file', $file->in(), $file->filename)
->finish()
;
Console::writeLine($response->value());

You can also stream uploads from InputStreams as follows:

// ...setup code from above...$upload= $ai->api('/audio/transcriptions')->open(['model' => 'whisper-1']);
$stream= $upload->stream('file', 'audio.mp3');
while ($in->available()) {
$stream->write($in->read());
}
$response= $upload->finish();
Console::writeLine($response->value());

Tracing the calls

REST API calls can be traced with the logging library:

usecom\openai\rest\OpenAIEndpoint;
useutil\log\Logging;
$ai= newOpenAIEndpoint('https://'.getenv('OPENAI_API_KEY').'@api.openai.com/v1');
$ai->setTrace(Logging::all()->toConsole());
// ...perform API calls...

Tool calls

There are two types of tools: Built-ins like file_search and code_interpreter (available in the assistants API) as well as custom functions, see https://platform.openai.com/docs/guides/function-calling

Defining functions

Custom functions map to instance methods in a class:

usecom\openai\tools\Param;
usewebservices\rest\Endpoint;
class Weather {
private$endpoint;
publicfunction__construct(string$base= 'https://wttr.in/') {
$this->endpoint= newEndpoint($base);
}
publicfunctionin(#[Param] string$city): string {
return$this->endpoint->resource('/{0}?0mT', [$city])->get()->content(); }
}

The Param annnotation may define a description and a JSON schema type:

  • #[Param('The name of the city')] $name
  • #[Param(type: ['type' => 'string', 'enum' => ['C', 'F']])] $unit

Passing custom functions

Custom functions are registered in a Functions instance and passed via tools inside the payload.

usecom\openai\rest\OpenAIEndpoint;
usecom\openai\tools\{Tools, Functions};
$functions= (newFunctions())->register('weather', newWeather());
$ai= newOpenAIEndpoint('https://'.getenv('OPENAI_API_KEY').'@api.openai.com/v1');
$payload= [
'model' => 'gpt-5.2',
'tools' => newTools($functions),
'input' => [['type' => 'message', 'role' => 'user', 'content' => $content]],
];

Invoking custom functions

If tool calls are requested by the LLM, invoke them and return to next completion cycle. See https://platform.openai.com/docs/guides/function-calling/configuring-parallel-function-calling

useutil\cmd\Console;
// ...setup code from above...$calls= $functions->calls()->catching(fn($t) => $t->printStackTrace());
next: $result= $ai->api('/responses')->invoke($payload));
// If function calls are requested, invoke them and return to next response cycle$invokations= false;
foreach ($result['output'] as$output) {
if ('function_call' !== $output['type']) continue;
$invokations= true;
$return= $calls->call($call['name'], $call['arguments']);
$payload['input'][]= $call;
$payload['input'][]= [
'type' => 'function_call_output',
'call_id' => $call['call_id'],
'output' => $return,
];
}
if ($invokations) goto next;
// Print out final result
Console::writeLine($result);

Passing context

Functions can be passed a context as follows by annotating parameters with the Context annotation:

usecom\mongodb\{Collection, Document, ObjectId};
usecom\openai\tools\{Context, Param};
// Declarationclass Memory {
publicfunction__construct(privateCollection$facts) { }
publicfunctionstore(#[Context] Document$user, #[Param] string$fact): ObjectId {
return$this->facts->insert(newDocument(['owner' => $user->id(), 'fact' => $fact]))->id();
}
}
// ...shortened for brevity...$context= ['user' => $user];
$return= $calls->call($call['name'], $call['arguments'], $context);

Azure OpenAI

These endpoints differ slightly in how they are invoked, which is handled by the AzureAI implementation. See https://learn.microsoft.com/en-us/azure/ai-services/openai/overview

usecom\openai\rest\AzureAIEndpoint;
useutil\cmd\Console;
// Using V1 API$ai= newAzureAIEndpoint('https://'.getenv('AZUREAI_API_KEY').'@example.openai.azure.com/openai/v1');
// Using API version$ai= newAzureAIEndpoint(
'https://'.getenv('AZUREAI_API_KEY').'@example.openai.azure.com/openai/deployments/gpt-5.2',
'2025-04-01-preview'
);
Console::writeLine($ai->api('/responses')->invoke([
'model' => 'gpt-5.2',
'input' => $prompt,
]));

Distributing requests

The Distributed endpoint allows to distribute requests over multiple endpoints. The ByRemainingRequests class uses the x-ratelimit-remaining-requests header to determine the target. See https://platform.openai.com/docs/guides/rate-limits

usecom\openai\rest\{AzureAIEndpoint, Distributed, ByRemainingRequests};
useutil\cmd\Console;
$endpoints= [
newAzureAIEndpoint('https://...@r1.openai.azure.com/openai/v1'),
newAzureAIEndpoint('https://...@r2.openai.azure.com/openai/v1'),
];
$ai= newDistributed($endpoints, newByRemainingRequests());
Console::writeLine($ai->api('/responses')->invoke([
'model' => 'gpt-5.2',
'input' => $prompt,
]));
foreach ($endpointsas$i => $endpoint) {
Console::writeLine('Endpoint #', $i, ': ', $endpoint->rateLimit());
}

For more complex load balancing, have a look at this blog article using Azure API management

Realtime API

The realtime API allows streaming audio and/or text to and from language models, see https://platform.openai.com/docs/guides/realtime

usecom\openai\realtime\RealtimeApi;
useutil\cmd\Console;
$api= newRealtimeApi('wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview');
$session= $api->connect([
'Authorization' => 'Bearer '.getenv('OPENAI_API_KEY'),
'OpenAI-Beta' => 'realtime=v1',
];
Console::writeLine($session);
// Send prompt$api->transmit([
'type' => 'conversation.item.create',
'item' => [
'type' => 'message',
'role' => 'user',
'content' => [['type' => 'input_text', 'text' => $message]],
]
]);
// Receive response(s)$api->send(['type' => 'response.create', 'response' => ['modalities' => ['text']]]);
do {
$event= $api->receive();
Console::writeLine($event);
} while ('response.done' !== $event['type'] && 'error' !== $event['type']);
$api->close();

For Azure AI, the setup code is slightly different:

usecom\openai\realtime\RealtimeApi;
useutil\cmd\Console;
$api= newRealtimeApi('wss://example.openai.azure.com/openai/realtime', [
'api-version' => '2024-10-01-preview',
'deployment' => 'gpt-4o-realtime-preview',
]);
$session= $api->connect(['api-key' => getenv('AZUREAI_API_KEY')]);

Completions API

To use the legacy (but industry standard) chat completions API, see https://platform.openai.com/docs/quickstart?api-mode=chat:

usecom\openai\rest\OpenAIEndpoint;
useutil\cmd\Console;
$ai= newOpenAIEndpoint('https://'.getenv('OPENAI_API_KEY').'@api.openai.com/v1');
$flow= $ai->api('/chat/completions')->flow([
'model' => 'gpt-5.2',
'messages' => [['role' => 'user', 'content' => $prompt]],
]);
foreach ($flow->deltas() as$type => $delta) {
Console::writeLine('<', $type, '> ', $delta);
}
Console::writeLine();

The result object is computed from the streamed deltas and can be retrieved by accessing $flow->result().

See also