tryparse-derive
Procedural macro for tryparse. Provides the LlmDeserialize derive macro 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
The #[derive(LlmDeserialize)] macro generates fuzzy deserialization implementations that handle:
- Fuzzy field matching: camelCase ↔ snake_case, case-insensitive
- Fuzzy enum matching: case-insensitive, substring matching, edit distance
- Union types: Automatic variant selection with
#[llm(union)] - Type coercion: String numbers, array unwrapping, etc.
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!;
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)] |
| Parsing enums where LLM might use different casings | #[derive(LlmDeserialize)] |
| LLM outputs with typos in enum variants | #[derive(LlmDeserialize)] (edit-distance matching) |
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 LlmDeserialize;
// LLM returns inconsistent format - handles all of these issues:
// - camelCase instead of snake_case (apiKey → api_key)
// - String number ("3" → 3)
// - Case mismatch ("enabled" → Status::Enabled)
// - Missing optional field (timeout_ms)
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
// }
License
Apache-2.0