using MarketAlly.AIPlugin; using MarketAlly.AIPlugin.Conversation; using MarketAlly.AIPlugin.Conversation.Models; using System; using System.Collections.Generic; using System.Threading.Tasks; namespace TestConversationApp { // Test plugin 1: Calculator [AIPlugin("Calculator", "Performs basic arithmetic operations", FriendlyName = "Math Calculator")] public class CalculatorPlugin : IAIPlugin { [AIParameter("First number", required: true)] public double Number1 { get; set; } [AIParameter("Second number", required: true)] public double Number2 { get; set; } [AIParameter("Operation (add, subtract, multiply, divide)", required: true)] public string Operation { get; set; } public IReadOnlyDictionary SupportedParameters => new Dictionary { ["number1"] = typeof(double), ["number2"] = typeof(double), ["operation"] = typeof(string) }; public async Task ExecuteAsync(IReadOnlyDictionary parameters) { double n1 = Convert.ToDouble(parameters["number1"]); double n2 = Convert.ToDouble(parameters["number2"]); string op = parameters["operation"].ToString().ToLower(); double result = op switch { "add" => n1 + n2, "subtract" => n1 - n2, "multiply" => n1 * n2, "divide" => n2 != 0 ? n1 / n2 : throw new DivideByZeroException(), _ => throw new ArgumentException($"Unknown operation: {op}") }; await Task.CompletedTask; return new AIPluginResult(result, $"Calculated {n1} {op} {n2} = {result}"); } } // Test plugin 2: Weather (for structured output) [AIPlugin("GetWeather", "Get current weather for a city", FriendlyName = "Weather Service")] public class WeatherPlugin : IAIPlugin { [AIParameter("City name", required: true)] public string City { get; set; } public IReadOnlyDictionary SupportedParameters => new Dictionary { ["city"] = typeof(string) }; public async Task ExecuteAsync(IReadOnlyDictionary parameters) { var city = parameters["city"].ToString(); // Simulated weather data var weatherData = new { city = city, temperature = 72, condition = "Sunny", humidity = 45, windSpeed = 10 }; await Task.CompletedTask; return new AIPluginResult(weatherData); } } class Program { static async Task Main(string[] args) { Console.WriteLine("=== AIConversation Test App ===\n"); // Collect API keys for all providers var apiKeys = new Dictionary(); var providerModels = new Dictionary(); var claudeKey = "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";// Environment.GetEnvironmentVariable("CLAUDE_API_KEY"); if (!string.IsNullOrEmpty(claudeKey)) { apiKeys[AIProvider.Claude] = claudeKey; providerModels[AIProvider.Claude] = ModelConstants.Claude.Sonnet45; } var openaiKey = "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";// Environment.GetEnvironmentVariable("OPENAI_API_KEY"); if (!string.IsNullOrEmpty(openaiKey)) { apiKeys[AIProvider.OpenAI] = openaiKey; providerModels[AIProvider.OpenAI] = "gpt-4o"; } var geminiKey = "AIzaSyDKdJeSOoL9UxdcxEPVzUEqcIiqRkkJ2mM";//Environment.GetEnvironmentVariable("GEMINI_API_KEY"); if (!string.IsNullOrEmpty(geminiKey)) { apiKeys[AIProvider.Gemini] = geminiKey; providerModels[AIProvider.Gemini] = "gemini-2.0-flash-exp"; } // Check if any API keys are available if (apiKeys.Count == 0) { Console.WriteLine("ERROR: No API keys found."); Console.WriteLine("Please set at least one of the following environment variables:"); Console.WriteLine(" CLAUDE_API_KEY - For Claude API"); Console.WriteLine(" OPENAI_API_KEY - For OpenAI API"); Console.WriteLine(" GEMINI_API_KEY - For Gemini API"); Console.WriteLine("\nExample:"); Console.WriteLine(" export CLAUDE_API_KEY='your-key-here' (Linux/Mac)"); Console.WriteLine(" $env:CLAUDE_API_KEY='your-key-here' (PowerShell)"); return; } Console.WriteLine($"Found API keys for {apiKeys.Count} provider(s): {string.Join(", ", apiKeys.Keys)}"); Console.WriteLine(); try { // Run tests 1-6 and 8 for each available provider foreach (var providerEntry in apiKeys) { var provider = providerEntry.Key; var apiKey = providerEntry.Value; var model = providerModels[provider]; Console.WriteLine(new string('=', 80)); Console.WriteLine($"TESTING PROVIDER: {provider.ToString().ToUpper()} (Model: {model})"); Console.WriteLine(new string('=', 80)); Console.WriteLine(); try { // Non-streaming tests await Test1_BasicConversation(provider, apiKey, model); Console.WriteLine("\n" + new string('-', 60) + "\n"); await Test2_WithTools(provider, apiKey, model); Console.WriteLine("\n" + new string('-', 60) + "\n"); await Test3_StructuredOutput(provider, apiKey, model); Console.WriteLine("\n" + new string('-', 60) + "\n"); await Test4_DynamicTools(provider, apiKey, model); Console.WriteLine("\n" + new string('-', 60) + "\n"); // Streaming tests await Test5_BasicStreaming(provider, apiKey, model); Console.WriteLine("\n" + new string('-', 60) + "\n"); await Test6_StreamingWithTools(provider, apiKey, model); Console.WriteLine("\n" + new string('-', 60) + "\n"); await Test8_StreamingWithCustomOptions(provider, apiKey, model); Console.WriteLine("\n" + new string('-', 60) + "\n"); Console.WriteLine($"✅ All tests completed successfully for {provider}!"); } catch (Exception ex) { Console.WriteLine($"\n❌ ERROR testing {provider}: {ex.Message}"); Console.WriteLine($"Stack trace: {ex.StackTrace}"); } Console.WriteLine(); } // Run Test7 separately (it handles multiple providers) Console.WriteLine(new string('=', 80)); Console.WriteLine("MULTI-PROVIDER TEST"); Console.WriteLine(new string('=', 80)); Console.WriteLine(); await Test7_StreamingWithMultipleProviders(apiKeys); Console.WriteLine("\n" + new string('-', 60) + "\n"); // Summary Console.WriteLine(new string('=', 80)); Console.WriteLine("SUMMARY"); Console.WriteLine(new string('=', 80)); Console.WriteLine($"✅ All tests completed for {apiKeys.Count} provider(s): {string.Join(", ", apiKeys.Keys)}"); } catch (Exception ex) { Console.WriteLine($"\n❌ FATAL ERROR: {ex.Message}"); Console.WriteLine($"Stack trace: {ex.StackTrace}"); } } static async Task Test1_BasicConversation(AIProvider provider, string apiKey, string model) { Console.WriteLine($"TEST 1 [{provider.ToString().ToUpper()}]: Basic Conversation (No Tools)"); Console.WriteLine("---------------------------------------"); var conversation = AIConversationBuilder.Create() .UseProvider(provider, apiKey) .UseModel(model) .Build(); var response = await conversation.SendAsync("Say hello in exactly 3 words"); Console.WriteLine($"Response: {response.FinalMessage}"); Console.WriteLine($"Tokens used: {response.TotalTokensUsed}"); Console.WriteLine($"Duration: {response.Duration.TotalMilliseconds}ms"); } static async Task Test2_WithTools(AIProvider provider, string apiKey, string model) { Console.WriteLine($"TEST 2 [{provider.ToString().ToUpper()}]: Conversation with Automatic Tool Execution"); Console.WriteLine("---------------------------------------------------"); var conversation = AIConversationBuilder.Create() .UseProvider(provider, apiKey) .UseModel(model) .RegisterPlugin() .Build(); var response = await conversation.SendAsync("What is 25 multiplied by 48?"); Console.WriteLine($"Response: {response.FinalMessage}"); Console.WriteLine($"Tools executed: {response.ToolExecutionCount}"); Console.WriteLine($"Tokens used: {response.TotalTokensUsed}"); if (response.ToolResults.Count > 0) { Console.WriteLine("\nTool Results:"); foreach (var result in response.ToolResults) { Console.WriteLine($" - {result.ToolName}: {result.Result}"); } } } static async Task Test3_StructuredOutput(AIProvider provider, string apiKey, string model) { Console.WriteLine($"TEST 3 [{provider.ToString().ToUpper()}]: Structured Output (No AI Chatter)"); Console.WriteLine("------------------------------------------"); var conversation = AIConversationBuilder.Create() .UseProvider(provider, apiKey) .UseModel(model) .RegisterPlugin() .Build(); var response = await conversation.SendForStructuredOutputAsync( "Get weather for London", "GetWeather" ); Console.WriteLine($"Structured output received: {response.IsStructuredOutput}"); if (response.IsStructuredOutput) { dynamic weather = response.StructuredOutput; Console.WriteLine($"City: {weather.city}"); Console.WriteLine($"Temperature: {weather.temperature}°F"); Console.WriteLine($"Condition: {weather.condition}"); Console.WriteLine($"Humidity: {weather.humidity}%"); Console.WriteLine($"Wind Speed: {weather.windSpeed} mph"); } Console.WriteLine($"AI Message: {response.FinalMessage ?? "(none - structured output only)"}"); } static async Task Test4_DynamicTools(AIProvider provider, string apiKey, string model) { Console.WriteLine($"TEST 4 [{provider.ToString().ToUpper()}]: Dynamic Tool Loading/Unloading"); Console.WriteLine("---------------------------------------"); var conversation = AIConversationBuilder.Create() .UseProvider(provider, apiKey) .UseModel(model) .WithDynamicTools(true) .RegisterPlugin(activateImmediately: false) .RegisterPlugin(activateImmediately: false) .Build(); // Request 1: Only calculator Console.WriteLine("\nRequest 1: Activating Calculator only"); conversation.ActivatePlugins("Calculator"); var activePlugins = conversation.GetActivePlugins(); Console.WriteLine($"Active plugins: {string.Join(", ", activePlugins)}"); var response1 = await conversation.SendAsync("Calculate 100 divided by 4"); Console.WriteLine($"Response: {response1.FinalMessage}"); // Request 2: Only weather Console.WriteLine("\nRequest 2: Switching to Weather only"); conversation.DeactivatePlugins("Calculator"); conversation.ActivatePlugins("GetWeather"); activePlugins = conversation.GetActivePlugins(); Console.WriteLine($"Active plugins: {string.Join(", ", activePlugins)}"); var response2 = await conversation.SendAsync("What's the weather in Paris?"); Console.WriteLine($"Response: {response2.FinalMessage}"); } static async Task Test5_BasicStreaming(AIProvider provider, string apiKey, string model) { Console.WriteLine($"TEST 5 [{provider.ToString().ToUpper()}]: Basic Streaming (No Tools)"); Console.WriteLine("-----------------------------------"); var conversation = AIConversationBuilder.Create() .UseProvider(provider, apiKey) .UseModel(model) .Build(); Console.Write("Response: "); var fullText = ""; await foreach (var chunk in conversation.SendStreamingAsync("Write a haiku about coding")) { switch (chunk.Type) { case ConversationStreamingResponseType.TextDelta: Console.Write(chunk.TextDelta); fullText += chunk.TextDelta; break; case ConversationStreamingResponseType.Complete: Console.WriteLine(); Console.WriteLine($"\nTokens used: {chunk.TotalTokensUsed} (in: {chunk.InputTokens}, out: {chunk.OutputTokens})"); Console.WriteLine($"Duration: {chunk.Elapsed?.TotalMilliseconds}ms"); Console.WriteLine($"Stop reason: {chunk.StopReason}"); break; case ConversationStreamingResponseType.Error: Console.WriteLine($"\n❌ Error: {chunk.ErrorMessage}"); break; } } } static async Task Test6_StreamingWithTools(AIProvider provider, string apiKey, string model) { Console.WriteLine($"TEST 6 [{provider.ToString().ToUpper()}]: Streaming with Automatic Tool Execution"); Console.WriteLine("------------------------------------------------"); var conversation = AIConversationBuilder.Create() .UseProvider(provider, apiKey) .UseModel(model) .RegisterPlugin() .RegisterPlugin() .Build(); Console.WriteLine("Query: Calculate 156 times 89, then tell me the weather in Tokyo\n"); Console.Write("Response: "); var toolCount = 0; await foreach (var chunk in conversation.SendStreamingAsync("Calculate 156 times 89, then tell me the weather in Tokyo")) { switch (chunk.Type) { case ConversationStreamingResponseType.TextDelta: Console.Write(chunk.TextDelta); break; case ConversationStreamingResponseType.ToolCallDetected: Console.WriteLine($"\n\n🔧 Tool detected: {chunk.ToolFriendlyName ?? chunk.ToolName} (ID: {chunk.ToolCallId})"); break; case ConversationStreamingResponseType.ToolExecutionStarting: Console.Write($" ▶ Executing {chunk.ToolFriendlyName ?? chunk.ToolName}..."); break; case ConversationStreamingResponseType.ToolExecutionComplete: Console.WriteLine($" ✅"); Console.WriteLine($" Result: {chunk.ToolResult.Result}"); toolCount++; break; case ConversationStreamingResponseType.ToolExecutionFailed: Console.WriteLine($" ❌"); Console.WriteLine($" Error: {chunk.ErrorMessage}"); break; case ConversationStreamingResponseType.ContinuingAfterTools: Console.WriteLine("\n🔄 Continuing conversation after tool execution...\n"); Console.Write("Response: "); break; case ConversationStreamingResponseType.Complete: Console.WriteLine(); Console.WriteLine($"\n📊 Summary:"); Console.WriteLine($" Tools executed: {chunk.ToolExecutionCount}"); Console.WriteLine($" Tokens used: {chunk.TotalTokensUsed} (in: {chunk.InputTokens}, out: {chunk.OutputTokens})"); Console.WriteLine($" Duration: {chunk.Elapsed?.TotalMilliseconds}ms"); break; case ConversationStreamingResponseType.Error: Console.WriteLine($"\n❌ Error: {chunk.ErrorMessage}"); break; } } } static async Task Test7_StreamingWithMultipleProviders(Dictionary apiKeys) { Console.WriteLine("TEST 7: Streaming Across Multiple Providers"); Console.WriteLine("--------------------------------------------"); var providerModels = new Dictionary { { AIProvider.Claude, (ModelConstants.Claude.Sonnet45, "Claude") }, { AIProvider.OpenAI, ("gpt-4o", "OpenAI") }, { AIProvider.Gemini, ("gemini-2.0-flash-exp", "Gemini") } }; foreach (var providerEntry in apiKeys) { var provider = providerEntry.Key; var key = providerEntry.Value; if (!providerModels.ContainsKey(provider)) continue; var (model, name) = providerModels[provider]; try { Console.WriteLine($"\n{name} ({model}):"); Console.Write(" "); var conversation = AIConversationBuilder.Create() .UseProvider(provider, key) .UseModel(model) .RegisterPlugin() .Build(); await foreach (var chunk in conversation.SendStreamingAsync("What is 12 times 12?")) { switch (chunk.Type) { case ConversationStreamingResponseType.TextDelta: Console.Write(chunk.TextDelta); break; case ConversationStreamingResponseType.ToolCallDetected: Console.Write($"[Tool: {chunk.ToolFriendlyName}] "); break; case ConversationStreamingResponseType.Complete: Console.WriteLine($"\n ✅ Completed in {chunk.Elapsed?.TotalMilliseconds}ms"); break; case ConversationStreamingResponseType.Error: Console.WriteLine($"\n ❌ Error: {chunk.ErrorMessage}"); break; } } } catch (Exception ex) { Console.WriteLine($" ❌ Failed: {ex.Message}"); } } } static async Task Test8_StreamingWithCustomOptions(AIProvider provider, string apiKey, string model) { Console.WriteLine($"TEST 8 [{provider.ToString().ToUpper()}]: Streaming with Custom Options"); Console.WriteLine("--------------------------------------"); var conversation = AIConversationBuilder.Create() .UseProvider(provider, apiKey) .UseModel(model) .RegisterPlugin() .WithStreaming(options => { options.EnableStreaming = true; options.BufferTextDeltas = true; options.BufferSize = 20; options.EnableHeartbeat = true; options.HeartbeatInterval = TimeSpan.FromSeconds(1); options.IncludeToolArguments = true; options.UseFriendlyToolNames = true; }) .Build(); Console.WriteLine("Query: Calculate 999 multiplied by 888\n"); Console.Write("Response: "); await foreach (var chunk in conversation.SendStreamingAsync("Calculate 999 multiplied by 888")) { switch (chunk.Type) { case ConversationStreamingResponseType.TextDelta: Console.Write(chunk.TextDelta); break; case ConversationStreamingResponseType.ToolCallDetected: Console.WriteLine($"\n\n🔧 Tool: {chunk.ToolFriendlyName}"); break; case ConversationStreamingResponseType.ToolExecutionStarting: if (chunk.ToolArguments != null) { Console.WriteLine($" Arguments:"); foreach (var arg in chunk.ToolArguments) { Console.WriteLine($" {arg.Key}: {arg.Value}"); } } Console.Write($" Executing..."); break; case ConversationStreamingResponseType.ToolExecutionComplete: Console.WriteLine($" ✅"); break; case ConversationStreamingResponseType.Heartbeat: Console.Write("."); break; case ConversationStreamingResponseType.Complete: Console.WriteLine(); Console.WriteLine($"\n📊 Buffered text streaming with size={20}"); Console.WriteLine($" Tokens: {chunk.TotalTokensUsed}, Duration: {chunk.Elapsed?.TotalMilliseconds}ms"); break; case ConversationStreamingResponseType.Error: Console.WriteLine($"\n❌ Error: {chunk.ErrorMessage}"); break; } } } } }