docs(examples): rework streaming agent example.

Christian Rocha created

Change summary

examples/moon/main.go                   |   2 
examples/puppy/main.go                  | 142 +++++++++++++++++++++++++++
examples/streaming-agent-simple/main.go |  98 ------------------
3 files changed, 143 insertions(+), 99 deletions(-)

Detailed changes

examples/moon/main.go 🔗

@@ -67,7 +67,7 @@ func main() {
 	fmt.Println("\n" + formatText(prompt))
 
 	// Let's go! Ask the agent to generate a response.
-	result, err := agent.Generate(context.Background(), fantasy.AgentCall{Prompt: prompt})
+	result, err := agent.Generate(ctx, fantasy.AgentCall{Prompt: prompt})
 	if err != nil {
 		log.Fatalf("agent generation failed: %v", err)
 	}

examples/puppy/main.go 🔗

@@ -0,0 +1,142 @@
+package main
+
+// This example demonstrates how to hook into the various parts of a streaming
+// tool call.
+
+import (
+	"context"
+	"fmt"
+	"math/rand/v2"
+	"os"
+	"strings"
+
+	"charm.land/fantasy"
+	"charm.land/fantasy/providers/openai"
+)
+
+const systemPrompt = `
+You are moderately helpful assistant with a new puppy named Chuck. Chuck is
+moody and ranges from very happy to very annoyed. He's pretty happy-go-lucky,
+but new encounters make him pretty uncomfortable.
+
+You despise emojis and never use them. Same with Markdown. Same with em-dashes.
+You prefer "welp" to "well" when starting a sentence (that's just how you were
+raised). You also don't use run-on sentences, including entering a comma where
+there should be a period. You had a decent education and did well in elementary
+school grammer. You grew up in the United States, speficially Kansas City,
+Missouri.
+`
+
+// Input for a tool call. The LLM will look at the struct tags and fill out the
+// values as necessary.
+type dogInteraction struct {
+	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."`
+}
+
+// Here's a tool call. In this case it's a set of random barks.
+func letsBark(ctx context.Context, i dogInteraction, _ fantasy.ToolCall) (fantasy.ToolResponse, error) {
+	var r fantasy.ToolResponse
+	if rand.Float64() >= 0.5 {
+		r.Content = randomBarks(1, 3)
+	} else {
+		r.Content = randomBarks(5, 10)
+	}
+	return r, nil
+}
+
+func main() {
+	// We're going to use OpenAI.
+	apiKey := os.Getenv("OPENAI_API_KEY")
+	if apiKey == "" {
+		fmt.Println("Please set OPENAI_API_KEY environment variable")
+		os.Exit(1)
+	}
+
+	// Make sure we have the API key we need.
+	provider, err := openai.New(openai.WithAPIKey(apiKey))
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Error creating Anthropic provider: %v\n", err)
+		os.Exit(1)
+	}
+
+	ctx := context.Background()
+
+	// Choose the model.
+	model, err := provider.LanguageModel(ctx, "gpt-5")
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+
+	// Let's add a tool to our belt. A tool for dogs.
+	barkTool := fantasy.NewAgentTool(
+		"bark",
+		"Have Chuck express his feelings by barking. A few barks means he's happy and many barks means he's not.",
+		letsBark,
+	)
+
+	// Time to make the agent.
+	agent := fantasy.NewAgent(
+		model,
+		fantasy.WithSystemPrompt(systemPrompt),
+		fantasy.WithTools(barkTool),
+	)
+
+	// Alright, let's setup a streaming request!
+	streamCall := fantasy.AgentStreamCall{
+		// The prompt.
+		Prompt: "Chuck just met a new dog at the park. Find out what he thinks of the dog. Make sure to thank Chuck afterwards.",
+
+		// When we receive a chunk of streamind data.
+		OnTextDelta: func(id, text string) error {
+			_, fmtErr := fmt.Print(text)
+			return fmtErr
+		},
+
+		// When tool calls are invoked.
+		OnToolCall: func(toolCall fantasy.ToolCallContent) error {
+			fmt.Printf("-> Invoking the %s tool with input %s", toolCall.ToolName, toolCall.Input)
+			return nil
+		},
+
+		// When a cool call completes.
+		OnToolResult: func(res fantasy.ToolResultContent) error {
+			text, ok := fantasy.AsToolResultOutputType[fantasy.ToolResultOutputContentText](res.Result)
+			if !ok {
+				return fmt.Errorf("failed to cast result to text")
+			}
+			_, fmtErr := fmt.Printf("\n-> Using the %s tool: %s", res.ToolName, text.Text)
+			return fmtErr
+		},
+
+		// When a step finishes, such as a tool call or a response from the
+		// LLM.
+		OnStepFinish: func(_ fantasy.StepResult) error {
+			fmt.Print("\n-> Step completed\n")
+			return nil
+		},
+	}
+
+	fmt.Println("Generating...")
+
+	// Finally, let's stream everything!
+	_, err = agent.Stream(ctx, streamCall)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Error generating response: %v\n", err)
+		os.Exit(1)
+	}
+}
+
+// Return a random number of barks between low and high.
+func randomBarks(low, high int) string {
+	const bark = "ruff"
+	numBarks := low + rand.IntN(high-low+1)
+	var barks strings.Builder
+	for i := range numBarks {
+		if i > 0 {
+			barks.WriteString(" ")
+		}
+		barks.WriteString(bark)
+	}
+	return barks.String()
+}

