Skip to content

RFC: Attribute-driven structured output with typed DTOs #416

@FullnessYousef

Description

@FullnessYousef

Right now, structured output requires building the schema manually with the JsonSchemaTypeFactory builder, and the response comes back as a plain array:

class ReviewAgent implements Agent, HasStructuredOutput
{
    use Promptable;

    public function schema(JsonSchema $schema): array
    {
        return [
            'score' => $schema->integer()->required()->min(1)->max(10),
            'summary' => $schema->string()->required()->description('Brief summary'),
            'tags' => $schema->array()->required()->items($schema->string()),
        ];
    }
}

$response = (new ReviewAgent)->prompt('Review this code');
$score = $response['score']; // mixed — no type safety

It would be great if you could define the schema as a plain PHP class instead, using the type system and attributes for constraints:

use Laravel\Ai\Attributes\Description;

class ReviewResult
{
    public function __construct(
        #[Description('Score from 1-10')]
        public int $score,

        #[Description('Brief summary of the review')]
        public string $summary,

        /** @var string[] */
        public array $tags,
    ) {}
}

class ReviewAgent implements Agent, HasStructuredOutput
{
    use Promptable;

    public function schema(): string
    {
        return ReviewResult::class;
    }
}

$response = (new ReviewAgent)->prompt('Review this code');
$result = $response->object(); // ReviewResult — full IDE completion

The DTO class becomes the single source of truth — schema definition, response typing, and IDE support all in one place. Under the hood it would map PHP types to the existing JsonSchemaTypeFactory types (int->integer(), string->string(), etc.) and feed into the same provider pipeline, so zero gateway changes needed.

Why not use an existing package?

basillangevin/instructor-laravel does something similar but requires spatie/laravel-data as a dependency. This approach would use plain PHP classes with zero external dependencies — just native PHP 8 types and attributes.

Scope

I'm thinking this could be phased:

  1. Schema generation — DTO class → array<string, Type> conversion (backward compatible with existing HasStructuredOutput)
  2. Response hydration$response->object() returns a typed instance
  3. Validation + retry — validate LLM response against DTO types, retry on mismatch

Would this be something the team is interested in having in the SDK, or is it better suited as a separate package? Happy to put together a PR for phase 1 if there's interest.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions