ChatRAG/Services/ResponseService/QdrantResponseService.cs
2025-06-15 21:34:47 -03:00

206 lines
7.3 KiB
C#

#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<QdrantResponseService> _logger;
public QdrantResponseService(
IVectorSearchService vectorSearchService,
ITextEmbeddingGenerationService embeddingService,
IChatCompletionService chatService,
ILogger<QdrantResponseService> logger)
{
_vectorSearchService = vectorSearchService;
_embeddingService = embeddingService;
_chatService = chatService;
_logger = logger;
}
public string ProviderName => "Qdrant";
public async Task<string> GetResponse(
UserData userData,
string projectId,
string sessionId,
string userMessage)
{
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<string> GetResponseWithHistory(
UserData userData,
string projectId,
string sessionId,
string userMessage,
List<string> 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<VectorSearchResult> 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<string> 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<Dictionary<string, object>> GetProviderStatsAsync()
{
try
{
var vectorStats = await _vectorSearchService.GetStatsAsync();
return new Dictionary<string, object>(vectorStats)
{
["response_service_provider"] = "Qdrant",
["supports_history"] = true,
["supports_dynamic_threshold"] = true,
["last_check"] = DateTime.UtcNow
};
}
catch (Exception ex)
{
return new Dictionary<string, object>
{
["response_service_provider"] = "Qdrant",
["health"] = "error",
["error"] = ex.Message,
["last_check"] = DateTime.UtcNow
};
}
}
public async Task<bool> IsHealthyAsync()
{
try
{
return await _vectorSearchService.IsHealthyAsync();
}
catch
{
return false;
}
}
}
}
#pragma warning restore SKEXP0001