Skip to content

Custom Tools

Extend Iris's capabilities by creating custom tools.

Creating a Tool

Tools live in app/Tools/ and extend Prism's Tool class.

Basic Structure

php
<?php

declare(strict_types=1);

namespace App\Tools;

use Prism\Prism\Tool;

class MyCustomTool extends Tool
{
    public function __construct()
    {
        $this
            ->as('my_custom_tool')
            ->for('Description of what this tool does and when to use it')
            ->withStringParameter('input', 'Description of the input')
            ->using($this);
    }

    public function __invoke(string $input): string
    {
        return "Processed: {$input}";
    }
}

With Dependencies

php
public function __construct(
    protected SomeService $service,
    protected User $user,  // Automatically injected
) {
    $this
        ->as('my_custom_tool')
        ->for('Does something useful')
        ->withStringParameter('input', 'The input to process')
        ->using($this);
}

public function __invoke(string $input): string
{
    $result = $this->service->process($input, $this->user);
    return "Result: {$result}";
}

Parameter Types

php
->withStringParameter('name', 'A text value')
->withNumberParameter('count', 'A numeric value')
->withBooleanParameter('enabled', 'A true/false value')
->withArrayParameter('items', 'A list of values')
->withEnumParameter('status', 'Status option', ['pending', 'active', 'done'])

Mark parameters as optional:

php
->withStringParameter('optional_param', 'Can be omitted', required: false)

Returning Results

Simple String

php
return "Successfully processed: {$input}";

With Artifacts

For images or files:

php
use Prism\Prism\ValueObjects\Artifact;
use Prism\Prism\ValueObjects\ToolOutput;

return new ToolOutput(
    result: json_encode(['status' => 'success']),
    artifacts: [new Artifact(
        data: base64_encode($imageData),
        mimeType: 'image/png',
        metadata: ['description' => $description],
    )],
);

Registering Tools

Add your tool to app/Services/ToolRegistry.php:

php
public function __construct(
    // ... existing tools
    protected MyCustomTool $myCustomTool,
) {}

public function getTools(): array
{
    return [
        // ... existing tools
        $this->myCustomTool,
    ];
}

Laravel's container automatically injects dependencies. The User binding is configured in AppServiceProvider.

Best Practices

Clear Descriptions

php
// Good - helps LLM understand when to use
->for('Store important information about the user for later recall')

// Bad - vague
->for('Saves data')

Informative Results

php
// Good
return "Created task 'Buy groceries' with high priority, due tomorrow.";

// Bad
return "Done";

Error Handling

php
try {
    $event = $this->calendar->find($eventId);
    $this->calendar->delete($event);
    return "Deleted event: {$event->title}";
} catch (NotFoundException $e) {
    return "Could not find an event with ID '{$eventId}'.";
} catch (Throwable $e) {
    Log::error('Event deletion failed', ['error' => $e->getMessage()]);
    return "An error occurred. Please try again.";
}

Testing Tools

php
it('returns weather information', function () {
    Http::fake([
        'api.weatherapi.com/*' => Http::response([
            'location' => ['name' => 'London'],
            'current' => ['temp_c' => 15, 'condition' => ['text' => 'Cloudy']],
        ]),
    ]);

    $tool = new WeatherTool();
    $result = $tool('London');

    expect($result)->toContain('London')->toContain('15°C');
});