Structured output | laravel-url-ai-transformer | Spatie       

 SPATIE  

  Laravel URL AI Transformer 
=============================

spatie.be/open-source

  [Docs](https://spatie.be/docs)  [Laravel-url-ai-transformer](https://spatie.be/docs/laravel-url-ai-transformer/v2)  Advanced-usage  Structured output

 Version   v2   v1      

 Other versions for crawler [v2](https://spatie.be/docs/laravel-url-ai-transformer/v2) [v1](https://spatie.be/docs/laravel-url-ai-transformer/v1) 

  Structured output    
- [ Introduction ](https://spatie.be/docs/laravel-url-ai-transformer/v2/introduction)
- [ Support us ](https://spatie.be/docs/laravel-url-ai-transformer/v2/support-us)
- [ Installation &amp; setup ](https://spatie.be/docs/laravel-url-ai-transformer/v2/installation-setup)
- [ Questions and issues ](https://spatie.be/docs/laravel-url-ai-transformer/v2/questions-issues)
- [ Changelog ](https://spatie.be/docs/laravel-url-ai-transformer/v2/changelog)
- [ About us ](https://spatie.be/docs/laravel-url-ai-transformer/v2/about-us)

Basic usage
-----------

- [ Getting started ](https://spatie.be/docs/laravel-url-ai-transformer/v2/basic-usage/getting-started)
- [ Registering transformations ](https://spatie.be/docs/laravel-url-ai-transformer/v2/basic-usage/registering-transformations)
- [ Writing your own transformers ](https://spatie.be/docs/laravel-url-ai-transformer/v2/basic-usage/writing-your-own-transformers)

Advanced usage
--------------

- [ Structured output ](https://spatie.be/docs/laravel-url-ai-transformer/v2/advanced-usage/structured-output)
- [ Customizing AI models ](https://spatie.be/docs/laravel-url-ai-transformer/v2/advanced-usage/customizing-ai-models)
- [ Customizing the stored result ](https://spatie.be/docs/laravel-url-ai-transformer/v2/advanced-usage/customizing-the-result)
- [ Conditional transformations ](https://spatie.be/docs/laravel-url-ai-transformer/v2/advanced-usage/conditional-transformations)
- [ Crawling URLs ](https://spatie.be/docs/laravel-url-ai-transformer/v2/advanced-usage/crawling-urls)
- [ Exploring command options ](https://spatie.be/docs/laravel-url-ai-transformer/v2/advanced-usage/exploring-command-options)
- [ Regenerating results ](https://spatie.be/docs/laravel-url-ai-transformer/v2/advanced-usage/regenerating-results)
- [ Handling failures ](https://spatie.be/docs/laravel-url-ai-transformer/v2/advanced-usage/handling-failures)
- [ Listening for events ](https://spatie.be/docs/laravel-url-ai-transformer/v2/advanced-usage/events)
- [ Using your own model ](https://spatie.be/docs/laravel-url-ai-transformer/v2/advanced-usage/using-your-own-model)
- [ Customizing the job ](https://spatie.be/docs/laravel-url-ai-transformer/v2/advanced-usage/customizing-the-job)
- [ Overriding actions ](https://spatie.be/docs/laravel-url-ai-transformer/v2/advanced-usage/overriding-actions)
- [ Testing transformers ](https://spatie.be/docs/laravel-url-ai-transformer/v2/advanced-usage/testing-transformers)

 Structured output
=================

###  On this page 

1. [ Defining a schema ](#content-defining-a-schema)
2. [ How the result is stored ](#content-how-the-result-is-stored)
3. [ Forcing valid JSON while keeping the structure flexible ](#content-forcing-valid-json-while-keeping-the-structure-flexible)
4. [ Testing structured transformers ](#content-testing-structured-transformers)

By default, a transformer stores the AI's free-form text response. When you need reliable, machine-readable data instead, a transformer can return structured output that is validated against a schema you define.

Defining a schema
-----------------------------------------------------------------------------------------------------------

Implement Laravel AI's `HasStructuredOutput` contract and add a `schema()` method. The schema is built with the `JsonSchema` builder that is passed to the method.

```
// app/Transformers/ProductTransformer.php
namespace App\Transformers;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Spatie\LaravelUrlAiTransformer\Transformers\Transformer;
use Stringable;

class ProductTransformer extends Transformer implements HasStructuredOutput
{
    public function instructions(): Stringable|string
    {
        return 'Extract the product details from this webpage.';
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'name' => $schema->string()->required(),
            'price' => $schema->number()->required(),
            'in_stock' => $schema->boolean(),
        ];
    }
}
```

How the result is stored
--------------------------------------------------------------------------------------------------------------------------------

When a transformer defines a schema, the AI response is validated against it and stored as JSON on the transformation result. For the example above, `result` would contain something like:

```
{"name":"Wireless keyboard","price":49.99,"in_stock":true}
```

You can retrieve and decode it like any other transformation result:

```
use Spatie\LaravelUrlAiTransformer\Models\TransformationResult;

$result = TransformationResult::forUrl('https://example.com/product', 'product');

$product = json_decode($result, true);
```

Forcing valid JSON while keeping the structure flexible
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

A structured schema normally describes each field in advance. When you want guaranteed valid JSON but the shape of the data should stay open, wrap the free-form part in a single string property:

```
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Responses\AgentResponse;
use Laravel\Ai\Responses\StructuredAgentResponse;
use Spatie\LaravelUrlAiTransformer\Transformers\Transformer;
use Stringable;

class LdJsonTransformer extends Transformer implements HasStructuredOutput
{
    public function instructions(): Stringable|string
    {
        return 'Summarize this webpage to ld+json. Put the ld+json in the `json` key.';
    }

    public function schema(JsonSchema $schema): array
    {
        return [
            'json' => $schema->string()->required(),
        ];
    }

    protected function resultFrom(AgentResponse $response): string
    {
        $json = $response instanceof StructuredAgentResponse
            ? $response['json']
            : $response->text;

        json_decode($json, flags: JSON_THROW_ON_ERROR);

        return $json;
    }
}
```

The schema makes the provider return an outer object with a `json` string. `json_decode()` then validates that string before it is stored. Invalid JSON throws, which marks the transformation as failed and allows the queued job to retry. The built-in `LdJsonTransformer` uses this technique.

Testing structured transformers
-----------------------------------------------------------------------------------------------------------------------------------------------------

Fake the transformer with an array to stand in for the structured response:

```
use App\Transformers\ProductTransformer;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Http;
use Spatie\LaravelUrlAiTransformer\Models\TransformationResult;
use Spatie\LaravelUrlAiTransformer\Support\Transform;

it('extracts product details', function () {
    Http::fake([
        'https://example.com/product' => Http::response('...'),
    ]);

    ProductTransformer::fake([
        ['name' => 'Wireless keyboard', 'price' => 49.99, 'in_stock' => true],
    ]);

    Transform::urls('https://example.com/product')
        ->usingTransformers(new ProductTransformer);

    Artisan::call('transform-urls', ['--now' => true]);

    $result = TransformationResult::forUrl('https://example.com/product', 'product');

    expect($result)->not->toBeNull();

    $product = json_decode($result, associative: true, flags: JSON_THROW_ON_ERROR);

    expect($product['name'])->toBe('Wireless keyboard');
});
```

 A good
match?
-------------

### What we do best

- All things Laravel
- Custom frontend components
- Building APIs
- AI-powered features
- Simplifying things
- Clean solutions
- Integrating services

### Not our cup of tea

- WordPress themes
- Cutting corners
- Free mockups to win a job
- "Just execute the briefing"

 In short: we'd like to be a **substantial part** of your project.

 [ Get in touch via email ](mailto:info@spatie.be?subject=A%20good%20match%21&body=Tell%20us%20as%20much%20as%20you%20can%20about%0A-%20your%20online%20project%0A-%20your%20planning%0A-%20your%20budget%0A-%20%E2%80%A6%0A%0AAnything%20that%20helps%20us%20to%20start%20straightforward%21)
