diff --git a/providers/examples/agent/main.go b/examples/agent/main.go similarity index 55% rename from providers/examples/agent/main.go rename to examples/agent/main.go index 7076a6fd3b458c892a6dec5e40531b67a0866f22..35972979c3482a9aaf83da0708f053fdedbcab7e 100644 --- a/providers/examples/agent/main.go +++ b/examples/agent/main.go @@ -7,49 +7,31 @@ import ( "github.com/charmbracelet/crush/internal/ai" "github.com/charmbracelet/crush/internal/ai/providers" - "github.com/charmbracelet/crush/internal/llm/tools" ) -type weatherTool struct{} - -// Info implements tools.BaseTool. -func (w *weatherTool) Info() tools.ToolInfo { - return tools.ToolInfo{ - Name: "weather", - Parameters: map[string]any{ - "location": map[string]string{ - "type": "string", - "description": "the city", - }, - }, - Required: []string{"location"}, - } -} - -// Name implements tools.BaseTool. -func (w *weatherTool) Name() string { - return "weather" -} - -// Run implements tools.BaseTool. -func (w *weatherTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolResponse, error) { - return tools.NewTextResponse("40 C"), nil -} - -func newWeatherTool() tools.BaseTool { - return &weatherTool{} -} - func main() { provider := providers.NewOpenAIProvider( providers.WithOpenAIApiKey(os.Getenv("OPENAI_API_KEY")), ) model := provider.LanguageModel("gpt-4o") + // Create weather tool using the new type-safe API + type WeatherInput struct { + Location string `json:"location" description:"the city"` + } + + weatherTool := ai.NewTypedToolFunc( + "weather", + "Get weather information for a location", + func(ctx context.Context, input WeatherInput, _ ai.ToolCall) (ai.ToolResponse, error) { + return ai.NewTextResponse("40 C"), nil + }, + ) + agent := ai.NewAgent( model, ai.WithSystemPrompt("You are a helpful assistant"), - ai.WithTools(newWeatherTool()), + ai.WithTools(weatherTool), ) result, _ := agent.Generate(context.Background(), ai.AgentCall{ diff --git a/providers/examples/simple/main.go b/examples/simple/main.go similarity index 100% rename from providers/examples/simple/main.go rename to examples/simple/main.go diff --git a/providers/examples/stream/main.go b/examples/stream/main.go similarity index 100% rename from providers/examples/stream/main.go rename to examples/stream/main.go diff --git a/providers/examples/streaming-agent-simple/main.go b/examples/streaming-agent-simple/main.go similarity index 73% rename from providers/examples/streaming-agent-simple/main.go rename to examples/streaming-agent-simple/main.go index 2f8392e8640f3bff6bffa9338076d407dbaab864..2c84ae5c94085ee38add7353d35e87dba50a8c41 100644 --- a/providers/examples/streaming-agent-simple/main.go +++ b/examples/streaming-agent-simple/main.go @@ -7,30 +7,8 @@ import ( "github.com/charmbracelet/crush/internal/ai" "github.com/charmbracelet/crush/internal/ai/providers" - "github.com/charmbracelet/crush/internal/llm/tools" ) -// Simple echo tool for demonstration -type EchoTool struct{} - -func (e *EchoTool) Info() tools.ToolInfo { - return tools.ToolInfo{ - Name: "echo", - Description: "Echo back the provided message", - Parameters: map[string]any{ - "message": map[string]any{ - "type": "string", - "description": "The message to echo back", - }, - }, - Required: []string{"message"}, - } -} - -func (e *EchoTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolResponse, error) { - return tools.NewTextResponse("Echo: " + params.Input), nil -} - func main() { // Check for API key apiKey := os.Getenv("OPENAI_API_KEY") @@ -45,11 +23,24 @@ func main() { ) model := provider.LanguageModel("gpt-4o-mini") + // Create echo tool using the new type-safe API + type EchoInput struct { + Message string `json:"message" description:"The message to echo back"` + } + + echoTool := ai.NewTypedToolFunc( + "echo", + "Echo back the provided message", + func(ctx context.Context, input EchoInput, _ ai.ToolCall) (ai.ToolResponse, error) { + return ai.NewTextResponse("Echo: " + input.Message), nil + }, + ) + // Create streaming agent agent := ai.NewAgent( model, ai.WithSystemPrompt("You are a helpful assistant."), - ai.WithTools(&EchoTool{}), + ai.WithTools(echoTool), ) ctx := context.Background() @@ -61,22 +52,22 @@ func main() { // Basic streaming with key callbacks streamCall := ai.AgentStreamCall{ Prompt: "Please echo back 'Hello, streaming world!'", - + // Show real-time text as it streams OnTextDelta: func(id, text string) { fmt.Print(text) }, - + // Show when tools are called OnToolCall: func(toolCall ai.ToolCallContent) { fmt.Printf("\n[Tool: %s called]\n", toolCall.ToolName) }, - + // Show tool results OnToolResult: func(result ai.ToolResultContent) { fmt.Printf("[Tool result received]\n") }, - + // Show when each step completes OnStepFinish: func(step ai.StepResult) { fmt.Printf("\n[Step completed: %s]\n", step.FinishReason) @@ -92,4 +83,5 @@ func main() { 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) -} \ No newline at end of file +} + diff --git a/providers/examples/streaming-agent/main.go b/examples/streaming-agent/main.go similarity index 66% rename from providers/examples/streaming-agent/main.go rename to examples/streaming-agent/main.go index 722a81dab6050649c9c6f9f8482037e4ddfef454..da7ef79db9e91cb45b3095404e2762f58769d408 100644 --- a/providers/examples/streaming-agent/main.go +++ b/examples/streaming-agent/main.go @@ -8,87 +8,8 @@ import ( "github.com/charmbracelet/crush/internal/ai" "github.com/charmbracelet/crush/internal/ai/providers" - "github.com/charmbracelet/crush/internal/llm/tools" ) -// WeatherTool is a simple tool that simulates weather lookup -type WeatherTool struct{} - -func (w *WeatherTool) Info() tools.ToolInfo { - return tools.ToolInfo{ - Name: "get_weather", - Description: "Get the current weather for a specific location", - Parameters: map[string]any{ - "location": map[string]any{ - "type": "string", - "description": "The city and country, e.g. 'London, UK'", - }, - "unit": map[string]any{ - "type": "string", - "description": "Temperature unit (celsius or fahrenheit)", - "enum": []string{"celsius", "fahrenheit"}, - "default": "celsius", - }, - }, - Required: []string{"location"}, - } -} - -func (w *WeatherTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolResponse, error) { - // Simulate weather lookup with some fake data - location := "Unknown" - if strings.Contains(params.Input, "pristina") || strings.Contains(params.Input, "Pristina") { - location = "Pristina, Kosovo" - } else if strings.Contains(params.Input, "london") || strings.Contains(params.Input, "London") { - location = "London, UK" - } else if strings.Contains(params.Input, "new york") || strings.Contains(params.Input, "New York") { - location = "New York, USA" - } - - unit := "celsius" - if strings.Contains(params.Input, "fahrenheit") { - unit = "fahrenheit" - } - - var temp string - if unit == "fahrenheit" { - temp = "72°F" - } else { - temp = "22°C" - } - - weather := fmt.Sprintf("The current weather in %s is %s with partly cloudy skies and light winds.", location, temp) - return tools.NewTextResponse(weather), nil -} - -// CalculatorTool demonstrates a second tool for multi-tool scenarios -type CalculatorTool struct{} - -func (c *CalculatorTool) Info() tools.ToolInfo { - return tools.ToolInfo{ - Name: "calculate", - Description: "Perform basic mathematical calculations", - Parameters: map[string]any{ - "expression": map[string]any{ - "type": "string", - "description": "Mathematical expression to evaluate (e.g., '2 + 2', '10 * 5')", - }, - }, - Required: []string{"expression"}, - } -} - -func (c *CalculatorTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolResponse, error) { - // Simple calculator simulation - expr := strings.ReplaceAll(params.Input, "\"", "") - if strings.Contains(expr, "2 + 2") || strings.Contains(expr, "2+2") { - return tools.NewTextResponse("2 + 2 = 4"), nil - } else if strings.Contains(expr, "10 * 5") || strings.Contains(expr, "10*5") { - return tools.NewTextResponse("10 * 5 = 50"), nil - } - return tools.NewTextResponse("I can calculate simple expressions like '2 + 2' or '10 * 5'"), nil -} - func main() { // Check for API key apiKey := os.Getenv("OPENAI_API_KEY") @@ -108,11 +29,80 @@ func main() { ) model := provider.LanguageModel("gpt-4o-mini") // Using mini for faster/cheaper responses + // Define input types for type-safe tools + type WeatherInput struct { + Location string `json:"location" description:"The city and country, e.g. 'London, UK'"` + Unit string `json:"unit,omitempty" enum:"celsius,fahrenheit" description:"Temperature unit (celsius or fahrenheit)"` + } + + type CalculatorInput struct { + Expression string `json:"expression" description:"Mathematical expression to evaluate (e.g., '2 + 2', '10 * 5')"` + } + + // Create weather tool using the new type-safe API + weatherTool := ai.NewTypedToolFunc( + "get_weather", + "Get the current weather for a specific location", + func(ctx context.Context, input WeatherInput, _ ai.ToolCall) (ai.ToolResponse, error) { + // Simulate weather lookup with some fake data + location := input.Location + if location == "" { + location = "Unknown" + } + + // Default to celsius if not specified + unit := input.Unit + if unit == "" { + unit = "celsius" + } + + // Simulate different temperatures for different cities + var temp string + if strings.Contains(strings.ToLower(location), "pristina") { + temp = "15°C" + if unit == "fahrenheit" { + temp = "59°F" + } + } else if strings.Contains(strings.ToLower(location), "london") { + temp = "12°C" + if unit == "fahrenheit" { + temp = "54°F" + } + } else { + temp = "22°C" + if unit == "fahrenheit" { + temp = "72°F" + } + } + + weather := fmt.Sprintf("The current weather in %s is %s with partly cloudy skies and light winds.", location, temp) + return ai.NewTextResponse(weather), nil + }, + ) + + // Create calculator tool using the new type-safe API + calculatorTool := ai.NewTypedToolFunc( + "calculate", + "Perform basic mathematical calculations", + func(ctx context.Context, input CalculatorInput, _ ai.ToolCall) (ai.ToolResponse, error) { + // Simple calculator simulation + expr := strings.TrimSpace(input.Expression) + if strings.Contains(expr, "2 + 2") || strings.Contains(expr, "2+2") { + return ai.NewTextResponse("2 + 2 = 4"), nil + } else if strings.Contains(expr, "10 * 5") || strings.Contains(expr, "10*5") { + return ai.NewTextResponse("10 * 5 = 50"), nil + } else if strings.Contains(expr, "15 + 27") || strings.Contains(expr, "15+27") { + return ai.NewTextResponse("15 + 27 = 42"), nil + } + return ai.NewTextResponse("I can calculate simple expressions like '2 + 2', '10 * 5', or '15 + 27'"), nil + }, + ) + // Create agent with tools agent := ai.NewAgent( model, ai.WithSystemPrompt("You are a helpful assistant that can check weather and do calculations. Be concise and friendly."), - ai.WithTools(&WeatherTool{}, &CalculatorTool{}), + ai.WithTools(weatherTool, calculatorTool), ) ctx := context.Background() @@ -224,11 +214,11 @@ func main() { fmt.Println("📋 Final Summary") fmt.Println("================") fmt.Printf("Steps executed: %d\n", len(result.Steps)) - fmt.Printf("Total tokens used: %d (input: %d, output: %d)\n", - result.TotalUsage.TotalTokens, - result.TotalUsage.InputTokens, + fmt.Printf("Total tokens used: %d (input: %d, output: %d)\n", + result.TotalUsage.TotalTokens, + result.TotalUsage.InputTokens, result.TotalUsage.OutputTokens) - + if result.TotalUsage.ReasoningTokens > 0 { fmt.Printf("Reasoning tokens: %d\n", result.TotalUsage.ReasoningTokens) } @@ -243,13 +233,13 @@ func main() { fmt.Printf("Step %d:\n", i+1) fmt.Printf(" Finish reason: %s\n", step.FinishReason) fmt.Printf(" Content types: ") - + var contentTypes []string for _, content := range step.Content { contentTypes = append(contentTypes, string(content.GetType())) } fmt.Printf("%s\n", strings.Join(contentTypes, ", ")) - + // Show tool calls and results toolCalls := step.Content.ToolCalls() if len(toolCalls) > 0 { @@ -260,15 +250,16 @@ func main() { } fmt.Printf("%s\n", strings.Join(toolNames, ", ")) } - + toolResults := step.Content.ToolResults() if len(toolResults) > 0 { fmt.Printf(" Tool results: %d\n", len(toolResults)) } - + fmt.Printf(" Tokens: %d\n", step.Usage.TotalTokens) fmt.Println() } fmt.Println("✨ Example completed successfully!") -} \ No newline at end of file +} +