{"id":27056284,"url":"https://github.com/clifton/rstructor","last_synced_at":"2026-04-01T20:43:56.796Z","repository":{"id":284633729,"uuid":"955567690","full_name":"clifton/rstructor","owner":"clifton","description":"Pydantic + Instructor for Rust","archived":false,"fork":false,"pushed_at":"2026-02-06T19:35:49.000Z","size":459,"stargazers_count":17,"open_issues_count":1,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-07T05:43:36.659Z","etag":null,"topics":["declarative","instructor","llm-tools","llms","pydantic","rust","serde-json"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/clifton.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-03-26T21:00:37.000Z","updated_at":"2026-02-06T19:36:25.000Z","dependencies_parsed_at":"2025-08-01T16:23:51.852Z","dependency_job_id":"70e72574-dedb-484a-b411-45552a925ee2","html_url":"https://github.com/clifton/rstructor","commit_stats":null,"previous_names":["clifton/rstructor"],"tags_count":54,"template":false,"template_full_name":null,"purl":"pkg:github/clifton/rstructor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clifton%2Frstructor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clifton%2Frstructor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clifton%2Frstructor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clifton%2Frstructor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/clifton","download_url":"https://codeload.github.com/clifton/rstructor/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clifton%2Frstructor/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31291781,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["declarative","instructor","llm-tools","llms","pydantic","rust","serde-json"],"created_at":"2025-04-05T10:16:46.522Z","updated_at":"2026-04-01T20:43:56.784Z","avatar_url":"https://github.com/clifton.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# rstructor: Structured LLM Outputs for Rust\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://crates.io/crates/rstructor\"\u003e\u003cimg src=\"https://img.shields.io/crates/v/rstructor\" alt=\"crates.io\"/\u003e\u003c/a\u003e\n  \u003ca href=\"https://crates.io/crates/rstructor\"\u003e\u003cimg src=\"https://img.shields.io/crates/d/rstructor\" alt=\"downloads\"/\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/clifton/rstructor/actions\"\u003e\u003cimg src=\"https://github.com/clifton/rstructor/actions/workflows/test.yml/badge.svg\" alt=\"CI\"/\u003e\u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/rust-2024-orange\" alt=\"Rust 2024\"/\u003e\n  \u003cimg src=\"https://img.shields.io/badge/license-MIT-blue\" alt=\"MIT\"/\u003e\n\u003c/p\u003e\n\nExtract structured, validated data from LLMs using native Rust types. Define your schema as structs/enums, and rstructor handles JSON Schema generation, API communication, parsing, and validation.\n\nThe Rust equivalent of [Instructor](https://github.com/jxnl/instructor) for Python.\n\n## Features\n\n- **Type-safe schemas** — Define models as Rust structs/enums with derive macros\n- **Multi-provider** — OpenAI, Anthropic, Grok (xAI), and Gemini with unified API\n- **Auto-validation** — Type checking plus custom business rules with automatic retry\n- **Complex types** — Nested objects, arrays, optionals, enums with associated data\n- **Extended thinking** — Native support for reasoning models (GPT-5.2, Claude 4.5, Gemini 3)\n\n## Installation\n\n```toml\n[dependencies]\nrstructor = \"0.2\"\nserde = { version = \"1.0\", features = [\"derive\"] }\ntokio = { version = \"1.0\", features = [\"rt-multi-thread\", \"macros\"] }\n```\n\n## Quick Start\n\n```rust\nuse rstructor::{Instructor, LLMClient, OpenAIClient};\nuse serde::{Deserialize, Serialize};\n\n#[derive(Instructor, Serialize, Deserialize, Debug)]\nstruct Movie {\n    #[llm(description = \"Title of the movie\")]\n    title: String,\n    #[llm(description = \"Director of the movie\")]\n    director: String,\n    #[llm(description = \"Year released\", example = 2010)]\n    year: u16,\n}\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    let client = OpenAIClient::from_env()?\n        .temperature(0.0);\n\n    let movie: Movie = client.materialize(\"Tell me about Inception\").await?;\n    println!(\"{}: {} ({})\", movie.title, movie.director, movie.year);\n    Ok(())\n}\n```\n\n## Providers\n\n```rust\nuse rstructor::{OpenAIClient, AnthropicClient, GrokClient, GeminiClient, LLMClient};\n\n// OpenAI (reads OPENAI_API_KEY)\nlet client = OpenAIClient::from_env()?.model(\"gpt-5.2\");\n\n// Anthropic (reads ANTHROPIC_API_KEY)\nlet client = AnthropicClient::from_env()?.model(\"claude-opus-4-6\");\n\n// Grok/xAI (reads XAI_API_KEY)\nlet client = GrokClient::from_env()?.model(\"grok-4-1-fast-non-reasoning\");\n\n// Gemini (reads GEMINI_API_KEY)\nlet client = GeminiClient::from_env()?.model(\"gemini-3-flash-preview\");\n\n// Custom endpoint (local LLMs, proxies)\nlet client = OpenAIClient::new(\"key\")?\n    .base_url(\"http://localhost:1234/v1\")\n    .model(\"llama-3.1-70b\");\n```\n\n## Validation\n\nAdd custom validation with automatic retry on failure:\n\n```rust\nuse rstructor::{Instructor, RStructorError, Result};\n\n#[derive(Instructor, Serialize, Deserialize)]\n#[llm(validate = \"validate_movie\")]\nstruct Movie {\n    title: String,\n    year: u16,\n    rating: f32,\n}\n\nfn validate_movie(movie: \u0026Movie) -\u003e Result\u003c()\u003e {\n    if movie.year \u003c 1888 || movie.year \u003e 2030 {\n        return Err(RStructorError::ValidationError(\n            format!(\"Invalid year: {}\", movie.year)\n        ));\n    }\n    if movie.rating \u003c 0.0 || movie.rating \u003e 10.0 {\n        return Err(RStructorError::ValidationError(\n            format!(\"Rating must be 0-10, got {}\", movie.rating)\n        ));\n    }\n    Ok(())\n}\n\n// Retries are enabled by default (3 attempts with error feedback)\n// To increase retries:\nlet client = OpenAIClient::from_env()?.max_retries(5);\n\n// To disable retries:\nlet client = OpenAIClient::from_env()?.no_retries();\n```\n\n## Complex Types\n\n### Nested Structures\n\n```rust\n#[derive(Instructor, Serialize, Deserialize)]\nstruct Ingredient {\n    name: String,\n    amount: f32,\n    unit: String,\n}\n\n#[derive(Instructor, Serialize, Deserialize)]\nstruct Recipe {\n    name: String,\n    ingredients: Vec\u003cIngredient\u003e,\n    prep_time_minutes: u16,\n}\n```\n\n### Enums with Data\n\n```rust\n#[derive(Instructor, Serialize, Deserialize)]\nenum PaymentMethod {\n    #[llm(description = \"Credit card payment\")]\n    Card { number: String, expiry: String },\n    #[llm(description = \"PayPal account\")]\n    PayPal(String),\n    #[llm(description = \"Cash on delivery\")]\n    CashOnDelivery,\n}\n```\n\n### Serde Rename Support\n\nrstructor respects `#[serde(rename)]` and `#[serde(rename_all)]` attributes:\n\n```rust\n#[derive(Instructor, Serialize, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct UserProfile {\n    first_name: String,      // becomes \"firstName\" in schema\n    last_name: String,       // becomes \"lastName\" in schema\n    email_address: String,   // becomes \"emailAddress\" in schema\n}\n\n#[derive(Instructor, Serialize, Deserialize)]\nstruct CommitMessage {\n    #[serde(rename = \"type\")]  // use \"type\" as JSON key\n    commit_type: String,\n    description: String,\n}\n\n#[derive(Instructor, Serialize, Deserialize)]\n#[serde(rename_all = \"lowercase\")]\nenum CommitType {\n    Fix,       // becomes \"fix\"\n    Feat,      // becomes \"feat\"\n    Refactor,  // becomes \"refactor\"\n}\n```\n\nSupported case conversions: `lowercase`, `UPPERCASE`, `camelCase`, `PascalCase`, `snake_case`, `SCREAMING_SNAKE_CASE`, `kebab-case`, `SCREAMING-KEBAB-CASE`.\n\n### Custom Types (Dates, UUIDs)\n\n```rust\nuse chrono::{DateTime, Utc};\nuse rstructor::schema::CustomTypeSchema;\n\nimpl CustomTypeSchema for DateTime\u003cUtc\u003e {\n    fn schema_type() -\u003e \u0026'static str { \"string\" }\n    fn schema_format() -\u003e Option\u003c\u0026'static str\u003e { Some(\"date-time\") }\n}\n\n#[derive(Instructor, Serialize, Deserialize)]\nstruct Event {\n    name: String,\n    start_time: DateTime\u003cUtc\u003e,\n}\n```\n\n## Multimodal (Image Input)\n\nAnalyze images with structured extraction across all major providers using `materialize_with_media`:\n\n```rust\nuse rstructor::{Instructor, LLMClient, OpenAIClient, MediaFile};\n\n#[derive(Instructor, Serialize, Deserialize, Debug)]\nstruct ImageAnalysis {\n    subject: String,\n    summary: String,\n}\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    // Download or load image bytes (real-world fixture)\n    let image_bytes = reqwest::get(\"https://example.com/image.png\")\n        .await?.bytes().await?;\n\n    // Inline media is base64-encoded automatically\n    let media = MediaFile::from_bytes(\u0026image_bytes, \"image/png\");\n\n    // Works with OpenAI, Anthropic, Grok, and Gemini clients\n    let client = OpenAIClient::from_env()?;\n    let analysis: ImageAnalysis = client\n        .materialize_with_media(\"Describe this image\", \u0026[media])\n        .await?;\n    println!(\"{:?}\", analysis);\n    Ok(())\n}\n```\n\n`MediaFile::new(uri, mime_type)` is also available for URL/URI-based media input.\n\nProvider examples:\n- `cargo run --example openai_multimodal_example --features openai`\n- `cargo run --example anthropic_multimodal_example --features anthropic`\n- `cargo run --example grok_multimodal_example --features grok`\n- `cargo run --example gemini_multimodal_example --features gemini`\n\n## Extended Thinking\n\nConfigure reasoning depth for supported models:\n\n```rust\nuse rstructor::ThinkingLevel;\n\n// GPT-5.2, Claude 4.5 (Sonnet/Opus), Gemini 3\nlet client = OpenAIClient::from_env()?\n    .model(\"gpt-5.2\")\n    .thinking_level(ThinkingLevel::High);\n\n// Levels: Off, Minimal, Low, Medium, High\n```\n\n## Token Usage\n\n```rust\nlet result = client.materialize_with_metadata::\u003cMovie\u003e(\"...\").await?;\nprintln!(\"Movie: {}\", result.data.title);\nif let Some(usage) = result.usage {\n    println!(\"Tokens: {} in, {} out\", usage.input_tokens, usage.output_tokens);\n}\n```\n\n## Error Handling\n\n```rust\nuse rstructor::{ApiErrorKind, RStructorError};\n\nmatch client.materialize::\u003cMovie\u003e(\"...\").await {\n    Ok(movie) =\u003e println!(\"{:?}\", movie),\n    Err(e) if e.is_retryable() =\u003e {\n        println!(\"Transient error: {}\", e);\n        if let Some(delay) = e.retry_delay() {\n            tokio::time::sleep(delay).await;\n        }\n    }\n    Err(e) =\u003e match e.api_error_kind() {\n        Some(ApiErrorKind::RateLimited { retry_after }) =\u003e { /* ... */ }\n        Some(ApiErrorKind::AuthenticationFailed) =\u003e { /* ... */ }\n        _ =\u003e eprintln!(\"Error: {}\", e),\n    }\n}\n```\n\n## Feature Flags\n\n```toml\n[dependencies]\nrstructor = { version = \"0.2\", features = [\"openai\", \"anthropic\", \"grok\", \"gemini\"] }\n```\n\n- `openai`, `anthropic`, `grok`, `gemini` — Provider backends\n- `derive` — Derive macro (default)\n- `logging` — Tracing integration\n\n## Examples\n\nSee `examples/` for complete working examples:\n\n```bash\nexport OPENAI_API_KEY=your_key\ncargo run --example structured_movie_info\ncargo run --example nested_objects_example\ncargo run --example enum_with_data_example\ncargo run --example serde_rename_example\ncargo run --example gemini_multimodal_example\n```\n\n## For Python Developers\n\nIf you're coming from Python and searching for:\n- **\"pydantic rust\"** or **\"rust pydantic\"** — rstructor provides similar schema validation and type safety\n- **\"instructor rust\"** or **\"rust instructor\"** — same structured LLM output extraction pattern\n- **\"structured output rust\"** or **\"llm structured output\"** — exactly what rstructor does\n- **\"type-safe llm rust\"** — ensures type safety from LLM responses to Rust structs\n\n## License\n\nMIT — see [LICENSE](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclifton%2Frstructor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclifton%2Frstructor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclifton%2Frstructor/lists"}