examples/streaming-agent-simple/main.go 🔗

@@ -1,98 +0,0 @@
-// Package main provides a simple streaming agent example of using the fantasy AI SDK.
-package main
-
-import (
-	"context"
-	"fmt"
-	"os"
-
-	"charm.land/fantasy"
-	"charm.land/fantasy/providers/openai"
-)
-
-func main() {
-	// Check for API key
-	apiKey := os.Getenv("OPENAI_API_KEY")
-	if apiKey == "" {
-		fmt.Println("Please set OPENAI_API_KEY environment variable")
-		os.Exit(1)
-	}
-
-	// Create provider and model
-	provider, err := openai.New(openai.WithAPIKey(apiKey))
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "Error creating OpenAI provider: %v\n", err)
-		os.Exit(1)
-	}
-
-	ctx := context.Background()
-
-	model, err := provider.LanguageModel(ctx, "gpt-4o-mini")
-	if err != nil {
-		fmt.Println(err)
-		os.Exit(1)
-	}
-
-	// Create echo tool using the new type-safe API
-	type EchoInput struct {
-		Message string `json:"message" description:"The message to echo back"`
-	}
-
-	echoTool := fantasy.NewAgentTool(
-		"echo",
-		"Echo back the provided message",
-		func(_ context.Context, input EchoInput, _ fantasy.ToolCall) (fantasy.ToolResponse, error) {
-			return fantasy.NewTextResponse("Echo: " + input.Message), nil
-		},
-	)
-
-	// Create streaming agent
-	agent := fantasy.NewAgent(
-		model,
-		fantasy.WithSystemPrompt("You are a helpful assistant."),
-		fantasy.WithTools(echoTool),
-	)
-
-	fmt.Println("Simple Streaming Agent Example")
-	fmt.Println("==============================")
-	fmt.Println()
-
-	// Basic streaming with key callbacks
-	streamCall := fantasy.AgentStreamCall{
-		Prompt: "Please echo back 'Hello, streaming world!'",
-
-		// Show real-time text as it streams
-		OnTextDelta: func(_ string, text string) error {
-			fmt.Print(text)
-			return nil
-		},
-
-		// Show when tools are called
-		OnToolCall: func(toolCall fantasy.ToolCallContent) error {
-			fmt.Printf("\n[Tool: %s called]\n", toolCall.ToolName)
-			return nil
-		},
-
-		// Show tool results
-		OnToolResult: func(_ fantasy.ToolResultContent) error {
-			fmt.Printf("[Tool result received]\n")
-			return nil
-		},
-
-		// Show when each step completes
-		OnStepFinish: func(step fantasy.StepResult) error {
-			fmt.Printf("\n[Step completed: %s]\n", step.FinishReason)
-			return nil
-		},
-	}
-
-	fmt.Println("Assistant response:")
-	result, err := agent.Stream(ctx, streamCall)
-	if err != nil {
-		fmt.Printf("Error: %v\n", err)
-		os.Exit(1)
-	}
-
-	fmt.Printf("\n\nFinal result: %s\n", result.Response.Content.Text())
-	fmt.Printf("Steps: %d, Total tokens: %d\n", len(result.Steps), result.TotalUsage.TotalTokens)
-}