1// Package main provides a comprehensive streaming agent example of using the fantasy AI SDK.
2package main
3
4import (
5 "context"
6 "fmt"
7 "os"
8 "strings"
9
10 "charm.land/fantasy"
11 "charm.land/fantasy/providers/anthropic"
12)
13
14func main() {
15 // Check for API key
16 apiKey := os.Getenv("ANTHROPIC_API_KEY")
17 if apiKey == "" {
18 fmt.Println("❌ Please set ANTHROPIC_API_KEY environment variable")
19 fmt.Println(" export ANTHROPIC_API_KEY=your_api_key_here")
20 os.Exit(1)
21 }
22
23 fmt.Println("🚀 Streaming Agent Example")
24 fmt.Println("==========================")
25 fmt.Println()
26
27 // Create OpenAI provider and model
28 provider := anthropic.New(anthropic.WithAPIKey(os.Getenv("ANTHROPIC_API_KEY")))
29 model, err := provider.LanguageModel("claude-sonnet-4-20250514")
30 if err != nil {
31 fmt.Println(err)
32 return
33 }
34
35 // Define input types for type-safe tools
36 type WeatherInput struct {
37 Location string `json:"location" description:"The city and country, e.g. 'London, UK'"`
38 Unit string `json:"unit,omitempty" enum:"celsius,fahrenheit" description:"Temperature unit (celsius or fahrenheit)"`
39 }
40
41 type CalculatorInput struct {
42 Expression string `json:"expression" description:"Mathematical expression to evaluate (e.g., '2 + 2', '10 * 5')"`
43 }
44
45 // Create weather tool using the new type-safe API
46 weatherTool := fantasy.NewAgentTool(
47 "get_weather",
48 "Get the current weather for a specific location",
49 func(_ context.Context, input WeatherInput, _ fantasy.ToolCall) (fantasy.ToolResponse, error) {
50 // Simulate weather lookup with some fake data
51 location := input.Location
52 if location == "" {
53 location = "Unknown"
54 }
55
56 // Default to celsius if not specified
57 unit := input.Unit
58 if unit == "" {
59 unit = "celsius"
60 }
61
62 // Simulate different temperatures for different cities
63 var temp string
64 if strings.Contains(strings.ToLower(location), "pristina") {
65 temp = "15°C"
66 if unit == "fahrenheit" {
67 temp = "59°F"
68 }
69 } else if strings.Contains(strings.ToLower(location), "london") {
70 temp = "12°C"
71 if unit == "fahrenheit" {
72 temp = "54°F"
73 }
74 } else {
75 temp = "22°C"
76 if unit == "fahrenheit" {
77 temp = "72°F"
78 }
79 }
80
81 weather := fmt.Sprintf("The current weather in %s is %s with partly cloudy skies and light winds.", location, temp)
82 return fantasy.NewTextResponse(weather), nil
83 },
84 )
85
86 // Create calculator tool using the new type-safe API
87 calculatorTool := fantasy.NewAgentTool(
88 "calculate",
89 "Perform basic mathematical calculations",
90 func(_ context.Context, input CalculatorInput, _ fantasy.ToolCall) (fantasy.ToolResponse, error) {
91 // Simple calculator simulation
92 expr := strings.TrimSpace(input.Expression)
93 if strings.Contains(expr, "2 + 2") || strings.Contains(expr, "2+2") {
94 return fantasy.NewTextResponse("2 + 2 = 4"), nil
95 } else if strings.Contains(expr, "10 * 5") || strings.Contains(expr, "10*5") {
96 return fantasy.NewTextResponse("10 * 5 = 50"), nil
97 } else if strings.Contains(expr, "15 + 27") || strings.Contains(expr, "15+27") {
98 return fantasy.NewTextResponse("15 + 27 = 42"), nil
99 }
100 return fantasy.NewTextResponse("I can calculate simple expressions like '2 + 2', '10 * 5', or '15 + 27'"), nil
101 },
102 )
103
104 // Create agent with tools
105 agent := fantasy.NewAgent(
106 model,
107 fantasy.WithSystemPrompt("You are a helpful assistant that can check weather and do calculations. Be concise and friendly."),
108 fantasy.WithTools(weatherTool, calculatorTool),
109 )
110
111 ctx := context.Background()
112
113 // Demonstrate streaming with comprehensive callbacks
114 fmt.Println("💬 Asking: \"What's the weather in Pristina and what's 2 + 2?\"")
115 fmt.Println()
116
117 // Track streaming events
118 var stepCount int
119 var textBuffer strings.Builder
120 var reasoningBuffer strings.Builder
121
122 // Create streaming call with all callbacks
123 streamCall := fantasy.AgentStreamCall{
124 Prompt: "What's the weather in Pristina and what's 2 + 2?",
125
126 // Agent-level callbacks
127 OnAgentStart: func() {
128 fmt.Println("🎬 Agent started")
129 },
130 OnAgentFinish: func(result *fantasy.AgentResult) error {
131 fmt.Printf("🏁 Agent finished with %d steps, total tokens: %d\n", len(result.Steps), result.TotalUsage.TotalTokens)
132 return nil
133 },
134 OnStepStart: func(stepNumber int) error {
135 stepCount++
136 fmt.Printf("📝 Step %d started\n", stepNumber+1)
137 return nil
138 },
139 OnStepFinish: func(stepResult fantasy.StepResult) error {
140 fmt.Printf("✅ Step completed (reason: %s, tokens: %d)\n", stepResult.FinishReason, stepResult.Usage.TotalTokens)
141 return nil
142 },
143 OnFinish: func(result *fantasy.AgentResult) {
144 fmt.Printf("🎯 Final result ready with %d steps\n", len(result.Steps))
145 },
146 OnError: func(err error) {
147 fmt.Printf("❌ Error: %v\n", err)
148 },
149
150 // Stream part callbacks
151 OnWarnings: func(warnings []fantasy.CallWarning) error {
152 for _, warning := range warnings {
153 fmt.Printf("⚠️ Warning: %s\n", warning.Message)
154 }
155 return nil
156 },
157 OnTextStart: func(_ string) error {
158 fmt.Print("💭 Assistant: ")
159 return nil
160 },
161 OnTextDelta: func(_ string, text string) error {
162 fmt.Print(text)
163 textBuffer.WriteString(text)
164 return nil
165 },
166 OnTextEnd: func(_ string) error {
167 fmt.Println()
168 return nil
169 },
170 OnReasoningStart: func(_ string, _ fantasy.ReasoningContent) error {
171 fmt.Print("🤔 Thinking: ")
172 return nil
173 },
174 OnReasoningDelta: func(_ string, text string) error {
175 reasoningBuffer.WriteString(text)
176 return nil
177 },
178 OnReasoningEnd: func(_ string, _ fantasy.ReasoningContent) error {
179 if reasoningBuffer.Len() > 0 {
180 fmt.Printf("%s\n", reasoningBuffer.String())
181 reasoningBuffer.Reset()
182 }
183 return nil
184 },
185 OnToolInputStart: func(_ string, toolName string) error {
186 fmt.Printf("🔧 Calling tool: %s\n", toolName)
187 return nil
188 },
189 OnToolInputDelta: func(_ string, _ string) error {
190 // Could show tool input being built, but it's often noisy
191 return nil
192 },
193 OnToolInputEnd: func(_ string) error {
194 // Tool input complete
195 return nil
196 },
197 OnToolCall: func(toolCall fantasy.ToolCallContent) error {
198 fmt.Printf("🛠️ Tool call: %s\n", toolCall.ToolName)
199 fmt.Printf(" Input: %s\n", toolCall.Input)
200 return nil
201 },
202 OnToolResult: func(result fantasy.ToolResultContent) error {
203 fmt.Printf("🎯 Tool result from %s:\n", result.ToolName)
204 switch output := result.Result.(type) {
205 case fantasy.ToolResultOutputContentText:
206 fmt.Printf(" %s\n", output.Text)
207 case fantasy.ToolResultOutputContentError:
208 fmt.Printf(" Error: %s\n", output.Error.Error())
209 }
210 return nil
211 },
212 OnSource: func(source fantasy.SourceContent) error {
213 fmt.Printf("📚 Source: %s (%s)\n", source.Title, source.URL)
214 return nil
215 },
216 OnStreamFinish: func(usage fantasy.Usage, finishReason fantasy.FinishReason, _ fantasy.ProviderMetadata) error {
217 fmt.Printf("📊 Stream finished (reason: %s, tokens: %d)\n", finishReason, usage.TotalTokens)
218 return nil
219 },
220 }
221
222 // Execute streaming agent
223 result, err := agent.Stream(ctx, streamCall)
224 if err != nil {
225 fmt.Printf("❌ Agent failed: %v\n", err)
226 os.Exit(1)
227 }
228
229 // Display final results
230 fmt.Println()
231 fmt.Println("📋 Final Summary")
232 fmt.Println("================")
233 fmt.Printf("Steps executed: %d\n", len(result.Steps))
234 fmt.Printf("Total tokens used: %d (input: %d, output: %d)\n",
235 result.TotalUsage.TotalTokens,
236 result.TotalUsage.InputTokens,
237 result.TotalUsage.OutputTokens)
238
239 if result.TotalUsage.ReasoningTokens > 0 {
240 fmt.Printf("Reasoning tokens: %d\n", result.TotalUsage.ReasoningTokens)
241 }
242
243 fmt.Printf("Final response: %s\n", result.Response.Content.Text())
244
245 // Show step details
246 fmt.Println()
247 fmt.Println("🔍 Step Details")
248 fmt.Println("===============")
249 for i, step := range result.Steps {
250 fmt.Printf("Step %d:\n", i+1)
251 fmt.Printf(" Finish reason: %s\n", step.FinishReason)
252 fmt.Printf(" Content types: ")
253
254 var contentTypes []string
255 for _, content := range step.Content {
256 contentTypes = append(contentTypes, string(content.GetType()))
257 }
258 fmt.Printf("%s\n", strings.Join(contentTypes, ", "))
259
260 // Show tool calls and results
261 toolCalls := step.Content.ToolCalls()
262 if len(toolCalls) > 0 {
263 fmt.Printf(" Tool calls: ")
264 var toolNames []string
265 for _, tc := range toolCalls {
266 toolNames = append(toolNames, tc.ToolName)
267 }
268 fmt.Printf("%s\n", strings.Join(toolNames, ", "))
269 }
270
271 toolResults := step.Content.ToolResults()
272 if len(toolResults) > 0 {
273 fmt.Printf(" Tool results: %d\n", len(toolResults))
274 }
275
276 fmt.Printf(" Tokens: %d\n", step.Usage.TotalTokens)
277 fmt.Println()
278 }
279
280 fmt.Println("✨ Example completed successfully!")
281}