using Microsoft.Extensions.Options; using Rag.Models.Settings; using Rag.Data.Repositories.Contracts; using Api.Clients.Ai.Contracts; using CommonHelpers; namespace Api.Clients.Ai; public sealed class CachedRagAiClient : IAiClient { private readonly RagAiClient _client; private readonly IRagRepository _repository; private readonly AiSettings _settings; public CachedRagAiClient(RagAiClient client, IRagRepository repository, IOptions options) { _client = client; _repository = repository; _settings = options.Value; } public async Task CreateEmbeddingAsync(string input, CancellationToken ct) { var model = GetEmbeddingModel(); var textHash = HashHelper.Compute(input); var cacheKey = HashHelper.Compute($"embedding:{_settings.Provider}:{model}:{textHash}"); var cached = await _repository.GetEmbeddingAsync(cacheKey, ct); if (cached is not null) return cached; var vector = await _client.CreateEmbeddingAsync(input, ct); await _repository.SaveEmbeddingAsync(cacheKey, model, textHash, vector, ct); return vector; } public async Task CreateChatCompletionAsync(string systemPrompt, string userPrompt, decimal temperature, CancellationToken ct) { var model = GetChatModel(); var cacheKey = HashHelper.Compute($"chat:{_settings.Provider}:{model}:{temperature:0.00}:{systemPrompt}:{userPrompt}"); var cached = await _repository.GetChatCompletionAsync(cacheKey, ct); if (cached is not null) return cached; var response = await _client.CreateChatCompletionAsync(systemPrompt, userPrompt, temperature, ct); await _repository.SaveChatCompletionAsync(cacheKey, model, temperature, response, ct); return response; } private string GetEmbeddingModel() => string.Equals(_settings.Provider, "Ollama", StringComparison.OrdinalIgnoreCase) ? _settings.Ollama.EmbeddingModel : _settings.OpenAI.EmbeddingModel; private string GetChatModel() => string.Equals(_settings.Provider, "Ollama", StringComparison.OrdinalIgnoreCase) ? _settings.Ollama.ChatModel : _settings.OpenAI.ChatModel; }