using Microsoft.Extensions.AI; using Svrnty.CQRS.Abstractions; using Svrnty.Sample.AI.Tools; namespace Svrnty.Sample.AI.Commands; /// /// Handler for executing AI agent commands with function calling support /// public class ExecuteAgentCommandHandler(IChatClient chatClient) : ICommandHandler { // In-memory conversation store (replace with proper persistence in production) private static readonly Dictionary> ConversationStore = new(); private const int MaxFunctionCallIterations = 10; // Prevent infinite loops public async Task HandleAsync( ExecuteAgentCommand command, CancellationToken cancellationToken = default) { var conversationId = Guid.NewGuid(); var messages = new List { new(ChatRole.User, command.Prompt) }; // Register available tools var mathTool = new MathTool(); var dbTool = new DatabaseQueryTool(); var tools = new List { // Math tools AIFunctionFactory.Create(mathTool.Add), AIFunctionFactory.Create(mathTool.Multiply), // Business tools AIFunctionFactory.Create(dbTool.GetMonthlyRevenue), AIFunctionFactory.Create(dbTool.GetRevenueRange), AIFunctionFactory.Create(dbTool.CountCustomersByState), AIFunctionFactory.Create(dbTool.CountCustomersByTier), AIFunctionFactory.Create(dbTool.GetCustomers) }; var options = new ChatOptions { ModelId = "qwen2.5-coder:7b", Tools = tools.Cast().ToList() }; // Create function lookup by name for invocation var functionLookup = tools.ToDictionary( f => f.Metadata.Name, f => f, StringComparer.OrdinalIgnoreCase ); // Initial AI completion var completion = await chatClient.CompleteAsync(messages, options, cancellationToken); messages.Add(completion.Message); // Function calling loop - continue until no more function calls or max iterations var iterations = 0; while (completion.Message.Contents.OfType().Any() && iterations < MaxFunctionCallIterations) { iterations++; // Execute all function calls from the response foreach (var functionCall in completion.Message.Contents.OfType()) { try { // Look up the actual function if (!functionLookup.TryGetValue(functionCall.Name, out var function)) { throw new InvalidOperationException($"Function '{functionCall.Name}' not found"); } // Invoke the function with arguments var result = await function.InvokeAsync(functionCall.Arguments, cancellationToken); // Add function result to conversation as a tool message var toolMessage = new ChatMessage(ChatRole.Tool, result?.ToString() ?? "null"); toolMessage.Contents.Add(new FunctionResultContent(functionCall.CallId, functionCall.Name, result)); messages.Add(toolMessage); } catch (Exception ex) { // Handle function call errors gracefully var errorMessage = new ChatMessage(ChatRole.Tool, $"Error executing {functionCall.Name}: {ex.Message}"); errorMessage.Contents.Add(new FunctionResultContent(functionCall.CallId, functionCall.Name, $"Error: {ex.Message}")); messages.Add(errorMessage); } } // Get next completion with function results completion = await chatClient.CompleteAsync(messages, options, cancellationToken); messages.Add(completion.Message); } // Store conversation for potential future use ConversationStore[conversationId] = messages; return new AgentResponse( Content: completion.Message.Text ?? "No response", ConversationId: conversationId ); } }