Documentation Index
Fetch the complete documentation index at: https://docs.instructorphp.com/llms.txt
Use this file to discover all available pages before exploring further.
Response Models
Response models define the structure of data you want to extract from unstructured text. They are plain PHP classes with typed constructor properties that serve a dual purpose: they tell the LLM what data to produce (via the generated JSON Schema), and they provide a strongly typed container for the extracted result.
Creating Response Models
Using Artisan Command
The make:response-model command generates a ready-to-use response model class with the correct namespace, typed properties, and docblock descriptions.
# Basic response model
php artisan make:response-model PersonData
# Collection response model (model with an array of child items)
php artisan make:response-model ProductList --collection
# Nested objects response model (model with child object properties)
php artisan make:response-model CompanyProfile --nested
# With a custom description in the class docblock
php artisan make:response-model Invoice --description="Invoice extracted from PDF"
# @doctest id="a3aa"
Manual Creation
Create a class in app/ResponseModels/ (or any namespace you prefer):
<?php
namespace App\ResponseModels;
final class PersonData
{
public function __construct(
/** The person's full name */
public readonly string $name,
/** The person's age in years */
public readonly int $age,
/** The person's email address */
public readonly ?string $email = null,
) {}
}
// @doctest id="9116"
The class requires no base class, interface, or attribute — any PHP class with typed constructor properties works. The package inspects the constructor signature and docblocks at runtime to build the JSON Schema that guides the LLM.
Property Types
Basic Types
PHP’s scalar types map directly to JSON Schema types. The LLM receives clear instructions about the expected data type for each field.
final class BasicTypes
{
public function __construct(
public readonly string $text,
public readonly int $count,
public readonly float $price,
public readonly bool $isActive,
) {}
}
// @doctest id="d495"
Nullable Properties
Mark optional fields as nullable with a default of null. The LLM is allowed to omit these fields, and the resulting object will have null for any missing values.
final class WithOptional
{
public function __construct(
public readonly string $required,
public readonly ?string $optional = null,
public readonly ?int $maybeNumber = null,
) {}
}
// @doctest id="2fbf"
Arrays
Use @var docblocks to specify the element type for array properties. This information is included in the generated schema and helps the LLM produce correctly typed array elements.
final class WithArrays
{
public function __construct(
/** @var string[] List of tags */
public readonly array $tags,
/** @var int[] List of scores */
public readonly array $scores,
) {}
}
// @doctest id="bfdf"
Enums
Backed enums map to their underlying scalar type in the schema. The LLM receives the list of allowed values, which significantly improves extraction accuracy for categorical fields.
enum Priority: string
{
case Low = 'low';
case Medium = 'medium';
case High = 'high';
}
final class Task
{
public function __construct(
public readonly string $title,
public readonly Priority $priority,
) {}
}
// @doctest id="e106"
Nested Objects
Use another response model class as a property type to create hierarchical structures. The package generates nested schemas automatically.
final class Address
{
public function __construct(
public readonly string $street,
public readonly string $city,
public readonly string $country,
) {}
}
final class Person
{
public function __construct(
public readonly string $name,
public readonly Address $address,
) {}
}
// @doctest id="c659"
Collections
Combine nested objects with typed arrays for collections of structured items. The @var docblock on the array property tells the package which class each element should be deserialized into.
final class OrderItem
{
public function __construct(
public readonly string $product,
public readonly int $quantity,
public readonly float $price,
) {}
}
final class Order
{
public function __construct(
public readonly string $orderId,
/** @var OrderItem[] */
public readonly array $items,
public readonly float $total,
) {}
}
// @doctest id="3bc4"
Property Descriptions
Docblock comments on constructor properties serve as field-level instructions for the LLM. Clear, specific descriptions dramatically improve extraction accuracy — they are included verbatim in the JSON Schema sent to the model.
final class ProductReview
{
public function __construct(
/** The overall sentiment: positive, negative, or neutral */
public readonly string $sentiment,
/** A score from 1-5 indicating review quality */
public readonly int $rating,
/** Key points mentioned in the review */
public readonly array $highlights,
/** Any concerns or complaints raised */
public readonly ?array $concerns = null,
) {}
}
// @doctest id="ae36"
Using Response Models
use Cognesy\Instructor\Laravel\Facades\StructuredOutput;
use App\ResponseModels\PersonData;
$person = StructuredOutput::with(
messages: 'John Smith is a 30-year-old developer at john@example.com',
responseModel: PersonData::class,
)->get();
echo $person->name; // "John Smith"
echo $person->age; // 30
echo $person->email; // "john@example.com"
// @doctest id="5873"
With Array Schema
For quick prototyping or one-off extractions, you can pass a raw JSON Schema array instead of a class. The result is returned as an associative array rather than a typed object.
$person = StructuredOutput::with(
messages: 'John is 30 years old',
responseModel: [
'type' => 'object',
'properties' => [
'name' => ['type' => 'string', 'description' => 'Person name'],
'age' => ['type' => 'integer', 'description' => 'Person age'],
],
'required' => ['name', 'age'],
],
)->get();
echo $person['name']; // "John"
echo $person['age']; // 30
// @doctest id="bb9d"
To extract a list of objects from a single input, wrap the response model class in an array schema descriptor.
final class Product
{
public function __construct(
public readonly string $name,
public readonly float $price,
) {}
}
$products = StructuredOutput::with(
messages: 'Products: iPhone $999, MacBook $1299, AirPods $199',
responseModel: [
'type' => 'array',
'items' => Product::class,
],
)->get();
foreach ($products as $product) {
echo "{$product->name}: \${$product->price}\n";
}
// @doctest id="3343"
Validation
Using Symfony Validator
Add Symfony Validator constraint attributes to your properties for automatic validation. When the LLM’s response violates a constraint, the package sends the validation errors back to the model and retries (up to max_retries times).
use Symfony\Component\Validator\Constraints as Assert;
final class UserRegistration
{
public function __construct(
#[Assert\NotBlank]
#[Assert\Length(min: 2, max: 100)]
public readonly string $name,
#[Assert\NotBlank]
#[Assert\Email]
public readonly string $email,
#[Assert\Range(min: 18, max: 120)]
public readonly int $age,
) {}
}
// @doctest id="38b2"
Custom Validation
Implement the CanValidateObject contract for business-rule validation that goes beyond simple type and format checks. The validate method must return a ValidationResult instance.
use Cognesy\Instructor\Validation\Contracts\CanValidateObject;
use Cognesy\Instructor\Validation\ValidationResult;
class AgeValidator implements CanValidateObject
{
public function validate(object $dataObject): ValidationResult
{
if ($dataObject->age < 0) {
return ValidationResult::fieldError(
field: 'age',
value: $dataObject->age,
message: 'Age cannot be negative',
);
}
return ValidationResult::valid();
}
}
// @doctest id="5e1a"
Custom validators are registered on the StructuredOutputRuntime, not on the facade directly:
use Cognesy\Instructor\StructuredOutputRuntime;
use Cognesy\Polyglot\Inference\LLMProvider;
$runtime = StructuredOutputRuntime::fromProvider(LLMProvider::new())
->withValidator(new AgeValidator());
$user = StructuredOutput::withRuntime($runtime)->with(
messages: 'User: John, age -5',
responseModel: UserData::class,
)->get();
// @doctest id="004e"
Best Practices
1. Use Descriptive Property Names
Property names are part of the schema the LLM sees. Clear names reduce ambiguity and improve extraction accuracy.
final class CustomerContactData
{
// Good
public readonly string $customerEmailAddress;
// Less clear
public readonly string $email;
}
// @doctest id="45ef"
2. Add Detailed Descriptions
Docblock descriptions are your primary tool for steering the LLM. Be specific about formats, ranges, and edge cases.
final class ProductData
{
public function __construct(
/**
* The product SKU in format XXX-YYYY-ZZZ
* Example: ABC-1234-XYZ
*/
public readonly string $sku,
) {}
}
// @doctest id="7cad"
3. Use Appropriate Types
Choose the most specific type available. Enums are preferable to free-form strings for fields with a fixed set of values.
final class OrderLineData
{
// Use int for counts
public readonly int $quantity;
// Use float for prices
public readonly float $price;
// Use enums for fixed options
public readonly Status $status;
}
// @doctest id="dcbe"
4. Make Optional Properties Nullable
Distinguish between required and optional fields clearly. Required properties should not have defaults; optional ones should be nullable with a null default.
final class PersonData
{
public function __construct(
// Required
public readonly string $name,
// Optional
public readonly ?string $nickname = null,
) {}
}
// @doctest id="7343"
5. Use Readonly Properties
Readonly properties enforce immutability, which prevents accidental mutation of extracted data. This is the recommended approach for all response models.
final class ImmutablePersonData
{
// Immutable -- recommended
public readonly string $name;
}
final class MutablePersonData
{
// Mutable -- avoid unless necessary
public string $name;
}
// @doctest id="d087"
Generated Stubs
The make:response-model command generates from these stub types. Publish them with php artisan vendor:publish --tag=instructor-stubs to customize.
Basic Stub
final class {{ class }}
{
public function __construct(
/** The name of the person */
public readonly string $name,
/** The age of the person in years */
public readonly int $age,
/** Optional email address */
public readonly ?string $email = null,
) {}
}
// @doctest id="24e1"
Collection Stub (--collection)
final class {{ class }}
{
public function __construct(
/** List of extracted items */
public readonly array $items,
) {}
}
final class {{ class }}Item
{
public function __construct(
public readonly string $name,
public readonly ?string $description = null,
) {}
}
// @doctest id="54cc"
Nested Stub (--nested)
final class {{ class }}
{
public function __construct(
public readonly string $title,
public readonly {{ class }}Contact $contact,
public readonly ?{{ class }}Address $address = null,
) {}
}
final class {{ class }}Contact
{
public function __construct(
public readonly string $name,
public readonly string $email,
) {}
}
final class {{ class }}Address
{
public function __construct(
public readonly string $street,
public readonly string $city,
public readonly string $country,
) {}
}
// @doctest id="c3ce"