-
Notifications
You must be signed in to change notification settings - Fork 0
Structured Output with Parse Retries
Arian Amiramjadi edited this page Dec 24, 2025
·
1 revision
Instructor-style structured output extraction with automatic retry on parse errors. When parsing fails, the library tells the model what went wrong and asks for correction.
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email"`
}
var person Person
err := ai.Claude().
IntoWithRetry("Extract person info: John Smith, 32, [email protected]", &person, 3)
fmt.Printf("%+v\n", person)
// {Name:John Smith Age:32 Email:[email protected]}The original Into method parses without retries:
var result MyStruct
err := ai.Claude().Into("Extract from this text...", &result)Parse with automatic retry on failure. The model receives error feedback:
var result MyStruct
err := ai.Claude().IntoWithRetry("Extract from this text...", &result, 3)
// Retries up to 3 times if parsing failsParse or panic (useful for scripts and tests):
var result MyStruct
ai.Claude().MustInto("Extract from this text...", &result)When parsing fails, the library:
- Sends the failed response back to the model
- Explains what went wrong (JSON syntax error, missing field, wrong type)
- Asks the model to fix and retry
- Repeats until success or max retries exhausted
Attempt 1: Model returns invalid JSON
→ "Your response had an error: unexpected end of JSON input"
→ "Please fix the JSON and try again"
Attempt 2: Model returns valid JSON but missing field
→ "Your response had an error: field 'email' is required"
→ "Please fix the JSON and try again"
Attempt 3: Success!
Type-safe extraction with generics:
type Product struct {
Name string `json:"name"`
Price float64 `json:"price"`
}
product, err := ai.Extract[Product](ai.Claude(), "iPhone 15 costs $999")With custom retry count:
product, err := ai.ExtractWithRetry[Product](ai.Claude(), "iPhone 15 costs $999", 5)Extract multiple items:
products, err := ai.ExtractList[Product](ai.Claude(),
"Products: iPhone $999, MacBook $1299, AirPods $249")
for _, p := range products {
fmt.Printf("%s: $%.2f\n", p.Name, p.Price)
}Add custom validation logic to your structs:
type Order struct {
ID string `json:"id"`
Amount float64 `json:"amount"`
Currency string `json:"currency"`
}
// Implement the StructValidator interface
func (o *Order) Validate() error {
if o.Amount <= 0 {
return fmt.Errorf("amount must be positive")
}
if o.Currency == "" {
return fmt.Errorf("currency is required")
}
if len(o.Currency) != 3 {
return fmt.Errorf("currency must be 3 characters (e.g., USD)")
}
return nil
}
// Validation runs automatically after parsing
var order Order
err := ai.Claude().IntoWithRetry("Order #123 for $50", &order, 3)
// If validation fails, model is asked to fix the outputBuilt-in validation functions:
func (o *Order) Validate() error {
// Check required fields
if err := ai.ValidateRequired(o, "ID", "Amount", "Currency"); err != nil {
return err
}
// Check string length
if err := ai.ValidateStringLength(o.ID, 1, 50); err != nil {
return err
}
// Check enum values
if err := ai.ValidateOneOf(o.Currency, "USD", "EUR", "GBP"); err != nil {
return err
}
return nil
}Full control with ParseConfig:
config := &ai.ParseConfig{
MaxRetries: 5, // retry up to 5 times
ValidateOutput: true, // run struct validators
IncludeSchema: true, // include JSON schema in prompt
StrictMode: false, // allow extra fields
Timeout: 30 * time.Second, // timeout per attempt
}
err := ai.Claude().ParseIntoWithConfig("Extract...", &result, config)Classify text into categories:
labels := []string{"spam", "not_spam", "unsure"}
result, err := ai.Classify(ai.Claude(), "Win a free iPhone now!!!", labels)
fmt.Printf("Label: %s (%.0f%% confidence)\n", result.Label, result.Confidence*100)
fmt.Printf("Reason: %s\n", result.Reasoning)
// Label: spam (95% confidence)
// Reason: Contains common spam indicators like "free" and "Win"Extract named entities:
entities, err := ai.ExtractEntities(ai.Claude(),
"Apple CEO Tim Cook announced the iPhone 15 in Cupertino",
[]string{"person", "company", "product", "location"})
for _, e := range entities {
fmt.Printf("%s: %s (%s)\n", e.Type, e.Name, e.Value)
}
// company: Apple
// person: Tim Cook
// product: iPhone 15
// location: CupertinoSentiment analysis:
sentiment, err := ai.AnalyzeSentiment(ai.Claude(),
"I absolutely love this product! Best purchase ever!")
fmt.Printf("Sentiment: %s (%.2f)\n", sentiment.Label, sentiment.Score)
fmt.Printf("Emotions: %v\n", sentiment.Emotions)
// Sentiment: positive (0.95)
// Emotions: [joy excitement satisfaction]type Event struct {
Name string `json:"name"` // required
Date string `json:"date"` // required
Location string `json:"location,omitempty"` // optional
Attendees int `json:"attendees,omitempty"` // optional
}Use the desc tag for field descriptions:
type Analysis struct {
Summary string `json:"summary" desc:"Brief 1-2 sentence summary"`
KeyPoints []string `json:"key_points" desc:"3-5 main points"`
Sentiment string `json:"sentiment" desc:"positive, negative, or neutral"`
Confidence float64 `json:"confidence" desc:"0.0 to 1.0"`
}type Article struct {
Title string `json:"title"`
Author Author `json:"author"`
Sections []Section `json:"sections"`
}
type Author struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
}
type Section struct {
Heading string `json:"heading"`
Content string `json:"content"`
}package main
import (
"fmt"
ai "gopkg.in/dragon-born/go-llm.v1"
)
type Recipe struct {
Name string `json:"name"`
PrepTime int `json:"prep_time" desc:"Preparation time in minutes"`
CookTime int `json:"cook_time" desc:"Cooking time in minutes"`
Servings int `json:"servings"`
Ingredients []Ingredient `json:"ingredients"`
Steps []string `json:"steps"`
}
type Ingredient struct {
Name string `json:"name"`
Amount float64 `json:"amount"`
Unit string `json:"unit"`
}
func (r *Recipe) Validate() error {
if r.Name == "" {
return fmt.Errorf("recipe name is required")
}
if len(r.Ingredients) == 0 {
return fmt.Errorf("at least one ingredient is required")
}
if len(r.Steps) == 0 {
return fmt.Errorf("at least one step is required")
}
return nil
}
func main() {
prompt := `Extract a recipe from this text:
Grandma's Chocolate Chip Cookies
Mix 2 cups flour, 1 cup sugar, and 1 cup chocolate chips.
Add 2 eggs and 1 cup butter. Mix well.
Bake at 350°F for 12 minutes. Makes 24 cookies.`
var recipe Recipe
err := ai.Claude().
ThinkLow().
IntoWithRetry(prompt, &recipe, 3)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Recipe: %s\n", recipe.Name)
fmt.Printf("Prep: %d min, Cook: %d min\n", recipe.PrepTime, recipe.CookTime)
fmt.Printf("Servings: %d\n", recipe.Servings)
fmt.Println("\nIngredients:")
for _, ing := range recipe.Ingredients {
fmt.Printf(" - %.1f %s %s\n", ing.Amount, ing.Unit, ing.Name)
}
fmt.Println("\nSteps:")
for i, step := range recipe.Steps {
fmt.Printf(" %d. %s\n", i+1, step)
}
}- Use clear field names - The model uses these to understand structure
-
Add descriptions - Use
desctag for complex fields -
Mark optional fields - Use
omitemptyfor non-required fields -
Implement validation - Catch errors early with
StructValidator - Set reasonable retries - 3 is usually enough, more for complex schemas
- Keep schemas flat - Deeply nested structures are harder to parse correctly