A provider package is a thin wrapper that connects a specific AI service (such as Groq, xAI, or OpenRouter) to the PHP AI SDK’s portable contracts. Because these services all speak the OpenAI-compatible wire format, theDocumentation 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 adapter handles the heavy lifting — request serialization, response parsing, SSE stream processing, message conversion, and tool formatting — leaving your provider package responsible only for authentication, base URLs, model catalogs, and any service-specific quirks.
Provider packages own exactly four things that
aisdk/openai-compatible deliberately does not touch: authentication credentials and headers, the base URL and endpoint paths, the model catalog (which model IDs are available and what their capabilities are), and any provider-specific adaptations applied after ChatRequestBuilder::build() returns (for example, downgrading structured output for models that don’t support json_schema).Declare both
aisdk/core and aisdk/openai-compatible as dependencies in your provider package’s composer.json. Your provider package sits on top of the shared adapter; it never needs to copy request-building or parsing logic.Run
composer install to pull in both packages. The AiSdk\OpenAICompatible\ namespace becomes available immediately.Extend
BaseProvider from aisdk/core. The provider class is the public entry point — it holds auth credentials, the base URL, and the factory methods that return model instances from your catalog.<?php
namespace Acme\GroqProvider;
use AiSdk\BaseProvider;
final class GroqProvider extends BaseProvider
{
public function __construct(private readonly string $apiKey) {}
protected function headers(): array
{
return [
'Authorization' => "Bearer {$this->apiKey}",
'Content-Type' => 'application/json',
];
}
protected function baseUrl(): string
{
return 'https://api.groq.com/openai/v1';
}
public function chat(string $modelId): GroqChatModel
{
return new GroqChatModel($modelId, $this);
}
}
Extend
BaseModel and implement the generate() method. Call ChatRequestBuilder::build() to produce the request body, POST it to the provider’s endpoint, then call ChatResponseParser::parse() to convert the raw JSON payload into a typed TextModelResponse.<?php
namespace Acme\GroqProvider;
use AiSdk\BaseModel;
use AiSdk\OpenAICompatible\ChatRequestBuilder;
use AiSdk\OpenAICompatible\ChatResponseParser;
use AiSdk\Requests\TextModelRequest;
use AiSdk\Responses\TextModelResponse;
final class GroqChatModel extends BaseModel
{
public function generate(TextModelRequest $request): TextModelResponse
{
$body = ChatRequestBuilder::build(
$this->modelId,
'groq',
$request,
stream: false,
);
$payload = $this->runner()->postJson(
url: $this->provider->baseUrl() . '/chat/completions',
body: $body,
headers: $this->provider->headers(),
providerName: 'groq',
);
return ChatResponseParser::parse($payload, 'groq');
}
}
ChatRequestBuilder::build() encodes messages, system prompt, temperature, tools, structured output, and the raw provider escape hatch. ChatResponseParser::parse() extracts text parts, tool calls, usage, and provider metadata from the response.For streaming responses pass
stream: true to ChatRequestBuilder::build() — the builder automatically appends stream_options: {include_usage: true} so usage is available at stream end. Pipe the raw SSE events through ChatStreamParser::parse(), which returns a Generator of typed StreamPart objects.use AiSdk\OpenAICompatible\ChatStreamParser;
use AiSdk\Streaming\StreamState;
use Generator;
public function stream(TextModelRequest $request): Generator
{
$body = ChatRequestBuilder::build(
$this->modelId,
'groq',
$request,
stream: true,
);
$events = $this->runner()->streamJson(
url: $this->provider->baseUrl() . '/chat/completions',
body: $body,
headers: $this->provider->headers(),
providerName: 'groq',
);
return ChatStreamParser::parse($events, 'groq');
}
$state = new StreamState();
foreach ($model->stream($request) as $part) {
$state->record($part);
}
echo $state->text();
See Streaming for the full list of emitted
StreamPart types.If the provider exposes an
/images/generations endpoint, implement ImageModelInterface. Call ImageRequestBuilder::build() to produce the request body and ImageResponseParser::parse() to decode the base64 images.<?php
namespace Acme\GroqProvider;
use AiSdk\ImageModelInterface;
use AiSdk\OpenAICompatible\ImageRequestBuilder;
use AiSdk\OpenAICompatible\ImageResponseParser;
use AiSdk\Requests\ImageRequest;
use AiSdk\Responses\ImageResponse;
final class GroqImageModel implements ImageModelInterface
{
public function __construct(
private readonly string $modelId,
private readonly GroqProvider $provider,
) {}
public function generate(ImageRequest $request): ImageResponse
{
$body = ImageRequestBuilder::build(
$this->modelId,
'groq',
$request,
// Pass provider-specific options here if needed
);
$payload = $this->provider->runner()->postJson(
url: $this->provider->baseUrl() . '/images/generations',
body: $body,
headers: $this->provider->headers(),
providerName: 'groq',
);
return ImageResponseParser::parse($payload, 'groq');
}
}
See Image Generation for the full
$options reference and aspect ratio mapping.Expose named factory methods or a catalog array on your
GroqProvider class so callers can look up model IDs by capability tier. The catalog is entirely your responsibility — aisdk/openai-compatible has no opinion about which models exist.// In GroqProvider
public function chat(string $modelId = 'llama-3.3-70b-versatile'): GroqChatModel
{
return new GroqChatModel($modelId, $this);
}
public function image(string $modelId = 'playai-tts'): GroqImageModel
{
return new GroqImageModel($modelId, $this);
}
What the adapter owns vs. what you own
aisdk/openai-compatible owns
- Request body serialization (
ChatRequestBuilder) - Response parsing (
ChatResponseParser) - SSE stream parsing (
ChatStreamParser) - Image request building (
ImageRequestBuilder) - Image response parsing (
ImageResponseParser) - Message and tool conversion
- Usage normalization
- Finish reason mapping
Your provider package owns
- API key handling and auth headers
- Base URLs and endpoint paths
- Model catalog and capability metadata
- Provider-specific quirks and feature flags
- Public facades and end-user ergonomics