diff --git a/Luna.Agents.Abstractions/IAgent.cs b/Luna.Agents.Abstractions/IAgent.cs new file mode 100644 index 0000000..63e2a4d --- /dev/null +++ b/Luna.Agents.Abstractions/IAgent.cs @@ -0,0 +1,19 @@ +using Luna.Configuration; +using Microsoft.Extensions.AI; + +namespace Luna.Agents.Abstractions; + +public interface IAgent +{ + public static virtual string Name { get; } = null!; + + public string DisplayName { get; } + public string Description { get; } + public AgentOptions Options { get; init; } + + Task> ProcessAsync(IReadOnlyList messages, + CancellationToken ct = default); + + IAsyncEnumerable ProcessStreamingAsync(IReadOnlyList messages, + CancellationToken ct = default); +} diff --git a/Luna.Agents.Abstractions/Luna.Agents.Abstractions.csproj b/Luna.Agents.Abstractions/Luna.Agents.Abstractions.csproj new file mode 100644 index 0000000..99e7d90 --- /dev/null +++ b/Luna.Agents.Abstractions/Luna.Agents.Abstractions.csproj @@ -0,0 +1,18 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + + diff --git a/Luna.Agents/Core/CoreAgent.cs b/Luna.Agents/Core/CoreAgent.cs new file mode 100644 index 0000000..5a58ea8 --- /dev/null +++ b/Luna.Agents/Core/CoreAgent.cs @@ -0,0 +1,55 @@ +using System.Text.Json; +using Luna.Agents.Abstractions; +using Luna.Configuration; +using Luna.Providers.Abstractions; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Luna.Agents.Core; + +public class CoreAgent( + [FromKeyedServices("Mistral")] IProvider provider, + IOptionsMonitor options, + ILogger logger) + : IAgent +{ + private readonly IChatClient chatClient = provider.GetChatClient(options.Get(Name).ModelId); + + public static string Name => "Core"; + + public string DisplayName => Options.DisplayName ?? Name; + public string Description => Options.Description ?? string.Empty; + public AgentOptions Options { get; init; } = options.Get(Name); + + public async Task> ProcessAsync( + IReadOnlyList messages, + CancellationToken ct = default) + { + var messagesJson = JsonSerializer.Serialize(messages); + logger.LogInformation("Processing messages: {MessagesJson}", messagesJson); + + var chatOptions = new ChatOptions + { + Instructions = Options.Instructions + }; + + var response = await chatClient.GetResponseAsync(messages, chatOptions, ct); + + logger.LogInformation("Response: {ResponseJson}", JsonSerializer.Serialize(response)); + return response.Messages; + } + + public IAsyncEnumerable ProcessStreamingAsync( + IReadOnlyList messages, + CancellationToken ct = default) + { + var chatOptions = new ChatOptions + { + Instructions = Options.Instructions + }; + + return chatClient.GetStreamingResponseAsync(messages, chatOptions, ct); + } +} \ No newline at end of file diff --git a/Luna.Agents/Extensions/ServiceCollectionExtensions.cs b/Luna.Agents/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..89905ea --- /dev/null +++ b/Luna.Agents/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,32 @@ +using Luna.Agents.Abstractions; +using Luna.Agents.Core; +using Luna.Agents.Librarian; +using Luna.Configuration.Extensions; +using Microsoft.Extensions.DependencyInjection; + +namespace Luna.Agents.Extensions; + +public static class ServiceCollectionExtensions +{ + extension(IServiceCollection services) + { + public IServiceCollection AddAgents() + { + services.AddAgent(); + services.AddAgent(); + + return services; + } + + public IServiceCollection AddAgent(string? name = null) + where TAgent : class, IAgent + { + name ??= TAgent.Name; + + services.AddAgentOptions(name); + services.AddKeyedTransient(name); + + return services; + } + } +} \ No newline at end of file diff --git a/Luna.Agents/Librarian/LibrarianAgent.cs b/Luna.Agents/Librarian/LibrarianAgent.cs new file mode 100644 index 0000000..902ba66 --- /dev/null +++ b/Luna.Agents/Librarian/LibrarianAgent.cs @@ -0,0 +1,60 @@ +using Luna.Agents.Abstractions; +using Luna.Configuration; +using Luna.Providers.Abstractions; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Luna.Agents.Librarian; + +public class LibrarianAgent( + [FromKeyedServices("Mistral")] IProvider provider, + IOptionsMonitor options) + : IAgent +{ + private readonly IChatClient chatClient = provider.GetChatClient(options.Get(Name).ModelId); + + public static string Name => "Librarian"; + + public string DisplayName => Options.DisplayName ?? Name; + public string Description => Options.Description ?? string.Empty; + public AgentOptions Options { get; init; } = options.Get(Name); + + public async Task> ProcessAsync( + IReadOnlyList messages, + CancellationToken ct) + { + var chatOptions = new ChatOptions + { + Instructions = Options.Instructions + }; + + var messagesWithoutSystemPrompt = messages + .Where(m => m.Role != ChatRole.System) + .ToList(); + + var response = await chatClient.GetResponseAsync(messagesWithoutSystemPrompt, chatOptions, ct); + return response.Messages + .Where(m => m.Role != ChatRole.System); + } + + public async IAsyncEnumerable ProcessStreamingAsync(IReadOnlyList messages, CancellationToken ct = default) + { + var chatOptions = new ChatOptions + { + Instructions = Options.Instructions + }; + + var messagesWithoutSystemPrompt = messages + .Where(m => m.Role != ChatRole.System) + .ToList(); + + await foreach (var update in chatClient.GetStreamingResponseAsync(messagesWithoutSystemPrompt, chatOptions, ct)) + { + if (update.Role != ChatRole.System) + { + yield return update; + } + } + } +} \ No newline at end of file diff --git a/Luna.Agents/Luna.Agents.csproj b/Luna.Agents/Luna.Agents.csproj new file mode 100644 index 0000000..4efe773 --- /dev/null +++ b/Luna.Agents/Luna.Agents.csproj @@ -0,0 +1,22 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + + + + + +