main.go

  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}