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.

The chat completions round-trip is three steps: build the request body with ChatRequestBuilder::build(), POST it to the provider’s /chat/completions endpoint, then parse the JSON payload with ChatResponseParser::parse(). The adapter handles all wire-format details — message encoding, tool serialization, structured output schemas, and provider metadata extraction — so your provider package stays focused on auth and endpoint routing.

Building the request body

ChatRequestBuilder::build() takes the model ID, provider name, a TextModelRequest, and a $stream flag, and returns a plain PHP array ready to JSON-encode and POST.
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 concise geography assistant.',
    temperature: 0.7,
    maxTokens: 256,
    topP: 0.9,
);

$body = ChatRequestBuilder::build(
    modelId: 'gpt-4o',
    providerName: 'openai',
    request: $request,
    stream: false,
);
The resulting array always contains these fields:
FieldSource
model$modelId parameter
messagesConverted from $request->messages + $request->system
temperature$request->temperature
max_tokens$request->maxTokens
stream$stream parameter
top_p$request->topP (omitted when null)
tools + tool_choiceIncluded when $request->tools !== []
response_formatIncluded when $request->output is set
stream_options{include_usage: true} when $stream === true
reasoning_effort$request->reasoning->effort (when set)
The raw escape hatch lets you merge provider-specific fields that the portable API doesn’t expose. Pass them under providerOptions and they are merged last, overwriting any key the builder already set:
use AiSdk\Requests\TextModelRequest;

$request = new TextModelRequest(
    messages: [Message::user('Repeat after me: hello')],
    providerOptions: [
        'openai' => ['raw' => ['temperature' => 0.1, 'seed' => 42]],
    ],
);

$body = ChatRequestBuilder::build('gpt-4o', 'openai', $request, false);
// $body['temperature'] === 0.1
// $body['seed']        === 42

Structured output

When $request->output is an Output instance, ChatRequestBuilder appends a response_format key to the body. Two formats are supported: JSON schema — used when $request->output has Output::KIND_OBJECT and an associated Schema object:
use AiSdk\Outputs\Output;
use AiSdk\Schema;

$request = new TextModelRequest(
    messages: [Message::user('Extract the event details.')],
    output: Output::object(Schema::fromArray([
        'name' => 'event',
        'schema' => [
            'type' => 'object',
            'properties' => [
                'title'    => ['type' => 'string'],
                'location' => ['type' => 'string'],
                'date'     => ['type' => 'string'],
            ],
            'required' => ['title', 'location', 'date'],
        ],
    ])),
);

$body = ChatRequestBuilder::build('gpt-4o', 'openai', $request, false);
// $body['response_format'] === [
//     'type' => 'json_schema',
//     'json_schema' => [
//         'name'   => 'event',
//         'strict' => true,
//         'schema' => [...],
//     ],
// ]
JSON object — used when $request->output has Output::KIND_OBJECT but no schema:
$body['response_format'] = ['type' => 'json_object'];

Parsing the response

Pass the raw decoded JSON payload to ChatResponseParser::parse(). It returns a TextModelResponse with typed parts, usage, finish reason, and provider metadata.
use AiSdk\OpenAICompatible\ChatResponseParser;

$payload = $this->runner()->postJson($url, $body, $headers, 'openai');
$response = ChatResponseParser::parse($payload, 'openai');

Accessing response data

// Plain text content
echo $response->text();

// Tool calls (returns ToolCallPart[])
foreach ($response->toolCalls() as $call) {
    echo $call->id;          // e.g. 'call_abc123'
    echo $call->name;        // e.g. 'get_weather'
    print_r($call->arguments); // decoded array, e.g. ['city' => 'Lahore']
}

// Token usage
echo $response->usage->inputTokens;   // prompt tokens
echo $response->usage->outputTokens;  // completion tokens

// Provider metadata (response-level fields and choice-level fields)
$meta = $response->providerMetadata['openai'];
echo $meta['id'];                   // e.g. 'chatcmpl_123'
echo $meta['model'];                // e.g. 'gpt-4o'
echo $meta['choice_finish_reason']; // e.g. 'tool_calls'
echo $meta['system_fingerprint'];

Full example — parse a tool-call response

The following example mirrors the test in ChatWireFormatTest.php and shows exactly what the parser extracts from a real provider payload:
$payload = [
    'id'               => 'chatcmpl_123',
    'object'           => 'chat.completion',
    'created'          => 1710000000,
    'model'            => 'gpt-4o',
    'system_fingerprint' => 'fp_abc',
    'choices' => [[
        'index'   => 0,
        'message' => [
            'content'    => 'hello',
            'tool_calls' => [[
                'id'       => 'call_1',
                'function' => [
                    'name'      => 'weather',
                    'arguments' => '{"city":"Lahore"}',
                ],
            ]],
        ],
        'finish_reason' => 'tool_calls',
    ]],
    'usage' => ['prompt_tokens' => 10, 'completion_tokens' => 4],
];

$response = ChatResponseParser::parse($payload, 'openai');

echo $response->text();                           // 'hello'
echo $response->toolCalls()[0]->name;             // 'weather'
print_r($response->toolCalls()[0]->arguments);    // ['city' => 'Lahore']
echo $response->usage->inputTokens;               // 10
echo $response->providerMetadata['openai']['id']; // 'chatcmpl_123'

Reasoning support

Reasoning effort is mapped through a single, unambiguous path. Use Reasoning::effort() to pass a portable effort string:
use AiSdk\Reasoning;

$request = new TextModelRequest(
    messages: [Message::user('Solve this step by step.')],
    reasoning: Reasoning::effort('high'),
);

$body = ChatRequestBuilder::build('o3', 'openai', $request, false);
// $body['reasoning_effort'] === 'high'
Token budget reasoning (Reasoning::budget(N)) is not supported by the OpenAI-compatible wire format and throws an InvalidArgumentException. Use Reasoning::effort() instead, or pass provider-specific budget parameters through the raw escape hatch.
For tool calling and multi-turn conversations, see Tool Calling. For multimodal content in messages, see Multimodal Content.

Build docs developers (and LLMs) love