diff --git a/examples/moon/main.go b/examples/moon/main.go index c59b0636ee1469de9fa6fc9123bcbc502c849e61..1bc574ab9643a94b760ad5410406e4658f223a4a 100644 --- a/examples/moon/main.go +++ b/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) } diff --git a/examples/puppy/main.go b/examples/puppy/main.go new file mode 100644 index 0000000000000000000000000000000000000000..a41b352f4b1e71cac289c270bbf6a597142e2c06 --- /dev/null +++ b/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() +} diff --git a/examples/streaming-agent-simple/main.go b/examples/streaming-agent-simple/main.go deleted file mode 100644 index 367667ebf1cbb2bbe1dfa5b033966b18db6b91b5..0000000000000000000000000000000000000000 --- a/examples/streaming-agent-simple/main.go +++ /dev/null @@ -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) -}