main.go

 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}