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