Skip to main content

Documentation 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.

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, the 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).
1
Add dependencies
2
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.
3
{
  "require": {
    "aisdk/core": "^1.0",
    "aisdk/openai-compatible": "^1.0"
  }
}
4
Run composer install to pull in both packages. The AiSdk\OpenAICompatible\ namespace becomes available immediately.
5
Create a provider class
6
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.
7
<?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);
    }
}
8
Create a text model with chat completions
9
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.
10
<?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');
    }
}
11
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.
12
Add streaming support
13
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.
14
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');
}
15
Callers iterate the generator or accumulate parts into a StreamState:
16
$state = new StreamState();
foreach ($model->stream($request) as $part) {
    $state->record($part);
}

echo $state->text();
17
See Streaming for the full list of emitted StreamPart types.
18
(Optional) Create an image model
19
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.
20
<?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');
    }
}
21
See Image Generation for the full $options reference and aspect ratio mapping.
22
Register the model catalog
23
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.
24
// 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);
}
25
End users of your provider package call the facade directly:
26
$groq = new GroqProvider(apiKey: getenv('GROQ_API_KEY'));
$response = $groq->chat('llama-3.3-70b-versatile')->generate($request);

echo $response->text();

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

Build docs developers (and LLMs) love