#[derive(Instructor)]
{
// Attributes available to this derive:
#[llm]
}
Expand description
Derive macro for implementing Instructor and SchemaType
This macro automatically implements the SchemaType trait for a struct or enum, generating a JSON Schema representation based on the Rust type.
§Nested Types and Schema Embedding
When you have nested structs or enums, they should also derive Instructor
to ensure their full schema is embedded in the parent type. This produces
complete JSON schemas that help LLMs generate correct structured output.
use rstructor::Instructor;
use serde::{Serialize, Deserialize};
// Parent type derives Instructor
#[derive(Instructor, Serialize, Deserialize)]
struct Parent {
child: Child, // Child's schema will be embedded
}
// Nested types should also derive Instructor for complete schema
#[derive(Instructor, Serialize, Deserialize)]
struct Child {
name: String,
}The schema embedding happens at compile time, avoiding any runtime overhead.
§Validation
To add custom validation, use the validate attribute with a function path:
use rstructor::{Instructor, RStructorError};
use serde::{Serialize, Deserialize};
#[derive(Instructor, Serialize, Deserialize)]
#[llm(validate = "validate_product")]
struct Product {
name: String,
price: f64,
}
fn validate_product(product: &Product) -> rstructor::Result<()> {
if product.price <= 0.0 {
return Err(RStructorError::ValidationError(
"price must be positive".into()
));
}
Ok(())
}The validation function is called automatically when the LLM response is deserialized.
§Examples
§Field-level attributes
use rstructor::Instructor;
use serde::{Serialize, Deserialize};
#[derive(Instructor, Serialize, Deserialize, Debug)]
struct Person {
#[llm(description = "Full name of the person")]
name: String,
#[llm(description = "Age of the person in years", example = 30)]
age: u32,
#[llm(description = "List of skills", example = ["Programming", "Writing", "Design"])]
skills: Vec<String>,
}§Container-level attributes
You can add additional information to the struct or enum itself:
use rstructor::Instructor;
use serde::{Serialize, Deserialize};
#[derive(Instructor, Serialize, Deserialize, Debug)]
#[llm(description = "Represents a person with their basic information",
title = "PersonDetail",
examples = [
::serde_json::json!({"name": "John Doe", "age": 30}),
::serde_json::json!({"name": "Jane Smith", "age": 25})
])]
struct Person {
#[llm(description = "Full name of the person")]
name: String,
#[llm(description = "Age of the person in years")]
age: u32,
}
#[derive(Instructor, Serialize, Deserialize, Debug)]
#[llm(description = "Represents a person's role in an organization")]
#[serde(rename_all = "camelCase")]
struct Employee {
first_name: String,
last_name: String,
employee_id: u32,
}
#[derive(Instructor, Serialize, Deserialize, Debug)]
#[llm(description = "Represents a person's role in an organization",
examples = ["Manager", "Director"])]
enum Role {
Employee,
Manager,
Director,
Executive,
}§Container Attributes
description: A description of the struct or enumtitle: A custom title for the JSON Schema (defaults to the type name)examples: Example instances of the struct or enumvalidate: A quoted Rust path to a custom validation function
§Field and Variant Attributes
Fields accept description, example, and examples. Enum variants accept
description. Field optionality is inferred from Option<T>; there is no
optional attribute.
The llm namespace is checked strictly. Unknown attributes, malformed
values, invalid validation paths, and unsupported tuple/unit structs produce
errors at the relevant source span instead of being ignored.
§Serde Integration
Supported Serde name and skip metadata is interpreted from the deserialization side of the wire contract:
- Respects
rename,rename_all, andrename_all_fields - Uses
deserialize = "..."when names differ by direction - Omits fields and variants marked
skiporskip_deserializing - Keeps
skip_serializingfields because they remain valid inputs
Supported case transformations include “lowercase”, “UPPERCASE”,
“camelCase”, “PascalCase”, and “snake_case”. For example, with
#[serde(rename_all = "camelCase")], user_id becomes userId.