#pragma warning disable SKEXP0001 using ChatApi.Models; using ChatRAG.Contracts.VectorSearch; using ChatRAG.Models; using ChatRAG.Services.Contracts; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Embeddings; namespace ChatRAG.Services.ResponseService { public class QdrantResponseService : IResponseService { private readonly IVectorSearchService _vectorSearchService; private readonly ITextEmbeddingGenerationService _embeddingService; private readonly IChatCompletionService _chatService; private readonly ILogger _logger; public QdrantResponseService( IVectorSearchService vectorSearchService, ITextEmbeddingGenerationService embeddingService, IChatCompletionService chatService, ILogger logger) { _vectorSearchService = vectorSearchService; _embeddingService = embeddingService; _chatService = chatService; _logger = logger; } public string ProviderName => "Qdrant"; public async Task GetResponse( UserData userData, string projectId, string sessionId, string userMessage, string language = "pt") { try { _logger.LogInformation("Processando consulta RAG com Qdrant para projeto {ProjectId}", projectId); // 1. Gerar embedding da pergunta do usuário var questionEmbedding = await _embeddingService.GenerateEmbeddingAsync(userMessage); var embeddingArray = questionEmbedding.ToArray().Select(e => (double)e).ToArray(); // 2. Buscar documentos similares no Qdrant var searchResults = await _vectorSearchService.SearchSimilarDynamicAsync( queryEmbedding: embeddingArray, projectId: projectId, minThreshold: 0.5, limit: 5 ); // 3. Construir contexto a partir dos resultados var context = BuildContextFromResults(searchResults); // 4. Criar prompt com contexto var prompt = BuildRagPrompt(userMessage, context); // 5. Gerar resposta usando LLM var response = await _chatService.GetChatMessageContentAsync(prompt); _logger.LogDebug("Resposta RAG gerada com {ResultCount} documentos do Qdrant", searchResults.Count); return response.Content ?? "Desculpe, não foi possível gerar uma resposta."; } catch (Exception ex) { _logger.LogError(ex, "Erro ao processar consulta RAG com Qdrant"); return "Ocorreu um erro ao processar sua consulta. Tente novamente."; } } public async Task GetResponseWithHistory( UserData userData, string projectId, string sessionId, string userMessage, List conversationHistory) { try { // Combina histórico com mensagem atual para melhor contexto var enhancedMessage = BuildEnhancedMessageWithHistory(userMessage, conversationHistory); return await GetResponse(userData, projectId, sessionId, enhancedMessage); } catch (Exception ex) { _logger.LogError(ex, "Erro ao processar consulta RAG com histórico"); return "Ocorreu um erro ao processar sua consulta. Tente novamente."; } } // ======================================== // MÉTODOS AUXILIARES PRIVADOS // ======================================== private string BuildContextFromResults(List results) { if (!results.Any()) { return "Nenhum documento relevante encontrado."; } var contextBuilder = new System.Text.StringBuilder(); contextBuilder.AppendLine("=== CONTEXTO DOS DOCUMENTOS ==="); foreach (var result in results.Take(5)) // Limita a 5 documentos { contextBuilder.AppendLine($"\n--- Documento: {result.Title} (Relevância: {result.GetScorePercentage()}) ---"); contextBuilder.AppendLine(result.Content); contextBuilder.AppendLine(); } return contextBuilder.ToString(); } private string BuildRagPrompt(string userQuestion, string context) { return $@" Você é um assistente especializado que responde perguntas baseado nos documentos fornecidos. CONTEXTO DOS DOCUMENTOS: {context} PERGUNTA DO USUÁRIO: {userQuestion} INSTRUÇÕES: - Responda baseado APENAS nas informações dos documentos fornecidos - Se a informação não estiver nos documentos, diga que não encontrou a informação - Seja preciso e cite trechos relevantes quando possível - Mantenha um tom profissional e prestativo - Se houver múltiplas informações relevantes, organize-as de forma clara RESPOSTA: "; } private string BuildEnhancedMessageWithHistory(string currentMessage, List history) { if (!history.Any()) return currentMessage; var enhancedMessage = new System.Text.StringBuilder(); enhancedMessage.AppendLine("HISTÓRICO DA CONVERSA:"); foreach (var message in history.TakeLast(3)) // Últimas 3 mensagens para contexto { enhancedMessage.AppendLine($"- {message}"); } enhancedMessage.AppendLine($"\nPERGUNTA ATUAL: {currentMessage}"); return enhancedMessage.ToString(); } // ======================================== // MÉTODOS DE ESTATÍSTICAS // ======================================== public async Task> GetProviderStatsAsync() { try { var vectorStats = await _vectorSearchService.GetStatsAsync(); return new Dictionary(vectorStats) { ["response_service_provider"] = "Qdrant", ["supports_history"] = true, ["supports_dynamic_threshold"] = true, ["last_check"] = DateTime.UtcNow }; } catch (Exception ex) { return new Dictionary { ["response_service_provider"] = "Qdrant", ["health"] = "error", ["error"] = ex.Message, ["last_check"] = DateTime.UtcNow }; } } public async Task IsHealthyAsync() { try { return await _vectorSearchService.IsHealthyAsync(); } catch { return false; } } public Task GetResponse(UserData userData, string projectId, string sessionId, string question) { return this.GetResponse(userData, projectId, sessionId, question, "pt"); } } } #pragma warning restore SKEXP0001