feat: chatbot e prompts exernos em arquivos
This commit is contained in:
parent
e75abe7fc8
commit
d5b0c32a66
11
Configuration/Prompts/Domains/Financeiro.json
Normal file
11
Configuration/Prompts/Domains/Financeiro.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"Name": "Financeiro",
|
||||
"Description": "Configurações para projetos financeiros e contábeis",
|
||||
"Keywords": [ "financeiro", "contábil", "faturamento", "cobrança", "pagamento", "receita", "despesa" ],
|
||||
"Concepts": [ "fluxo de caixa", "conciliação", "relatórios financeiros", "impostos", "audit trail" ],
|
||||
"Prompts": {
|
||||
"pt": {
|
||||
"Response": "Você é um especialista em sistemas financeiros e contabilidade.\n\nSISTEMA FINANCEIRO: {0}\nPERGUNTA: \"{1}\"\nCONTEXTO FINANCEIRO: {2}\nANÁLISE REALIZADA: {3}\n\nResponda considerando:\n- Controles financeiros\n- Auditoria e compliance\n- Fluxos de aprovação\n- Relatórios gerenciais\n- Segurança de dados financeiros\n\nSeja preciso e considere aspectos regulatórios."
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Configuration/Prompts/Domains/QA.json
Normal file
11
Configuration/Prompts/Domains/QA.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"Name": "Quality Assurance",
|
||||
"Description": "Configurações para projetos de QA e testes",
|
||||
"Keywords": [ "teste", "qa", "qualidade", "bug", "defeito", "validação", "verificação" ],
|
||||
"Concepts": [ "test cases", "automation", "regression", "performance", "security testing" ],
|
||||
"Prompts": {
|
||||
"pt": {
|
||||
"Response": "Você é um especialista em Quality Assurance e testes de software.\n\nPROJETO: {0}\nPERGUNTA DE QA: \"{1}\"\nCONTEXTO DE TESTES: {2}\nANÁLISE EXECUTADA: {3}\n\nResponda com foco em:\n- Estratégias de teste\n- Casos de teste específicos\n- Automação e ferramentas\n- Critérios de aceitação\n- Cobertura de testes\n\nSeja detalhado e metodológico na abordagem."
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Configuration/Prompts/Domains/RH.json
Normal file
11
Configuration/Prompts/Domains/RH.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"Name": "Recursos Humanos",
|
||||
"Description": "Configurações para projetos de RH e gestão de pessoas",
|
||||
"Keywords": [ "funcionário", "colaborador", "cargo", "departamento", "folha", "benefícios", "treinamento" ],
|
||||
"Concepts": [ "gestão de pessoas", "recrutamento", "seleção", "avaliação", "desenvolvimento" ],
|
||||
"Prompts": {
|
||||
"pt": {
|
||||
"Response": "Você é um especialista em Recursos Humanos e gestão de pessoas.\n\nSISTEMA DE RH: {0}\nPERGUNTA: \"{1}\"\nCONTEXTO: {2}\nPROCESSOS ANALISADOS: {3}\n\nResponda considerando:\n- Políticas de RH\n- Fluxos de trabalho\n- Compliance e regulamentações\n- Melhores práticas em gestão de pessoas\n\nSeja claro e prático nas recomendações."
|
||||
}
|
||||
}
|
||||
}
|
||||
74
Configuration/Prompts/Domains/Servicos.json
Normal file
74
Configuration/Prompts/Domains/Servicos.json
Normal file
@ -0,0 +1,74 @@
|
||||
{
|
||||
"Name": "Serviços JobMaker",
|
||||
"Description": "Chatbot especializado em serviços de RAG, IA empresarial e desenvolvimento da JobMaker",
|
||||
"Keywords": [
|
||||
"rag",
|
||||
"retrieval augmented generation",
|
||||
"semantic kernel",
|
||||
"chatbot",
|
||||
"ia",
|
||||
"inteligencia artificial",
|
||||
"desenvolvimento",
|
||||
"consultoria",
|
||||
"c#",
|
||||
"dotnet",
|
||||
".net",
|
||||
"python",
|
||||
"migracao",
|
||||
"sistema",
|
||||
"sap",
|
||||
"salesforce",
|
||||
"integracao",
|
||||
"web scraping",
|
||||
"rpa",
|
||||
"etl",
|
||||
"mongodb",
|
||||
"qdrant",
|
||||
"workshop",
|
||||
"treinamento",
|
||||
"poc",
|
||||
"prova conceito",
|
||||
"enterprise",
|
||||
"corporativo",
|
||||
"automacao",
|
||||
"performance",
|
||||
"otimizacao",
|
||||
"suporte",
|
||||
"preço",
|
||||
"valor",
|
||||
"custo",
|
||||
"orçamento",
|
||||
"quanto custa",
|
||||
"prazo",
|
||||
"tempo",
|
||||
"entrega",
|
||||
"projeto",
|
||||
"solução"
|
||||
],
|
||||
"Concepts": [
|
||||
"retrieval augmented generation",
|
||||
"microsoft semantic kernel",
|
||||
"chatbot empresarial",
|
||||
"inteligencia artificial conversacional",
|
||||
"migracao python para c#",
|
||||
"integracao sap salesforce",
|
||||
"web scraping rpa",
|
||||
"etl sincronizacao dados",
|
||||
"arquitetura enterprise",
|
||||
"sistemas escaláveis",
|
||||
"poc prova conceito",
|
||||
"consultoria ia empresarial",
|
||||
"apresentação",
|
||||
"boas vindas",
|
||||
"consultoria",
|
||||
"agendamento"
|
||||
],
|
||||
"Prompts": {
|
||||
"pt": {
|
||||
"Response": "Você é um assistente virtual especializado nos serviços da JobMaker, empresa líder em RAG (Retrieval-Augmented Generation) e IA empresarial. Você atende chamadas via chat e responde com cordialidade\n\n🏢 EMPRESA: {0}\n❓ PERGUNTA DO CLIENTE: \"{1}\"\n📊 INFORMAÇÕES DISPONÍVEIS: {2}\n\nResponda de forma:\n✅ **Profissional e técnica** (mas acessível)\n✅ ***Se a pergunta for técnica ou envolver algum termo técnico***\n **Específica sobre nossos serviços**\n✅ **Highlighting nossos diferenciais**: C#/.NET, Semantic Kernel, economia 40-60%, performance 3-5x\n✅ **Incentivando contato** para demonstração ou consultoria\n\n\n- Call-to-action(não adicionar na resposta) para demo/consultoria\n\n⚠️ **Se não tiver informação suficiente:** Seja honesto, destaque que temos expertise em RAG/IA empresarial e ofereça consultoria personalizada."
|
||||
},
|
||||
"en": {
|
||||
"Response": "You are a virtual assistant specialized in JobMaker services, leading company in RAG (Retrieval-Augmented Generation) and enterprise AI.\n\n🏢 COMPANY: {0}\n❓ CUSTOMER QUESTION: \"{1}\"\n📊 AVAILABLE INFORMATION: {2}\n\nRespond in a:\n✅ **Professional and technical** (but accessible) manner\n✅ **Specific about our services**\n✅ **Highlighting our differentiators**: C#/.NET, Semantic Kernel, 40-60% savings, 3-5x performance\n✅ **Encouraging contact** for demonstration or consultation\n\n💡 **Always include:**\n- Concrete benefits (savings, performance)\n- Technologies used\n- Estimated timeline when relevant\n- Call-to-action(do not add to response) for demo/consultation\n\n⚠️ **If insufficient information:** Be honest, highlight our RAG/enterprise AI expertise and offer personalized consultation."
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Configuration/Prompts/Domains/TI.json
Normal file
11
Configuration/Prompts/Domains/TI.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"Name": "Tecnologia da Informação",
|
||||
"Description": "Configurações para projetos de TI e desenvolvimento de software",
|
||||
"Keywords": [ "api", "backend", "frontend", "database", "arquitetura", "código", "classe", "método", "endpoint" ],
|
||||
"Concepts": [ "mvc", "rest", "microservices", "clean architecture", "design patterns", "authentication", "authorization" ],
|
||||
"Prompts": {
|
||||
"pt": {
|
||||
"Response": "Você é um especialista em desenvolvimento de software e arquitetura de sistemas.\n\nPROJETO: {0}\nPERGUNTA TÉCNICA: \"{1}\"\nCONTEXTO TÉCNICO: {2}\nANÁLISE REALIZADA: {3}\n\nResponda com foco técnico, incluindo:\n- Implementação prática\n- Boas práticas de código\n- Considerações de arquitetura\n- Exemplos de código quando relevante\n\nSeja preciso e técnico na resposta."
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Configuration/Prompts/base-prompts.json
Normal file
18
Configuration/Prompts/base-prompts.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"Prompts": {
|
||||
"pt": {
|
||||
"QueryAnalysis": "Analise esta pergunta e classifique com precisão:\nPERGUNTA: \"{0}\"\n\nResponda APENAS no formato JSON:\n{{\n \"strategy\": \"overview|specific|detailed\",\n \"complexity\": \"simple|medium|complex\",\n \"scope\": \"global|filtered|targeted\",\n \"concepts\": [\"conceito1\", \"conceito2\"],\n \"needs_hierarchy\": true|false\n}}",
|
||||
|
||||
"Response": "Você é um especialista em análise de software e QA.\n\nPROJETO: {0}\nPERGUNTA: \"{1}\"\nCONTEXTO HIERÁRQUICO: {2}\nETAPAS EXECUTADAS: {3}\n\nResponda à pergunta de forma precisa e estruturada, aproveitando todo o contexto hierárquico coletado.",
|
||||
|
||||
"Summary": "Resuma os pontos principais destes documentos sobre {0}:\n\n{1}\n\nResponda apenas com uma lista concisa dos pontos mais importantes:",
|
||||
|
||||
"GapAnalysis": "Baseado na pergunta e contexto atual, identifique que informações ainda faltam para uma resposta completa.\n\nPERGUNTA: {0}\nCONTEXTO ATUAL: {1}\n\nResponda APENAS com palavras-chave dos conceitos/informações que ainda faltam, separados por vírgula.\nSe o contexto for suficiente, responda 'SUFICIENTE'."
|
||||
},
|
||||
"en": {
|
||||
"QueryAnalysis": "Analyze this question and classify precisely:\nQUESTION: \"{0}\"\n\nAnswer ONLY in JSON format:\n{{\n \"strategy\": \"overview|specific|detailed\",\n \"complexity\": \"simple|medium|complex\",\n \"scope\": \"global|filtered|targeted\",\n \"concepts\": [\"concept1\", \"concept2\"],\n \"needs_hierarchy\": true|false\n}}",
|
||||
|
||||
"Response": "You are a software analysis and QA expert.\n\nPROJECT: {0}\nQUESTION: \"{1}\"\nHIERARCHICAL CONTEXT: {2}\nEXECUTED STEPS: {3}\n\nAnswer the question precisely and structured, leveraging all the hierarchical context collected."
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -43,7 +43,7 @@ namespace ChatApi.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[Route("response")]
|
||||
public async Task<IActionResult> GetResponse([FromForm] ChatRequest chatRequest)
|
||||
public async Task<IActionResult> GetResponse([FromBody] ChatRequest chatRequest)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
28
Program.cs
28
Program.cs
@ -7,10 +7,13 @@ using ChatRAG.Contracts.VectorSearch;
|
||||
using ChatRAG.Data;
|
||||
using ChatRAG.Extensions;
|
||||
using ChatRAG.Services;
|
||||
using ChatRAG.Services.Confidence;
|
||||
using ChatRAG.Services.Contracts;
|
||||
using ChatRAG.Services.PromptConfiguration;
|
||||
using ChatRAG.Services.ResponseService;
|
||||
using ChatRAG.Services.SearchVectors;
|
||||
using ChatRAG.Services.TextServices;
|
||||
using ChatRAG.Settings;
|
||||
using ChatRAG.Settings.ChatRAG.Configuration;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
@ -76,8 +79,12 @@ builder.Services.AddSwaggerGen(c =>
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.Configure<ChatRHSettings>(
|
||||
builder.Configuration.GetSection("ChatRHSettings"));
|
||||
builder.Services.Configure<ConfidenceSettings>(
|
||||
builder.Configuration.GetSection("Confidence"));
|
||||
|
||||
builder.Services.Configure<ConfidenceAwareSettings>(
|
||||
builder.Configuration.GetSection("ConfidenceAware"));
|
||||
|
||||
|
||||
//builder.Services.AddScoped<IVectorSearchService, MongoVectorSearchService>();
|
||||
builder.Services.AddScoped<QdrantVectorSearchService>();
|
||||
@ -157,8 +164,11 @@ builder.Services.AddScoped<IResponseService>(provider =>
|
||||
{
|
||||
var configuration = provider.GetService<IConfiguration>();
|
||||
var useHierarchical = configuration?.GetValue<bool>("Features:UseHierarchicalRAG") ?? false;
|
||||
var useConfidence = configuration?.GetValue<bool>("Features:UseConfidenceAwareRAG") ?? false;
|
||||
|
||||
return useHierarchical
|
||||
return useConfidence && useHierarchical
|
||||
? provider.GetRequiredService<ConfidenceAwareRAGService>()
|
||||
: useHierarchical
|
||||
? provider.GetRequiredService<HierarchicalRAGService>()
|
||||
: provider.GetRequiredService<ResponseRAGService>();
|
||||
});
|
||||
@ -167,6 +177,13 @@ builder.Services.AddTransient<UserDataRepository>();
|
||||
builder.Services.AddTransient<TextData>();
|
||||
builder.Services.AddSingleton<CryptUtil>();
|
||||
|
||||
// Registrar serviços de confiança
|
||||
builder.Services.AddScoped<ConfidenceVerifier>();
|
||||
builder.Services.AddSingleton<PromptConfigurationService>();
|
||||
|
||||
// Registrar ConfidenceAwareRAGService
|
||||
builder.Services.AddScoped<ConfidenceAwareRAGService>();
|
||||
|
||||
//builder.Services.AddOllamaChatCompletion("phi3.5", new Uri("http://localhost:11435"));
|
||||
//builder.Services.AddOllamaChatCompletion("tinydolphin", new Uri("http://localhost:11435"));
|
||||
//var apiClient = new OllamaApiClient(new Uri("http://localhost:11435"), "tinydolphin");
|
||||
@ -182,7 +199,6 @@ var model = "llama-3.1-8b-instant";
|
||||
var url = "https://api.groq.com/openai/v1";
|
||||
builder.Services.AddOpenAIChatCompletion(model, new Uri(url), key);
|
||||
|
||||
|
||||
//Notebook
|
||||
//var model = "meta-llama/Llama-3.2-3B-Instruct";
|
||||
//var url = "https://api.deepinfra.com/v1/openai"; // Adicione o /v1/openai
|
||||
@ -205,9 +221,9 @@ builder.Services.AddOpenAIChatCompletion(model, new Uri(url), key);
|
||||
//builder.Services.AddOllamaTextEmbeddingGeneration("all-minilm", new Uri("http://192.168.0.150:11434"));
|
||||
|
||||
//Desktop
|
||||
builder.Services.AddOllamaTextEmbeddingGeneration("all-minilm", new Uri("http://localhost:11434"));
|
||||
//builder.Services.AddOllamaTextEmbeddingGeneration("all-minilm", new Uri("http://localhost:11434"));
|
||||
//Notebook
|
||||
//builder.Services.AddOllamaTextEmbeddingGeneration("all-minilm", new Uri("http://localhost:11435"));
|
||||
builder.Services.AddOllamaTextEmbeddingGeneration("all-minilm", new Uri("http://localhost:11435"));
|
||||
|
||||
|
||||
//builder.Services.AddOllamaChatCompletion("phi3.5", new Uri("http://localhost:11435"));
|
||||
|
||||
389
Services/Confidence/ConfidenceVerifier.cs
Normal file
389
Services/Confidence/ConfidenceVerifier.cs
Normal file
@ -0,0 +1,389 @@
|
||||
using ChatRAG.Models;
|
||||
using ChatRAG.Services.ResponseService;
|
||||
using ChatRAG.Settings;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace ChatRAG.Services.Confidence
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifica se o RAG deve responder baseado na confiança dos resultados
|
||||
/// </summary>
|
||||
public class ConfidenceVerifier
|
||||
{
|
||||
private readonly ILogger<ConfidenceVerifier> _logger;
|
||||
private readonly ConfidenceSettings _settings;
|
||||
|
||||
public ConfidenceVerifier(
|
||||
ILogger<ConfidenceVerifier> logger,
|
||||
IOptions<ConfidenceSettings> settings)
|
||||
{
|
||||
_logger = logger;
|
||||
_settings = settings.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifica se deve responder baseado na análise, resultados e contexto
|
||||
/// </summary>
|
||||
public ConfidenceResult VerifyConfidence(
|
||||
QueryAnalysis analysis,
|
||||
List<VectorSearchResult> results,
|
||||
HierarchicalContext context,
|
||||
bool strictMode = true,
|
||||
string language = "pt")
|
||||
{
|
||||
var strategy = analysis.Strategy?.ToLower() ?? "specific";
|
||||
var thresholds = GetThresholds(strategy, strictMode);
|
||||
|
||||
_logger.LogInformation("Verificando confiança - Estratégia: {Strategy}, Modo Restrito: {StrictMode}, Idioma: {Language}",
|
||||
strategy, strictMode, language);
|
||||
|
||||
// Calcular métricas de confiança
|
||||
var metrics = CalculateConfidenceMetrics(results, context, analysis);
|
||||
|
||||
// Verificar se deve responder baseado na estratégia
|
||||
var shouldRespond = ShouldRespond(strategy, metrics, thresholds);
|
||||
|
||||
var result = new ConfidenceResult
|
||||
{
|
||||
ShouldRespond = shouldRespond,
|
||||
ConfidenceScore = metrics.OverallScore,
|
||||
Strategy = strategy,
|
||||
Metrics = metrics,
|
||||
Reason = GenerateReason(shouldRespond, strategy, metrics, thresholds, language),
|
||||
SuggestedResponse = shouldRespond ? null : GenerateFallbackResponse(strategy, metrics, language)
|
||||
};
|
||||
|
||||
_logger.LogInformation("Resultado da confiança: {ShouldRespond} (Score: {ConfidenceScore:P1}, Estratégia: {Strategy})",
|
||||
result.ShouldRespond, result.ConfidenceScore, strategy);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calcula métricas detalhadas de confiança
|
||||
/// </summary>
|
||||
private ConfidenceMetrics CalculateConfidenceMetrics(
|
||||
List<VectorSearchResult> results,
|
||||
HierarchicalContext context,
|
||||
QueryAnalysis analysis)
|
||||
{
|
||||
var relevantResults = results.Where(r => r.Score >= 0.3).ToList();
|
||||
var highQualityResults = results.Where(r => r.Score >= 0.6).ToList();
|
||||
|
||||
var metrics = new ConfidenceMetrics
|
||||
{
|
||||
TotalDocuments = results.Count,
|
||||
RelevantDocuments = relevantResults.Count,
|
||||
HighQualityDocuments = highQualityResults.Count,
|
||||
AverageScore = results.Any() ? results.Average(r => r.Score) : 0,
|
||||
MaxScore = results.Any() ? results.Max(r => r.Score) : 0,
|
||||
MinScore = results.Any() ? results.Min(r => r.Score) : 0,
|
||||
ContextLength = context.CombinedContext?.Length ?? 0,
|
||||
StepsExecuted = context.Steps.Count,
|
||||
HasSpecificContent = HasSpecificContent(results, analysis),
|
||||
OverallScore = CalculateOverallScore(results, context, analysis)
|
||||
};
|
||||
|
||||
// Métricas adicionais
|
||||
metrics.ScoreVariance = CalculateScoreVariance(results);
|
||||
metrics.ContentDiversity = CalculateContentDiversity(results);
|
||||
metrics.ConceptCoverage = CalculateConceptCoverage(results, analysis);
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifica se há conteúdo específico relacionado aos conceitos da query
|
||||
/// </summary>
|
||||
private bool HasSpecificContent(List<VectorSearchResult> results, QueryAnalysis analysis)
|
||||
{
|
||||
if (!analysis.Concepts?.Any() == true)
|
||||
return results.Any(); // Se não há conceitos específicos, qualquer resultado serve
|
||||
|
||||
var combinedContent = string.Join(" ", results.Select(r => $"{r.Title} {r.Content}")).ToLower();
|
||||
var conceptsFound = analysis.Concepts.Count(concept =>
|
||||
combinedContent.Contains(concept.ToLower()));
|
||||
|
||||
var minConceptsRequired = Math.Max(1, Math.Min(2, analysis.Concepts.Length));
|
||||
return conceptsFound >= minConceptsRequired;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calcula score geral considerando múltiplos fatores
|
||||
/// </summary>
|
||||
private double CalculateOverallScore(List<VectorSearchResult> results, HierarchicalContext context, QueryAnalysis analysis)
|
||||
{
|
||||
if (!results.Any()) return 0;
|
||||
|
||||
// Pesos para diferentes fatores
|
||||
const double scoreWeight = 0.35; // Qualidade dos scores
|
||||
const double countWeight = 0.25; // Quantidade de documentos
|
||||
const double contextWeight = 0.20; // Tamanho do contexto
|
||||
const double diversityWeight = 0.10; // Diversidade do conteúdo
|
||||
const double conceptWeight = 0.10; // Cobertura de conceitos
|
||||
|
||||
// Score baseado na qualidade dos resultados
|
||||
var avgScore = results.Average(r => r.Score);
|
||||
var maxScore = results.Max(r => r.Score);
|
||||
var qualityScore = (avgScore * 0.7) + (maxScore * 0.3); // Média ponderada
|
||||
|
||||
// Score baseado na quantidade (com saturação)
|
||||
var countScore = Math.Min(1.0, results.Count / 5.0);
|
||||
|
||||
// Score baseado no tamanho do contexto (com saturação)
|
||||
var contextScore = Math.Min(1.0, (context.CombinedContext?.Length ?? 0) / 2000.0);
|
||||
|
||||
// Score baseado na diversidade do conteúdo
|
||||
var diversityScore = CalculateContentDiversity(results);
|
||||
|
||||
// Score baseado na cobertura de conceitos
|
||||
var conceptScore = CalculateConceptCoverage(results, analysis);
|
||||
|
||||
var overallScore = (qualityScore * scoreWeight) +
|
||||
(countScore * countWeight) +
|
||||
(contextScore * contextWeight) +
|
||||
(diversityScore * diversityWeight) +
|
||||
(conceptScore * conceptWeight);
|
||||
|
||||
return Math.Min(1.0, overallScore);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calcula variância dos scores para medir consistência
|
||||
/// </summary>
|
||||
private double CalculateScoreVariance(List<VectorSearchResult> results)
|
||||
{
|
||||
if (results.Count < 2) return 0;
|
||||
|
||||
var mean = results.Average(r => r.Score);
|
||||
var variance = results.Average(r => Math.Pow(r.Score - mean, 2));
|
||||
|
||||
// Normalizar para 0-1 (menor variância = melhor)
|
||||
return Math.Max(0, 1 - (variance * 4)); // Multiplica por 4 para normalizar
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calcula diversidade do conteúdo (documentos diferentes)
|
||||
/// </summary>
|
||||
private double CalculateContentDiversity(List<VectorSearchResult> results)
|
||||
{
|
||||
if (!results.Any()) return 0;
|
||||
|
||||
// Simples heurística: títulos únicos / total
|
||||
var uniqueTitles = results.Select(r => r.Title?.ToLower() ?? "").Distinct().Count();
|
||||
return Math.Min(1.0, (double)uniqueTitles / results.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calcula cobertura dos conceitos da query
|
||||
/// </summary>
|
||||
private double CalculateConceptCoverage(List<VectorSearchResult> results, QueryAnalysis analysis)
|
||||
{
|
||||
if (!analysis.Concepts?.Any() == true) return 1.0; // Se não há conceitos, assume cobertura total
|
||||
|
||||
var combinedContent = string.Join(" ", results.Select(r => $"{r.Title} {r.Content}")).ToLower();
|
||||
var conceptsFound = analysis.Concepts.Count(concept =>
|
||||
combinedContent.Contains(concept.ToLower()));
|
||||
|
||||
return (double)conceptsFound / analysis.Concepts.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decide se deve responder baseado na estratégia e métricas
|
||||
/// </summary>
|
||||
private bool ShouldRespond(string strategy, ConfidenceMetrics metrics, ConfidenceThresholds thresholds)
|
||||
{
|
||||
return strategy switch
|
||||
{
|
||||
"overview" => ShouldRespondOverview(metrics, thresholds),
|
||||
"specific" => ShouldRespondSpecific(metrics, thresholds),
|
||||
"detailed" => ShouldRespondDetailed(metrics, thresholds),
|
||||
_ => ShouldRespondSpecific(metrics, thresholds) // Default para specific
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lógica específica para estratégia Overview
|
||||
/// </summary>
|
||||
private bool ShouldRespondOverview(ConfidenceMetrics metrics, ConfidenceThresholds thresholds)
|
||||
{
|
||||
// Para overview, precisamos de quantidade e contexto amplo
|
||||
return metrics.TotalDocuments >= thresholds.MinDocuments &&
|
||||
metrics.ContextLength >= thresholds.MinContextLength &&
|
||||
metrics.OverallScore >= thresholds.MinOverallScore &&
|
||||
(metrics.RelevantDocuments >= thresholds.MinRelevantDocuments || metrics.TotalDocuments >= 10); // Flexibilidade para projetos grandes
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lógica específica para estratégia Specific
|
||||
/// </summary>
|
||||
private bool ShouldRespondSpecific(ConfidenceMetrics metrics, ConfidenceThresholds thresholds)
|
||||
{
|
||||
// Para specific, precisamos de relevância e qualidade
|
||||
return metrics.RelevantDocuments >= thresholds.MinRelevantDocuments &&
|
||||
metrics.MaxScore >= thresholds.MinMaxScore &&
|
||||
metrics.OverallScore >= thresholds.MinOverallScore &&
|
||||
metrics.HasSpecificContent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lógica específica para estratégia Detailed
|
||||
/// </summary>
|
||||
private bool ShouldRespondDetailed(ConfidenceMetrics metrics, ConfidenceThresholds thresholds)
|
||||
{
|
||||
// Para detailed, precisamos de alta qualidade e cobertura
|
||||
return metrics.HighQualityDocuments >= thresholds.MinHighQualityDocuments &&
|
||||
metrics.AverageScore >= thresholds.MinAverageScore &&
|
||||
metrics.HasSpecificContent &&
|
||||
metrics.OverallScore >= thresholds.MinOverallScore &&
|
||||
metrics.ConceptCoverage >= 0.5; // Pelo menos 50% dos conceitos cobertos
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtém thresholds ajustados para modo restrito/relaxado
|
||||
/// </summary>
|
||||
private ConfidenceThresholds GetThresholds(string strategy, bool strictMode)
|
||||
{
|
||||
if (!_settings.Thresholds.ContainsKey(strategy))
|
||||
{
|
||||
_logger.LogWarning("Estratégia '{Strategy}' não encontrada, usando 'specific'", strategy);
|
||||
strategy = "specific";
|
||||
}
|
||||
|
||||
var baseThresholds = _settings.Thresholds[strategy];
|
||||
|
||||
if (!strictMode)
|
||||
{
|
||||
// Modo relaxado: reduz os thresholds
|
||||
return new ConfidenceThresholds
|
||||
{
|
||||
MinDocuments = Math.Max(1, baseThresholds.MinDocuments - 2),
|
||||
MinRelevantDocuments = Math.Max(1, baseThresholds.MinRelevantDocuments - 1),
|
||||
MinHighQualityDocuments = Math.Max(0, baseThresholds.MinHighQualityDocuments - 1),
|
||||
MinContextLength = Math.Max(100, baseThresholds.MinContextLength - 500),
|
||||
MinOverallScore = Math.Max(0.1, baseThresholds.MinOverallScore - 0.15),
|
||||
MinMaxScore = Math.Max(0.2, baseThresholds.MinMaxScore - 0.15),
|
||||
MinAverageScore = Math.Max(0.2, baseThresholds.MinAverageScore - 0.15)
|
||||
};
|
||||
}
|
||||
|
||||
return baseThresholds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gera explicação do motivo da decisão
|
||||
/// </summary>
|
||||
private string GenerateReason(bool shouldRespond, string strategy, ConfidenceMetrics metrics, ConfidenceThresholds thresholds, string language)
|
||||
{
|
||||
if (shouldRespond)
|
||||
{
|
||||
return language == "en"
|
||||
? $"Sufficient confidence for '{strategy}' strategy - Score: {metrics.OverallScore:P1}, Docs: {metrics.RelevantDocuments}/{metrics.TotalDocuments}"
|
||||
: $"Confiança suficiente para estratégia '{strategy}' - Score: {metrics.OverallScore:P1}, Docs: {metrics.RelevantDocuments}/{metrics.TotalDocuments}";
|
||||
}
|
||||
|
||||
var issues = new List<string>();
|
||||
|
||||
if (metrics.TotalDocuments < thresholds.MinDocuments)
|
||||
{
|
||||
var msg = language == "en"
|
||||
? $"few documents ({metrics.TotalDocuments} < {thresholds.MinDocuments})"
|
||||
: $"poucos documentos ({metrics.TotalDocuments} < {thresholds.MinDocuments})";
|
||||
issues.Add(msg);
|
||||
}
|
||||
|
||||
if (metrics.RelevantDocuments < thresholds.MinRelevantDocuments)
|
||||
{
|
||||
var msg = language == "en"
|
||||
? $"few relevant documents ({metrics.RelevantDocuments} < {thresholds.MinRelevantDocuments})"
|
||||
: $"poucos documentos relevantes ({metrics.RelevantDocuments} < {thresholds.MinRelevantDocuments})";
|
||||
issues.Add(msg);
|
||||
}
|
||||
|
||||
if (metrics.OverallScore < thresholds.MinOverallScore)
|
||||
{
|
||||
var msg = language == "en"
|
||||
? $"low overall score ({metrics.OverallScore:P1} < {thresholds.MinOverallScore:P1})"
|
||||
: $"score geral baixo ({metrics.OverallScore:P1} < {thresholds.MinOverallScore:P1})";
|
||||
issues.Add(msg);
|
||||
}
|
||||
|
||||
if (!metrics.HasSpecificContent)
|
||||
{
|
||||
var msg = language == "en" ? "no specific content found" : "conteúdo específico não encontrado";
|
||||
issues.Add(msg);
|
||||
}
|
||||
|
||||
var prefix = language == "en" ? "Insufficient confidence: " : "Confiança insuficiente: ";
|
||||
return prefix + string.Join(", ", issues);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gera resposta de fallback apropriada
|
||||
/// </summary>
|
||||
private string GenerateFallbackResponse(string strategy, ConfidenceMetrics metrics, string language)
|
||||
{
|
||||
if (!_settings.FallbackMessages.ContainsKey(language))
|
||||
{
|
||||
language = "pt"; // Fallback para português
|
||||
}
|
||||
|
||||
var messages = _settings.FallbackMessages[language];
|
||||
|
||||
// Escolher mensagem baseada no problema principal
|
||||
if (metrics.TotalDocuments == 0)
|
||||
{
|
||||
return messages.NoDocuments;
|
||||
}
|
||||
|
||||
if (metrics.RelevantDocuments == 0)
|
||||
{
|
||||
return messages.NoRelevantDocuments;
|
||||
}
|
||||
|
||||
return strategy switch
|
||||
{
|
||||
"overview" => messages.InsufficientOverview,
|
||||
"specific" => messages.InsufficientSpecific,
|
||||
"detailed" => messages.InsufficientDetailed,
|
||||
_ => messages.Generic
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resultado da verificação de confiança
|
||||
/// </summary>
|
||||
public class ConfidenceResult
|
||||
{
|
||||
public bool ShouldRespond { get; set; }
|
||||
public double ConfidenceScore { get; set; }
|
||||
public string Strategy { get; set; } = "";
|
||||
public ConfidenceMetrics Metrics { get; set; } = new();
|
||||
public string Reason { get; set; } = "";
|
||||
public string? SuggestedResponse { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Métricas detalhadas de confiança
|
||||
/// </summary>
|
||||
public class ConfidenceMetrics
|
||||
{
|
||||
// Métricas básicas
|
||||
public int TotalDocuments { get; set; }
|
||||
public int RelevantDocuments { get; set; }
|
||||
public int HighQualityDocuments { get; set; }
|
||||
public double AverageScore { get; set; }
|
||||
public double MaxScore { get; set; }
|
||||
public double MinScore { get; set; }
|
||||
public int ContextLength { get; set; }
|
||||
public int StepsExecuted { get; set; }
|
||||
public bool HasSpecificContent { get; set; }
|
||||
public double OverallScore { get; set; }
|
||||
|
||||
// Métricas avançadas
|
||||
public double ScoreVariance { get; set; }
|
||||
public double ContentDiversity { get; set; }
|
||||
public double ConceptCoverage { get; set; }
|
||||
}
|
||||
}
|
||||
753
Services/PromptConfiguration/PromptConfigurationService.cs
Normal file
753
Services/PromptConfiguration/PromptConfigurationService.cs
Normal file
@ -0,0 +1,753 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ChatRAG.Services.Confidence;
|
||||
using ChatRAG.Settings;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace ChatRAG.Services.PromptConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Serviço para configuração e carregamento de prompts por domínio e idioma
|
||||
/// </summary>
|
||||
public class PromptConfigurationService
|
||||
{
|
||||
private readonly ILogger<PromptConfigurationService> _logger;
|
||||
private readonly string _configurationPath;
|
||||
private readonly LanguageSettings _languageSettings;
|
||||
private readonly CacheSettings _cacheSettings;
|
||||
|
||||
private Dictionary<string, DomainPromptConfig> _domainConfigs = new();
|
||||
private BasePromptConfig _baseConfig = new();
|
||||
private readonly Dictionary<string, DateTime> _fileLastModified = new();
|
||||
private readonly object _lockObject = new object();
|
||||
|
||||
public PromptConfigurationService(
|
||||
ILogger<PromptConfigurationService> logger,
|
||||
IConfiguration configuration,
|
||||
IOptions<ConfidenceAwareSettings> settings)
|
||||
{
|
||||
_logger = logger;
|
||||
_configurationPath = configuration["PromptConfiguration:Path"] ?? "Configuration/Prompts";
|
||||
_languageSettings = settings.Value.Languages;
|
||||
_cacheSettings = settings.Value.Cache;
|
||||
|
||||
// Carregar configurações na inicialização
|
||||
_ = Task.Run(LoadConfigurations);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtém prompts configurados para um domínio e idioma específicos
|
||||
/// </summary>
|
||||
public PromptTemplates GetPrompts(string? domain = null, string language = "pt")
|
||||
{
|
||||
// Verificar se precisa recarregar arquivos (se habilitado)
|
||||
if (_cacheSettings.AutoReloadOnFileChange)
|
||||
{
|
||||
CheckForFileChanges();
|
||||
}
|
||||
|
||||
// Detectar idioma se auto-detecção estiver habilitada
|
||||
var detectedLanguage = _languageSettings.AutoDetectLanguage
|
||||
? DetectOrValidateLanguage(language)
|
||||
: language;
|
||||
|
||||
var domainConfig = domain != null && _domainConfigs.ContainsKey(domain)
|
||||
? _domainConfigs[domain]
|
||||
: null;
|
||||
|
||||
return new PromptTemplates
|
||||
{
|
||||
QueryAnalysis = GetPrompt("QueryAnalysis", domainConfig, detectedLanguage),
|
||||
Overview = GetPrompt("Overview", domainConfig, detectedLanguage),
|
||||
Specific = GetPrompt("Specific", domainConfig, detectedLanguage),
|
||||
Detailed = GetPrompt("Detailed", domainConfig, detectedLanguage),
|
||||
Response = GetPrompt("Response", domainConfig, detectedLanguage),
|
||||
Summary = GetPrompt("Summary", domainConfig, detectedLanguage),
|
||||
GapAnalysis = GetPrompt("GapAnalysis", domainConfig, detectedLanguage)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detecta o domínio baseado na pergunta e descrição do projeto
|
||||
/// </summary>
|
||||
public string? DetectDomain(string question, string? projectDescription = null)
|
||||
{
|
||||
var content = $"{question} {projectDescription}".ToLower();
|
||||
var domainScores = new Dictionary<string, int>();
|
||||
|
||||
foreach (var (domain, config) in _domainConfigs)
|
||||
{
|
||||
var score = 0;
|
||||
|
||||
// Pontuação por palavras-chave (peso 2)
|
||||
foreach (var keyword in config.Keywords)
|
||||
{
|
||||
if (content.Contains(keyword.ToLower()))
|
||||
{
|
||||
score += 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Pontuação por conceitos (peso 1)
|
||||
foreach (var concept in config.Concepts)
|
||||
{
|
||||
if (content.Contains(concept.ToLower()))
|
||||
{
|
||||
score += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (score > 0)
|
||||
{
|
||||
domainScores[domain] = score;
|
||||
}
|
||||
}
|
||||
|
||||
if (domainScores.Any())
|
||||
{
|
||||
var bestDomain = domainScores.OrderByDescending(x => x.Value).First();
|
||||
_logger.LogDebug("Domínio detectado: {Domain} com score {Score}", bestDomain.Key, bestDomain.Value);
|
||||
return bestDomain.Key;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Nenhum domínio detectado, usando padrão");
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detecta o idioma da pergunta
|
||||
/// </summary>
|
||||
public string DetectLanguage(string question)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(question))
|
||||
return _languageSettings.DefaultLanguage;
|
||||
|
||||
var languageScores = new Dictionary<string, int>();
|
||||
var words = Regex.Split(question.ToLower(), @"\W+")
|
||||
.Where(w => w.Length > 2)
|
||||
.ToList();
|
||||
|
||||
foreach (var (language, keywords) in _languageSettings.LanguageKeywords)
|
||||
{
|
||||
var score = words.Count(word => keywords.Contains(word));
|
||||
if (score > 0)
|
||||
{
|
||||
languageScores[language] = score;
|
||||
}
|
||||
}
|
||||
|
||||
if (languageScores.Any())
|
||||
{
|
||||
var detectedLanguage = languageScores.OrderByDescending(x => x.Value).First().Key;
|
||||
_logger.LogDebug("Idioma detectado: {Language}", detectedLanguage);
|
||||
return detectedLanguage;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Idioma não detectado, usando padrão: {DefaultLanguage}", _languageSettings.DefaultLanguage);
|
||||
return _languageSettings.DefaultLanguage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lista domínios disponíveis
|
||||
/// </summary>
|
||||
public List<string> GetAvailableDomains()
|
||||
{
|
||||
return _domainConfigs.Keys.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lista idiomas suportados
|
||||
/// </summary>
|
||||
public List<string> GetSupportedLanguages()
|
||||
{
|
||||
return _languageSettings.SupportedLanguages;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Força recarregamento das configurações
|
||||
/// </summary>
|
||||
public async Task ReloadConfigurations()
|
||||
{
|
||||
await LoadConfigurations();
|
||||
}
|
||||
|
||||
// === MÉTODOS PRIVADOS ===
|
||||
|
||||
private void LoadBaseConfigurationSync()
|
||||
{
|
||||
var basePath = Path.Combine(_configurationPath, "base-prompts.json");
|
||||
|
||||
if (File.Exists(basePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(basePath);
|
||||
_baseConfig = JsonSerializer.Deserialize<BasePromptConfig>(json) ?? new BasePromptConfig();
|
||||
_fileLastModified[basePath] = File.GetLastWriteTime(basePath);
|
||||
_logger.LogDebug("Configuração base carregada de {Path}", basePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Erro ao carregar configuração base de {Path}", basePath);
|
||||
_baseConfig = GetDefaultBaseConfig();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Arquivo base não encontrado, criando padrão em {Path}", basePath);
|
||||
_baseConfig = GetDefaultBaseConfig();
|
||||
SaveBaseConfigurationSync(basePath);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadDomainConfigurationsSync()
|
||||
{
|
||||
var domainsPath = Path.Combine(_configurationPath, "Domains");
|
||||
|
||||
if (!Directory.Exists(domainsPath))
|
||||
{
|
||||
_logger.LogInformation("Pasta de domínios não encontrada, criando em {Path}", domainsPath);
|
||||
Directory.CreateDirectory(domainsPath);
|
||||
CreateDefaultDomainConfigurationsSync(domainsPath);
|
||||
}
|
||||
|
||||
var domainFiles = Directory.GetFiles(domainsPath, "*.json");
|
||||
var loadedDomains = new Dictionary<string, DomainPromptConfig>();
|
||||
|
||||
foreach (var file in domainFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(file);
|
||||
var config = JsonSerializer.Deserialize<DomainPromptConfig>(json);
|
||||
if (config != null)
|
||||
{
|
||||
var domainName = Path.GetFileNameWithoutExtension(file);
|
||||
loadedDomains[domainName] = config;
|
||||
_fileLastModified[file] = File.GetLastWriteTime(file);
|
||||
_logger.LogDebug("Domínio {Domain} carregado de {File}", domainName, file);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Erro ao carregar configuração do domínio: {File}", file);
|
||||
}
|
||||
}
|
||||
|
||||
_domainConfigs = loadedDomains;
|
||||
}
|
||||
|
||||
private void CheckForFileChanges()
|
||||
{
|
||||
if (!_cacheSettings.AutoReloadOnFileChange) return;
|
||||
|
||||
var needsReload = false;
|
||||
var filesToCheck = new List<string> { Path.Combine(_configurationPath, "base-prompts.json") };
|
||||
|
||||
var domainsPath = Path.Combine(_configurationPath, "Domains");
|
||||
if (Directory.Exists(domainsPath))
|
||||
{
|
||||
filesToCheck.AddRange(Directory.GetFiles(domainsPath, "*.json"));
|
||||
}
|
||||
|
||||
foreach (var file in filesToCheck)
|
||||
{
|
||||
if (File.Exists(file))
|
||||
{
|
||||
var lastModified = File.GetLastWriteTime(file);
|
||||
if (!_fileLastModified.ContainsKey(file) || _fileLastModified[file] < lastModified)
|
||||
{
|
||||
_logger.LogInformation("Arquivo modificado detectado: {File}", file);
|
||||
needsReload = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needsReload)
|
||||
{
|
||||
_ = Task.Run(LoadConfigurations);
|
||||
}
|
||||
}
|
||||
|
||||
private string DetectOrValidateLanguage(string language)
|
||||
{
|
||||
// Se o idioma é suportado, usar ele
|
||||
if (_languageSettings.SupportedLanguages.Contains(language))
|
||||
{
|
||||
return language;
|
||||
}
|
||||
|
||||
_logger.LogWarning("Idioma não suportado: {Language}, usando padrão: {DefaultLanguage}",
|
||||
language, _languageSettings.DefaultLanguage);
|
||||
return _languageSettings.DefaultLanguage;
|
||||
}
|
||||
|
||||
private string GetPrompt(string promptType, DomainPromptConfig? domainConfig, string language)
|
||||
{
|
||||
// 1. Tentar buscar no domínio específico e idioma específico
|
||||
if (domainConfig?.Prompts.ContainsKey(language) == true &&
|
||||
domainConfig.Prompts[language].ContainsKey(promptType))
|
||||
{
|
||||
return domainConfig.Prompts[language][promptType];
|
||||
}
|
||||
|
||||
// 2. Tentar buscar no domínio específico no idioma padrão
|
||||
if (domainConfig?.Prompts.ContainsKey(_languageSettings.DefaultLanguage) == true &&
|
||||
domainConfig.Prompts[_languageSettings.DefaultLanguage].ContainsKey(promptType))
|
||||
{
|
||||
var prompt = domainConfig.Prompts[_languageSettings.DefaultLanguage][promptType];
|
||||
return _languageSettings.AlwaysRespondInRequestedLanguage && language != _languageSettings.DefaultLanguage
|
||||
? AddLanguageInstruction(prompt, language)
|
||||
: prompt;
|
||||
}
|
||||
|
||||
// 3. Fallback para configuração base no idioma solicitado
|
||||
if (_baseConfig.Prompts.ContainsKey(language) &&
|
||||
_baseConfig.Prompts[language].ContainsKey(promptType))
|
||||
{
|
||||
return _baseConfig.Prompts[language][promptType];
|
||||
}
|
||||
|
||||
// 4. Fallback para configuração base no idioma padrão
|
||||
if (_baseConfig.Prompts.ContainsKey(_languageSettings.DefaultLanguage) &&
|
||||
_baseConfig.Prompts[_languageSettings.DefaultLanguage].ContainsKey(promptType))
|
||||
{
|
||||
var prompt = _baseConfig.Prompts[_languageSettings.DefaultLanguage][promptType];
|
||||
return _languageSettings.AlwaysRespondInRequestedLanguage && language != _languageSettings.DefaultLanguage
|
||||
? AddLanguageInstruction(prompt, language)
|
||||
: prompt;
|
||||
}
|
||||
|
||||
// 5. Fallback final
|
||||
return GetFallbackPrompt(promptType, language);
|
||||
}
|
||||
|
||||
private string AddLanguageInstruction(string prompt, string targetLanguage)
|
||||
{
|
||||
var instruction = targetLanguage switch
|
||||
{
|
||||
"en" => "\n\nIMPORTANT: Respond in English.",
|
||||
"pt" => "\n\nIMPORTANTE: Responda em português.",
|
||||
_ => $"\n\nIMPORTANT: Respond in {targetLanguage}."
|
||||
};
|
||||
|
||||
return prompt + instruction;
|
||||
}
|
||||
|
||||
private void SaveBaseConfigurationSync(string basePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(basePath)!);
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(_baseConfig, options);
|
||||
File.WriteAllText(basePath, json);
|
||||
_logger.LogInformation("Configuração base salva em {Path}", basePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Erro ao salvar configuração base em {Path}", basePath);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateDefaultDomainConfigurationsSync(string domainsPath)
|
||||
{
|
||||
var domains = new Dictionary<string, DomainPromptConfig>
|
||||
{
|
||||
["TI"] = GetTIDomainConfig(),
|
||||
["RH"] = GetRHDomainConfig(),
|
||||
["Financeiro"] = GetFinanceiroDomainConfig(),
|
||||
["QA"] = GetQADomainConfig()
|
||||
};
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
foreach (var (domain, config) in domains)
|
||||
{
|
||||
try
|
||||
{
|
||||
var filePath = Path.Combine(domainsPath, $"{domain}.json");
|
||||
var json = JsonSerializer.Serialize(config, options);
|
||||
File.WriteAllText(filePath, json);
|
||||
_logger.LogInformation("Configuração de domínio {Domain} criada em {Path}", domain, filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Erro ao criar configuração do domínio {Domain}", domain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BasePromptConfig GetDefaultBaseConfig()
|
||||
{
|
||||
return new BasePromptConfig
|
||||
{
|
||||
Prompts = new Dictionary<string, Dictionary<string, string>>
|
||||
{
|
||||
["pt"] = new Dictionary<string, string>
|
||||
{
|
||||
["QueryAnalysis"] = @"Analise esta pergunta e classifique com precisão:
|
||||
PERGUNTA: ""{0}""
|
||||
|
||||
Responda APENAS no formato JSON:
|
||||
{{
|
||||
""strategy"": ""overview|specific|detailed"",
|
||||
""complexity"": ""simple|medium|complex"",
|
||||
""scope"": ""global|filtered|targeted"",
|
||||
""concepts"": [""conceito1"", ""conceito2""],
|
||||
""needs_hierarchy"": true|false
|
||||
}}
|
||||
|
||||
DEFINIÇÕES PRECISAS:
|
||||
STRATEGY:
|
||||
- overview: Pergunta sobre o PROJETO COMO UM TODO
|
||||
- specific: Pergunta sobre MÓDULO/FUNCIONALIDADE ESPECÍFICA
|
||||
- detailed: Pergunta técnica específica que precisa de CONTEXTO PROFUNDO",
|
||||
|
||||
["Response"] = @"Você é um especialista em análise de software e QA.
|
||||
|
||||
PROJETO: {0}
|
||||
PERGUNTA: ""{1}""
|
||||
CONTEXTO HIERÁRQUICO: {2}
|
||||
ETAPAS EXECUTADAS: {3}
|
||||
|
||||
Responda à pergunta de forma precisa e estruturada, aproveitando todo o contexto hierárquico coletado.",
|
||||
|
||||
["Summary"] = @"Resuma os pontos principais destes documentos sobre {0}:
|
||||
|
||||
{1}
|
||||
|
||||
Responda apenas com uma lista concisa dos pontos mais importantes:",
|
||||
|
||||
["GapAnalysis"] = @"Baseado na pergunta e contexto atual, identifique que informações ainda faltam para uma resposta completa.
|
||||
|
||||
PERGUNTA: {0}
|
||||
CONTEXTO ATUAL: {1}
|
||||
|
||||
Responda APENAS com palavras-chave dos conceitos/informações que ainda faltam, separados por vírgula.
|
||||
Se o contexto for suficiente, responda 'SUFICIENTE'."
|
||||
},
|
||||
["en"] = new Dictionary<string, string>
|
||||
{
|
||||
["QueryAnalysis"] = @"Analyze this question and classify precisely:
|
||||
QUESTION: ""{0}""
|
||||
|
||||
Answer ONLY in JSON format:
|
||||
{{
|
||||
""strategy"": ""overview|specific|detailed"",
|
||||
""complexity"": ""simple|medium|complex"",
|
||||
""scope"": ""global|filtered|targeted"",
|
||||
""concepts"": [""concept1"", ""concept2""],
|
||||
""needs_hierarchy"": true|false
|
||||
}}
|
||||
|
||||
PRECISE DEFINITIONS:
|
||||
STRATEGY:
|
||||
- overview: Question about the PROJECT AS A WHOLE
|
||||
- specific: Question about SPECIFIC MODULE/FUNCTIONALITY
|
||||
- detailed: Technical specific question needing DEEP CONTEXT",
|
||||
|
||||
["Response"] = @"You are a software analysis and QA expert.
|
||||
|
||||
PROJECT: {0}
|
||||
QUESTION: ""{1}""
|
||||
HIERARCHICAL CONTEXT: {2}
|
||||
EXECUTED STEPS: {3}
|
||||
|
||||
Answer the question precisely and structured, leveraging all the hierarchical context collected.",
|
||||
|
||||
["Summary"] = @"Summarize the main points of these documents about {0}:
|
||||
|
||||
{1}
|
||||
|
||||
Answer only with a concise list of the most important points:",
|
||||
|
||||
["GapAnalysis"] = @"Based on the question and current context, identify what information is still missing for a complete answer.
|
||||
|
||||
QUESTION: {0}
|
||||
CURRENT CONTEXT: {1}
|
||||
|
||||
Answer ONLY with keywords of missing concepts/information, separated by commas.
|
||||
If the context is sufficient, answer 'SUFFICIENT'."
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private DomainPromptConfig GetTIDomainConfig()
|
||||
{
|
||||
return new DomainPromptConfig
|
||||
{
|
||||
Name = "Tecnologia da Informação",
|
||||
Description = "Configurações para projetos de TI e desenvolvimento de software",
|
||||
Keywords = ["api", "backend", "frontend", "database", "arquitetura", "código", "classe", "método", "endpoint", "sistema", "software"],
|
||||
Concepts = ["mvc", "rest", "microservices", "clean architecture", "design patterns", "authentication", "authorization", "crud"],
|
||||
Prompts = new Dictionary<string, Dictionary<string, string>>
|
||||
{
|
||||
["pt"] = new Dictionary<string, string>
|
||||
{
|
||||
["Response"] = @"Você é um especialista em desenvolvimento de software e arquitetura de sistemas.
|
||||
|
||||
PROJETO TÉCNICO: {0}
|
||||
PERGUNTA TÉCNICA: ""{1}""
|
||||
CONTEXTO TÉCNICO: {2}
|
||||
ANÁLISE REALIZADA: {3}
|
||||
|
||||
Responda com foco técnico, incluindo:
|
||||
- Implementação prática
|
||||
- Boas práticas de código
|
||||
- Considerações de arquitetura
|
||||
- Exemplos de código quando relevante
|
||||
|
||||
Seja preciso e técnico na resposta."
|
||||
},
|
||||
["en"] = new Dictionary<string, string>
|
||||
{
|
||||
["Response"] = @"You are a software development and system architecture expert.
|
||||
|
||||
TECHNICAL PROJECT: {0}
|
||||
TECHNICAL QUESTION: ""{1}""
|
||||
TECHNICAL CONTEXT: {2}
|
||||
ANALYSIS PERFORMED: {3}
|
||||
|
||||
Answer with technical focus, including:
|
||||
- Practical implementation
|
||||
- Code best practices
|
||||
- Architecture considerations
|
||||
- Code examples when relevant
|
||||
|
||||
Be precise and technical in your response."
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private DomainPromptConfig GetRHDomainConfig()
|
||||
{
|
||||
return new DomainPromptConfig
|
||||
{
|
||||
Name = "Recursos Humanos",
|
||||
Description = "Configurações para projetos de RH e gestão de pessoas",
|
||||
Keywords = ["funcionário", "colaborador", "cargo", "departamento", "folha", "benefícios", "treinamento", "employee", "hr"],
|
||||
Concepts = ["gestão de pessoas", "recrutamento", "seleção", "avaliação", "desenvolvimento", "human resources"],
|
||||
Prompts = new Dictionary<string, Dictionary<string, string>>
|
||||
{
|
||||
["pt"] = new Dictionary<string, string>
|
||||
{
|
||||
["Response"] = @"Você é um especialista em Recursos Humanos e gestão de pessoas.
|
||||
|
||||
SISTEMA DE RH: {0}
|
||||
PERGUNTA: ""{1}""
|
||||
CONTEXTO: {2}
|
||||
PROCESSOS ANALISADOS: {3}
|
||||
|
||||
Responda considerando:
|
||||
- Políticas de RH
|
||||
- Fluxos de trabalho
|
||||
- Compliance e regulamentações
|
||||
- Melhores práticas em gestão de pessoas
|
||||
|
||||
Seja claro e prático nas recomendações."
|
||||
},
|
||||
["en"] = new Dictionary<string, string>
|
||||
{
|
||||
["Response"] = @"You are a Human Resources and people management expert.
|
||||
|
||||
HR SYSTEM: {0}
|
||||
QUESTION: ""{1}""
|
||||
CONTEXT: {2}
|
||||
ANALYZED PROCESSES: {3}
|
||||
|
||||
Answer considering:
|
||||
- HR policies
|
||||
- Workflows
|
||||
- Compliance and regulations
|
||||
- Best practices in people management
|
||||
|
||||
Be clear and practical in recommendations."
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private DomainPromptConfig GetFinanceiroDomainConfig()
|
||||
{
|
||||
return new DomainPromptConfig
|
||||
{
|
||||
Name = "Financeiro",
|
||||
Description = "Configurações para projetos financeiros e contábeis",
|
||||
Keywords = ["financeiro", "contábil", "faturamento", "cobrança", "pagamento", "receita", "despesa", "financial", "accounting"],
|
||||
Concepts = ["fluxo de caixa", "conciliação", "relatórios financeiros", "impostos", "audit trail", "cash flow"],
|
||||
Prompts = new Dictionary<string, Dictionary<string, string>>
|
||||
{
|
||||
["pt"] = new Dictionary<string, string>
|
||||
{
|
||||
["Response"] = @"Você é um especialista em sistemas financeiros e contabilidade.
|
||||
|
||||
SISTEMA FINANCEIRO: {0}
|
||||
PERGUNTA: ""{1}""
|
||||
CONTEXTO FINANCEIRO: {2}
|
||||
ANÁLISE REALIZADA: {3}
|
||||
|
||||
Responda considerando:
|
||||
- Controles financeiros
|
||||
- Auditoria e compliance
|
||||
- Fluxos de aprovação
|
||||
- Relatórios gerenciais
|
||||
- Segurança de dados financeiros
|
||||
|
||||
Seja preciso e considere aspectos regulatórios."
|
||||
},
|
||||
["en"] = new Dictionary<string, string>
|
||||
{
|
||||
["Response"] = @"You are a financial systems and accounting expert.
|
||||
|
||||
FINANCIAL SYSTEM: {0}
|
||||
QUESTION: ""{1}""
|
||||
FINANCIAL CONTEXT: {2}
|
||||
ANALYSIS PERFORMED: {3}
|
||||
|
||||
Answer considering:
|
||||
- Financial controls
|
||||
- Audit and compliance
|
||||
- Approval workflows
|
||||
- Management reports
|
||||
- Financial data security
|
||||
|
||||
Be precise and consider regulatory aspects."
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private DomainPromptConfig GetQADomainConfig()
|
||||
{
|
||||
return new DomainPromptConfig
|
||||
{
|
||||
Name = "Quality Assurance",
|
||||
Description = "Configurações para projetos de QA e testes",
|
||||
Keywords = ["teste", "qa", "qualidade", "bug", "defeito", "validação", "verificação", "quality", "testing"],
|
||||
Concepts = ["test cases", "automation", "regression", "performance", "security testing", "casos de teste"],
|
||||
Prompts = new Dictionary<string, Dictionary<string, string>>
|
||||
{
|
||||
["pt"] = new Dictionary<string, string>
|
||||
{
|
||||
["Response"] = @"Você é um especialista em Quality Assurance e testes de software.
|
||||
|
||||
PROJETO: {0}
|
||||
PERGUNTA DE QA: ""{1}""
|
||||
CONTEXTO DE TESTES: {2}
|
||||
ANÁLISE EXECUTADA: {3}
|
||||
|
||||
Responda com foco em:
|
||||
- Estratégias de teste
|
||||
- Casos de teste específicos
|
||||
- Automação e ferramentas
|
||||
- Critérios de aceitação
|
||||
- Cobertura de testes
|
||||
|
||||
Seja detalhado e metodológico na abordagem."
|
||||
},
|
||||
["en"] = new Dictionary<string, string>
|
||||
{
|
||||
["Response"] = @"You are a Quality Assurance and software testing expert.
|
||||
|
||||
PROJECT: {0}
|
||||
QA QUESTION: ""{1}""
|
||||
TESTING CONTEXT: {2}
|
||||
ANALYSIS EXECUTED: {3}
|
||||
|
||||
Answer focusing on:
|
||||
- Testing strategies
|
||||
- Specific test cases
|
||||
- Automation and tools
|
||||
- Acceptance criteria
|
||||
- Test coverage
|
||||
|
||||
Be detailed and methodical in your approach."
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private string GetFallbackPrompt(string promptType, string language)
|
||||
{
|
||||
return language == "en" ? promptType switch
|
||||
{
|
||||
"QueryAnalysis" => "Analyze the question: {0}",
|
||||
"Response" => "Answer based on context: {2}",
|
||||
"Summary" => "Summarize: {1}",
|
||||
"GapAnalysis" => "Identify gaps for: {0}",
|
||||
_ => "Process the request: {0}"
|
||||
} : promptType switch
|
||||
{
|
||||
"QueryAnalysis" => "Analise a pergunta: {0}",
|
||||
"Response" => "Responda baseado no contexto: {2}",
|
||||
"Summary" => "Resuma: {1}",
|
||||
"GapAnalysis" => "Identifique lacunas para: {0}",
|
||||
_ => "Processe a solicitação: {0}"
|
||||
};
|
||||
}
|
||||
|
||||
/// Carrega todas as configurações de prompts
|
||||
/// </summary>
|
||||
public async Task LoadConfigurations()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadBaseConfigurationSync();
|
||||
LoadDomainConfigurationsSync();
|
||||
_logger.LogInformation("Carregados {DomainCount} domínios de prompt em {ConfigPath}",
|
||||
_domainConfigs.Count, _configurationPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Erro ao carregar configurações de prompt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
}
|
||||
|
||||
// === MODELOS DE DADOS ===
|
||||
|
||||
public class PromptTemplates
|
||||
{
|
||||
public string QueryAnalysis { get; set; } = "";
|
||||
public string Overview { get; set; } = "";
|
||||
public string Specific { get; set; } = "";
|
||||
public string Detailed { get; set; } = "";
|
||||
public string Response { get; set; } = "";
|
||||
public string Summary { get; set; } = "";
|
||||
public string GapAnalysis { get; set; } = "";
|
||||
}
|
||||
|
||||
public class BasePromptConfig
|
||||
{
|
||||
public Dictionary<string, Dictionary<string, string>> Prompts { get; set; } = new();
|
||||
}
|
||||
|
||||
public class DomainPromptConfig
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public string Description { get; set; } = "";
|
||||
public List<string> Keywords { get; set; } = new();
|
||||
public List<string> Concepts { get; set; } = new();
|
||||
public Dictionary<string, Dictionary<string, string>> Prompts { get; set; } = new();
|
||||
}
|
||||
}
|
||||
385
Services/ResponseService/ConfidenceAwareRAGService.cs
Normal file
385
Services/ResponseService/ConfidenceAwareRAGService.cs
Normal file
@ -0,0 +1,385 @@
|
||||
using ChatApi;
|
||||
using ChatApi.Models;
|
||||
using ChatRAG.Contracts.VectorSearch;
|
||||
using ChatRAG.Data;
|
||||
using ChatRAG.Models;
|
||||
using ChatRAG.Services.Contracts;
|
||||
using ChatRAG.Services.Confidence;
|
||||
using ChatRAG.Services.PromptConfiguration;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
using Microsoft.SemanticKernel.Embeddings;
|
||||
using System.Text.Json;
|
||||
using ChatRAG.Settings;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
#pragma warning disable SKEXP0001
|
||||
|
||||
namespace ChatRAG.Services.ResponseService
|
||||
{
|
||||
public class ConfidenceAwareRAGService : IResponseService
|
||||
{
|
||||
private readonly ChatHistoryService _chatHistoryService;
|
||||
private readonly Kernel _kernel;
|
||||
private readonly TextFilter _textFilter;
|
||||
private readonly IProjectDataRepository _projectDataRepository;
|
||||
private readonly IChatCompletionService _chatCompletionService;
|
||||
private readonly IVectorSearchService _vectorSearchService;
|
||||
private readonly ILogger<ConfidenceAwareRAGService> _logger;
|
||||
private readonly ConfidenceVerifier _confidenceVerifier;
|
||||
private readonly PromptConfigurationService _promptService;
|
||||
private readonly ConfidenceAwareSettings _settings;
|
||||
|
||||
public ConfidenceAwareRAGService(
|
||||
ChatHistoryService chatHistoryService,
|
||||
Kernel kernel,
|
||||
TextFilter textFilter,
|
||||
IProjectDataRepository projectDataRepository,
|
||||
IChatCompletionService chatCompletionService,
|
||||
IVectorSearchService vectorSearchService,
|
||||
ILogger<ConfidenceAwareRAGService> logger,
|
||||
ConfidenceVerifier confidenceVerifier,
|
||||
PromptConfigurationService promptService,
|
||||
IOptions<ConfidenceAwareSettings> settings)
|
||||
{
|
||||
_chatHistoryService = chatHistoryService;
|
||||
_kernel = kernel;
|
||||
_textFilter = textFilter;
|
||||
_projectDataRepository = projectDataRepository;
|
||||
_chatCompletionService = chatCompletionService;
|
||||
_vectorSearchService = vectorSearchService;
|
||||
_logger = logger;
|
||||
_confidenceVerifier = confidenceVerifier;
|
||||
_promptService = promptService;
|
||||
_settings = settings.Value;
|
||||
}
|
||||
|
||||
public async Task<string> GetResponse(UserData userData, string projectId, string sessionId, string question, string language = "pt")
|
||||
{
|
||||
var stopWatch = new System.Diagnostics.Stopwatch();
|
||||
stopWatch.Start();
|
||||
string detectedLanguage = language;
|
||||
|
||||
try
|
||||
{
|
||||
detectedLanguage = _settings.Languages.AutoDetectLanguage
|
||||
? _promptService.DetectLanguage(question)
|
||||
: language;
|
||||
|
||||
var projectData = await _projectDataRepository.GetAsync(projectId);
|
||||
var detectedDomain = _promptService.DetectDomain(question, projectData?.Descricao);
|
||||
var prompts = _promptService.GetPrompts(detectedDomain, detectedLanguage);
|
||||
var queryAnalysis = await AnalyzeQuery(question, detectedLanguage, prompts.QueryAnalysis);
|
||||
var context = await ExecuteHierarchicalSearch(question, projectId, queryAnalysis, prompts, detectedLanguage);
|
||||
var confidenceResult = await VerifyConfidenceIfEnabled(queryAnalysis, context, projectId, detectedLanguage);
|
||||
|
||||
if (!confidenceResult.ShouldRespond)
|
||||
{
|
||||
stopWatch.Stop();
|
||||
var fallbackResponse = confidenceResult.SuggestedResponse ?? GetGenericFallbackMessage(detectedLanguage);
|
||||
return FormatFinalResponse(fallbackResponse, stopWatch.ElapsedMilliseconds, context.Steps.Count, confidenceResult);
|
||||
}
|
||||
|
||||
var response = await GenerateResponse(question, projectId, context, sessionId, detectedLanguage, prompts.Response, detectedDomain);
|
||||
stopWatch.Stop();
|
||||
return FormatFinalResponse(response, stopWatch.ElapsedMilliseconds, context.Steps.Count, confidenceResult);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Erro no ConfidenceAwareRAG");
|
||||
stopWatch.Stop();
|
||||
var errorMessage = detectedLanguage == "en" ? $"Error: {ex.Message}" : $"Erro: {ex.Message}";
|
||||
return $"{errorMessage}\nTempo: {stopWatch.ElapsedMilliseconds / 1000}s";
|
||||
}
|
||||
}
|
||||
|
||||
private string GetGenericFallbackMessage(string language)
|
||||
{
|
||||
return language == "en"
|
||||
? "I don't have enough information to respond safely. Could you try rephrasing the question?"
|
||||
: "Não tenho informações suficientes para responder com segurança. Pode tentar reformular a pergunta?";
|
||||
}
|
||||
|
||||
private async Task<QueryAnalysis> AnalyzeQuery(string question, string language, string promptTemplate)
|
||||
{
|
||||
var prompt = string.Format(promptTemplate, question);
|
||||
var response = await _chatCompletionService.GetChatMessageContentAsync(prompt, new OpenAIPromptExecutionSettings { Temperature = 0.1, MaxTokens = 300 });
|
||||
|
||||
try
|
||||
{
|
||||
var jsonResponse = response.Content?.Trim() ?? "{}";
|
||||
var startIndex = jsonResponse.IndexOf('{');
|
||||
var endIndex = jsonResponse.LastIndexOf('}');
|
||||
if (startIndex >= 0 && endIndex >= startIndex)
|
||||
jsonResponse = jsonResponse.Substring(startIndex, endIndex - startIndex + 1);
|
||||
|
||||
var analysis = JsonSerializer.Deserialize<QueryAnalysis>(jsonResponse, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
});
|
||||
return analysis ?? new QueryAnalysis { Strategy = "specific", Complexity = "medium" };
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Erro ao parsear análise da query, usando padrão");
|
||||
return new QueryAnalysis { Strategy = "specific", Complexity = "medium" };
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<HierarchicalContext> ExecuteHierarchicalSearch(string question, string projectId, QueryAnalysis analysis, PromptTemplates prompts, string language)
|
||||
{
|
||||
var context = new HierarchicalContext();
|
||||
var embeddingService = _kernel.GetRequiredService<ITextEmbeddingGenerationService>();
|
||||
context.Metadata["DetectedLanguage"] = language;
|
||||
context.Metadata["SearchResults"] = new List<VectorSearchResult>();
|
||||
|
||||
switch (analysis.Strategy)
|
||||
{
|
||||
case "overview":
|
||||
await ExecuteOverviewStrategy(context, question, projectId, embeddingService, prompts);
|
||||
break;
|
||||
case "detailed":
|
||||
await ExecuteDetailedStrategy(context, question, projectId, embeddingService, analysis, prompts);
|
||||
break;
|
||||
default:
|
||||
await ExecuteSpecificStrategy(context, question, projectId, embeddingService, prompts);
|
||||
break;
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
private async Task<ConfidenceResult> VerifyConfidenceIfEnabled(QueryAnalysis analysis, HierarchicalContext context, string projectId, string language)
|
||||
{
|
||||
if (!_settings.EnableConfidenceCheck)
|
||||
{
|
||||
return new ConfidenceResult
|
||||
{
|
||||
ShouldRespond = true,
|
||||
ConfidenceScore = 1.0,
|
||||
Reason = language == "en" ? "Confidence check disabled" : "Verificação de confiança desabilitada"
|
||||
};
|
||||
}
|
||||
|
||||
var results = ExtractResultsFromContext(context, projectId);
|
||||
return _confidenceVerifier.VerifyConfidence(analysis, results, context, _settings.UseStrictMode, language);
|
||||
}
|
||||
|
||||
private List<VectorSearchResult> ExtractResultsFromContext(HierarchicalContext context, string projectId)
|
||||
{
|
||||
if (context.Metadata.ContainsKey("SearchResults") && context.Metadata["SearchResults"] is List<VectorSearchResult> storedResults)
|
||||
return storedResults;
|
||||
|
||||
var results = new List<VectorSearchResult>();
|
||||
var contextLength = context.CombinedContext?.Length ?? 0;
|
||||
|
||||
if (contextLength > 0)
|
||||
{
|
||||
var estimatedDocuments = Math.Max(1, Math.Min(10, contextLength / 500));
|
||||
for (int i = 0; i < estimatedDocuments; i++)
|
||||
{
|
||||
results.Add(new VectorSearchResult
|
||||
{
|
||||
Id = $"estimated_doc_{i}",
|
||||
Score = Math.Max(0.1, 0.8 - (i * 0.1)),
|
||||
Content = "Conteúdo estimado do contexto",
|
||||
Title = $"Documento estimado {i + 1}",
|
||||
ProjectId = projectId ?? "unknown"
|
||||
});
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private async Task ExecuteOverviewStrategy(HierarchicalContext context, string question, string projectId, ITextEmbeddingGenerationService embeddingService, PromptTemplates prompts)
|
||||
{
|
||||
context.AddStep("Buscando todos os documentos do projeto");
|
||||
var allProjectDocs = await _vectorSearchService.GetDocumentsByProjectAsync(projectId);
|
||||
StoreSearchResults(context, allProjectDocs);
|
||||
|
||||
var requirementsDocs = allProjectDocs.Where(d => d.Title.ToLower().Contains("requisito") || d.Content.ToLower().Contains("requisito")).ToList();
|
||||
var architectureDocs = allProjectDocs.Where(d => d.Title.ToLower().Contains("arquitetura") || d.Content.ToLower().Contains("arquitetura")).ToList();
|
||||
var otherDocs = allProjectDocs.Except(requirementsDocs).Except(architectureDocs).ToList();
|
||||
|
||||
context.AddStep("Resumindo documentos por categoria");
|
||||
var requirementsSummary = await SummarizeDocuments(requirementsDocs, "requisitos", prompts.Summary);
|
||||
var architectureSummary = await SummarizeDocuments(architectureDocs, "arquitetura", prompts.Summary);
|
||||
var otherSummary = await SummarizeDocuments(otherDocs, "outros documentos", prompts.Summary);
|
||||
|
||||
var questionEmbedding = await embeddingService.GenerateEmbeddingAsync(_textFilter.ToLowerAndWithoutAccents(question));
|
||||
var embeddingArray = questionEmbedding.ToArray().Select(e => (double)e).ToArray();
|
||||
var relevantDocs = await _vectorSearchService.SearchSimilarAsync(embeddingArray, projectId, 0.3, 8);
|
||||
AddToSearchResults(context, relevantDocs);
|
||||
|
||||
var contextParts = new List<string>();
|
||||
if (!string.IsNullOrEmpty(requirementsSummary)) contextParts.Add($"RESUMO DOS REQUISITOS:\n{requirementsSummary}");
|
||||
if (!string.IsNullOrEmpty(architectureSummary)) contextParts.Add($"RESUMO DA ARQUITETURA:\n{architectureSummary}");
|
||||
if (!string.IsNullOrEmpty(otherSummary)) contextParts.Add($"OUTROS DOCUMENTOS:\n{otherSummary}");
|
||||
contextParts.Add($"DOCUMENTOS RELEVANTES:\n{FormatResults(relevantDocs)}");
|
||||
context.CombinedContext = string.Join("\n\n", contextParts);
|
||||
}
|
||||
|
||||
private async Task ExecuteSpecificStrategy(HierarchicalContext context, string question, string projectId, ITextEmbeddingGenerationService embeddingService, PromptTemplates prompts)
|
||||
{
|
||||
var questionEmbedding = await embeddingService.GenerateEmbeddingAsync(_textFilter.ToLowerAndWithoutAccents(question));
|
||||
var embeddingArray = questionEmbedding.ToArray().Select(e => (double)e).ToArray();
|
||||
var initialResults = await _vectorSearchService.SearchSimilarAsync(embeddingArray, projectId, 0.4, 3);
|
||||
StoreSearchResults(context, initialResults);
|
||||
|
||||
if (initialResults.Any())
|
||||
{
|
||||
var expandedContext = await ExpandContext(initialResults, projectId, embeddingService);
|
||||
AddToSearchResults(context, expandedContext);
|
||||
context.CombinedContext = $"CONTEXTO PRINCIPAL:\n{FormatResults(initialResults)}\n\nCONTEXTO EXPANDIDO:\n{FormatResults(expandedContext)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
var fallbackResults = await _vectorSearchService.SearchSimilarAsync(embeddingArray, projectId, 0.2, 5);
|
||||
StoreSearchResults(context, fallbackResults);
|
||||
context.CombinedContext = FormatResults(fallbackResults);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteDetailedStrategy(HierarchicalContext context, string question, string projectId, ITextEmbeddingGenerationService embeddingService, QueryAnalysis analysis, PromptTemplates prompts)
|
||||
{
|
||||
var questionEmbedding = await embeddingService.GenerateEmbeddingAsync(_textFilter.ToLowerAndWithoutAccents(question));
|
||||
var embeddingArray = questionEmbedding.ToArray().Select(e => (double)e).ToArray();
|
||||
var conceptualResults = new List<VectorSearchResult>();
|
||||
|
||||
if (analysis.Concepts?.Any() == true)
|
||||
{
|
||||
foreach (var concept in analysis.Concepts)
|
||||
{
|
||||
var conceptEmbedding = await embeddingService.GenerateEmbeddingAsync(_textFilter.ToLowerAndWithoutAccents(concept));
|
||||
var conceptArray = conceptEmbedding.ToArray().Select(e => (double)e).ToArray();
|
||||
var conceptResults = await _vectorSearchService.SearchSimilarAsync(conceptArray, projectId, 0.3, 2);
|
||||
conceptualResults.AddRange(conceptResults);
|
||||
}
|
||||
}
|
||||
|
||||
var directResults = await _vectorSearchService.SearchSimilarAsync(embeddingArray, projectId, 0.3, 3);
|
||||
var allResults = conceptualResults.Concat(directResults).DistinctBy(r => r.Id).ToList();
|
||||
StoreSearchResults(context, allResults);
|
||||
|
||||
var intermediateContext = FormatResults(allResults);
|
||||
var gaps = await IdentifyKnowledgeGaps(question, intermediateContext, prompts.GapAnalysis);
|
||||
|
||||
if (!string.IsNullOrEmpty(gaps))
|
||||
{
|
||||
var gapEmbedding = await embeddingService.GenerateEmbeddingAsync(_textFilter.ToLowerAndWithoutAccents(gaps));
|
||||
var gapArray = gapEmbedding.ToArray().Select(e => (double)e).ToArray();
|
||||
var gapResults = await _vectorSearchService.SearchSimilarAsync(gapArray, projectId, 0.25, 2);
|
||||
AddToSearchResults(context, gapResults);
|
||||
context.CombinedContext = $"CONTEXTO CONCEITUAL:\n{FormatResults(conceptualResults)}\n\nCONTEXTO DIRETO:\n{FormatResults(directResults)}\n\nCONTEXTO COMPLEMENTAR:\n{FormatResults(gapResults)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
context.CombinedContext = $"CONTEXTO CONCEITUAL:\n{FormatResults(conceptualResults)}\n\nCONTEXTO DIRETO:\n{FormatResults(directResults)}";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> GenerateResponse(string question, string projectId, HierarchicalContext context, string sessionId, string language, string promptTemplate, string? domain)
|
||||
{
|
||||
var projectData = await _projectDataRepository.GetAsync(projectId);
|
||||
var project = $"Nome: {projectData.Nome}\nDescrição: {projectData.Descricao}";
|
||||
if (!string.IsNullOrEmpty(domain)) project += $"\nDomínio: {domain}";
|
||||
|
||||
var finalPrompt = string.Format(promptTemplate, project, question, context.CombinedContext, string.Join(" → ", context.Steps));
|
||||
var history = _chatHistoryService.GetSumarizer(sessionId);
|
||||
history.AddUserMessage(finalPrompt);
|
||||
|
||||
var response = await _chatCompletionService.GetChatMessageContentAsync(history, new OpenAIPromptExecutionSettings { Temperature = 0.6 });
|
||||
history.AddMessage(response.Role, response.Content ?? "");
|
||||
_chatHistoryService.UpdateHistory(sessionId, history);
|
||||
return response.Content ?? "";
|
||||
}
|
||||
|
||||
private string FormatFinalResponse(string response, long milliseconds, int steps, ConfidenceResult? confidence = null)
|
||||
{
|
||||
var result = response;
|
||||
if (_settings.ShowDebugInfo)
|
||||
{
|
||||
result += $"\n\n📊 **Debug Info:**\n⏱️ Tempo: {milliseconds / 1000}s\n🔍 Etapas: {steps}";
|
||||
if (confidence != null)
|
||||
{
|
||||
result += $"\n🎯 Confiança: {confidence.ConfidenceScore:P1}\n📋 Estratégia: {confidence.Strategy}";
|
||||
result += $"\n✅ Deve responder: {(confidence.ShouldRespond ? "Sim" : "Não")}";
|
||||
if (!string.IsNullOrEmpty(confidence.Reason)) result += $"\n💭 Motivo: {confidence.Reason}";
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private string FormatResults(IEnumerable<VectorSearchResult> results)
|
||||
{
|
||||
return string.Join("\n\n", results.Select((item, index) => $"=== DOCUMENTO {index + 1} ===\nRelevância: {item.Score:P1}\nConteúdo: {item.Content}"));
|
||||
}
|
||||
|
||||
private void StoreSearchResults(HierarchicalContext context, List<VectorSearchResult> results)
|
||||
{
|
||||
if (!context.Metadata.ContainsKey("SearchResults")) context.Metadata["SearchResults"] = new List<VectorSearchResult>();
|
||||
((List<VectorSearchResult>)context.Metadata["SearchResults"]).AddRange(results);
|
||||
}
|
||||
|
||||
private void AddToSearchResults(HierarchicalContext context, List<VectorSearchResult> additionalResults)
|
||||
{
|
||||
if (context.Metadata.ContainsKey("SearchResults"))
|
||||
{
|
||||
var storedResults = (List<VectorSearchResult>)context.Metadata["SearchResults"];
|
||||
storedResults.AddRange(additionalResults.Where(r => !storedResults.Any(sr => sr.Id == r.Id)));
|
||||
}
|
||||
else StoreSearchResults(context, additionalResults);
|
||||
}
|
||||
|
||||
private async Task<List<VectorSearchResult>> ExpandContext(List<VectorSearchResult> initialResults, string projectId, ITextEmbeddingGenerationService embeddingService)
|
||||
{
|
||||
var expandedResults = new List<VectorSearchResult>();
|
||||
foreach (var result in initialResults.Take(2))
|
||||
{
|
||||
var resultEmbedding = await embeddingService.GenerateEmbeddingAsync(_textFilter.ToLowerAndWithoutAccents(result.Content));
|
||||
var embeddingArray = resultEmbedding.ToArray().Select(e => (double)e).ToArray();
|
||||
var relatedDocs = await _vectorSearchService.SearchSimilarAsync(embeddingArray, projectId, 0.4, 2);
|
||||
expandedResults.AddRange(relatedDocs.Where(r => !initialResults.Any(ir => ir.Id == r.Id)));
|
||||
}
|
||||
return expandedResults.DistinctBy(r => r.Id).ToList();
|
||||
}
|
||||
|
||||
private async Task<string> SummarizeDocuments(List<VectorSearchResult> documents, string category, string promptTemplate)
|
||||
{
|
||||
if (!documents.Any()) return string.Empty;
|
||||
if (documents.Count <= 3) return FormatResults(documents);
|
||||
|
||||
var chunks = documents.Chunk(5).ToList();
|
||||
var tasks = chunks.Select(async chunk =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var prompt = string.Format(promptTemplate, category, FormatResults(chunk));
|
||||
var response = await _chatCompletionService.GetChatMessageContentAsync(prompt, new OpenAIPromptExecutionSettings { Temperature = 0.1, MaxTokens = 300 });
|
||||
return response.Content ?? string.Empty;
|
||||
}
|
||||
catch { return FormatResults(chunk); }
|
||||
});
|
||||
|
||||
var summaries = await Task.WhenAll(tasks);
|
||||
var validSummaries = summaries.Where(s => !string.IsNullOrEmpty(s)).ToList();
|
||||
return validSummaries.Count > 1 ? string.Join("\n\n", validSummaries) : validSummaries.FirstOrDefault() ?? string.Empty;
|
||||
}
|
||||
|
||||
private async Task<string> IdentifyKnowledgeGaps(string question, string currentContext, string promptTemplate)
|
||||
{
|
||||
var prompt = string.Format(promptTemplate, question, currentContext.Substring(0, Math.Min(1000, currentContext.Length)));
|
||||
var response = await _chatCompletionService.GetChatMessageContentAsync(prompt, new OpenAIPromptExecutionSettings { Temperature = 0.2, MaxTokens = 100 });
|
||||
var gaps = response.Content?.Trim() ?? "";
|
||||
return gaps.Equals("SUFICIENTE", StringComparison.OrdinalIgnoreCase) ? "" : gaps;
|
||||
}
|
||||
|
||||
public Task<string> GetResponse(UserData userData, string projectId, string sessionId, string question)
|
||||
{
|
||||
return GetResponse(userData, projectId, sessionId, question, "pt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore SKEXP0001
|
||||
@ -78,22 +78,24 @@ namespace ChatRAG.Services.ResponseService
|
||||
|
||||
Responda APENAS no formato JSON:
|
||||
{{
|
||||
""strategy"": ""overview|specific|detailed"",
|
||||
""strategy"": ""overview|specific|detailed|out_of_scope"",
|
||||
""complexity"": ""simple|medium|complex"",
|
||||
""scope"": ""global|filtered|targeted"",
|
||||
""concepts"": [""conceito1"", ""conceito2""],
|
||||
""needs_hierarchy"": true|false
|
||||
""needs_hierarchy"": true|false,
|
||||
}}
|
||||
|
||||
DEFINIÇÕES PRECISAS:
|
||||
|
||||
STRATEGY:
|
||||
- overview: Pergunta sobre o PROJETO COMO UM TODO. Palavras-chave: ""projeto"", ""sistema"", ""aplicação"", ""este projeto"", ""todo o"", ""geral"", ""inteiro"". NÃO menciona módulos, funcionalidades ou tecnologias específicas.
|
||||
- out_of_scope: Pergunta/frase sem sentido relacionada à projetos. Saudações, cumprimentos, perguntas sem sentido, etc. Palavras-chave: ""oi"", ""olá"", ""bom dia"", ""boa tarde"", ""quem"", ""quando"", ""etc"". NÃO menciona projetos e contém apenas uma saudação ou o usuário se apresentando, sem falar mais nada além disso.
|
||||
- specific: Pergunta sobre MÓDULO/FUNCIONALIDADE ESPECÍFICA. Menciona: nome de classe, controller, entidade, CRUD específico, funcionalidade particular, tecnologia específica.
|
||||
- detailed: Pergunta técnica específica que precisa de CONTEXTO PROFUNDO e detalhes de implementação.
|
||||
- out_of_scope: Suadação, pergunta sem relação com os textos ou fora de contexto
|
||||
|
||||
SCOPE:
|
||||
- global: Busca informações de TODO o projeto (usar com overview)
|
||||
- global: Busca informações de TODO o projeto (usar com overview ou com out_of_scope)
|
||||
- filtered: Busca com filtros específicos (usar com specific/detailed)
|
||||
- targeted: Busca muito específica e direcionada
|
||||
|
||||
@ -103,6 +105,11 @@ namespace ChatRAG.Services.ResponseService
|
||||
- ""Gere casos de teste para o CRUD de usuário"" → specific/filtered
|
||||
- ""Como implementar autenticação JWT neste controller"" → detailed/targeted
|
||||
- ""Documente este sistema"" → overview/global
|
||||
- ""Oi!"" → out_of_scope/global
|
||||
- ""Boa tarde!"" → out_of_scope/global
|
||||
- ""Meu nome é [nome de usuario]"" → out_of_scope/global
|
||||
- ""Faça uma conta"" → out_of_scope/global
|
||||
- ""Me passe a receita de bolo?"" → out_of_scope/global
|
||||
- ""Explique a classe UserService"" → specific/filtered" :
|
||||
|
||||
@"Analyze this question and classify precisely:
|
||||
@ -110,7 +117,7 @@ namespace ChatRAG.Services.ResponseService
|
||||
|
||||
Answer ONLY in JSON format:
|
||||
{{
|
||||
""strategy"": ""overview|specific|detailed"",
|
||||
""strategy"": ""overview|specific|detailed|out_of_scope"",
|
||||
""complexity"": ""simple|medium|complex"",
|
||||
""scope"": ""global|filtered|targeted"",
|
||||
""concepts"": [""concept1"", ""concept2""],
|
||||
@ -121,11 +128,12 @@ namespace ChatRAG.Services.ResponseService
|
||||
|
||||
STRATEGY:
|
||||
- overview: Question about the PROJECT AS A WHOLE. Keywords: ""project"", ""system"", ""application"", ""this project"", ""entire"", ""general"", ""whole"". Does NOT mention specific modules, functionalities or technologies.
|
||||
- out_of_scope: Meaningless question/phrase related to projects. Greetings, salutations, meaningless questions, etc. Keywords: ""hi"", ""hello"", ""good morning"", ""good afternoon"", ""who"", ""when"", ""etc"". Does NOT mention projects and contains only a greeting or the user introducing themselves, without saying anything else.
|
||||
- specific: Question about SPECIFIC MODULE/FUNCTIONALITY. Mentions: class name, controller, entity, specific CRUD, particular functionality, specific technology.
|
||||
- detailed: Technical specific question needing DEEP CONTEXT and implementation details.
|
||||
|
||||
SCOPE:
|
||||
- global: Search information from ENTIRE project (use with overview)
|
||||
- global: Search information from ENTIRE project (use with overview or out_of_scope)
|
||||
- filtered: Search with specific filters (use with specific/detailed)
|
||||
- targeted: Very specific and directed search
|
||||
|
||||
@ -134,6 +142,11 @@ namespace ChatRAG.Services.ResponseService
|
||||
- ""Generate test cases for user CRUD"" → specific/filtered
|
||||
- ""How to implement JWT authentication in this controller"" → detailed/targeted
|
||||
- ""Document this system"" → overview/global
|
||||
- ""Hi!"" → out_of_scope/global
|
||||
- ""Good afternoon!"" → out_of_scope/global
|
||||
- ""My name is [nome de usuario]"" → out_of_scope/global
|
||||
- ""Do an operation math for me"" → out_of_scope/global
|
||||
- ""Give me the recipe for a cake?"" → out_of_scope/global
|
||||
- ""Explain the UserService class"" → specific/filtered";
|
||||
|
||||
var prompt = string.Format(analysisPrompt, question);
|
||||
@ -182,6 +195,9 @@ namespace ChatRAG.Services.ResponseService
|
||||
|
||||
switch (analysis.Strategy)
|
||||
{
|
||||
case "out_of_scope":
|
||||
await ExecuteOutOfContextStrategy(context, question, projectId, embeddingService);
|
||||
break;
|
||||
case "overview":
|
||||
await ExecuteOverviewStrategy(context, question, projectId, embeddingService);
|
||||
break;
|
||||
@ -198,6 +214,16 @@ namespace ChatRAG.Services.ResponseService
|
||||
return context;
|
||||
}
|
||||
|
||||
private async Task ExecuteOutOfContextStrategy(HierarchicalContext context, string question, string projectId, ITextEmbeddingGenerationService embeddingService)
|
||||
{
|
||||
context.AddStep("Buscando o projeto");
|
||||
var project = _projectDataRepository.GetAsync(projectId);
|
||||
|
||||
context.CombinedContext = string.Join("\n\n", project);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async Task ExecuteOverviewStrategy(HierarchicalContext context, string question, string projectId, ITextEmbeddingGenerationService embeddingService)
|
||||
{
|
||||
// Etapa 1: Buscar TODOS os documentos do projeto
|
||||
@ -467,14 +493,14 @@ namespace ChatRAG.Services.ResponseService
|
||||
var project = $"Nome: {projectData.Nome} \n\n Descrição:{projectData.Descricao}";
|
||||
|
||||
var prompt = language == "pt" ?
|
||||
@"Você é um especialista em análise de software e QA.
|
||||
@"Você é um especialista em análise de software e QA, mas também atende ao chat.
|
||||
|
||||
PROJETO: {0}
|
||||
PERGUNTA: ""{1}""
|
||||
CONTEXTO HIERÁRQUICO: {2}
|
||||
ETAPAS EXECUTADAS: {3}
|
||||
|
||||
Responda à pergunta de forma precisa e estruturada, aproveitando todo o contexto hierárquico coletado." :
|
||||
Responda à pergunta de forma precisa e estruturada, aproveitando todo o contexto hierárquico coletado. Se for uma saudação ou não for uma pergunta relativa ao contexto, avise que não entendeu." :
|
||||
|
||||
@"You are a software analysis and QA expert.
|
||||
|
||||
|
||||
572
Services/ResponseService/dxsmsw5a.kio~
Normal file
572
Services/ResponseService/dxsmsw5a.kio~
Normal file
@ -0,0 +1,572 @@
|
||||
using ChatApi;
|
||||
using ChatApi.Models;
|
||||
using ChatRAG.Contracts.VectorSearch;
|
||||
using ChatRAG.Data;
|
||||
using ChatRAG.Models;
|
||||
using ChatRAG.Services.Contracts;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
using Microsoft.SemanticKernel.Embeddings;
|
||||
using System.Text.Json;
|
||||
|
||||
#pragma warning disable SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||
namespace ChatRAG.Services.ResponseService
|
||||
{
|
||||
public class HierarchicalRAGService : IResponseService
|
||||
{
|
||||
private readonly ChatHistoryService _chatHistoryService;
|
||||
private readonly Kernel _kernel;
|
||||
private readonly TextFilter _textFilter;
|
||||
private readonly IProjectDataRepository _projectDataRepository;
|
||||
private readonly IChatCompletionService _chatCompletionService;
|
||||
private readonly IVectorSearchService _vectorSearchService;
|
||||
private readonly ILogger<HierarchicalRAGService> _logger;
|
||||
|
||||
public HierarchicalRAGService(
|
||||
ChatHistoryService chatHistoryService,
|
||||
Kernel kernel,
|
||||
TextFilter textFilter,
|
||||
IProjectDataRepository projectDataRepository,
|
||||
IChatCompletionService chatCompletionService,
|
||||
IVectorSearchService vectorSearchService,
|
||||
ILogger<HierarchicalRAGService> logger)
|
||||
{
|
||||
_chatHistoryService = chatHistoryService;
|
||||
_kernel = kernel;
|
||||
_textFilter = textFilter;
|
||||
_projectDataRepository = projectDataRepository;
|
||||
_chatCompletionService = chatCompletionService;
|
||||
_vectorSearchService = vectorSearchService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<string> GetResponse(UserData userData, string projectId, string sessionId, string question, string language = "pt")
|
||||
{
|
||||
var stopWatch = new System.Diagnostics.Stopwatch();
|
||||
stopWatch.Start();
|
||||
|
||||
try
|
||||
{
|
||||
// 1. Análise da query para determinar estratégia
|
||||
var queryAnalysis = await AnalyzeQuery(question, language);
|
||||
_logger.LogInformation("Query Analysis: {Strategy}, Complexity: {Complexity}",
|
||||
queryAnalysis.Strategy, queryAnalysis.Complexity);
|
||||
|
||||
// 2. Execução hierárquica baseada na análise
|
||||
var context = await ExecuteHierarchicalSearch(question, projectId, queryAnalysis);
|
||||
|
||||
// 3. Geração da resposta final
|
||||
var response = await GenerateResponse(question, projectId, context, sessionId, language);
|
||||
|
||||
stopWatch.Stop();
|
||||
return $"{response}\n\nTempo: {stopWatch.ElapsedMilliseconds / 1000}s\nEtapas: {context.Steps.Count}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Erro no RAG Hierárquico");
|
||||
stopWatch.Stop();
|
||||
return $"Erro: {ex.Message}\nTempo: {stopWatch.ElapsedMilliseconds / 1000}s";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<QueryAnalysis> AnalyzeQuery(string question, string language)
|
||||
{
|
||||
var analysisPrompt = language == "pt" ?
|
||||
@"Analise esta pergunta e classifique com precisão:
|
||||
PERGUNTA: ""{0}""
|
||||
|
||||
Responda APENAS no formato JSON:
|
||||
{{
|
||||
""strategy"": ""overview|specific|detailed|out_of_scope"",
|
||||
""complexity"": ""simple|medium|complex"",
|
||||
""scope"": ""global|filtered|targeted"",
|
||||
""concepts"": [""conceito1"", ""conceito2""],
|
||||
""needs_hierarchy"": true|false,
|
||||
}}
|
||||
|
||||
DEFINIÇÕES PRECISAS:
|
||||
|
||||
STRATEGY:
|
||||
- overview: Pergunta sobre o PROJETO COMO UM TODO. Palavras-chave: ""projeto"", ""sistema"", ""aplicação"", ""este projeto"", ""todo o"", ""geral"", ""inteiro"". NÃO menciona módulos, funcionalidades ou tecnologias específicas.
|
||||
- out_of_scope: Pergunta/frase sem sentido relacionada à projetos. Saudações, cumprimentos, perguntas sem sentido, etc. Palavras-chave: ""oi"", ""olá"", ""bom dia"", ""boa tarde"", ""quem"", ""quando"", ""etc"". NÃO menciona projetos e contém apenas uma saudação ou o usuário se apresentando, sem falar mais nada além disso.
|
||||
- specific: Pergunta sobre MÓDULO/FUNCIONALIDADE ESPECÍFICA. Menciona: nome de classe, controller, entidade, CRUD específico, funcionalidade particular, tecnologia específica.
|
||||
- detailed: Pergunta técnica específica que precisa de CONTEXTO PROFUNDO e detalhes de implementação.
|
||||
- out_of_scope: Suadação, pergunta sem relação com os textos ou fora de contexto
|
||||
|
||||
SCOPE:
|
||||
- global: Busca informações de TODO o projeto (usar com overview ou com out_of_scope)
|
||||
- filtered: Busca com filtros específicos (usar com specific/detailed)
|
||||
- targeted: Busca muito específica e direcionada
|
||||
|
||||
EXEMPLOS:
|
||||
- ""Gere casos de teste para este projeto"" → overview/global
|
||||
- ""Gere casos de teste do projeto"" → overview/global
|
||||
- ""Gere casos de teste para o CRUD de usuário"" → specific/filtered
|
||||
- ""Como implementar autenticação JWT neste controller"" → detailed/targeted
|
||||
- ""Documente este sistema"" → overview/global
|
||||
- ""Oi!"" → out_of_scope/global
|
||||
- ""Boa tarde!"" → out_of_scope/global
|
||||
- ""Meu nome é [nome de usuario]"" → out_of_scope/global
|
||||
- ""Faça uma conta"" → out_of_scope/global
|
||||
- ""Me passe a receita de bolo?"" → out_of_scope/global
|
||||
- ""Explique a classe UserService"" → specific/filtered" :
|
||||
|
||||
@"Analyze this question and classify precisely:
|
||||
QUESTION: ""{0}""
|
||||
|
||||
Answer ONLY in JSON format:
|
||||
{{
|
||||
""strategy"": ""overview|specific|detailed|out_of_scope"",
|
||||
""complexity"": ""simple|medium|complex"",
|
||||
""scope"": ""global|filtered|targeted"",
|
||||
""concepts"": [""concept1"", ""concept2""],
|
||||
""needs_hierarchy"": true|false
|
||||
}}
|
||||
|
||||
PRECISE DEFINITIONS:
|
||||
|
||||
STRATEGY:
|
||||
- overview: Question about the PROJECT AS A WHOLE. Keywords: ""project"", ""system"", ""application"", ""this project"", ""entire"", ""general"", ""whole"". Does NOT mention specific modules, functionalities or technologies.
|
||||
- out_of_scope: Meaningless question/phrase related to projects. Greetings, salutations, meaningless questions, etc. Keywords: ""hi"", ""hello"", ""good morning"", ""good afternoon"", ""who"", ""when"", ""etc"". Does NOT mention projects and contains only a greeting or the user introducing themselves, without saying anything else.
|
||||
- specific: Question about SPECIFIC MODULE/FUNCTIONALITY. Mentions: class name, controller, entity, specific CRUD, particular functionality, specific technology.
|
||||
- detailed: Technical specific question needing DEEP CONTEXT and implementation details.
|
||||
|
||||
SCOPE:
|
||||
- global: Search information from ENTIRE project (use with overview or out_of_scope)
|
||||
- filtered: Search with specific filters (use with specific/detailed)
|
||||
- targeted: Very specific and directed search
|
||||
|
||||
EXAMPLES:
|
||||
- ""Generate test cases for this project"" → overview/global
|
||||
- ""Generate test cases for user CRUD"" → specific/filtered
|
||||
- ""How to implement JWT authentication in this controller"" → detailed/targeted
|
||||
- ""Document this system"" → overview/global
|
||||
- ""Hi!"" → out_of_scope/global
|
||||
- ""Good afternoon!"" → out_of_scope/global
|
||||
- ""My name is [nome de usuario]"" → out_of_scope/global
|
||||
- ""Do an operation math for me"" → out_of_scope/global
|
||||
- ""Give me the recipe for a cake?"" → out_of_scope/global
|
||||
- ""Explain the UserService class"" → specific/filtered";
|
||||
|
||||
var prompt = string.Format(analysisPrompt, question);
|
||||
var executionSettings = new OpenAIPromptExecutionSettings
|
||||
{
|
||||
Temperature = 0.1,
|
||||
MaxTokens = 300 // Aumentei um pouco para acomodar o prompt maior
|
||||
};
|
||||
var response = await _chatCompletionService.GetChatMessageContentAsync(prompt, executionSettings);
|
||||
|
||||
try
|
||||
{
|
||||
var jsonResponse = response.Content?.Trim() ?? "{}";
|
||||
// Extrair JSON se vier com texto extra
|
||||
var startIndex = jsonResponse.IndexOf('{');
|
||||
var endIndex = jsonResponse.LastIndexOf('}');
|
||||
if (startIndex >= 0 && endIndex >= startIndex)
|
||||
{
|
||||
jsonResponse = jsonResponse.Substring(startIndex, endIndex - startIndex + 1);
|
||||
}
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
var analysis = System.Text.Json.JsonSerializer.Deserialize<QueryAnalysis>(jsonResponse, options);
|
||||
|
||||
// Log para debug - remover em produção
|
||||
_logger.LogInformation($"Query: '{question}' → Strategy: {analysis?.Strategy}, Scope: {analysis?.Scope}");
|
||||
|
||||
return analysis ?? new QueryAnalysis { Strategy = "specific", Complexity = "medium" };
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Erro ao parsear análise da query, usando padrão");
|
||||
return new QueryAnalysis { Strategy = "specific", Complexity = "medium" };
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<HierarchicalContext> ExecuteHierarchicalSearch(string question, string projectId, QueryAnalysis analysis)
|
||||
{
|
||||
var context = new HierarchicalContext();
|
||||
var embeddingService = _kernel.GetRequiredService<ITextEmbeddingGenerationService>();
|
||||
|
||||
switch (analysis.Strategy)
|
||||
{
|
||||
case "out_of_scope":
|
||||
await ExecuteOutOfContextStrategy(context, question, projectId, embeddingService);
|
||||
break;
|
||||
case "overview":
|
||||
await ExecuteOverviewStrategy(context, question, projectId, embeddingService);
|
||||
break;
|
||||
|
||||
case "detailed":
|
||||
await ExecuteDetailedStrategy(context, question, projectId, embeddingService, analysis);
|
||||
break;
|
||||
|
||||
default: // specific
|
||||
await ExecuteSpecificStrategy(context, question, projectId, embeddingService);
|
||||
break;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
private async Task ExecuteOutOfContextStrategy(HierarchicalContext context, string question, string projectId, ITextEmbeddingGenerationService embeddingService)
|
||||
{
|
||||
context.AddStep("Buscando o projeto");
|
||||
var project = _projectDataRepository.GetAsync(projectId);
|
||||
|
||||
context.CombinedContext = string.Join("\n\n", project);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async Task ExecuteOverviewStrategy(HierarchicalContext context, string question, string projectId, ITextEmbeddingGenerationService embeddingService)
|
||||
{
|
||||
// Etapa 1: Buscar TODOS os documentos do projeto
|
||||
context.AddStep("Buscando todos os documentos do projeto");
|
||||
var allProjectDocs = await _vectorSearchService.GetDocumentsByProjectAsync(projectId);
|
||||
|
||||
// Etapa 2: Categorizar documentos por tipo/importância
|
||||
context.AddStep("Categorizando e resumindo contexto do projeto");
|
||||
|
||||
// Etapa 2: Categorizar documentos por tipo baseado nos seus dados reais
|
||||
context.AddStep("Categorizando e resumindo contexto do projeto");
|
||||
|
||||
var requirementsDocs = allProjectDocs.Where(d =>
|
||||
d.Title.ToLower().StartsWith("requisito") ||
|
||||
d.Title.ToLower().Contains("requisito") ||
|
||||
d.Content.ToLower().Contains("requisito") ||
|
||||
d.Content.ToLower().Contains("funcionalidade") ||
|
||||
d.Content.ToLower().Contains("aplicação deve") ||
|
||||
d.Content.ToLower().Contains("sistema deve")).ToList();
|
||||
|
||||
var architectureDocs = allProjectDocs.Where(d =>
|
||||
d.Title.ToLower().Contains("arquitetura") ||
|
||||
d.Title.ToLower().Contains("estrutura") ||
|
||||
d.Title.ToLower().Contains("documentação") ||
|
||||
d.Title.ToLower().Contains("readme") ||
|
||||
d.Content.ToLower().Contains("arquitetura") ||
|
||||
d.Content.ToLower().Contains("estrutura") ||
|
||||
d.Content.ToLower().Contains("tecnologia")).ToList();
|
||||
|
||||
// Documentos que não são requisitos nem arquitetura (códigos, outros docs)
|
||||
var otherDocs = allProjectDocs
|
||||
.Except(requirementsDocs)
|
||||
.Except(architectureDocs)
|
||||
.ToList();
|
||||
|
||||
// Etapa 3: Resumir cada categoria se tiver muitos documentos
|
||||
var requirementsSummary = await SummarizeDocuments(requirementsDocs, "requisitos e funcionalidades do projeto");
|
||||
var architectureSummary = await SummarizeDocuments(architectureDocs, "arquitetura e documentação técnica");
|
||||
var otherSummary = await SummarizeDocuments(otherDocs, "outros documentos do projeto");
|
||||
|
||||
// Etapa 4: Busca específica para a pergunta (mantém precisão)
|
||||
var questionEmbedding = await embeddingService.GenerateEmbeddingAsync(_textFilter.ToLowerAndWithoutAccents(question));
|
||||
var embeddingArray = questionEmbedding.ToArray().Select(e => (double)e).ToArray();
|
||||
|
||||
context.AddStep("Identificando documentos específicos para a pergunta");
|
||||
var relevantDocs = await _vectorSearchService.SearchSimilarAsync(embeddingArray, projectId, 0.3, 8);
|
||||
|
||||
// Etapa 5: Combinar resumos + documentos específicos
|
||||
var contextParts = new List<string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(requirementsSummary))
|
||||
contextParts.Add($"RESUMO DOS REQUISITOS E FUNCIONALIDADES:\n{requirementsSummary}");
|
||||
|
||||
if (!string.IsNullOrEmpty(architectureSummary))
|
||||
contextParts.Add($"RESUMO DA ARQUITETURA E DOCUMENTAÇÃO:\n{architectureSummary}");
|
||||
|
||||
if (!string.IsNullOrEmpty(otherSummary))
|
||||
contextParts.Add($"OUTROS DOCUMENTOS DO PROJETO:\n{otherSummary}");
|
||||
|
||||
contextParts.Add($"DOCUMENTOS MAIS RELEVANTES PARA A PERGUNTA:\n{FormatResults(relevantDocs)}");
|
||||
|
||||
context.CombinedContext = string.Join("\n\n", contextParts);
|
||||
}
|
||||
|
||||
private async Task<string> SummarizeDocuments(List<VectorSearchResult> documents, string category)
|
||||
{
|
||||
if (!documents.Any()) return string.Empty;
|
||||
|
||||
// Se poucos documentos, usar todos sem resumir
|
||||
if (documents.Count <= 3)
|
||||
{
|
||||
return FormatResults(documents);
|
||||
}
|
||||
|
||||
// Se muitos documentos, resumir em chunks
|
||||
var chunks = documents.Chunk(5).ToList(); // Grupos de 5 documentos
|
||||
var tasks = new List<Task<string>>();
|
||||
|
||||
// Semáforo para controlar concorrência (máximo 3 chamadas simultâneas)
|
||||
var semaphore = new SemaphoreSlim(3, 3);
|
||||
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
var chunkContent = FormatResults(chunk);
|
||||
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
await semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
var summaryPrompt = $@"Resuma os pontos principais destes documentos sobre {category}:
|
||||
|
||||
{chunkContent}
|
||||
|
||||
Responda apenas com uma lista concisa dos pontos mais importantes:";
|
||||
|
||||
var response = await _chatCompletionService.GetChatMessageContentAsync(
|
||||
summaryPrompt,
|
||||
new OpenAIPromptExecutionSettings
|
||||
{
|
||||
Temperature = 0.1,
|
||||
MaxTokens = 300
|
||||
});
|
||||
|
||||
return response.Content ?? string.Empty;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, $"Erro ao resumir chunk de {category}, usando conteúdo original");
|
||||
return chunkContent;
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Aguardar todas as tasks de resumo
|
||||
var summaries = await Task.WhenAll(tasks);
|
||||
var validSummaries = summaries.Where(s => !string.IsNullOrEmpty(s)).ToList();
|
||||
|
||||
// Se tiver múltiplos resumos, consolidar
|
||||
if (validSummaries.Count > 1)
|
||||
{
|
||||
var consolidationPrompt = $@"Consolide estes resumos sobre {category} em um resumo final:
|
||||
|
||||
{string.Join("\n\n", validSummaries)}
|
||||
|
||||
Responda com os pontos mais importantes organizados:";
|
||||
|
||||
try
|
||||
{
|
||||
var finalResponse = await _chatCompletionService.GetChatMessageContentAsync(
|
||||
consolidationPrompt,
|
||||
new OpenAIPromptExecutionSettings
|
||||
{
|
||||
Temperature = 0.1,
|
||||
MaxTokens = 400
|
||||
});
|
||||
|
||||
return finalResponse.Content ?? string.Empty;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Join("\n\n", validSummaries);
|
||||
}
|
||||
}
|
||||
|
||||
return validSummaries.FirstOrDefault() ?? string.Empty;
|
||||
}
|
||||
|
||||
private async Task ExecuteSpecificStrategy(HierarchicalContext context, string question, string projectId, ITextEmbeddingGenerationService embeddingService)
|
||||
{
|
||||
// Etapa 1: Busca inicial por similaridade
|
||||
context.AddStep("Busca inicial por similaridade");
|
||||
var questionEmbedding = await embeddingService.GenerateEmbeddingAsync(_textFilter.ToLowerAndWithoutAccents(question));
|
||||
var embeddingArray = questionEmbedding.ToArray().Select(e => (double)e).ToArray();
|
||||
|
||||
var initialResults = await _vectorSearchService.SearchSimilarAsync(embeddingArray, projectId, 0.4, 3);
|
||||
|
||||
if (initialResults.Any())
|
||||
{
|
||||
context.AddStep("Expandindo contexto com documentos relacionados");
|
||||
|
||||
// Etapa 2: Expandir com contexto relacionado
|
||||
var expandedContext = await ExpandContext(initialResults, projectId, embeddingService);
|
||||
context.CombinedContext = $"CONTEXTO PRINCIPAL:\n{FormatResults(initialResults)}\n\nCONTEXTO EXPANDIDO:\n{FormatResults(expandedContext)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
context.AddStep("Fallback para busca ampla");
|
||||
var fallbackResults = await _vectorSearchService.SearchSimilarAsync(embeddingArray, projectId, 0.2, 5);
|
||||
context.CombinedContext = FormatResults(fallbackResults);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteDetailedStrategy(HierarchicalContext context, string question, string projectId, ITextEmbeddingGenerationService embeddingService, QueryAnalysis analysis)
|
||||
{
|
||||
var questionEmbedding = await embeddingService.GenerateEmbeddingAsync(_textFilter.ToLowerAndWithoutAccents(question));
|
||||
var embeddingArray = questionEmbedding.ToArray().Select(e => (double)e).ToArray();
|
||||
|
||||
// Etapa 1: Busca conceitual baseada nos conceitos identificados
|
||||
context.AddStep("Busca conceitual inicial");
|
||||
var conceptualResults = new List<VectorSearchResult>();
|
||||
|
||||
if (analysis.Concepts?.Any() == true)
|
||||
{
|
||||
foreach (var concept in analysis.Concepts)
|
||||
{
|
||||
var conceptEmbedding = await embeddingService.GenerateEmbeddingAsync(_textFilter.ToLowerAndWithoutAccents(concept));
|
||||
var conceptArray = conceptEmbedding.ToArray().Select(e => (double)e).ToArray();
|
||||
var conceptResults = await _vectorSearchService.SearchSimilarAsync(conceptArray, projectId, 0.3, 2);
|
||||
conceptualResults.AddRange(conceptResults);
|
||||
}
|
||||
}
|
||||
|
||||
// Etapa 2: Busca direta pela pergunta
|
||||
context.AddStep("Busca direta pela pergunta");
|
||||
var directResults = await _vectorSearchService.SearchSimilarAsync(embeddingArray, projectId, 0.3, 3);
|
||||
|
||||
// Etapa 3: Síntese intermediária para identificar lacunas
|
||||
context.AddStep("Identificando lacunas de conhecimento");
|
||||
var intermediateContext = FormatResults(conceptualResults.Concat(directResults).DistinctBy(r => r.Id));
|
||||
var gaps = await IdentifyKnowledgeGaps(question, intermediateContext);
|
||||
|
||||
// Etapa 4: Busca complementar baseada nas lacunas
|
||||
if (!string.IsNullOrEmpty(gaps))
|
||||
{
|
||||
context.AddStep("Preenchendo lacunas de conhecimento");
|
||||
var gapEmbedding = await embeddingService.GenerateEmbeddingAsync(_textFilter.ToLowerAndWithoutAccents(gaps));
|
||||
var gapArray = gapEmbedding.ToArray().Select(e => (double)e).ToArray();
|
||||
var gapResults = await _vectorSearchService.SearchSimilarAsync(gapArray, projectId, 0.25, 2);
|
||||
|
||||
context.CombinedContext = $"CONTEXTO CONCEITUAL:\n{FormatResults(conceptualResults)}\n\nCONTEXTO DIRETO:\n{FormatResults(directResults)}\n\nCONTEXTO COMPLEMENTAR:\n{FormatResults(gapResults)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
context.CombinedContext = $"CONTEXTO CONCEITUAL:\n{FormatResults(conceptualResults)}\n\nCONTEXTO DIRETO:\n{FormatResults(directResults)}";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<VectorSearchResult>> ExpandContext(List<VectorSearchResult> initialResults, string projectId, ITextEmbeddingGenerationService embeddingService)
|
||||
{
|
||||
var expandedResults = new List<VectorSearchResult>();
|
||||
|
||||
// Para cada resultado inicial, buscar documentos relacionados
|
||||
foreach (var result in initialResults.Take(2)) // Limitar para evitar explosão de contexto
|
||||
{
|
||||
var resultEmbedding = await embeddingService.GenerateEmbeddingAsync(_textFilter.ToLowerAndWithoutAccents(result.Content));
|
||||
var embeddingArray = resultEmbedding.ToArray().Select(e => (double)e).ToArray();
|
||||
|
||||
var relatedDocs = await _vectorSearchService.SearchSimilarAsync(embeddingArray, projectId, 0.4, 2);
|
||||
expandedResults.AddRange(relatedDocs.Where(r => !initialResults.Any(ir => ir.Id == r.Id)));
|
||||
}
|
||||
|
||||
return expandedResults.DistinctBy(r => r.Id).ToList();
|
||||
}
|
||||
|
||||
private async Task<string> IdentifyKnowledgeGaps(string question, string currentContext)
|
||||
{
|
||||
var gapPrompt = @"Baseado na pergunta e contexto atual, identifique que informações ainda faltam para uma resposta completa.
|
||||
|
||||
PERGUNTA: {0}
|
||||
CONTEXTO ATUAL: {1}
|
||||
|
||||
Responda APENAS com palavras-chave dos conceitos/informações que ainda faltam, separados por vírgula.
|
||||
Se o contexto for suficiente, responda 'SUFICIENTE'.";
|
||||
|
||||
var prompt = string.Format(gapPrompt, question, currentContext.Substring(0, Math.Min(1000, currentContext.Length)));
|
||||
|
||||
var executionSettings = new OpenAIPromptExecutionSettings
|
||||
{
|
||||
Temperature = 0.2,
|
||||
MaxTokens = 100
|
||||
};
|
||||
|
||||
var response = await _chatCompletionService.GetChatMessageContentAsync(prompt, executionSettings);
|
||||
var gaps = response.Content?.Trim() ?? "";
|
||||
|
||||
return gaps.Equals("SUFICIENTE", StringComparison.OrdinalIgnoreCase) ? "" : gaps;
|
||||
}
|
||||
|
||||
private async Task<string> GenerateResponse(string question, string projectId, HierarchicalContext context, string sessionId, string language)
|
||||
{
|
||||
var projectData = await _projectDataRepository.GetAsync(projectId);
|
||||
var project = $"Nome: {projectData.Nome} \n\n Descrição:{projectData.Descricao}";
|
||||
|
||||
var prompt = language == "pt" ?
|
||||
@"Você é um especialista em análise de software e QA, mas também atende ao chat.
|
||||
|
||||
PROJETO: {0}
|
||||
PERGUNTA: ""{1}""
|
||||
CONTEXTO HIERÁRQUICO: {2}
|
||||
ETAPAS EXECUTADAS: {3}
|
||||
|
||||
Responda à pergunta de forma precisa e estruturada, aproveitando todo o contexto hierárquico coletado." :
|
||||
|
||||
@"You are a software analysis and QA expert.
|
||||
|
||||
PROJECT: {0}
|
||||
QUESTION: ""{1}""
|
||||
HIERARCHICAL CONTEXT: {2}
|
||||
EXECUTED STEPS: {3}
|
||||
|
||||
Answer the question precisely and structured, leveraging all the hierarchical context collected.";
|
||||
|
||||
var finalPrompt = string.Format(prompt, project, question, context.CombinedContext,
|
||||
string.Join(" → ", context.Steps));
|
||||
|
||||
var history = _chatHistoryService.GetSumarizer(sessionId);
|
||||
history.AddUserMessage(finalPrompt);
|
||||
|
||||
var executionSettings = new OpenAIPromptExecutionSettings
|
||||
{
|
||||
Temperature = 0.7,
|
||||
TopP = 1.0,
|
||||
FrequencyPenalty = 0,
|
||||
PresencePenalty = 0
|
||||
};
|
||||
|
||||
var response = await _chatCompletionService.GetChatMessageContentAsync(history, executionSettings);
|
||||
history.AddMessage(response.Role, response.Content ?? "");
|
||||
_chatHistoryService.UpdateHistory(sessionId, history);
|
||||
|
||||
return response.Content ?? "";
|
||||
}
|
||||
|
||||
private string FormatResults(IEnumerable<VectorSearchResult> results)
|
||||
{
|
||||
return string.Join("\n\n", results.Select((item, index) =>
|
||||
$"=== DOCUMENTO {index + 1} ===\n" +
|
||||
$"Relevância: {item.Score:P1}\n" +
|
||||
$"Conteúdo: {item.Content}"));
|
||||
}
|
||||
|
||||
public Task<string> GetResponse(UserData userData, string projectId, string sessionId, string question)
|
||||
{
|
||||
return GetResponse(userData, projectId, sessionId, question, "pt");
|
||||
}
|
||||
}
|
||||
|
||||
// Classes de apoio para o RAG Hierárquico
|
||||
public class QueryAnalysis
|
||||
{
|
||||
public string Strategy { get; set; } = "specific";
|
||||
public string Complexity { get; set; } = "medium";
|
||||
public string Scope { get; set; } = "filtered";
|
||||
public string[] Concepts { get; set; } = Array.Empty<string>();
|
||||
public bool Needs_Hierarchy { get; set; } = false;
|
||||
}
|
||||
|
||||
public class HierarchicalContext
|
||||
{
|
||||
public List<string> Steps { get; set; } = new();
|
||||
public string CombinedContext { get; set; } = "";
|
||||
public Dictionary<string, object> Metadata { get; set; } = new();
|
||||
|
||||
public void AddStep(string step)
|
||||
{
|
||||
Steps.Add($"{DateTime.Now:HH:mm:ss} - {step}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#pragma warning restore SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||
@ -68,13 +68,20 @@ namespace ChatRAG.Services.SearchVectors
|
||||
// Verificar se deve usar RAG Hierárquico
|
||||
var configuration = _serviceProvider.GetService<IConfiguration>();
|
||||
var useHierarchical = configuration?.GetValue<bool>("Features:UseHierarchicalRAG") ?? false;
|
||||
var useConfidenceAware = configuration?.GetValue<bool>("Features:UseConfidenceAwareRAG") ?? false;
|
||||
|
||||
if (useHierarchical)
|
||||
if (useHierarchical && !useConfidenceAware)
|
||||
{
|
||||
_logger.LogInformation("Usando HierarchicalRAGService");
|
||||
return GetService<HierarchicalRAGService>();
|
||||
}
|
||||
|
||||
if (useConfidenceAware)
|
||||
{
|
||||
_logger.LogInformation("Usando ConfidenceAwareRAGService");
|
||||
return GetService<ConfidenceAwareRAGService>();
|
||||
}
|
||||
|
||||
// Usar estratégia baseada no provider ou configuração
|
||||
var ragStrategy = configuration?.GetValue<string>("Features:RAGStrategy");
|
||||
|
||||
|
||||
120
Settings/ConfidenceAwareSettings.cs
Normal file
120
Settings/ConfidenceAwareSettings.cs
Normal file
@ -0,0 +1,120 @@
|
||||
namespace ChatRAG.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Configurações específicas para o ConfidenceAwareRAG
|
||||
/// </summary>
|
||||
public class ConfidenceAwareSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Habilita/desabilita verificação de confiança
|
||||
/// true = só responde com confiança, false = sempre responde (como hoje)
|
||||
/// </summary>
|
||||
public bool EnableConfidenceCheck { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Modo restrito (critérios rigorosos) vs modo relaxado
|
||||
/// </summary>
|
||||
public bool UseStrictMode { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Mostra informações de debug na resposta (confiança, tempo, etc.)
|
||||
/// </summary>
|
||||
public bool ShowDebugInfo { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Domínio padrão quando não conseguir detectar automaticamente
|
||||
/// </summary>
|
||||
public string DefaultDomain { get; set; } = "TI";
|
||||
|
||||
/// <summary>
|
||||
/// Mapeamento de palavras-chave para domínios (detecção automática)
|
||||
/// </summary>
|
||||
public Dictionary<string, string> DomainMappings { get; set; } = new()
|
||||
{
|
||||
["software"] = "TI",
|
||||
["sistema"] = "TI",
|
||||
["api"] = "TI",
|
||||
["backend"] = "TI",
|
||||
["frontend"] = "TI",
|
||||
["database"] = "TI",
|
||||
["funcionário"] = "RH",
|
||||
["colaborador"] = "RH",
|
||||
["employee"] = "RH",
|
||||
["hr"] = "RH",
|
||||
["financeiro"] = "Financeiro",
|
||||
["contábil"] = "Financeiro",
|
||||
["financial"] = "Financeiro",
|
||||
["accounting"] = "Financeiro",
|
||||
["teste"] = "QA",
|
||||
["qualidade"] = "QA",
|
||||
["quality"] = "QA",
|
||||
["testing"] = "QA"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Configuração de idiomas suportados
|
||||
/// </summary>
|
||||
public LanguageSettings Languages { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Configurações de cache para prompts
|
||||
/// </summary>
|
||||
public CacheSettings Cache { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configurações de idioma
|
||||
/// </summary>
|
||||
public class LanguageSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Idioma padrão do sistema
|
||||
/// </summary>
|
||||
public string DefaultLanguage { get; set; } = "pt";
|
||||
|
||||
/// <summary>
|
||||
/// Idiomas suportados
|
||||
/// </summary>
|
||||
public List<string> SupportedLanguages { get; set; } = new() { "pt", "en" };
|
||||
|
||||
/// <summary>
|
||||
/// Auto-detectar idioma da pergunta
|
||||
/// </summary>
|
||||
public bool AutoDetectLanguage { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Sempre responder no idioma detectado/solicitado, mesmo que prompts estejam em PT
|
||||
/// </summary>
|
||||
public bool AlwaysRespondInRequestedLanguage { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Palavras-chave para detecção automática de idioma
|
||||
/// </summary>
|
||||
public Dictionary<string, List<string>> LanguageKeywords { get; set; } = new()
|
||||
{
|
||||
["en"] = new() { "what", "how", "why", "where", "when", "which", "who", "can", "could", "would", "should", "will", "the", "and", "or", "but", "system", "project", "document", "explain", "generate" },
|
||||
["pt"] = new() { "que", "como", "por", "onde", "quando", "qual", "quem", "pode", "poderia", "deveria", "será", "o", "a", "e", "ou", "mas", "sistema", "projeto", "documento", "explique", "gere" }
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configurações de cache
|
||||
/// </summary>
|
||||
public class CacheSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Habilitar cache de prompts carregados
|
||||
/// </summary>
|
||||
public bool EnablePromptCache { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Tempo de cache em minutos
|
||||
/// </summary>
|
||||
public int CacheExpirationMinutes { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Recarregar arquivos automaticamente quando modificados
|
||||
/// </summary>
|
||||
public bool AutoReloadOnFileChange { get; set; } = true;
|
||||
}
|
||||
}
|
||||
126
Settings/ConfidenceSettings.cs
Normal file
126
Settings/ConfidenceSettings.cs
Normal file
@ -0,0 +1,126 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ChatRAG.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Configurações para verificação de confiança por estratégia
|
||||
/// </summary>
|
||||
public class ConfidenceSettings
|
||||
{
|
||||
public bool StrictModeByDefault { get; set; } = true;
|
||||
|
||||
[Required]
|
||||
public Dictionary<string, ConfidenceThresholds> Thresholds { get; set; } = new()
|
||||
{
|
||||
["overview"] = new ConfidenceThresholds
|
||||
{
|
||||
MinDocuments = 5,
|
||||
MinRelevantDocuments = 3,
|
||||
MinHighQualityDocuments = 1,
|
||||
MinContextLength = 1000,
|
||||
MinOverallScore = 0.3,
|
||||
MinMaxScore = 0.4,
|
||||
MinAverageScore = 0.3
|
||||
},
|
||||
["specific"] = new ConfidenceThresholds
|
||||
{
|
||||
MinDocuments = 2,
|
||||
MinRelevantDocuments = 2,
|
||||
MinHighQualityDocuments = 1,
|
||||
MinContextLength = 500,
|
||||
MinOverallScore = 0.4,
|
||||
MinMaxScore = 0.5,
|
||||
MinAverageScore = 0.4
|
||||
},
|
||||
["detailed"] = new ConfidenceThresholds
|
||||
{
|
||||
MinDocuments = 3,
|
||||
MinRelevantDocuments = 2,
|
||||
MinHighQualityDocuments = 2,
|
||||
MinContextLength = 800,
|
||||
MinOverallScore = 0.5,
|
||||
MinMaxScore = 0.6,
|
||||
MinAverageScore = 0.5
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Mensagens de fallback por idioma
|
||||
/// </summary>
|
||||
public Dictionary<string, ConfidenceFallbackMessages> FallbackMessages { get; set; } = new()
|
||||
{
|
||||
["pt"] = new ConfidenceFallbackMessages
|
||||
{
|
||||
NoDocuments = "Não encontrei informações sobre isso no projeto atual. Você poderia reformular a pergunta ou ser mais específico?",
|
||||
NoRelevantDocuments = "Encontrei alguns documentos, mas nenhum parece diretamente relacionado à sua pergunta. Pode tentar ser mais específico ou usar outras palavras-chave?",
|
||||
InsufficientOverview = "Não tenho informações suficientes para fornecer uma visão geral completa do projeto. Talvez você possa fazer uma pergunta mais específica?",
|
||||
InsufficientSpecific = "Não encontrei documentação suficiente sobre esse tópico específico. Você pode tentar reformular a pergunta?",
|
||||
InsufficientDetailed = "Preciso de mais contexto técnico para responder adequadamente. Você pode ser mais específico sobre o que está procurando?",
|
||||
Generic = "Não tenho informações suficientes para responder com segurança. Pode tentar reformular a pergunta?"
|
||||
},
|
||||
["en"] = new ConfidenceFallbackMessages
|
||||
{
|
||||
NoDocuments = "I couldn't find information about this in the current project. Could you rephrase the question or be more specific?",
|
||||
NoRelevantDocuments = "I found some documents, but none seem directly related to your question. Could you try being more specific or use different keywords?",
|
||||
InsufficientOverview = "I don't have enough information to provide a complete project overview. Perhaps you could ask a more specific question?",
|
||||
InsufficientSpecific = "I didn't find sufficient documentation about this specific topic. Could you try rephrasing the question?",
|
||||
InsufficientDetailed = "I need more technical context to respond adequately. Could you be more specific about what you're looking for?",
|
||||
Generic = "I don't have enough information to respond safely. Could you try rephrasing the question?"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Thresholds de confiança para uma estratégia específica
|
||||
/// </summary>
|
||||
public class ConfidenceThresholds
|
||||
{
|
||||
/// <summary>
|
||||
/// Número mínimo de documentos encontrados
|
||||
/// </summary>
|
||||
public int MinDocuments { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Número mínimo de documentos relevantes (score >= 0.3)
|
||||
/// </summary>
|
||||
public int MinRelevantDocuments { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Número mínimo de documentos de alta qualidade (score >= 0.6)
|
||||
/// </summary>
|
||||
public int MinHighQualityDocuments { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tamanho mínimo do contexto combinado (caracteres)
|
||||
/// </summary>
|
||||
public int MinContextLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Score geral mínimo (0.0 a 1.0)
|
||||
/// </summary>
|
||||
public double MinOverallScore { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Score máximo mínimo entre os documentos encontrados
|
||||
/// </summary>
|
||||
public double MinMaxScore { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Score médio mínimo entre os documentos encontrados
|
||||
/// </summary>
|
||||
public double MinAverageScore { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mensagens de fallback quando não há confiança suficiente
|
||||
/// </summary>
|
||||
public class ConfidenceFallbackMessages
|
||||
{
|
||||
public string NoDocuments { get; set; } = "";
|
||||
public string NoRelevantDocuments { get; set; } = "";
|
||||
public string InsufficientOverview { get; set; } = "";
|
||||
public string InsufficientSpecific { get; set; } = "";
|
||||
public string InsufficientDetailed { get; set; } = "";
|
||||
public string Generic { get; set; } = "";
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,35 @@
|
||||
{
|
||||
"DomvsDatabase": {
|
||||
//"ConnectionString": "mongodb://192.168.0.82:30017/?directConnection=true",
|
||||
"ConnectionString": "mongodb://localhost:27017/?directConnection=true",
|
||||
"DatabaseName": "DomvsSites",
|
||||
"SharepointCollectionName": "SharepointSite",
|
||||
"ChatBotRHCollectionName": "ChatBotRHData",
|
||||
"ClassifierCollectionName": "ClassifierData"
|
||||
"VectorDatabase": {
|
||||
"Provider": "Qdrant",
|
||||
"MongoDB": {
|
||||
"ConnectionString": "mongodb://admin:c4rn31r0@k3sw2:27017,k3ss1:27017/?authSource=admin",
|
||||
"DatabaseName": "RAGProjects-dev-pt",
|
||||
"TextCollectionName": "Texts",
|
||||
"ProjectCollectionName": "Groups",
|
||||
"UserDataName": "UserData"
|
||||
},
|
||||
"Qdrant": {
|
||||
"Host": "192.168.0.100",
|
||||
"Port": 6334,
|
||||
"CollectionName": "texts-whats",
|
||||
"GroupsCollectionName": "projects-whats",
|
||||
"VectorSize": 384,
|
||||
"Distance": "Cosine",
|
||||
"HnswM": 16,
|
||||
"HnswEfConstruct": 200,
|
||||
"OnDisk": false
|
||||
},
|
||||
"Chroma": {
|
||||
"Host": "localhost",
|
||||
"Port": 8000,
|
||||
"CollectionName": "rag_documents"
|
||||
}
|
||||
},
|
||||
"ChatRHSettings": {
|
||||
"Url": "http://localhost:8070/",
|
||||
"Create": "/CallRH"
|
||||
"Features": {
|
||||
"UseQdrant": true,
|
||||
"UseHierarchicalRAG": true,
|
||||
"UseConfidenceAwareRAG": true,
|
||||
"EnableConfidenceCheck": false
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
|
||||
@ -16,10 +16,10 @@
|
||||
"UserDataName": "UserData"
|
||||
},
|
||||
"Qdrant": {
|
||||
"Host": "localhost",
|
||||
"Host": "192.168.0.100",
|
||||
"Port": 6334,
|
||||
"CollectionName": "texts",
|
||||
"GroupsCollectionName": "projects",
|
||||
"CollectionName": "texts-whats",
|
||||
"GroupsCollectionName": "projects-whats",
|
||||
"VectorSize": 384,
|
||||
"Distance": "Cosine",
|
||||
"HnswM": 16,
|
||||
@ -34,7 +34,60 @@
|
||||
},
|
||||
"Features": {
|
||||
"UseQdrant": true,
|
||||
"UseHierarchicalRAG": true
|
||||
"UseHierarchicalRAG": true,
|
||||
"UseConfidenceAwareRAG": true,
|
||||
"EnableConfidenceCheck": false
|
||||
},
|
||||
|
||||
"ConfidenceAware": {
|
||||
"EnableConfidenceCheck": true,
|
||||
"UseStrictMode": true,
|
||||
"ShowDebugInfo": false,
|
||||
"DefaultDomain": "Servicos",
|
||||
"Languages": {
|
||||
"DefaultLanguage": "pt",
|
||||
"AutoDetectLanguage": true
|
||||
},
|
||||
"DomainMappings": {
|
||||
"software": "TI",
|
||||
"sistema": "TI",
|
||||
"funcionário": "RH",
|
||||
"colaborador": "RH",
|
||||
"financeiro": "Financeiro",
|
||||
"contábil": "Financeiro",
|
||||
"teste": "QA",
|
||||
"qualidade": "QA"
|
||||
}
|
||||
},
|
||||
|
||||
"Confidence": {
|
||||
"Thresholds": {
|
||||
"overview": {
|
||||
"MinDocuments": 2, // reduzido para chatbot
|
||||
"MinRelevantDocuments": 1,
|
||||
"MinOverallScore": 0.25 // mais flexível
|
||||
},
|
||||
"specific": {
|
||||
"MinDocuments": 1, // bem flexível
|
||||
"MinRelevantDocuments": 1,
|
||||
"MinOverallScore": 0.3
|
||||
},
|
||||
"detailed": {
|
||||
"MinDocuments": 1, // bem flexível
|
||||
"MinMaxScore": 0.5,
|
||||
"MinRelevantDocuments": 1,
|
||||
"MinOverallScore": 0.3
|
||||
}
|
||||
},
|
||||
"FallbackMessages": {
|
||||
"pt": {
|
||||
"NoDocuments": "Desculpe, não encontrei informações sobre esse serviço. Você pode falar com nosso atendente para mais detalhes.",
|
||||
"Generic": "Não tenho informações suficientes sobre isso. Posso te conectar com um especialista?"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PromptConfiguration": {
|
||||
"Path": "Configuration/Prompts"
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"AppTenantId": "20190830-5fd4-4a72-b8fd-1c1cb35b25bc",
|
||||
|
||||
343
vvijr2kk.3vs~
Normal file
343
vvijr2kk.3vs~
Normal file
@ -0,0 +1,343 @@
|
||||
using ChatApi;
|
||||
using ChatApi.Data;
|
||||
using ChatApi.Middlewares;
|
||||
using ChatApi.Services.Crypt;
|
||||
using ChatApi.Settings;
|
||||
using ChatRAG.Contracts.VectorSearch;
|
||||
using ChatRAG.Data;
|
||||
using ChatRAG.Extensions;
|
||||
using ChatRAG.Services;
|
||||
using ChatRAG.Services.Confidence;
|
||||
using ChatRAG.Services.Contracts;
|
||||
using ChatRAG.Services.PromptConfiguration;
|
||||
using ChatRAG.Services.ResponseService;
|
||||
using ChatRAG.Services.SearchVectors;
|
||||
using ChatRAG.Services.TextServices;
|
||||
using ChatRAG.Settings;
|
||||
using ChatRAG.Settings.ChatRAG.Configuration;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.IdentityModel.JsonWebTokens;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.SemanticKernel;
|
||||
using System.Text;
|
||||
using static OllamaSharp.OllamaApiClient;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
using static System.Net.WebRequestMethods;
|
||||
|
||||
#pragma warning disable SKEXP0010 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
|
||||
// Adicionar serviço CORS
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowSpecificOrigin",
|
||||
builder =>
|
||||
{
|
||||
builder
|
||||
.WithOrigins("http://localhost:5094") // Sua origem específica
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials();
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.AddControllers();
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "apichat", Version = "v1" });
|
||||
|
||||
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
|
||||
{
|
||||
Name = "Authorization",
|
||||
Type = SecuritySchemeType.ApiKey,
|
||||
Scheme = "Bearer",
|
||||
BearerFormat = "JWT",
|
||||
In = ParameterLocation.Header,
|
||||
Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer'[space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 12345abcdef\"",
|
||||
});
|
||||
c.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
}
|
||||
},
|
||||
new string[] {}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.Configure<ConfidenceSettings>(
|
||||
builder.Configuration.GetSection("Confidence"));
|
||||
|
||||
builder.Services.Configure<ConfidenceAwareSettings>(
|
||||
builder.Configuration.GetSection("ConfidenceAware"));
|
||||
|
||||
|
||||
//builder.Services.AddScoped<IVectorSearchService, MongoVectorSearchService>();
|
||||
builder.Services.AddScoped<QdrantVectorSearchService>();
|
||||
builder.Services.AddScoped<MongoVectorSearchService>();
|
||||
builder.Services.AddScoped<ChromaVectorSearchService>();
|
||||
|
||||
builder.Services.AddVectorDatabase(builder.Configuration);
|
||||
|
||||
builder.Services.AddScoped<IVectorSearchService>(provider =>
|
||||
{
|
||||
var useQdrant = builder.Configuration["Features:UseQdrant"] == "true";
|
||||
var factory = provider.GetRequiredService<IVectorDatabaseFactory>();
|
||||
return factory.CreateVectorSearchService();
|
||||
});
|
||||
|
||||
builder.Services.AddScoped<QdrantProjectDataRepository>();
|
||||
builder.Services.AddScoped<MongoProjectDataRepository>();
|
||||
builder.Services.AddScoped<ChromaProjectDataRepository>();
|
||||
|
||||
builder.Services.AddScoped<IProjectDataRepository>(provider =>
|
||||
{
|
||||
var database = builder.Configuration["VectorDatabase:Provider"];
|
||||
if (string.IsNullOrEmpty(database))
|
||||
{
|
||||
throw new InvalidOperationException("VectorDatabase:Provider is not configured.");
|
||||
}
|
||||
else if (database.Equals("Qdrant", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return provider.GetRequiredService<QdrantProjectDataRepository>();
|
||||
}
|
||||
else if (database.Equals("MongoDB", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return provider.GetRequiredService<MongoProjectDataRepository>();
|
||||
}
|
||||
else if (database.Equals("Chroma", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return provider.GetRequiredService<ChromaProjectDataRepository>();
|
||||
}
|
||||
return provider.GetRequiredService<MongoProjectDataRepository>();
|
||||
});
|
||||
|
||||
builder.Services.AddScoped<QdrantTextDataService>();
|
||||
builder.Services.AddScoped<MongoTextDataService>();
|
||||
builder.Services.AddScoped<ChromaTextDataService>();
|
||||
|
||||
builder.Services.AddScoped<ITextDataService>(provider =>
|
||||
{
|
||||
var database = builder.Configuration["VectorDatabase:Provider"];
|
||||
if (string.IsNullOrEmpty(database))
|
||||
{
|
||||
throw new InvalidOperationException("VectorDatabase:Provider is not configured.");
|
||||
}
|
||||
else if (database.Equals("Qdrant", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return provider.GetRequiredService<QdrantTextDataService>();
|
||||
}
|
||||
else if (database.Equals("MongoDB", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return provider.GetRequiredService<MongoTextDataService>();
|
||||
}
|
||||
else if (database.Equals("Chroma", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return provider.GetRequiredService<ChromaTextDataService>();
|
||||
}
|
||||
return provider.GetRequiredService<MongoTextDataService>();
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton<ChatHistoryService>();
|
||||
builder.Services.AddScoped<TextDataRepository>();
|
||||
builder.Services.AddSingleton<TextFilter>();
|
||||
|
||||
//builder.Services.AddScoped<IResponseService, ResponseRAGService>();
|
||||
builder.Services.AddScoped<ResponseRAGService>();
|
||||
builder.Services.AddScoped<HierarchicalRAGService>();
|
||||
|
||||
builder.Services.AddScoped<IResponseService>(provider =>
|
||||
{
|
||||
var configuration = provider.GetService<IConfiguration>();
|
||||
var useHierarchical = configuration?.GetValue<bool>("Features:UseHierarchicalRAG") ?? false;
|
||||
var useConfidence = configuration?.GetValue<bool>("Features:UseConfidenceAwareRAG") ?? false;
|
||||
|
||||
return useConfidence && useHierarchical
|
||||
? provider.GetRequiredService<ConfidenceAwareRAGService>()
|
||||
: useHierarchical
|
||||
? provider.GetRequiredService<HierarchicalRAGService>()
|
||||
: provider.GetRequiredService<ResponseRAGService>();
|
||||
});
|
||||
|
||||
builder.Services.AddTransient<UserDataRepository>();
|
||||
builder.Services.AddTransient<TextData>();
|
||||
builder.Services.AddSingleton<CryptUtil>();
|
||||
|
||||
// Registrar serviços de confiança
|
||||
builder.Services.AddScoped<ConfidenceVerifier>();
|
||||
builder.Services.AddSingleton<PromptConfigurationService>();
|
||||
|
||||
// Registrar ConfidenceAwareRAGService
|
||||
builder.Services.AddScoped<ConfidenceAwareRAGService>();
|
||||
|
||||
//builder.Services.AddOllamaChatCompletion("phi3.5", new Uri("http://localhost:11435"));
|
||||
//builder.Services.AddOllamaChatCompletion("tinydolphin", new Uri("http://localhost:11435"));
|
||||
//var apiClient = new OllamaApiClient(new Uri("http://localhost:11435"), "tinydolphin");
|
||||
|
||||
//Olllama
|
||||
//Desktop
|
||||
var key = "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";
|
||||
//uilder.Services.AddOllamaChatCompletion("llama3.2", new Uri("http://localhost:11434"));
|
||||
//var model = "llama-3.3-70b-versatile";
|
||||
var model = "llama-3.1-8b-instant";
|
||||
//var model = "meta-llama/llama-guard-4-12b";
|
||||
//var url = "https://api.groq.com/openai/v1/chat/completions"; // Adicione o /v1/openai
|
||||
var url = "https://api.groq.com/openai/v1";
|
||||
builder.Services.AddOpenAIChatCompletion(model, new Uri(url), key);
|
||||
|
||||
//Notebook
|
||||
//var model = "meta-llama/Llama-3.2-3B-Instruct";
|
||||
//var url = "https://api.deepinfra.com/v1/openai"; // Adicione o /v1/openai
|
||||
//builder.Services.AddOpenAIChatCompletion(model, new Uri(url), "HedaR4yPrp9N2XSHfwdZjpZvPIxejPFK");
|
||||
|
||||
//builder.Services.AddOllamaChatCompletion("llama3.2:3b", new Uri("http://localhost:11435"));
|
||||
//builder.Services.AddOllamaChatCompletion("llama3.2:1b", new Uri("http://localhost:11435"));
|
||||
|
||||
|
||||
//builder.Services.AddOllamaChatCompletion("tinydolphin", new Uri("http://localhost:11435"));
|
||||
//builder.Services.AddOllamaChatCompletion("tinyllama", new Uri("http://localhost:11435"));
|
||||
//builder.Services.AddOllamaChatCompletion("starling-lm", new Uri("http://localhost:11435"));
|
||||
|
||||
//ServerSpace - GPT Service
|
||||
//builder.Services.AddOpenAIChatCompletion("openchat-3.5-0106", new Uri("https://gpt.serverspace.com.br/v1/chat/completions"), "tIAXVf3AkCkkpSX+PjFvktfEeSPyA1ZYam50UO3ye/qmxVZX6PIXstmJsLZXkQ39C33onFD/81mdxvhbGHm7tQ==");
|
||||
|
||||
//Ollama local server (scorpion)
|
||||
//builder.Services.AddOllamaChatCompletion("llama3.1:latest", new Uri("http://192.168.0.150:11434"));
|
||||
|
||||
//builder.Services.AddOllamaTextEmbeddingGeneration("all-minilm", new Uri("http://192.168.0.150:11434"));
|
||||
|
||||
//Desktop
|
||||
//builder.Services.AddOllamaTextEmbeddingGeneration("all-minilm", new Uri("http://localhost:11434"));
|
||||
//Notebook
|
||||
builder.Services.AddOllamaTextEmbeddingGeneration("all-minilm", new Uri("http://localhost:11435"));
|
||||
|
||||
|
||||
//builder.Services.AddOllamaChatCompletion("phi3.5", new Uri("http://localhost:11435"));
|
||||
//builder.Services.AddOpenAIChatCompletion("gpt-4o-mini", "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");
|
||||
//builder.Services.AddGoogleAIGeminiChatCompletion("gemini-1.5-flash-latest", "AIzaSyDKBMX5yW77vxJFVJVE-5VLxlQRxCepck8");
|
||||
|
||||
//Anthropic / Claude
|
||||
//builder.Services.AddAnthropicChatCompletion(
|
||||
// modelId: "claude-3-5-sonnet-latest", // ou outro modelo Claude desejado
|
||||
// apiKey: "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"
|
||||
//);
|
||||
|
||||
|
||||
builder.Services.AddKernel();
|
||||
|
||||
//builder.Services.AddKernel()
|
||||
// .AddOllamaChatCompletion("phi3", new Uri("http://localhost:11435"))
|
||||
// .AddOllamaTextEmbeddingGeneration()
|
||||
// .Build();
|
||||
|
||||
//builder.Services.AddOllamaChatCompletion("phi3.5", new Uri("http://192.168.0.150:11436"));
|
||||
|
||||
builder.Services.AddHttpClient();
|
||||
|
||||
var tenantId = builder.Configuration.GetSection("AppTenantId");
|
||||
var clientId = builder.Configuration.GetSection("AppClientID");
|
||||
|
||||
//builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
// .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
|
||||
|
||||
builder.Services.AddControllers();
|
||||
|
||||
//builder.Services.AddAuthentication(options =>
|
||||
// {
|
||||
// options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
// options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
// })
|
||||
// .AddJwtBearer(options =>
|
||||
// {
|
||||
// // Configurações anteriores...
|
||||
|
||||
// // Eventos para log e tratamento de erros
|
||||
// options.Events = new JwtBearerEvents
|
||||
// {
|
||||
// OnAuthenticationFailed = context =>
|
||||
// {
|
||||
// // Log de erros de autenticação
|
||||
// Console.WriteLine($"Erro de autenticação: {context.Exception.Message}");
|
||||
// return Task.CompletedTask;
|
||||
// },
|
||||
// OnTokenValidated = context =>
|
||||
// {
|
||||
// // Validações adicionais se necessário
|
||||
// return Task.CompletedTask;
|
||||
// }
|
||||
// };
|
||||
// });
|
||||
|
||||
builder.Services.AddSingleton<IConfigurationManager>(builder.Configuration);
|
||||
|
||||
builder.Services.Configure<IISServerOptions>(options =>
|
||||
{
|
||||
options.MaxRequestBodySize = int.MaxValue;
|
||||
});
|
||||
|
||||
builder.Services.Configure<KestrelServerOptions>(options =>
|
||||
{
|
||||
options.Limits.MaxRequestBodySize = int.MaxValue;
|
||||
});
|
||||
|
||||
builder.Services.Configure<FormOptions>(options =>
|
||||
{
|
||||
options.ValueLengthLimit = int.MaxValue;
|
||||
options.MultipartBodyLengthLimit = int.MaxValue;
|
||||
options.MultipartHeadersLengthLimit = int.MaxValue;
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
app.UseDeveloperExceptionPage(); // Isso mostra erros detalhados
|
||||
}
|
||||
|
||||
//app.UseHttpsRedirection();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
var cookieOpt = new CookieOptions()
|
||||
{
|
||||
Path = "/",
|
||||
Expires = DateTimeOffset.UtcNow.AddDays(1),
|
||||
IsEssential = true,
|
||||
HttpOnly = false,
|
||||
Secure = false,
|
||||
};
|
||||
|
||||
await next();
|
||||
});
|
||||
|
||||
app.UseMiddleware<ErrorHandlingMiddleware>();
|
||||
|
||||
app.UseCors("AllowSpecificOrigin");
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.Run();
|
||||
|
||||
#pragma warning restore SKEXP0010 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
|
||||
Loading…
Reference in New Issue
Block a user