1package main
2
3// This example demonstrates how to hook into the various parts of a streaming
4// tool call.
5
6import (
7 "context"
8 "fmt"
9 "math/rand/v2"
10 "os"
11 "strings"
12
13 "charm.land/fantasy"
14 "charm.land/fantasy/providers/openai"
15)
16
17const systemPrompt = `
18You are moderately helpful assistant with a new puppy named Chuck. Chuck is
19moody and ranges from very happy to very annoyed. He's pretty happy-go-lucky,
20but new encounters make him pretty uncomfortable.
21
22You despise emojis and never use them. Same with Markdown. Same with em-dashes.
23You prefer "welp" to "well" when starting a sentence (that's just how you were
24raised). You also don't use run-on sentences, including entering a comma where
25there should be a period. You had a decent education and did well in elementary
26school grammer. You grew up in the United States, speficially Kansas City,
27Missouri.
28`
29
30// Input for a tool call. The LLM will look at the struct tags and fill out the
31// values as necessary.
32type dogInteraction struct {
33 OtherDogName string `json:"dogName" description:"Name of the other dog. Just make something up. All the dogs are named after Japanese cars from the 80s."`
34}
35
36// Here's a tool call. In this case it's a set of random barks.
37func letsBark(ctx context.Context, i dogInteraction, _ fantasy.ToolCall) (fantasy.ToolResponse, error) {
38 var r fantasy.ToolResponse
39 if rand.Float64() >= 0.5 {
40 r.Content = randomBarks(1, 3)
41 } else {
42 r.Content = randomBarks(5, 10)
43 }
44 return r, nil
45}
46
47func main() {
48 // We're going to use OpenAI.
49 apiKey := os.Getenv("OPENAI_API_KEY")
50 if apiKey == "" {
51 fmt.Println("Please set OPENAI_API_KEY environment variable")
52 os.Exit(1)
53 }
54
55 // Make sure we have the API key we need.
56 provider, err := openai.New(openai.WithAPIKey(apiKey))
57 if err != nil {
58 fmt.Fprintf(os.Stderr, "Error creating Anthropic provider: %v\n", err)
59 os.Exit(1)
60 }
61
62 ctx := context.Background()
63
64 // Choose the model.
65 model, err := provider.LanguageModel(ctx, "gpt-5")
66 if err != nil {
67 fmt.Println(err)
68 os.Exit(1)
69 }
70
71 // Let's add a tool to our belt. A tool for dogs.
72 barkTool := fantasy.NewAgentTool(
73 "bark",
74 "Have Chuck express his feelings by barking. A few barks means he's happy and many barks means he's not.",
75 letsBark,
76 )
77
78 // Time to make the agent.
79 agent := fantasy.NewAgent(
80 model,
81 fantasy.WithSystemPrompt(systemPrompt),
82 fantasy.WithTools(barkTool),
83 )
84
85 // Alright, let's setup a streaming request!
86 streamCall := fantasy.AgentStreamCall{
87 // The prompt.
88 Prompt: "Chuck just met a new dog at the park. Find out what he thinks of the dog. Make sure to thank Chuck afterwards.",
89
90 // When we receive a chunk of streamind data.
91 OnTextDelta: func(id, text string) error {
92 _, fmtErr := fmt.Print(text)
93 return fmtErr
94 },
95
96 // When tool calls are invoked.
97 OnToolCall: func(toolCall fantasy.ToolCallContent) error {
98 fmt.Printf("-> Invoking the %s tool with input %s", toolCall.ToolName, toolCall.Input)
99 return nil
100 },
101
102 // When a cool call completes.
103 OnToolResult: func(res fantasy.ToolResultContent) error {
104 text, ok := fantasy.AsToolResultOutputType[fantasy.ToolResultOutputContentText](res.Result)
105 if !ok {
106 return fmt.Errorf("failed to cast result to text")
107 }
108 _, fmtErr := fmt.Printf("\n-> Using the %s tool: %s", res.ToolName, text.Text)
109 return fmtErr
110 },
111
112 // When a step finishes, such as a tool call or a response from the
113 // LLM.
114 OnStepFinish: func(_ fantasy.StepResult) error {
115 fmt.Print("\n-> Step completed\n")
116 return nil
117 },
118 }
119
120 fmt.Println("Generating...")
121
122 // Finally, let's stream everything!
123 _, err = agent.Stream(ctx, streamCall)
124 if err != nil {
125 fmt.Fprintf(os.Stderr, "Error generating response: %v\n", err)
126 os.Exit(1)
127 }
128}
129
130// Return a random number of barks between low and high.
131func randomBarks(low, high int) string {
132 const bark = "ruff"
133 numBarks := low + rand.IntN(high-low+1)
134 var barks strings.Builder
135 for i := range numBarks {
136 if i > 0 {
137 barks.WriteString(" ")
138 }
139 barks.WriteString(bark)
140 }
141 return barks.String()
142}