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:
- Schema generation — DTO class →
array<string, Type> conversion (backward compatible with existing HasStructuredOutput)
- Response hydration —
$response->object() returns a typed instance
- 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.
Right now, structured output requires building the schema manually with the
JsonSchemaTypeFactorybuilder, and the response comes back as a plain array:It would be great if you could define the schema as a plain PHP class instead, using the type system and attributes for constraints:
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
JsonSchemaTypeFactorytypes (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-laraveldoes something similar but requiresspatie/laravel-dataas 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:
array<string, Type>conversion (backward compatible with existingHasStructuredOutput)$response->object()returns a typed instanceWould 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.