1package main
2
3// This example demonstrates how to get structured, type-safe outputs from an
4// LLM. Here we're getting a recipe with validated fields that we can use
5// directly in our code.
6
7import (
8 "context"
9 "fmt"
10 "os"
11
12 "charm.land/fantasy"
13 "charm.land/fantasy/object"
14 "charm.land/fantasy/providers/openai"
15)
16
17// Here's what we want the LLM to fill out. The struct tags tell the model
18// what each field is for.
19type Recipe struct {
20 Name string `json:"name" description:"The name of the recipe"`
21 Ingredients []string `json:"ingredients" description:"List of ingredients needed"`
22 Steps []string `json:"steps" description:"Step-by-step cooking instructions"`
23 PrepTime int `json:"prep_time" description:"Preparation time in minutes"`
24}
25
26func main() {
27 // We'll use OpenAI for this one.
28 apiKey := os.Getenv("OPENAI_API_KEY")
29 if apiKey == "" {
30 fmt.Println("Please set OPENAI_API_KEY environment variable")
31 os.Exit(1)
32 }
33
34 ctx := context.Background()
35
36 // Set up the provider.
37 provider, err := openai.New(openai.WithAPIKey(apiKey))
38 if err != nil {
39 fmt.Fprintf(os.Stderr, "Whoops: %v\n", err)
40 os.Exit(1)
41 }
42
43 // Pick the model.
44 model, err := provider.LanguageModel(ctx, "gpt-4o-mini")
45 if err != nil {
46 fmt.Fprintf(os.Stderr, "Dang: %v\n", err)
47 os.Exit(1)
48 }
49
50 fmt.Println("\nšŖ Generating a recipe...")
51
52 // Ask for a structured recipe. The model will return a Recipe struct
53 // that's been validated against our schema.
54 result, err := object.Generate[Recipe](ctx, model, fantasy.ObjectCall{
55 Prompt: fantasy.Prompt{
56 fantasy.NewUserMessage("Give me a recipe for chocolate chip cookies"),
57 },
58 })
59 if err != nil {
60 fmt.Fprintf(os.Stderr, "Oof: %v\n", err)
61 os.Exit(1)
62 }
63
64 // Now we have a type-safe Recipe we can use directly.
65 fmt.Printf("Recipe: %s\n", result.Object.Name)
66 fmt.Printf("Prep time: %d minutes\n", result.Object.PrepTime)
67 fmt.Printf("Ingredients: %d\n", len(result.Object.Ingredients))
68 for i, ing := range result.Object.Ingredients {
69 fmt.Printf(" %d. %s\n", i+1, ing)
70 }
71 fmt.Printf("Steps: %d\n", len(result.Object.Steps))
72 for i, step := range result.Object.Steps {
73 fmt.Printf(" %d. %s\n", i+1, step)
74 }
75 fmt.Printf("\nTokens used: %d\n\n", result.Usage.TotalTokens)
76
77 // Want to see progressive updates as the object builds? Use streaming!
78 fmt.Println("š Now let's try streaming...")
79
80 stream, err := object.Stream[Recipe](ctx, model, fantasy.ObjectCall{
81 Prompt: fantasy.Prompt{
82 fantasy.NewUserMessage("Give me a recipe for banana bread"),
83 },
84 })
85 if err != nil {
86 fmt.Fprintf(os.Stderr, "Oof: %v\n", err)
87 os.Exit(1)
88 }
89
90 // Watch the recipe build in real-time!
91 updateCount := 0
92 for partial := range stream.PartialObjectStream() {
93 updateCount++
94 fmt.Printf(" Update %d: %s (%d ingredients, %d steps)\n",
95 updateCount, partial.Name, len(partial.Ingredients), len(partial.Steps))
96 }
97
98 fmt.Println()
99}