aisdk / core
Framework-agnostic PHP AI SDK.
Fund package maintenance!
Requires
- php: ^8.3
- guzzlehttp/guzzle: ^7.8
- php-http/discovery: ^1.19
- psr/container: ^2.0
- psr/http-client: ^1.0
- psr/http-factory: ^1.1
- psr/http-message: ^2.0
- psr/log: ^3.0
Requires (Dev)
- laravel/pint: ^1.18
- nyholm/psr7: ^1.8
- pestphp/pest: ^3.0 || ^4.0
- phpstan/phpstan: ^2.0
- rector/rector: ^2.0
This package is auto-updated.
Last update: 2026-07-17 02:24:12 UTC
README
Framework-agnostic PHP AI SDK core: contracts, fluent API, value objects, streaming, tools, structured output, and PSR integration.
Installation
composer require aisdk/core
Basic Usage
use AiSdk\Generate; use AiSdk\OpenAI; $result = Generate::text() ->model(OpenAI::model('gpt-4o')) ->instructions('Write short, clear answers.') ->prompt('Explain closures in PHP.') ->run(); echo $result->text;
Default models allow terse call sites:
Generate::model(OpenAI::model('gpt-4o')); $result = Generate::text('Explain closures in PHP.')->run();
For long-running workers, concurrent applications, or dependency-injected code, use an instance runtime so configuration is not stored in static process state:
use AiSdk\OpenAI\OpenAIOptions; use AiSdk\OpenAI\OpenAIProvider; use AiSdk\Support\SdkFactory; $runtime = (new SdkFactory) ->withConnectTimeout(10) ->withTimeout(120) ->make(); $provider = new OpenAIProvider(OpenAIOptions::fromArray([ 'apiKey' => $_ENV['OPENAI_API_KEY'], 'sdk' => $runtime, ])); $sdk = $runtime->withDefaultTextModel($provider->model('gpt-4o')); $result = $sdk->text('Explain closures in PHP.')->run();
The static Generate and provider entry points remain convenient for scripts and request-scoped applications; the instance API is the safer choice when one PHP process serves multiple independent workloads.
Structured Output
use AiSdk\Generate; use AiSdk\OpenAI; use AiSdk\Schema; $result = Generate::text() ->model(OpenAI::model('gpt-4o')) ->prompt('Extract the city and country from: Lahore, Pakistan.') ->output(Schema::object( name: 'address', description: 'The city and country extracted from the prompt.', properties: [ Schema::string(name: 'city')->required(), Schema::string(name: 'country')->required(), ], )) ->run(); echo $result->output['city']; // "Lahore"
Tools
use AiSdk\Generate; use AiSdk\OpenAI; use AiSdk\Schema; use AiSdk\Tool; $weather = Tool::make('weather', 'Get current weather') ->input(Schema::string(name: 'city')->required()) ->run(fn (string $city): string => "Sunny in {$city}"); $result = Generate::text() ->model(OpenAI::model('gpt-4o')) ->prompt('What is the weather in Lahore?') ->tool($weather) ->run();
Class-based tools:
final class WeatherTool extends Tool { public function __construct() { $this->as('weather') ->for('Get current weather') ->input(Schema::string(name: 'city')->required()); } public function __invoke(string $city): string { return "Sunny in {$city}"; } }
Streaming
use AiSdk\Generate; use AiSdk\OpenAI; $stream = Generate::text('Tell me a story.') ->model(OpenAI::model('gpt-4o')) ->stream(); foreach ($stream->chunks() as $chunk) { echo $chunk; } $result = $stream->run();
Stream hooks:
$stream->onChunk(fn (string $text) => log($text)) ->onFinish(fn (TextResult $result) => log($result->usage)) ->onError(fn (\Throwable $e) => log($e));
Image Generation
use AiSdk\Generate; use AiSdk\OpenAI; $result = Generate::image() ->model(OpenAI::model('gpt-image-1')) ->prompt('A clean app icon for a PHP AI SDK') ->size('1024x1024') ->run(); $result->output->save(__DIR__.'/icon.png');
Generate multiple images or use portable image options:
$result = Generate::image('Minimal line art of Lahore Fort') ->model(OpenAI::model('gpt-image-1')) ->count(2) ->aspectRatio('1:1') ->run(); foreach ($result->images as $index => $image) { $image->save(__DIR__."/image-{$index}.png"); }
Speech Generation
use AiSdk\Generate; use AiSdk\OpenAI; $result = Generate::speech() ->model(OpenAI::model('gpt-4o-mini-tts')) ->input('Welcome to the PHP AI SDK.') ->voice('coral') ->format('mp3') ->run(); $result->output->save(__DIR__.'/welcome.mp3');
The portable speech surface supports input(), voice(), format(), and provider-specific options:
$result = Generate::speech('Read this in a warm tone.') ->model(OpenAI::model('gpt-4o-mini-tts')) ->providerOptions('openai', [ 'instructions' => 'Speak clearly and warmly.', ]) ->run();
Embeddings
Generate one vector or a batch of vectors through the same text-only API:
use AiSdk\Generate; use AiSdk\OpenAI; $result = Generate::embedding([ 'First document', 'Second document', ]) ->model(OpenAI::model('text-embedding-3-small')) ->dimensions(512) ->run(); $firstVector = $result->output->vector; $allVectors = $result->embeddings;
dimensions() is the only portable embedding option. Provider-specific fields such as retrieval task type or truncation can be sent with providerOptions() using the provider package's documented field names.
Transcription
use AiSdk\Content; use AiSdk\Generate; use AiSdk\OpenAI; $result = Generate::transcription(Content::audio(__DIR__.'/meeting.mp3')) ->model(OpenAI::model('gpt-4o-transcribe')) ->run(); echo $result->output->text;
The portable transcription surface accepts typed audio content. Language hints, diarization, timestamp detail, and other provider-specific controls remain available through providerOptions().
Video Generation
Video providers expose asynchronous jobs through one portable API. run() waits for completion; job() starts the operation and returns its job descriptor.
$result = Generate::video('A cinematic product reveal') ->model($provider->model('video-model-id')) ->aspectRatio('16:9') ->resolution('1280x720') ->duration(8) ->run(timeout: 600); $job = Generate::videoJob('A glass PHP logo rotating in space') ->model($provider->model('video-model-id')) ->job();
Live Voice, Transcription, and Translation
AiSdk\Live is part of core. Provider packages prepare their endpoint,
authentication, messages, and normalized events; the application supplies a
transport that implements core's two small transport contracts.
use AiSdk\Live; use AiSdk\Live\AudioDelta; use AiSdk\Live\TranscriptCompleted; use AiSdk\OpenAI; $session = Live::voice() ->model(OpenAI::model('gpt-realtime-2.1')) ->instructions('Answer briefly and clearly.') ->voice('marin') ->inputAudioFormat('pcm16') ->outputAudioFormat('pcm16') ->connect($applicationTransport); $session->sendAudio($pcmBytes); $session->commitAudio(); foreach ($session->events() as $event) { if ($event instanceof AudioDelta) { $speaker->write($event->bytes); } if ($event instanceof TranscriptCompleted) { echo $event->text; } }
The session API is transport-independent:
$session->sendAudio($bytes); $session->sendText('Hello'); $session->commitAudio(); $session->clearAudio(); $session->requestResponse(); $session->cancelResponse(); $session->sendToolResult($callId, $result); $events = $session->events(); $session->close();
Use separate builders so an operation only exposes portable fields that apply to it:
$captions = Live::transcribe() ->model($provider->model('live-transcription-model')) ->language('en') ->audioFormat('pcm16') ->connect($applicationTransport); $translator = Live::translate() ->model($provider->model('live-translation-model')) ->from('en') ->to('es') ->inputAudioFormat('pcm16') ->outputAudioFormat('pcm16') ->connect($applicationTransport);
providerOptions('provider-name', [...]) remains the escape hatch for
provider-only session fields. Proxy, TLS, timeout, buffer, and frame-limit
configuration belongs to the transport instead.
Ready-made transports
The optional aisdk/transport package supplies WebSocket and bidirectional
HTTP/2 implementations:
composer require aisdk/transport
use AiSdk\Transport; $session = Live::voice() ->model(OpenAI::model('gpt-realtime-2.1')) ->connect(Transport::auto());
Transport::auto() chooses from the endpoint prepared by the provider. Use
Transport::webSocket() or Transport::http2() when the application should
explicitly restrict the network protocol.
Core without aisdk/transport
Core has no Amp or WebSocket dependency. Install any networking library and
implement AiSdk\Live\Contracts\TransportInterface plus
TransportConnectionInterface. A complete Amp implementation is included in
examples/AppWebSocketTransport.php:
composer require amphp/websocket-client:^2.0
use App\Ai\AppWebSocketTransport; $session = Live::voice() ->model(OpenAI::model('gpt-realtime-2.1')) ->connect(new AppWebSocketTransport());
Provider adapters only exchange TransportFrame::text() and
TransportFrame::binary() with the transport. A custom transport must not
interpret provider event names.
events() waits for incoming network data. For continuous, full-duplex audio,
run the microphone sender and event consumer concurrently using the async
runtime chosen by your application. Core itself does not expose Amp, Revolt,
or another event-loop type; the ready-made transport README includes an Amp
example.
Tools and normalized events
Voice tools use the same core Tool objects as text generation. A registered
tool with a handler is executed automatically. The ToolCallEvent is still
emitted for observability, followed by an automatic ToolResultEvent. Unknown
or handler-less tools remain manual:
foreach ($session->events() as $event) { if ($event instanceof \AiSdk\Live\ToolCallEvent && $event->name === 'approval') { $session->sendToolResult($event->callId, ['approved' => true]); } }
Portable events cover audio and text deltas, append-only transcript deltas,
replaceable transcript updates, completed transcripts, speech boundaries,
interruptions, tool calls and results, usage, response completion, errors, and
closure. Unknown provider messages are preserved as ProviderEvent rather
than discarded.
WebRTC and provider-hosted calls
WebRTC and SIP are connection topologies, not PHP media transports. A browser
uses its native RTCPeerConnection; PHP creates a short-lived client secret or
exchanges an SDP offer through the provider adapter:
$secret = Live::voice() ->model(OpenAI::model('gpt-realtime-2.1')) ->voice('marin') ->clientSecret(); $answer = Live::voice() ->model(OpenAI::model('gpt-realtime-2.1')) ->webRtc($offerSdp); return ['sdp' => $answer->sdp, 'call_id' => $answer->callId];
For an incoming provider-hosted call, first verify the webhook with the provider package, then control the call through core:
$call = Live::voice() ->model(OpenAI::model('gpt-realtime-2.1')) ->instructions('You are the support desk.') ->call($verifiedWebhook['data']['call_id']); $call->accept(); $controlSession = $call->connect($applicationTransport); // Read normalized events and answer tool calls over the control connection. foreach ($controlSession->events() as $event) { // ... } $call->hangup();
That control connection is sometimes called a sideband connection: the provider continues carrying WebRTC or SIP media, while the backend attaches a WebSocket to the same call ID to update the session, observe events, and handle tools. Providers expose lifecycle methods only where their official protocol supports them.
Model IDs and Capabilities
Model IDs are opaque provider values. Packages do not ship model inventories, so new, private, routed, deployed, and locally installed models can be used without waiting for an SDK release:
$result = Generate::text('Hello') ->model(OpenAI::model('your-model-id')) ->run();
Provider::model() is the only public provider model selector. The operation resolves that opaque reference to its internal text, image, speech, transcription, embedding, video, or Live adapter when the request executes.
Adapter capabilities are internal implementation details. Before sending a request, the SDK verifies that the selected provider adapter can serialize the requested features. The provider remains the authority on whether the exact model accepts those features. Models never need to be registered with the SDK.
Providers whose available models depend on the current account, region, or local runtime may implement AvailableModelsProviderInterface and, where supported, ModelInspectionProviderInterface. Provider packages should query an authoritative runtime endpoint instead of presenting a bundled list as live availability. For example, Ollama exposes Ollama::availableModels() for its local registry and Ollama::inspectModel() for informational model details.
Supported Capabilities
- Text generation (
Generate::text()) - Streaming text generation (
->stream()) - Image generation (
Generate::image()) - Speech generation (
Generate::speech()) - Text embeddings (
Generate::embedding()) - Audio transcription (
Generate::transcription()) - Video generation and asynchronous jobs (
Generate::video(),Generate::videoJob()) - Live voice, transcription, and translation (
Live::voice(),Live::transcribe(),Live::translate()) - Tool calling (
->tool(...)) - Structured output (
->output(...)) - Provider options passthrough (
->providerOptions(...)) - Normalized provider errors
Testing
composer test
Core ships with fakes and assertions for deterministic testing:
use AiSdk\Testing\Fakes\FakeTextModel; $fake = FakeTextModel::text('Hello!'); $result = Generate::text('Hi') ->model($fake) ->run(); expect($result->text)->toBe('Hello!');
Embedding tests can use AiSdk\Testing\Fakes\FakeEmbeddingModel in the same way.
Transcription tests can use AiSdk\Testing\Fakes\FakeTranscriptionModel.
Documentation
Community
- Contributing
- Support
- For private security reports, email security@phpaisdk.com.