Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/phpaisdk/core/llms.txt

Use this file to discover all available pages before exploring further.

Tool calling lets a model request the execution of PHP functions you define. The model signals which tool to call and with what arguments; aisdk/core validates the input, invokes your handler, feeds the result back into the conversation, and repeats until the model produces a final answer — or until maxSteps is reached.

Defining tools

Inline tools

Tool::make() creates a tool in one fluent expression. Pass the tool name and description, declare inputs with ->input() or ->inputs(), then attach a handler with ->run():
use AiSdk\Schema;
use AiSdk\Tool;

$weather = Tool::make('weather', 'Get current weather for a city')
    ->input(Schema::string(name: 'city', description: 'City name')->required())
    ->run(fn (string $city): string => "Sunny, 28 °C in {$city}");
For tools that accept more than one parameter, use ->inputs() with an array of named schemas:
$distance = Tool::make('distance', 'Calculate driving distance between two cities')
    ->inputs([
        Schema::string(name: 'from', description: 'Origin city')->required(),
        Schema::string(name: 'to',   description: 'Destination city')->required(),
    ])
    ->run(fn (string $from, string $to): string => "320 km from {$from} to {$to}");

Fluent alias syntax

Use the static Tool::as() alias when you prefer to separate name declaration from the Tool::make() call:
$weather = Tool::as('weather')
    ->for('Get current weather')
    ->input(Schema::string(name: 'city')->required())
    ->run(fn (string $city): string => "Sunny in {$city}");

Class-based tools

Extend Tool when the handler requires injected services, has significant logic, or benefits from being tested in isolation. Configure the tool in __construct() using the same fluent methods (->as(), ->for(), ->input()), and implement __invoke() as the handler:
use AiSdk\Schema;
use AiSdk\Tool;

final class WeatherTool extends Tool
{
    public function __construct()
    {
        $this->as('weather')
            ->for('Get current weather for a city')
            ->input(Schema::string(name: 'city')->required());
    }

    public function __invoke(string $city): string
    {
        // Call a real weather API here
        return "Sunny, 28 °C in {$city}";
    }
}
Resolve a class-based tool with Tool::make(WeatherTool::class), or pass the class name string directly to ->tool() on the request — the SDK resolves it automatically:
$result = Generate::text('What is the weather in Lahore?')
    ->model(OpenAI::model('gpt-4o'))
    ->tool(WeatherTool::class)
    ->run();
If a PSR-11 container is registered via Generate::configure(), Tool::make(SomeClass::class) pulls the instance from the container. This lets you inject services into class-based tools transparently.

Attaching tools to a request

1

Single tool

$result = Generate::text('What is the weather in Lahore?')
    ->model(OpenAI::model('gpt-4o'))
    ->tool($weather)
    ->run();
2

Multiple tools (variadic)

$result = Generate::text('Plan my trip from Lahore to Islamabad.')
    ->model(OpenAI::model('gpt-4o'))
    ->tools($weather, $distance, $hotels)
    ->run();
3

Multiple tools (array)

$tools = [$weather, $distance, $hotels];

$result = Generate::text('Plan my trip from Lahore to Islamabad.')
    ->model(OpenAI::model('gpt-4o'))
    ->tools($tools)
    ->run();
Two tools with the same name on one request throw an InvalidArgumentException. Tool names must be unique within a request.

Tool choice

->toolChoice() controls whether and how the model uses tools. It accepts a ToolChoice instance or a plain string shorthand:
use AiSdk\ToolChoice;

// Let the model decide (default)
->toolChoice(ToolChoice::auto())

// Model must call at least one tool
->toolChoice(ToolChoice::required())

// Model must not call any tools
->toolChoice(ToolChoice::none())

// Force a specific tool by name
->toolChoice(ToolChoice::tool('weather'))

// String shorthands
->toolChoice('auto')
->toolChoice('required')
->toolChoice('none')

Multi-step agentic loops

By default (maxSteps(1)) the SDK runs the model once. If that response contains tool calls, they are executed, but the model does not see the results — the tool output is part of the returned TextResult only. Set ->maxSteps() to allow the model to loop: after each step that produces tool calls, the results are appended to the conversation and the model is called again, up to the limit:
$result = Generate::text('What is the weather in Lahore, and is it good for cycling?')
    ->model(OpenAI::model('gpt-4o'))
    ->tool($weather)
    ->maxSteps(5)
    ->run();

// The model may have called $weather multiple times before producing $result->text
echo $result->text;
The loop terminates early as soon as a step produces a response with no tool calls.

Inspecting tool calls and results

TextResult exposes all calls and results accumulated across every step:
foreach ($result->toolCalls as $call) {
    // AiSdk\ToolCall
    echo "{$call->name}({$call->id})\n";
    print_r($call->arguments); // decoded array
}

foreach ($result->toolResults as $result) {
    // AiSdk\ToolResult
    echo "{$result->toolName}: {$result->output}\n";
}

ToolExecutionContext

When a tool handler declares a ToolExecutionContext as its last parameter, aisdk/core injects it automatically. The context carries the call ID, tool name, decoded arguments, and the full conversation history at the point of execution:
use AiSdk\ToolExecutionContext;

$tool = Tool::make('log_request', 'Log the active request context')
    ->input(Schema::string(name: 'label')->required())
    ->run(function (string $label, ToolExecutionContext $context): string {
        logger()->info('Tool execution', [
            'call_id'   => $context->toolCallId,
            'tool'      => $context->toolName,
            'arguments' => $context->arguments,
            'messages'  => count($context->messages),
        ]);

        return "Logged: {$label}";
    });
ToolExecutionContext is detected by PHP reflection. It is only injected when the handler’s last parameter is type-hinted as ToolExecutionContext — no additional configuration is needed.

ToolExecutionContext properties

PropertyTypeDescription
$toolCallIdstringThe unique ID assigned to this tool call by the model.
$toolNamestringThe name of the tool being executed.
$argumentsarray<string, mixed>Decoded arguments as passed by the model.
$messagesarray<int, Message>Full conversation history at the time of execution.

Grouping tools with ToolPackageInterface

ToolPackageInterface lets you bundle related tools into a single object and pass it to ->tools():
use AiSdk\Tool;
use AiSdk\ToolPackageInterface;

final class TravelToolPackage implements ToolPackageInterface
{
    public function tools(): iterable
    {
        yield Tool::make('weather', 'Current weather')
            ->input(Schema::string(name: 'city')->required())
            ->run(fn (string $city): string => "Sunny in {$city}");

        yield Tool::make('hotels', 'Find hotels')
            ->input(Schema::string(name: 'city')->required())
            ->run(fn (string $city): string => "3 hotels available in {$city}");
    }
}

$result = Generate::text('Plan a trip to Lahore.')
    ->model(OpenAI::model('gpt-4o'))
    ->tools(new TravelToolPackage())
    ->maxSteps(5)
    ->run();

Complete example

use AiSdk\Generate;
use AiSdk\OpenAI;
use AiSdk\Schema;
use AiSdk\Tool;

$weather = Tool::make('weather', 'Get current weather')
    ->input(Schema::string(name: 'city')->required())
    ->run(fn (string $city): string => "Sunny, 28 °C in {$city}");

$result = Generate::text('What should I wear in Lahore today?')
    ->model(OpenAI::model('gpt-4o'))
    ->tool($weather)
    ->maxSteps(3)
    ->run();

echo $result->text;

Build docs developers (and LLMs) love