tryparse-derive
Procedural macros for tryparse. Provides derive macros for parsing messy LLM outputs with fuzzy field matching, enum matching, and union types.
Usage
Add to your Cargo.toml:
[]
= { = "0.1", = ["derive"] }
= "0.1"
What This Provides
Two derive macros:
LlmDeserialize- Fuzzy deserialization for LLM responsesSchemaInfo- Runtime schema introspection
LlmDeserialize
Without LlmDeserialize (serde only)
use Deserialize;
use parse;
// ✅ Works - exact match
let json = r#"{"user_name": "Alice", "max_count": 30}"#;
let user: User = parse.unwrap;
// ❌ Fails - field name mismatch
let json = r#"{"userName": "Alice", "maxCount": 30}"#;
let user: User = parse.unwrap; // Error: unknown field `userName`
With LlmDeserialize
use parse_llm;
use LlmDeserialize;
// ✅ All of these work
let json = r#"{"userName": "Alice", "maxCount": 30}"#;
let user: User = parse_llm.unwrap;
let json = r#"{"UserName": "Alice", "MaxCount": 30}"#;
let user: User = parse_llm.unwrap;
let json = r#"{"user-name": "Alice", "max-count": 30}"#;
let user: User = parse_llm.unwrap;
Fuzzy Enum Matching
use LlmDeserialize;
// All of these parse correctly
let s: Status = parse_llm.unwrap; // Status::InProgress
let s: Status = parse_llm.unwrap; // Status::Completed
let s: Status = parse_llm.unwrap; // Status::Cancelled
Union Types
Automatically picks the best matching variant based on structure:
use LlmDeserialize;
// Required attribute for union behavior
// Parses as Number(42)
let v: Value = parse_llm.unwrap;
// Parses as Text("hello")
let v: Value = parse_llm.unwrap;
Union matching uses a scoring algorithm to pick the variant with the least type coercions.
Implied Key (Single-Field Unwrapping)
When a struct has a single field, the value can be provided directly:
use LlmDeserialize;
// Instead of requiring {"data": "hello"}
// You can pass the value directly
let w: Wrapper = parse_llm.unwrap;
assert_eq!;
SchemaInfo
Generates runtime schema information for introspection:
use SchemaInfo;
use SchemaInfo;
// Get schema at runtime
let schema = schema;
println!;
// Schema::Object {
// name: "User",
// fields: [
// Field { name: "name", schema: String, required: true },
// Field { name: "age", schema: Int, required: true },
// Field { name: "email", schema: Optional(String), required: false }
// ]
// }
Works with enums too:
use SchemaInfo;
let schema = schema;
// Schema::Union {
// name: "Status",
// variants: [
// Variant { name: "Active", schema: Null },
// Variant { name: "Pending", schema: Null },
// Variant { name: "Completed", schema: Object { ... } }
// ]
// }
When to Use
| Scenario | Use This |
|---|---|
| Strict JSON from well-behaved APIs | serde::Deserialize (no derive macro needed) |
| LLM responses with inconsistent field names | #[derive(LlmDeserialize)] |
| Need to handle multiple possible types | #[derive(LlmDeserialize)] with #[llm(union)] |
| Runtime schema inspection | #[derive(SchemaInfo)] |
| Parsing enums where LLM might use different casings | #[derive(LlmDeserialize)] |
Technical Notes
- This is a procedural macro crate (separate from
tryparsedue to Rust compiler requirements) LlmDeserializegenerates implementations that use BAML's fuzzy matching algorithms- Field matching normalizes to snake_case and matches case-insensitively
- Union types try strict matching first, then fall back to lenient matching with scoring
- All transformations are tracked for debugging (see
tryparsedocs)
Example: Complete Usage
use parse_llm;
use ;
// LLM returns inconsistent format
let llm_output = r#"
{
"apiKey": "secret",
"maxRetries": "3",
"status": "enabled"
}
"#;
let config: Config = parse_llm.unwrap;
println!;
// Config {
// api_key: "secret",
// max_retries: 3,
// timeout_ms: None,
// status: Status::Enabled
// }
// Inspect schema
let schema = schema;
println!;
License
Apache-2.0