diff --git a/AI/Configuration/XAiApiConfiguration.cs b/AI/Configuration/XAiApiConfiguration.cs new file mode 100644 index 0000000..22d70ae --- /dev/null +++ b/AI/Configuration/XAiApiConfiguration.cs @@ -0,0 +1,29 @@ +namespace xAiApi.AI.Configuration +{ + public class XAiApiConfiguration + { + /// + /// AI Provider Client URL ... + /// + /// + public string Url { get; set; } + + /// + /// Text Generator Model Name ... + /// + /// + public string TextModel { get; set; } + + /// + /// Code Generator Model Name ... + /// + /// + public string CodeModel { get; set; } + + /// + /// Image Generator Model Name ... + /// + /// + public string ImageModel { get; set; } + } +} \ No newline at end of file diff --git a/AI/Constants/ConfigurationNodeNames.cs b/AI/Constants/ConfigurationNodeNames.cs new file mode 100644 index 0000000..d7bbf91 --- /dev/null +++ b/AI/Constants/ConfigurationNodeNames.cs @@ -0,0 +1,7 @@ +namespace xAiApi.AI.Constants +{ + public partial struct ConfigurationNodeNames + { + public const string AI_API_SERVICE_NODE = "AiApiServiceConfiguration"; + } +} \ No newline at end of file diff --git a/AI/DI/AIExtensions.cs b/AI/DI/AIExtensions.cs index 7ab2e5e..95b3e62 100644 --- a/AI/DI/AIExtensions.cs +++ b/AI/DI/AIExtensions.cs @@ -11,9 +11,10 @@ namespace xAiApi.AI.DI /// Register AI Services ... /// /// - public static void AddXAIServices(this IServiceCollection services) + public static void AddXAIServices( + this IServiceCollection services + ) { - // services.AddSingleton(); } @@ -23,5 +24,9 @@ namespace xAiApi.AI.DI /// public static void UseXAIServices(this IApplicationBuilder builder) { } + + // + #region Private ... + #endregion } } \ No newline at end of file diff --git a/AI/Extensions/XAiApiConfigurationExtension.cs b/AI/Extensions/XAiApiConfigurationExtension.cs new file mode 100644 index 0000000..d9da12f --- /dev/null +++ b/AI/Extensions/XAiApiConfigurationExtension.cs @@ -0,0 +1,70 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using xAiApi.AI.Configuration; +using xAiApi.AI.Constants; +using xCommons.Extensions; + +namespace xAiApi.AI.Extensions +{ + public static class XAiApiConfigurationExtension + { + /// + /// Extract Configuration ... + /// + /// + /// + public static XAiApiConfiguration GetXAiApiConfiguration(this IConfiguration source) + { + // + var section = source.GetSection(ConfigurationNodeNames.AI_API_SERVICE_NODE); + var result = section.Get(); + + // + return result; + } + + /// + /// Add XAiApiConfiguration ... + /// + /// + /// + public static void AddXAiApiConfiguration( + this IServiceCollection source, + XAiApiConfiguration configuration + ) + { + // + if (!configuration.IsValid()) + { + return; + } + + // + source.AddSingleton(configuration); + } + + /// + /// Validate XAiApiConfiguration ... + /// + /// + /// + public static bool IsValid(this XAiApiConfiguration configuration) + { + // + var result = + !configuration.IsNull() && + !configuration.Url.IsNullOrEmpty() && + configuration.Url.IsValidUrl() && + !configuration.TextModel.IsNullOrEmpty(); + + // + if (!result) + { + return result; + } + + // + return result; + } + } +} \ No newline at end of file diff --git a/AI/Interfaces/IXAIService.cs b/AI/Interfaces/IXAIService.cs index cbade33..0e37b82 100644 --- a/AI/Interfaces/IXAIService.cs +++ b/AI/Interfaces/IXAIService.cs @@ -1,28 +1,84 @@ +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.AI; +using Microsoft.SemanticKernel.ChatCompletion; +using xAiApi.AI.Configuration; namespace xAiApi.AI.Interfaces { public interface IXAIService { + /// + /// Provides Options for Chat ... + /// + /// + ChatOptions ChatOptions { get; } + + /// + /// Provides History for Chat ... + /// + /// + ChatHistory ChatHistory { get; } + + /// + /// a Client for Text Chat ... + /// + /// IChatClient TextGenreatorClient { get; } + + /// + /// a Client for Code Chat ... + /// + /// + IChatClient CodeGenreatorClient { get; } + + /// + /// a Client for Image Chats ... + /// + /// IChatClient ImageGenreatorClient { get; } + /// + /// Configuration ... + /// + /// + XAiApiConfiguration Configuration { get; } + // #region Actions ... /// /// Generated Text ... /// /// + /// /// - Task GetTextResponseAsync(string prompt); + Task GetTextResponseAsync( + string prompt, + CancellationToken cancellationToken = default + ); /// /// Generated Response ... /// /// + /// /// - Task GetResponseAsync(string prompt); + Task GetResponseAsync( + string prompt, + CancellationToken cancellationToken = default + ); + + /// + /// Generate Text Response Stream ... + /// + /// + /// + /// + IAsyncEnumerable GetTextReponseStreamAsync( + string prompt, + CancellationToken cancellationToken = default + ); #endregion } } \ No newline at end of file diff --git a/AI/Services/XAIService.cs b/AI/Services/XAIService.cs index 1e4a593..42fe3db 100644 --- a/AI/Services/XAIService.cs +++ b/AI/Services/XAIService.cs @@ -1,37 +1,140 @@ using System; +using OllamaSharp; using System.Threading.Tasks; using Microsoft.Extensions.AI; -using OllamaSharp; using xAiApi.AI.Interfaces; using xCommons.Extensions; using xExceptions.Constants; +using xAiApi.AI.Configuration; +using System.Collections.Generic; +using Microsoft.SemanticKernel.ChatCompletion; +using System.Threading; namespace xAiApi.AI.Services { public class XAIService : IXAIService { - // + /// + /// Provides Options for Chat ... + /// + /// + public ChatOptions ChatOptions { get; } + + /// + /// Provides History for Chat ... + /// + /// + public ChatHistory ChatHistory { get; } + + /// + /// a Client for Text Chat ... + /// + /// public IChatClient TextGenreatorClient { get; } + + /// + /// a Client for Code Chat ... + /// + /// + public IChatClient CodeGenreatorClient { get; } + + /// + /// a Client for Image Chats ... + /// + /// public IChatClient ImageGenreatorClient { get; } - public XAIService() + /// + /// Configuration ... + /// + /// + public XAiApiConfiguration Configuration { get; } + + /// + /// Constructing Service ... + /// + /// configuration ... + public XAIService( + XAiApiConfiguration configuration + ) { + // + Configuration = configuration; + // // here we are Initialize Text Generator Model ... - TextGenreatorClient = new OllamaApiClient( - new Uri("http://localhost:11434"), - "gemma3:1b" - ); + TextGenreatorClient = new ChatClientBuilder(new OllamaApiClient( + new Uri(configuration.Url), + configuration.TextModel + )) + // .UseFunctionInvocation() + .Build(); + + // + // Code Generator Client ... + if (!configuration.CodeModel.IsNullOrEmpty()) + { + // + CodeGenreatorClient = new ChatClientBuilder(new OllamaApiClient( + new Uri(configuration.Url), + configuration.CodeModel + )) + // .UseFunctionInvocation() + .Build(); + } + + // + // Image Generator Client ... + if (!configuration.ImageModel.IsNullOrEmpty()) + { + // + ImageGenreatorClient = new ChatClientBuilder(new OllamaApiClient( + new Uri(configuration.Url), + configuration.ImageModel + )) + // .UseFunctionInvocation() + .Build(); + } + + // + // Configuring Chant Options ... + ChatOptions = new ChatOptions() + { + // + // Preparing Tools ... + // Tools = [ + // // + // // Tempreature Tool ... + // AIFunctionFactory.Create((string location, string unit) => { + // // + // var temp = Random.Shared.Next(5, 20); + // var cond = Random.Shared.Next(0, 1) == 0 ? "sunny" : "rainy"; + + // // + // var result = $"The weather is {temp} degrees C and {cond}."; + + // // + // return result; + // }, + // "get_current_weather", + // "Get the current weather in given location" + // ) + // ], + }; } // - #region Actions ... + #region Text Actions ... /// /// Generated Response ... /// /// + /// /// - public async Task GetResponseAsync(string prompt) + public async Task GetResponseAsync( + string prompt, + CancellationToken cancellationToken = default + ) { // // Validate ... @@ -41,10 +144,21 @@ namespace xAiApi.AI.Services XException.InvalidArgs.Throw(); } + // + // Preparing Prompt ... + prompt = await PreparePrompt( + prompt: prompt, + cancellationToken: cancellationToken + ); + // // Generate Response ... var result = await TextGenreatorClient - .GetResponseAsync(prompt); + .GetResponseAsync( + chatMessage: prompt, + options: ChatOptions, + cancellationToken: cancellationToken + ); // return result; @@ -54,13 +168,88 @@ namespace xAiApi.AI.Services /// Generated Text ... /// /// + /// /// - public async Task GetTextResponseAsync(string prompt) + public async Task GetTextResponseAsync( + string prompt, + CancellationToken cancellationToken = default + ) { // - var response = await GetResponseAsync(prompt); + var response = await GetResponseAsync( + prompt: prompt, + cancellationToken: cancellationToken + ); + + // return response.Text; } + + /// + /// Generate Text Response Stream ... + /// + /// + /// + /// + public IAsyncEnumerable GetTextReponseStreamAsync( + string prompt, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + var isValid = !prompt.IsNullOrEmpty(); + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + // Preparing Prompt ... + prompt = PreparePrompt( + prompt: prompt, + cancellationToken: cancellationToken + ) + .RunTask(); + + // + // Generate Response ... + var result = TextGenreatorClient + .GetStreamingResponseAsync( + chatMessage: prompt, + options: ChatOptions, + cancellationToken: cancellationToken + ); + + // + return result; + } + #endregion + + // + #region Code Actions ... + #endregion + + // + #region Image Actions ... + #endregion + + // + #region Private ... + /// + /// Preparing Prompt for AI ... + /// + /// + /// + /// + private async Task PreparePrompt( + string prompt, + CancellationToken cancellationToken = default + ) + { + // + return await Task.FromResult(prompt); + } #endregion } } \ No newline at end of file diff --git a/Controllers/V1/AIController.cs b/Controllers/V1/AIController.cs index e666888..fd0bc21 100644 --- a/Controllers/V1/AIController.cs +++ b/Controllers/V1/AIController.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging; using xAiApi.AI.Interfaces; using xAiApi.Base; using xCommons.Configurations; +using xCommons.Extensions; using xCommons.Providers; using xIdentityService.Interfaces; @@ -57,6 +58,37 @@ namespace xAiApi.Controllers.V1 return result; } } + + [AllowAnonymous] + [HttpGet("AskAIStream")] + public async Task AskAIStream( + [FromQuery] string prompt + ) + { + // + // Do ... + try + { + // + var stream = aiService + .GetTextReponseStreamAsync(prompt); + await foreach (var message in stream) + { + // + // Do What we Have to Do with Message ... + + // + var msgBytes = message.Text.ToBytes(); + await Response.Body.WriteAsync(msgBytes, 0, msgBytes.Length); + await Response.Body.FlushAsync(); + } + + // + await Response.Body.FlushAsync(); + } + catch + { } + } #endregion } } \ No newline at end of file diff --git a/DI/.gitkeep b/DI/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/DI/XPushDIExtensions.cs b/DI/XPushDIExtensions.cs deleted file mode 100644 index 38c1175..0000000 --- a/DI/XPushDIExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; - -namespace xAiApi.DI -{ - public static class XPushDIExtensions - { - /// - /// Register XPushProvider Services - /// - /// - public static void AddXPushHubProvider( - this IServiceCollection services, - ServiceLifetime lifeTime = ServiceLifetime.Transient - ) - { - AddPushHubProvider(services, lifeTime); - } - - // - #region Private ... - private static void AddPushHubProvider( - this IServiceCollection services, - ServiceLifetime lifeTime = ServiceLifetime.Transient - ) - { } - #endregion - } -} \ No newline at end of file diff --git a/Startup.cs b/Startup.cs index 4b46c20..13ac009 100644 --- a/Startup.cs +++ b/Startup.cs @@ -23,11 +23,11 @@ using xDataService.DI; using xDataService.Interfaces; using xPushService.Helpers; using xAiApi.Extensions; -using xAiApi.DI; using xAiApi.Data.Helpers; using xAiApi.Data; using xAiApi.Data.Seeder; using xAiApi.AI.DI; +using xAiApi.AI.Extensions; // using xApi.Extensions; // using xDataHelper; // using xDataHelper.DbSeeder; @@ -122,7 +122,7 @@ namespace xAiApi // // Register XPushService ... services.AddXPushService(Configuration); - services.AddXPushHubProvider(lifeTime); + // services.AddXPushHubProvider(lifeTime); // // Register XIdentityService ... @@ -203,6 +203,12 @@ namespace xAiApi }); #endregion + // + // Registering Configurations ... + var xAiApiConfiguration = Configuration + .GetXAiApiConfiguration(); + services.AddXAiApiConfiguration(xAiApiConfiguration); + // // Register AI Service ... services.AddXAIServices(); diff --git a/appsettings.Development.json b/appsettings.Development.json index 9af7537..03590fd 100644 --- a/appsettings.Development.json +++ b/appsettings.Development.json @@ -7,6 +7,10 @@ "http://api.saherelmhub.ir", "https://api.saherelmhub.ir" ], + "AiApiServiceConfiguration": { + "Url":"http://localhost:11434", + "TextModel": "gemma3:1b" + }, "IdentityServiceConfiguration": { "Authority": "https://localhost:4001", "ApiName": "xSaherElmAPI",