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 { /// /// Serviço para configuração e carregamento de prompts por domínio e idioma /// public class PromptConfigurationService { private readonly ILogger _logger; private readonly string _configurationPath; private readonly LanguageSettings _languageSettings; private readonly CacheSettings _cacheSettings; private Dictionary _domainConfigs = new(); private BasePromptConfig _baseConfig = new(); private readonly Dictionary _fileLastModified = new(); private readonly object _lockObject = new object(); public PromptConfigurationService( ILogger logger, IConfiguration configuration, IOptions 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); } /// /// Obtém prompts configurados para um domínio e idioma específicos /// 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) }; } /// /// Detecta o domínio baseado na pergunta e descrição do projeto /// public string? DetectDomain(string question, string? projectDescription = null) { var content = $"{question} {projectDescription}".ToLower(); var domainScores = new Dictionary(); 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; } /// /// Detecta o idioma da pergunta /// public string DetectLanguage(string question) { if (string.IsNullOrWhiteSpace(question)) return _languageSettings.DefaultLanguage; var languageScores = new Dictionary(); 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; } /// /// Lista domínios disponíveis /// public List GetAvailableDomains() { return _domainConfigs.Keys.ToList(); } /// /// Lista idiomas suportados /// public List GetSupportedLanguages() { return _languageSettings.SupportedLanguages; } /// /// Força recarregamento das configurações /// 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(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(); foreach (var file in domainFiles) { try { var json = File.ReadAllText(file); var config = JsonSerializer.Deserialize(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 { 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 { ["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> { ["pt"] = new Dictionary { ["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 { ["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> { ["pt"] = new Dictionary { ["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 { ["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> { ["pt"] = new Dictionary { ["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 { ["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> { ["pt"] = new Dictionary { ["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 { ["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> { ["pt"] = new Dictionary { ["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 { ["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 /// 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"); } } } /// } // === 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> Prompts { get; set; } = new(); } public class DomainPromptConfig { public string Name { get; set; } = ""; public string Description { get; set; } = ""; public List Keywords { get; set; } = new(); public List Concepts { get; set; } = new(); public Dictionary> Prompts { get; set; } = new(); } }