This guide walks you through building a minimal PHP AI SDK provider that usesDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/phpaisdk/openai-compatible/llms.txt
Use this file to discover all available pages before exploring further.
aisdk/openai-compatible as its wire-format layer. By the end you will have a working model class that can send a chat request, parse a non-streaming response, consume an SSE stream, and generate images — all by delegating the wire format to this package and focusing your provider code only on authentication, endpoint paths, and model catalogs.
Chat Completions
Add both
aisdk/core (portable contracts) and aisdk/openai-compatible (wire adapters) to your provider’s dependencies:Use
ChatRequestBuilder::build() to convert a portable TextModelRequest into the JSON body for /chat/completions. Pass the model ID, your provider name, the request object, and whether you want streaming:use AiSdk\OpenAICompatible\ChatRequestBuilder;
use AiSdk\Requests\TextModelRequest;
use AiSdk\Message;
$request = new TextModelRequest(
messages: [Message::user('What is the capital of France?')],
system: 'You are a helpful assistant.',
);
// stream: false for a regular (non-streaming) completion
$body = ChatRequestBuilder::build(
modelId: 'gpt-4o',
providerName: 'openai',
request: $request,
stream: false,
);
// $body is now a fully-formed array ready to be JSON-encoded and POSTed.
// e.g. ['model' => 'gpt-4o', 'messages' => [...], 'temperature' => ..., 'stream' => false]
ChatRequestBuilder::build() handles system messages, multimodal content, tools, tool choice, structured output (response_format), reasoning effort, stream_options, and provider raw overrides — all from the single portable TextModelRequest object.Your provider class (extending
BaseProvider from aisdk/core) has access to a runner that executes the HTTP call. Pass the built body along with your provider’s base URL and authentication headers:use AiSdk\OpenAICompatible\ChatRequestBuilder;
// Inside your provider's text model class:
$body = ChatRequestBuilder::build($modelId, $this->providerName(), $request, stream: false);
$payload = $this->runner()->postJson(
url: 'https://api.openai.com/v1/chat/completions',
body: $body,
headers: ['Authorization' => 'Bearer ' . $this->apiKey],
providerName: $this->providerName(),
);
The runner returns the decoded JSON payload as a PHP array, which you pass directly to the parser in the next step.
ChatResponseParser::parse() converts the raw provider payload into a typed TextModelResponse. Access the generated text, any tool calls, usage statistics, and provider metadata through its methods:use AiSdk\OpenAICompatible\ChatResponseParser;
$response = ChatResponseParser::parse($payload, providerName: 'openai');
echo $response->text();
// "The capital of France is Paris."
echo $response->usage->inputTokens; // prompt tokens
echo $response->usage->outputTokens; // completion tokens
// Provider-specific metadata (id, model, system_fingerprint, etc.)
$meta = $response->providerMetadata['openai'];
echo $meta['id']; // "chatcmpl_..."
echo $meta['model']; // "gpt-4o"
For streaming responses, set
stream: true when building the request body and use ChatStreamParser::parse() to process the raw SSE events. Feed each yielded StreamPart into a StreamState instance to accumulate the full response:use AiSdk\OpenAICompatible\ChatRequestBuilder;
use AiSdk\OpenAICompatible\ChatStreamParser;
use AiSdk\Streaming\StreamState;
// Build with stream: true — this adds 'stream_options' => ['include_usage' => true] automatically.
$body = ChatRequestBuilder::build('gpt-4o', 'openai', $request, stream: true);
// Your runner yields raw SSE events: ['event' => ?string, 'data' => string]
$events = $this->runner()->streamJson(
url: 'https://api.openai.com/v1/chat/completions',
body: $body,
headers: ['Authorization' => 'Bearer ' . $this->apiKey],
providerName: 'openai',
);
$state = new StreamState();
foreach (ChatStreamParser::parse($events, providerName: 'openai') as $part) {
$state->record($part);
}
// Once the loop completes, $state holds the full accumulated result:
echo $state->text();
// "The capital of France is Paris."
echo $state->usage()->inputTokens;
echo $state->usage()->outputTokens;
// Accumulated tool calls are also available:
foreach ($state->toolCalls() as $call) {
echo $call->name; // e.g. "get_weather"
echo $call->arguments; // fully-assembled argument array
}
Image Generation
The same pattern applies to image generation endpoints. UseImageRequestBuilder to build the request body and ImageResponseParser to parse the response.
ImageRequestBuilder also supports aspect-ratio-to-size inference for the standard ratios (1:1 → 1024x1024, 16:9 → 1536x1024, 9:16 → 1024x1536), a configurable size parameter name, and provider raw overrides merged last via providerOptions.