541 lines
18 KiB
C#
Executable File
541 lines
18 KiB
C#
Executable File
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<string, Type> SupportedParameters => new Dictionary<string, Type>
|
|
{
|
|
["number1"] = typeof(double),
|
|
["number2"] = typeof(double),
|
|
["operation"] = typeof(string)
|
|
};
|
|
|
|
public async Task<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> 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<string, Type> SupportedParameters => new Dictionary<string, Type>
|
|
{
|
|
["city"] = typeof(string)
|
|
};
|
|
|
|
public async Task<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> 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<AIProvider, string>();
|
|
var providerModels = new Dictionary<AIProvider, string>();
|
|
|
|
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<CalculatorPlugin>()
|
|
.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<WeatherPlugin>()
|
|
.Build();
|
|
|
|
var response = await conversation.SendForStructuredOutputAsync<object>(
|
|
"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<CalculatorPlugin>(activateImmediately: false)
|
|
.RegisterPlugin<WeatherPlugin>(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<CalculatorPlugin>()
|
|
.RegisterPlugin<WeatherPlugin>()
|
|
.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<AIProvider, string> apiKeys)
|
|
{
|
|
Console.WriteLine("TEST 7: Streaming Across Multiple Providers");
|
|
Console.WriteLine("--------------------------------------------");
|
|
|
|
var providerModels = new Dictionary<AIProvider, (string Model, string Name)>
|
|
{
|
|
{ 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<CalculatorPlugin>()
|
|
.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<CalculatorPlugin>()
|
|
.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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|