diff --git a/Configurations/XAiApiConfiguration.cs b/Configurations/XAiApiConfiguration.cs
new file mode 100644
index 0000000..90d738e
--- /dev/null
+++ b/Configurations/XAiApiConfiguration.cs
@@ -0,0 +1,12 @@
+using xAiApi.Models;
+
+namespace xAiApi.Configurations
+{
+ public class XAiApiConfiguration
+ {
+ ///
+ /// Allowed Models to Use ...
+ ///
+ public XAiModelDescriptor[] Models { get; set; } = [];
+ }
+}
\ No newline at end of file
diff --git a/Constants/XAiProvider.cs b/Constants/XAiProvider.cs
new file mode 100644
index 0000000..686caee
--- /dev/null
+++ b/Constants/XAiProvider.cs
@@ -0,0 +1,10 @@
+namespace xAiApi.Constants
+{
+ public enum XAiProvider
+ {
+ None,
+ Ollama,
+ DeepSeek,
+ HuggingFace
+ }
+}
\ No newline at end of file
diff --git a/Extensions/XModelsExtensions.cs b/Extensions/XModelsExtensions.cs
new file mode 100644
index 0000000..45212d7
--- /dev/null
+++ b/Extensions/XModelsExtensions.cs
@@ -0,0 +1,101 @@
+using System.Linq;
+using xAiApi.Configurations;
+using xAiApi.Models;
+using xCommons.Extensions;
+
+namespace xAiApi.Extensions
+{
+ public static class XModelsExtensions
+ {
+ ///
+ /// Validate a Model Descriptor ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XAiModelDescriptor source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ !source.Name.IsNullOrEmpty() &&
+ !source.Url.IsNullOrEmpty() &&
+ source.Url.IsValidUrl() &&
+ !source.LLM.IsNullOrEmpty() &&
+ source.Provider != Constants.XAiProvider.None;
+
+ //
+ return result;
+ }
+
+ ///
+ /// Validate Configurations ...
+ ///
+ ///
+ ///
+ public static bool IsValid(this XAiApiConfiguration source)
+ {
+ //
+ var result =
+ !source.IsNullOrDefault() &&
+ source.Models.HasChild() &&
+ source.Models.All(m => m.IsValid());
+
+ //
+ return result;
+ }
+
+ ///
+ /// Extract Default Model Descriptor ...
+ ///
+ ///
+ ///
+ public static XAiModelDescriptor GetDefaultModel(this XAiApiConfiguration source)
+ {
+ //
+ XAiModelDescriptor result = null;
+
+ //
+ if (source.IsValid())
+ {
+ result = source.Models[0];
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Retrieve Specified Model from Config ...
+ ///
+ ///
+ ///
+ ///
+ public static XAiModelDescriptor GetModel(
+ this XAiApiConfiguration source,
+ string name
+ )
+ {
+ //
+ XAiModelDescriptor result = null;
+
+ //
+ if (source.IsValid())
+ {
+ //
+ if (name.IsNullOrEmpty())
+ {
+ result = source.GetDefaultModel();
+ }
+ else
+ {
+ result = source.Models
+ .FirstOrDefault(m => m.Name
+ .Equals(name, System.StringComparison.InvariantCultureIgnoreCase));
+ }
+ }
+
+ //
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Interfaces/IXAIApiServiceBase.cs b/Interfaces/IXAIApiServiceBase.cs
index 832d45e..5799066 100644
--- a/Interfaces/IXAIApiServiceBase.cs
+++ b/Interfaces/IXAIApiServiceBase.cs
@@ -10,7 +10,7 @@ namespace xAiApi.Interfaces
///
/// Describe a Base Service Actions of any Ai Based Services ...
///
- public interface IAIServiceBase : IDisposable
+ public interface IXAIServiceBase : IDisposable
{
///
/// Base Method for Communicate with LLM ...
diff --git a/Models/XAiModelDescriptor.cs b/Models/XAiModelDescriptor.cs
new file mode 100644
index 0000000..2a1ea4e
--- /dev/null
+++ b/Models/XAiModelDescriptor.cs
@@ -0,0 +1,35 @@
+using xAiApi.Constants;
+
+namespace xAiApi.Models
+{
+ ///
+ /// Describe an Ai Model ...
+ ///
+ public class XAiModelDescriptor
+ {
+ ///
+ /// Provider Name ...
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// LLM Provider Url ...
+ ///
+ public string Url { get; set; }
+
+ ///
+ /// Model Name ...
+ ///
+ public string LLM { get; set; }
+
+ ///
+ /// Api Key for Connection ...
+ ///
+ public string ApiKey { get; set; } = null;
+
+ ///
+ /// Model Provider ...
+ ///
+ public XAiProvider Provider { get; set; } = XAiProvider.Ollama;
+ }
+}
\ No newline at end of file
diff --git a/Providers/XAIServiceBase.cs b/Providers/XAIServiceBase.cs
new file mode 100644
index 0000000..a1f7cfc
--- /dev/null
+++ b/Providers/XAIServiceBase.cs
@@ -0,0 +1,306 @@
+using System;
+using System.Linq;
+using System.Threading;
+using xAiApi.Interfaces;
+using xCommons.Extensions;
+using xAiModels.Interfaces;
+using xExceptions.Constants;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using Microsoft.Extensions.Logging;
+using System.Runtime.CompilerServices;
+using Microsoft.Extensions.AI;
+using xAiApi.Models;
+using xAiApi.Configurations;
+using xAiApi.Extensions;
+
+namespace xAiApi.Providers
+{
+ public abstract class XAIServiceBase : IXAIServiceBase
+ {
+ private readonly IXAiDataProvider dataProvider;
+ private readonly ILogger logger;
+ private readonly XAiApiConfiguration configuration;
+
+ protected XAIServiceBase(
+ IXAiDataProvider dataProvider,
+ ILogger logger,
+ XAiApiConfiguration configuration
+ )
+ {
+ this.logger = logger;
+ this.dataProvider = dataProvider;
+ this.configuration = configuration;
+ }
+
+ //
+ #region Actions ...
+ ///
+ /// Base Method for Communicate with LLM ...
+ ///
+ ///
+ ///
+ ///
+ public virtual async Task AskAsync(
+ string prompt,
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Check Prompt Exists ...
+ if (prompt.IsNullOrEmpty())
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ using var client = GetClient();
+ var history = GetHistory(prompt);
+ var response = await client.GetResponseAsync(
+ messages: history,
+ cancellationToken: cancellationToken
+ );
+
+ //
+ // Validate Response ...
+ if (!response.IsValid())
+ {
+ //
+ // Dispose Client ...
+ client.Dispose();
+ XException.ActionFailed.Throw();
+ }
+
+ //
+ // Retrieve Response Text ...
+ var result = response.Text;
+
+ //
+ // Dispose Client ...
+ client.Dispose();
+
+ //
+ return result;
+ }
+
+ ///
+ /// Base Method for Communicate with LLM ...
+ ///
+ ///
+ ///
+ ///
+ public virtual async IAsyncEnumerable AskAsEnumerable(
+ string prompt,
+ [EnumeratorCancellation]
+ CancellationToken cancellationToken = default
+ )
+ {
+ //
+ // Check Prompt Exists ...
+ if (prompt.IsNullOrEmpty())
+ {
+ XException.InvalidArgs.Throw();
+ }
+
+ //
+ using var client = GetClient();
+ var history = GetHistory(prompt);
+ var enumerable = client.GetStreamingResponseAsync(
+ options: null,
+ messages: history
+ );
+
+ //
+ var result = "";
+ await foreach (var res in enumerable)
+ {
+ //
+ // Cancellation Token ...
+ if (cancellationToken.IsCancellationRequested)
+ {
+ yield break;
+ }
+
+ //
+ result += res.Text;
+ }
+
+ //
+ // Dispose Client ...
+ client.Dispose();
+
+ //
+ yield return result;
+ }
+
+ ///
+ /// Get LLM Client instance for Communicating with LLM ...
+ ///
+ ///
+ ///
+ public virtual IChatClient GetClient(string llm = null)
+ {
+ //
+ // Retrieve Model Descriptor ...
+ var modelDescriptor = configuration.GetModel(llm);
+ if (modelDescriptor.IsNullOrDefault())
+ {
+ XException.NotFound.Throw();
+ }
+
+ //
+ bool isOllamaBased = modelDescriptor.Provider == Constants.XAiProvider.Ollama;
+ bool isDeepSeekBased = modelDescriptor.Provider == Constants.XAiProvider.DeepSeek;
+
+ //
+ // Try to Initialize LLm ...
+ var model = modelDescriptor.LLM;
+ var apiKey = modelDescriptor.ApiKey;
+ var url = new Uri(modelDescriptor.Url);
+ var httpClient = GetHttpClient(modelDescriptor.Url);
+
+ //
+ IChatClient result = null;
+ if (isOllamaBased)
+ {
+ //
+ // Instance Ollama Client using Custom HttpCLient ...
+ var ollamaClient = new OllamaApiClient(httpClient, model);
+ result =
+ new ChatClientBuilder(ollamaClient)
+ .Build();
+ }
+ else if (isDeepSeekBased)
+ {
+ //
+ if (apiKey is null ||
+ string.IsNullOrWhiteSpace(apiKey))
+ {
+ throw new Exception("Inavlid Args ...");
+ }
+
+ //
+ var deepseekClient = new DeepSeekChatClient(apiKey: apiKey);
+ result = deepseekClient;
+ }
+ else
+ {
+ //
+ // TODO: Implement this ...
+ }
+
+ //
+ return result ?? throw new NotSupportedException("Chat Client not found ...");
+ }
+
+ ///
+ /// Create Custom HttpClient for Communicating with LLM API ...
+ ///
+ ///
+ ///
+ public virtual HttpClient GetHttpClient(string? url = null)
+ {
+ //
+ var result = new HttpClient
+ {
+ //
+ // Disable timeout completely (not recommended for production)
+ Timeout = Timeout.InfiniteTimeSpan,
+
+ //
+ BaseAddress = url is null
+ ? null
+ : new Uri(url!),
+
+ //
+ // OR set a very long timeout (e.g., 30 minutes for large file uploads)
+ // Timeout = TimeSpan.FromMinutes(30)
+ };
+
+ //
+ return result;
+ }
+
+ ///
+ /// Prepare a Message History List for LLM Communication based on Prompt ...
+ ///
+ ///
+ ///
+ ///
+ public virtual IList GetHistory(
+ string? prompt = null,
+ bool forcePrompt = true
+ )
+ {
+ //
+ // User Prompt ...
+ var promptMessage = new ChatMessage(
+ ChatRole.User,
+ prompt
+ );
+
+ //
+ // Preparing Introduction Prompt ...
+ var introductionPrompt = new ChatMessage(
+ ChatRole.System,
+ PromptHelper.GetIntroductionPrompt().Prompt
+ );
+
+ //
+ List result =
+ [
+ introductionPrompt,
+ ];
+ if (forcePrompt &&
+ prompt is not null &&
+ !string.IsNullOrWhiteSpace(prompt))
+ {
+ result.Add(promptMessage);
+ }
+
+ //
+ return result;
+ }
+
+ ///
+ /// Prepare a Message History List for LLM Communication based on Prompt ...
+ ///
+ ///
+ ///
+ ///
+ public virtual IList GetHistory(
+ ChatMessage? message = null,
+ bool forceMessage = true
+ )
+ {
+ //
+ // Preparing Introduction Prompt ...
+ var introductionPrompt = new ChatMessage(
+ ChatRole.System,
+ PromptHelper.GetIntroductionPrompt().Prompt
+ );
+
+ //
+ List result =
+ [
+ introductionPrompt,
+ ];
+ if (forceMessage &&
+ message is not null)
+ {
+ result.Add(message);
+ }
+
+ //
+ return result;
+ }
+ #endregion
+
+ ///
+ /// Base Dispose Action ...
+ ///
+ ///
+ public virtual void Dispose()
+ { }
+ }
+}
\ No newline at end of file
diff --git a/xAiApi.csproj b/xAiApi.csproj
index 790473d..42f3a9d 100644
--- a/xAiApi.csproj
+++ b/xAiApi.csproj
@@ -47,6 +47,7 @@
+