resources/boost/skills/neuron-structured-output/SKILL.md
Design and implement structured output classes for Neuron AI agents using SchemaProperty attributes and validation rules. Use this skill when the user mentions structured output, JSON schema extraction, data validation, output classes, DTOs for AI responses, extracting structured data from LLM, or configuring property schemas. Also trigger for any task involving SchemaProperty attribute, validation rules like NotBlank/Email/Url, nested objects, arrays of objects, enums, polymorphic types with anyOf, or the Validator class.
npx skillsauth add neuron-core/neuron-laravel neuron-structured-outputInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill helps you create structured output classes for extracting typed data from LLM responses in Neuron AI applications.
Neuron AI uses a two-layer approach for structured output:
use NeuronAI\StructuredOutput\SchemaProperty;
use NeuronAI\StructuredOutput\Validation\Rules\NotBlank;
class Person
{
#[SchemaProperty(description: 'The user name.', required: true)]
#[NotBlank]
public string $name;
}
The SchemaProperty attribute defines how properties are represented in the JSON schema sent to the LLM.
#[SchemaProperty(
string $title = null, // Property title in JSON schema
string $description = null, // Property description (guides LLM)
bool $required = null, // Override required status
int $min = null, // Minimum value (numbers) or items (arrays)
int $max = null, // Maximum value (numbers) or items (arrays)
int $minLength = null, // Minimum string length
int $maxLength = null, // Maximum string length
array $anyOf = null, // Array of allowed class/enum types
)]
| Argument | JSON Schema Output | Usage |
|----------|-------------------|-------|
| title | title: "..." | Human-readable property title |
| description | description: "..." | Critical: Guides LLM on what to generate |
| required: true | Adds to required array | Property must be present |
| required: false | Excludes from required | Property is optional |
| min | minimum (numbers) or minItems (arrays) | Lower bound constraint |
| max | maximum (numbers) or maxItems (arrays) | Upper bound constraint |
| minLength | minLength | Minimum string characters |
| maxLength | maxLength | Maximum string characters |
| anyOf | anyOf: [...] | Polymorphic types (multiple possible classes) |
Properties are required by default unless:
required: false is explicitly set in SchemaProperty?string $name)string $name = 'default')class Example
{
// Required - non-nullable, no default
public string $firstName;
// Optional - explicitly marked
#[SchemaProperty(required: false)]
public string $middleName;
// Optional - nullable
public ?string $lastName;
// Optional - has default
public string $country = 'US';
}
class Article
{
#[SchemaProperty(
description: 'The article title',
minLength: 10,
maxLength: 200
)]
public string $title;
#[SchemaProperty(description: 'Article content')]
public string $content;
}
Generated schema:
{
"title": {
"description": "The article title",
"type": "string",
"minLength": 10,
"maxLength": 200
},
"content": {
"description": "Article content",
"type": "string"
}
}
class Rating
{
#[SchemaProperty(
description: 'Rating from 1 to 5 stars',
min: 1,
max: 5
)]
public int $stars;
#[SchemaProperty(description: 'Price in dollars')]
public float $price;
}
Generated schema:
{
"stars": {
"description": "Rating from 1 to 5 stars",
"type": "integer",
"minimum": 1,
"maximum": 5
},
"price": {
"description": "Price in dollars",
"type": "number"
}
}
class Settings
{
#[SchemaProperty(description: 'Whether notifications are enabled')]
public bool $notificationsEnabled;
}
Define nested classes to create complex structures:
use NeuronAI\StructuredOutput\SchemaProperty;
use NeuronAI\StructuredOutput\Validation\Rules\NotBlank;
class Address
{
#[SchemaProperty(description: 'The street name')]
#[NotBlank]
public string $street;
public string $city;
#[SchemaProperty(description: 'Postal/ZIP code')]
public string $zip;
}
class Person
{
#[NotBlank]
public string $firstName;
public string $lastName;
// Nested object - type hint defines the schema
public Address $address;
}
Usage:
$person = $agent->structured(
new UserMessage('John Doe lives at 123 Main St, New York, 10001'),
Person::class
);
echo $person->address->city; // "New York"
class TagList
{
#[SchemaProperty(description: 'List of tags', min: 1, max: 10)]
public array $tags;
}
Use anyOf to specify the object type for arrays:
use NeuronAI\StructuredOutput\Validation\Rules\ArrayOf;
class Tag
{
#[SchemaProperty(description: 'The tag name')]
public string $name;
}
class Article
{
#[SchemaProperty(
description: 'Article tags',
anyOf: [Tag::class]
)]
#[ArrayOf(Tag::class)]
public array $tags;
}
Important: Use both:
SchemaProperty(anyOf: [Class::class]) - For JSON schema generation#[ArrayOf(Class::class)] - For validationclass TagProperty
{
#[SchemaProperty(description: 'The property value')]
public string $value;
}
class Tag
{
#[SchemaProperty(description: 'Tag name')]
public string $name;
#[SchemaProperty(
description: 'Additional tag properties',
anyOf: [TagProperty::class]
)]
#[ArrayOf(TagProperty::class)]
public array $properties;
}
class Person
{
public string $name;
#[SchemaProperty(anyOf: [Tag::class])]
#[ArrayOf(Tag::class)]
public array $tags;
}
enum Status: string
{
case ACTIVE = 'active';
case INACTIVE = 'inactive';
case PENDING = 'pending';
}
class User
{
public string $name;
// Enum type hint automatically generates enum schema
public Status $status;
}
Generated schema:
{
"status": {
"type": "string",
"enum": ["active", "inactive", "pending"]
}
}
enum Priority: int
{
case LOW = 1;
case MEDIUM = 2;
case HIGH = 3;
}
class Task
{
public string $title;
public Priority $priority;
}
Use anyOf when a property can be one of several types:
class FtpMode
{
public string $mode;
public string $account;
}
class EmailMode
{
public string $mode;
public string $mailingList;
}
class NotificationConfig
{
#[SchemaProperty(anyOf: [FtpMode::class, EmailMode::class])]
public array $modes;
}
For polymorphic arrays, Neuron uses a __classname__ discriminator field:
Generated schema includes:
{
"modes": {
"type": "array",
"items": {
"anyOf": [
{
"type": "object",
"properties": {
"__classname__": {
"type": "string",
"enum": ["ftpmode"],
"description": "This property is mandatory..."
},
"mode": {"type": "string"},
"account": {"type": "string"}
}
},
{
"type": "object",
"properties": {
"__classname__": {
"type": "string",
"enum": ["emailmode"]
},
"mode": {"type": "string"},
"mailingList": {"type": "string"}
}
}
]
}
}
}
The LLM must include __classname__ in responses for proper deserialization.
Validation rules verify LLM output. If validation fails, Neuron can retry the request.
use NeuronAI\StructuredOutput\Validation\Rules\NotBlank;
use NeuronAI\StructuredOutput\Validation\Rules\Length;
use NeuronAI\StructuredOutput\Validation\Rules\Email;
use NeuronAI\StructuredOutput\Validation\Rules\Url;
use NeuronAI\StructuredOutput\Validation\Rules\WordsCount;
class UserProfile
{
#[NotBlank] // Cannot be empty or whitespace only
public string $username;
#[Email]
public string $email;
#[Url]
public string $website;
#[Length(min: 10, max: 500)]
public string $bio;
#[WordsCount(min: 5, max: 100)]
public string $summary;
#[Length(exactly: 10)] // Must be exactly 10 characters
public string $code;
}
use NeuronAI\StructuredOutput\Validation\Rules\GreaterThan;
use NeuronAI\StructuredOutput\Validation\Rules\LowerThan;
use NeuronAI\StructuredOutput\Validation\Rules\OutOfRange;
use NeuronAI\StructuredOutput\Validation\Rules\EqualTo;
class Product
{
#[GreaterThan(0)]
public float $price;
#[LowerThan(1000)]
public int $stock;
#[OutOfRange(min: 0, max: 100)] // Must be within range
public int $discountPercentage;
#[EqualTo(42)]
public int $answer;
}
use NeuronAI\StructuredOutput\Validation\Rules\ArrayOf;
use NeuronAI\StructuredOutput\Validation\Rules\Count;
class Team
{
#[ArrayOf(User::class)]
public array $members;
#[Count(min: 1, max: 10)]
public array $tags;
#[Count(exactly: 3)]
public array $topThree;
#[ArrayOf('string')] // Array of scalar types
public array $categories;
}
use NeuronAI\StructuredOutput\Validation\Rules\IsTrue;
use NeuronAI\StructuredOutput\Validation\Rules\IsFalse;
use NeuronAI\StructuredOutput\Validation\Rules\IsNull;
use NeuronAI\StructuredOutput\Validation\Rules\IsNotNull;
class Settings
{
#[IsTrue]
public bool $agreedToTerms;
#[IsFalse]
public bool $isBlocked;
#[IsNotNull]
public ?string $optionalValue;
}
use NeuronAI\StructuredOutput\Validation\Rules\Enum;
class Order
{
// Validate against enum class
#[Enum(class: Status::class)]
public string $status;
// Validate against explicit values
#[Enum(values: ['urgent', 'normal', 'low'])]
public string $priority;
}
use NeuronAI\StructuredOutput\Validation\Rules\EqualTo;
use NeuronAI\StructuredOutput\Validation\Rules\NotEqualTo;
use NeuronAI\StructuredOutput\Validation\Rules\GreaterThanEqual;
use NeuronAI\StructuredOutput\Validation\Rules\LowerThanEqual;
class Comparison
{
#[EqualTo(100)]
public int $exactValue;
#[NotEqualTo(0)]
public int $nonZero;
#[GreaterThanEqual(1)]
public int $atLeastOne;
#[LowerThanEqual(10)]
public int $atMostTen;
}
use NeuronAI\StructuredOutput\Validation\Rules\Email;
use NeuronAI\StructuredOutput\Validation\Rules\Url;
use NeuronAI\StructuredOutput\Validation\Rules\IPAddress;
use NeuronAI\StructuredOutput\Validation\Rules\Json;
class Contact
{
#[Email]
public string $email;
#[Url]
public string $website;
#[IPAddress]
public string $serverIp;
#[Json] // Must be valid JSON string
public string $metadata;
}
| Rule | Description | Example |
|------|-------------|---------|
| NotBlank | Not empty/whitespace | #[NotBlank(allowNull: true)] |
| Length | String length bounds | #[Length(min: 1, max: 100)] |
| WordsCount | Word count bounds | #[WordsCount(min: 5, max: 50)] |
| Email | Valid email format | #[Email] |
| Url | Valid URL format | #[Url] |
| IPAddress | Valid IP address | #[IPAddress] |
| Json | Valid JSON string | #[Json] |
| Enum | Value in allowed list | #[Enum(class: Status::class)] |
| ArrayOf | Array of specific type | #[ArrayOf(User::class)] |
| Count | Array item count | #[Count(min: 1, max: 10)] |
| GreaterThan | value > reference | #[GreaterThan(0)] |
| GreaterThanOrEqual | value >= reference | #[GreaterThanEqual(0)] |
| LowerThan | value < reference | #[LowerThan(100)] |
| LowerThanOrEqual | value <= reference | #[LowerThanEqual(100)] |
| OutOfRange | Value not in range | #[OutOfRange(min: 0, max: 100)] |
| EqualTo | Exact match | #[EqualTo(42)] |
| NotEqualTo | Not equal | #[NotEqualTo(0)] |
| IsTrue | Boolean true | #[IsTrue] |
| IsFalse | Boolean false | #[IsFalse] |
| IsNull | Must be null | #[IsNull] |
| IsNotNull | Must not be null | #[IsNotNull] |
use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
// Define output class
class Person
{
#[SchemaProperty(description: 'The person full name')]
public string $name;
#[SchemaProperty(description: 'What the person likes to eat')]
public string $favoriteFood;
#[SchemaProperty(description: 'Age in years', min: 0, max: 150)]
public int $age;
}
// Use with agent
$agent = MyAgent::make();
$person = $agent->structured(
new UserMessage("I'm John Doe, I'm 30 years old and I love pizza!"),
Person::class
);
echo $person->name; // "John Doe"
echo $person->age; // 30
echo $person->favoriteFood; // "pizza"
use NeuronAI\StructuredOutput\Validation\Validator;
$person = new Person();
$person->name = '';
$person->age = -5;
$violations = Validator::validate($person);
if ($violations !== []) {
foreach ($violations as $violation) {
echo $violation . "\n";
}
}
use NeuronAI\StructuredOutput\Deserializer\Deserializer;
$json = '{"name": "John", "age": 30}';
$person = Deserializer::make()->fromJson($json, Person::class);
Properties with default values are optional:
class Settings
{
public string $theme = 'light';
public int $timeout = 30;
public bool $debug = false;
}
Generated schema includes defaults:
{
"theme": {"type": "string", "default": "light"},
"timeout": {"type": "integer", "default": 30},
"debug": {"type": "boolean", "default": false}
}
class Event
{
public string $name;
public DateTime $startDate;
public DateTimeImmutable $createdAt;
}
Deserializer handles various date formats:
"2024-01-15T10:30:00Z"1705320600"next Monday"// BAD - LLM doesn't know what to generate
public string $value;
// GOOD - Clear guidance for LLM
#[SchemaProperty(description: 'The monetary value in USD, must be positive')]
public float $value;
class UserRegistration
{
#[NotBlank]
#[Email]
public string $email;
#[Length(min: 8, max: 64)]
public string $password;
#[NotBlank]
public string $username;
}
// BAD - Too many fields, harder for LLM
class UserProfile
{
public string $name;
public string $email;
public string $phone;
public string $address;
public string $city;
public string $country;
public string $bio;
public string $website;
public string $company;
public string $title;
// ... 20 more fields
}
// GOOD - Focused on what you need
class UserContact
{
public string $name;
public string $email;
}
// BAD - LLM might return unexpected values
#[SchemaProperty(description: 'Priority: low, medium, or high')]
public string $priority;
// GOOD - Constrained options
enum Priority: string
{
case LOW = 'low';
case MEDIUM = 'medium';
case HIGH = 'high';
}
public Priority $priority;
class Rating
{
// Both guide LLM AND validate output
#[SchemaProperty(description: 'Rating from 1 to 5', min: 1, max: 5)]
#[GreaterThan(0)]
#[LowerThan(6)]
public int $stars;
}
class Contact
{
#[NotBlank]
public string $name;
#[Email]
public string $email;
#[Length(min: 10, max: 15)]
public string $phone;
}
class Address
{
#[NotBlank]
public string $street;
#[NotBlank]
public string $city;
#[NotBlank]
#[Length(exactly: 5)]
public string $zipCode;
public string $country;
}
class Product
{
#[NotBlank]
public string $name;
#[SchemaProperty(description: 'Price in USD', min: 0)]
public float $price;
#[Count(min: 1)]
public array $categories;
public bool $inStock;
}
class Tag
{
#[NotBlank]
public string $name;
}
class Article
{
#[NotBlank]
public string $title;
#[WordsCount(min: 50, max: 500)]
public string $summary;
#[SchemaProperty(anyOf: [Tag::class])]
#[ArrayOf(Tag::class)]
public array $tags;
}
development
Build custom Neuron AI workflows with nodes, events, middleware, and human-in-the-loop patterns. Use this skill whenever the user mentions workflows, orchestration, event-driven systems, custom agents, complex multi-step processes, human-in-the-loop patterns, or wants to build a custom agentic system from scratch. Also trigger for tasks involving node creation, event routing, workflow middleware, persistence, or interruption patterns.
tools
Create custom tools, toolkits, and MCP integrations for Neuron AI agents. Use this skill when the user mentions creating tools, building toolkits, extending Tool class, defining tool properties, implementing tool execution, MCP server integration, Model Context Protocol, connecting external tools, or tool guidelines. Also trigger for any task involving ToolProperty, ArrayProperty, ObjectProperty, AbstractToolkit, McpConnector, or StdioTransport/SseHttpTransport/StreamableHttpTransport.
tools
Write tests for Neuron AI agents, RAG systems, workflows, and tools using the built-in testing utilities. Use this skill when the user mentions testing agents, writing unit tests, mocking AI providers, testing tool execution, verifying RAG retrieval, testing workflow behavior, or creating test cases for Neuron AI components. Also trigger for any task involving PHPUnit tests, fake providers, test assertions, or quality assurance in Neuron AI projects.
development
Implement RAG (Retrieval-Augmented Generation) with Neuron AI including vector stores, embeddings providers, document loaders, and retrieval strategies. Use this skill whenever the user mentions RAG, retrieval, vector search, document retrieval, semantic search, knowledge bases, chat with documents, or wants to build AI systems that can query and understand external documents. Also trigger for tasks involving vector databases, embeddings, document chunking, or retrieval strategies.