While the #[Tool] attribute is recommended for most use cases, Tool Classes and Inline Tools offer additional flexibility for reusable or dynamically generated tools.
Tool classes are standalone PHP classes that encapsulate tool logic. Theyβre ideal for:
- Complex tools with extensive logic
- Tools shared across multiple agents
- Tools requiring their own dependencies or configuration
namespace App\AiTools;
use LarAgent\Tool;
use LarAgent\Core\Abstractions\DataModel;
class WeatherTool extends Tool
{
protected string $name = 'get_weather';
protected string $description = 'Get the current weather for a location';
protected array $properties = [
'location' => [
'type' => 'string',
'description' => 'The city and state, e.g. San Francisco, CA',
],
'unit' => [
'type' => 'string',
'description' => 'Temperature unit',
'enum' => ['celsius', 'fahrenheit'],
],
];
protected array $required = ['location'];
protected function handle(array|DataModel $input): mixed
{
$location = $input['location'];
$unit = $input['unit'] ?? 'celsius';
return WeatherService::get($location, $unit);
}
}
The handle() method replaces execute() for class-based tools from v1.2. It provides automatic DataModel and Enum conversion. The execute() method still works for backward compatibility.
Add tool classes to the $tools property in your agent:
class WeatherAgent extends Agent
{
protected $tools = [
\App\Tools\WeatherTool::class,
\App\Tools\LocationTool::class,
];
}
Or register them in the registerTools() method by instantiating:
public function registerTools()
{
return [
new \App\AiTools\WeatherTool(),
new \App\AiTools\LocationTool(),
];
}
Or add them at runtime:
$agent->withTool(WeatherTool::class);
// or
$agent->withTool(new WeatherTool());
| Property | Type | Description |
|---|
$name | string | Unique identifier for the tool |
$description | string | Description shown to the LLM |
$properties | array | Parameter definitions with types and descriptions |
$required | array | List of required parameter names |
$dataModelClass | ?string | DataModel class to use as the toolβs input schema |
$metaData | array | Optional metadata (not sent to LLM) |
Property Definitions
Properties are OpenAPI compatible schemas. Each property in $properties can have:
protected array $properties = [
'param_name' => [
'type' => 'string', // string, number, integer, boolean, array, object
'description' => 'Help text',
'enum' => ['a', 'b', 'c'], // Optional: restrict to specific values
],
];
Define your entire tool schema using a DataModel class:
use LarAgent\Tool;
use LarAgent\Core\Abstractions\DataModel;
class TaskDataModel extends DataModel
{
public string $title;
public int $estimatedHours;
public ?string $description = null;
}
class CreateTaskTool extends Tool
{
protected string $name = 'create_task';
protected string $description = 'Create a new task';
// Automatically populates all properties from TaskDataModel
protected ?string $dataModelClass = TaskDataModel::class;
protected function handle(array|DataModel $input): mixed
{
// $input is already a TaskDataModel instance!
$task = $input;
return "Task '{$task->title}' created with {$task->estimatedHours} hours.";
}
}
DataModel in Properties Array
Mix DataModel classes directly in your $properties array:
class PersonDataModel extends DataModel
{
public string $name;
public string $email;
}
class AddressDataModel extends DataModel
{
public string $street;
public string $city;
}
class ScheduleMeetingTool extends Tool
{
protected string $name = 'schedule_meeting';
protected string $description = 'Schedule a meeting';
protected array $properties = [
'title' => ['type' => 'string'],
'attendee' => PersonDataModel::class, // Auto-expanded!
'location' => AddressDataModel::class, // Auto-expanded!
];
protected array $required = ['title', 'attendee', 'location'];
protected function handle(array|DataModel $input): mixed
{
// DataModel properties are automatically converted!
$attendee = $input['attendee']; // PersonDataModel instance
$location = $input['location']; // AddressDataModel instance
return "Meeting '{$input['title']}' scheduled with {$attendee->name}";
}
}
Inline tools are created programmatically using the fluent Tool API. Theyβre ideal for:
- Tools that depend on runtime context (user state, request data)
- Dynamically generated tools based on configuration
- Quick prototyping before extracting to a class
Use the registerTools() method in your agent:
use LarAgent\Tool;
class MyAgent extends Agent
{
public function registerTools()
{
$user = auth()->user();
return [
Tool::create('get_user_location', "Get the current user's location")
->setCallback(fn() => $user->location->city),
Tool::create('get_weather', 'Get weather for a location')
->addProperty('location', 'string', 'City name')
->addProperty('unit', 'string', 'Temperature unit', ['celsius', 'fahrenheit'])
->setRequired('location')
->setCallback(fn($location, $unit = 'celsius') =>
WeatherService::get($location, $unit)
),
];
}
}
Fluent API Reference
Tool::create(string $name, string $description)
->addProperty(string $name, string $type, string $description, ?array $enum = null)
->addDataModelAsProperties(string $dataModelClass)
->setRequired(string|array $properties)
->setCallback(callable $callback);
Use addProperty() with a DataModel class as the type:
use LarAgent\Tool;
$tool = Tool::create('schedule_meeting', 'Schedule a meeting')
->addProperty('title', 'string', 'Meeting title')
->addProperty('attendee', PersonDataModel::class) // DataModel as type!
->addProperty('location', AddressDataModel::class) // DataModel as type!
->setRequired(['title', 'attendee', 'location'])
->setCallback(function (string $title, PersonDataModel $attendee, AddressDataModel $location) {
return "Meeting '{$title}' scheduled with {$attendee->name} at {$location->city}";
});
Or use addDataModelAsProperties() to use an entire DataModel as your toolβs input:
use LarAgent\Tool;
use LarAgent\Core\Abstractions\DataModel;
class TaskDataModel extends DataModel
{
public string $title;
public int $estimatedHours;
public ?string $description = null;
}
$tool = Tool::create('create_task', 'Create a task')
->addDataModelAsProperties(TaskDataModel::class)
->setCallback(function (TaskDataModel $task) {
return "Task '{$task->title}' created with {$task->estimatedHours} hours.";
});
Callback Options
The setCallback() method accepts any PHP callable, including:
// Closure
->setCallback(fn($param) => doSomething($param))
// Function name
->setCallback('myGlobalFunction')
// Class method (array syntax)
->setCallback([$this, 'methodName'])
Add inline tools at runtime using withTool():
$tool = Tool::create('custom_tool', 'Do something custom')
->addProperty('input', 'string', 'The input value')
->setCallback(fn($input) => processInput($input));
$response = MyAgent::for('user-123')
->withTool($tool)
->respond('Use the custom tool');
Context-Aware Tools Example
public function registerTools()
{
$user = auth()->user();
$tools = [];
// Always available
$tools[] = Tool::create('get_time', 'Get current server time')
->setCallback(fn() => now()->toIso8601String());
// User-specific tool
$tools[] = Tool::create('get_my_orders', 'Get my recent orders')
->addProperty('limit', 'integer', 'Number of orders to return')
->setCallback(fn($limit = 5) =>
$user->orders()->latest()->take($limit)->get()->toArray()
);
// Permission-based tool
if ($user->can('view_reports')) {
$tools[] = Tool::create('get_sales_report', 'Get sales report')
->addProperty('period', 'string', 'Time period', ['day', 'week', 'month'])
->setCallback(fn($period) => ReportService::sales($period));
}
return $tools;
}
Choosing the Right Approach
| Approach | Best For |
|---|
| #[Tool] Attribute | Most use cases β simple, type-safe, IDE support |
| Tool Classes | Complex, reusable tools with multiple dependencies |
| Inline Tools | Dynamic, context-dependent, or quick prototypes |
Start with the #[Tool] attribute. Use Inline Tools when the tool depends on runtime context.
Next Steps