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.
Tool calling (also called function calling) lets the model request execution of a named function instead of — or alongside — returning text. The OpenAI-compatible wire format represents tools as JSON-schema-described functions and tool calls as structured objects in the assistant message. ChatRequestBuilder serializes your portable Tool objects into this format, and ChatResponseParser turns the provider’s response back into typed ToolCallPart objects that your application can execute.
Tools in the request body
When $request->tools is a non-empty array, ChatRequestBuilder::build() automatically adds two keys to the request body:
tools — an array of function definitions produced by ChatToolConverter::convert()
tool_choice — a string or object produced by ChatToolConverter::choice()
use AiSdk\OpenAICompatible\ChatRequestBuilder;
use AiSdk\Requests\TextModelRequest;
use AiSdk\Message;
use AiSdk\Tool;
use AiSdk\ToolChoice;
$weatherTool = Tool::define(
name: 'get_weather',
description: 'Returns the current weather for a city.',
parameters: [
'type' => 'object',
'properties' => [
'city' => ['type' => 'string', 'description' => 'City name'],
],
'required' => ['city'],
],
);
$request = new TextModelRequest(
messages: [Message::user('What is the weather in Oslo?')],
tools: [$weatherTool],
);
$body = ChatRequestBuilder::build('gpt-4o', 'openai', $request, false);
Each Tool object is serialized to the OpenAI {type: function, function: {...}} shape:
// $body['tools'][0] looks like:
[
'type' => 'function',
'function' => [
'name' => 'get_weather',
'description' => 'Returns the current weather for a city.',
'parameters' => [
'type' => 'object',
'properties' => [
'city' => ['type' => 'string', 'description' => 'City name'],
],
'required' => ['city'],
],
],
]
The tool_choice field controls whether the model must call a tool, may call any tool, or must call a specific tool:
$request->toolChoice value | Wire format sent |
|---|
null | "auto" |
ToolChoice::none() | "none" |
ToolChoice::required() | "required" |
ToolChoice::tool('get_weather') | {type: "function", function: {name: "get_weather"}} |
use AiSdk\ToolChoice;
// Force the model to call get_weather specifically
$request = new TextModelRequest(
messages: [Message::user('Get the weather in Oslo.')],
tools: [$weatherTool],
toolChoice: ToolChoice::tool('get_weather'),
);
$body = ChatRequestBuilder::build('gpt-4o', 'openai', $request, false);
// $body['tool_choice'] === ['type' => 'function', 'function' => ['name' => 'get_weather']]
ChatResponseParser::parse() returns a TextModelResponse. Call $response->toolCalls() to get an array of ToolCallPart objects — one per function call the model requested.
use AiSdk\OpenAICompatible\ChatResponseParser;
$response = ChatResponseParser::parse($payload, 'openai');
foreach ($response->toolCalls() as $call) {
echo $call->id; // 'call_abc123' (use this in the tool result message)
echo $call->name; // 'get_weather'
print_r($call->arguments); // ['city' => 'Oslo']
}
The arguments field is already decoded from JSON — you get a plain PHP array, not a raw string.
// From ChatWireFormatTest.php:
$payload = [
'id' => 'chatcmpl_123',
'choices' => [[
'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->toolCalls()[0]->name; // 'weather'
echo $response->toolCalls()[0]->arguments['city']; // 'Lahore'
echo $response->providerMetadata['openai']['choice_finish_reason']; // 'tool_calls'
Multi-turn conversations
A complete tool-calling exchange requires at least three turns:
- User message — the original request
- Assistant message — the model’s tool call(s), replayed back verbatim
- Tool result message — your application’s function output, keyed by
tool_call_id
ChatMessageConverter handles both of these special message shapes automatically.
Use Message::assistant() and pass the ToolCall objects from the parsed response. The converter serializes them to the tool_calls array format the provider expects:
use AiSdk\Message;
use AiSdk\ToolCall;
// Step 1 – build the initial request
$body = ChatRequestBuilder::build('gpt-4o', 'openai', new TextModelRequest(
messages: [Message::user('What is the weather in Oslo?')],
tools: [$weatherTool],
), false);
// Step 2 – execute the tool call from the response
$call = $response->toolCalls()[0]; // id='call_1', name='weather', arguments=['city'=>'Oslo']
$result = $weatherService->get($call->arguments['city']); // 'Partly cloudy, 12°C'
// Step 3 – build the follow-up request with the full conversation history
$followUp = new TextModelRequest(
messages: [
Message::user('What is the weather in Oslo?'),
// Replay the assistant's tool call
Message::assistant('', toolCalls: [
new ToolCall($call->id, $call->name, $call->arguments),
]),
// Provide the tool result
Message::tool(
toolCallId: $call->id,
output: $result,
name: $call->name,
),
],
tools: [$weatherTool],
);
$finalResponse = ChatResponseParser::parse(
$httpClient->postJson($url, ChatRequestBuilder::build('gpt-4o', 'openai', $followUp, false), $headers),
'openai',
);
echo $finalResponse->text(); // 'The weather in Oslo is partly cloudy at 12°C.'
The following examples are drawn from ChatWireFormatTest.php and show the exact messages entries the converter produces.
Assistant message with a tool call (includes assistant tool calls in converted messages):
$body = ChatRequestBuilder::build('gpt-4o', 'openai', new TextModelRequest(
messages: [
Message::assistant('', toolCalls: [
new \AiSdk\ToolCall('call_1', 'weather', ['city' => 'Lahore']),
]),
],
), false);
// $body['messages'][0] ===
[
'role' => 'assistant',
'content' => null,
'tool_calls' => [[
'id' => 'call_1',
'type' => 'function',
'function' => [
'name' => 'weather',
'arguments' => '{"city":"Lahore"}',
],
]],
]
Tool result message (includes tool result names in converted messages):
$body = ChatRequestBuilder::build('gpt-4o', 'openai', new TextModelRequest(
messages: [
Message::user('Use the weather tool.'),
Message::tool(toolCallId: 'call_1', output: 'Sunny', name: 'weather'),
],
), false);
// $body['messages'][1] ===
[
'role' => 'tool',
'tool_call_id' => 'call_1',
'name' => 'weather',
'content' => 'Sunny',
]
For streaming tool-call fragments, see Streaming. For multimodal content in the same message as a tool call, see Multimodal Content.