"""Schema Loader - Carrega e injeta schemas JSON nos system prompts.""" import os import json import logging from pathlib import Path from typing import Dict, Optional logger = logging.getLogger(__name__) class SchemaLoader: """Carrega schemas JSON e injeta em system prompts.""" # Mapeamento especialista -> arquivo schema SCHEMA_MAP = { "metadados": "metadados_schema.json", "segmentacao": "segmentacao_schema.json", "transcricao": "transcricao_schema.json", "contexto": "contexto_schema.json", "fundamentacao": "fundamentacao_schema.json", "decisao": "decisao_schema.json", "arquivo": "arquivo_schema.json", "relatorio": "relatorio_schema.json", "auditoria": "auditoria_schema.json", } def __init__(self, schemas_dir: Optional[str] = None): """ Inicializa SchemaLoader. Args: schemas_dir: Diretório dos schemas (padrão: prompts/schemas/) """ if schemas_dir is None: # Tenta encontrar o diretório automaticamente base_dir = Path(__file__).parent.parent schemas_dir = base_dir / "prompts" / "schemas" self.schemas_dir = Path(schemas_dir) if not self.schemas_dir.exists(): logger.warning(f"⚠️ Diretório de schemas não encontrado: {self.schemas_dir}") else: logger.info(f"✅ SchemaLoader inicializado: {self.schemas_dir}") # Cache de schemas carregados self._cache: Dict[str, Dict] = {} def load_schema(self, specialist_name: str) -> Optional[Dict]: """ Carrega schema JSON de um especialista. Args: specialist_name: Nome do especialista (ex: "decisao", "metadados") Returns: Dict com schema JSON ou None se não encontrado """ # Verifica cache if specialist_name in self._cache: return self._cache[specialist_name] # Busca arquivo schema schema_filename = self.SCHEMA_MAP.get(specialist_name.lower()) if not schema_filename: logger.warning(f"⚠️ Schema não mapeado para especialista: {specialist_name}") return None schema_path = self.schemas_dir / schema_filename if not schema_path.exists(): logger.warning(f"⚠️ Arquivo schema não encontrado: {schema_path}") return None try: with open(schema_path, 'r', encoding='utf-8') as f: schema = json.load(f) # Armazena no cache self._cache[specialist_name] = schema logger.debug(f"✅ Schema carregado: {specialist_name}") return schema except Exception as e: logger.error(f"❌ Erro ao carregar schema {schema_path}: {e}") return None def inject_schema_in_prompt(self, system_prompt: str, specialist_name: str, format_style: str = "json") -> str: """ Injeta schema JSON no system prompt. Args: system_prompt: Prompt original do sistema specialist_name: Nome do especialista format_style: Estilo de formatação ("json", "markdown", "compact") Returns: System prompt com schema injetado """ schema = self.load_schema(specialist_name) if not schema: logger.warning(f"⚠️ Schema não disponível para {specialist_name}, retornando prompt original") return system_prompt # Formata schema conforme estilo if format_style == "json": schema_text = json.dumps(schema, indent=2, ensure_ascii=False) elif format_style == "compact": schema_text = json.dumps(schema, ensure_ascii=False) elif format_style == "markdown": schema_text = self._format_schema_markdown(schema) else: schema_text = json.dumps(schema, indent=2, ensure_ascii=False) # Monta prompt final com schema enhanced_prompt = f"""{system_prompt} # JSON SCHEMA OBRIGATÓRIO Você DEVE retornar sua resposta seguindo EXATAMENTE este JSON Schema: ```json {schema_text} ``` IMPORTANTE: - Retorne APENAS JSON válido - Siga TODOS os campos required do schema - Respeite os tipos de dados (string, integer, array, object) - Valide enums e patterns quando especificados - NÃO adicione comentários ou texto fora do JSON - NÃO invente campos que não estão no schema """ return enhanced_prompt def _format_schema_markdown(self, schema: Dict) -> str: """Formata schema em Markdown legível.""" lines = [] if 'title' in schema: lines.append(f"## {schema['title']}") if 'description' in schema: lines.append(f"{schema['description']}\n") if 'properties' in schema: lines.append("### Campos:") for field, props in schema['properties'].items(): field_type = props.get('type', 'any') desc = props.get('description', '') required = '(obrigatório)' if field in schema.get('required', []) else '(opcional)' lines.append(f"- **{field}** ({field_type}) {required}: {desc}") return '\n'.join(lines) def get_available_specialists(self) -> list: """Retorna lista de especialistas com schemas disponíveis.""" return list(self.SCHEMA_MAP.keys()) def validate_response(self, response: str, specialist_name: str) -> tuple: """ Valida resposta JSON contra schema. Args: response: Resposta JSON (string) specialist_name: Nome do especialista Returns: (is_valid: bool, errors: list) """ try: from jsonschema import validate, ValidationError except ImportError: logger.warning("jsonschema não instalado, validação desabilitada") return True, [] schema = self.load_schema(specialist_name) if not schema: return True, ["Schema não encontrado"] try: data = json.loads(response) validate(instance=data, schema=schema) return True, [] except json.JSONDecodeError as e: return False, [f"JSON inválido: {e}"] except ValidationError as e: return False, [f"Validação falhou: {e.message}"] except Exception as e: return False, [f"Erro na validação: {e}"] # ============================================================================ # SINGLETON GLOBAL (Opcional) # ============================================================================ _global_loader: Optional[SchemaLoader] = None def get_schema_loader() -> SchemaLoader: """Retorna instância global do SchemaLoader (singleton).""" global _global_loader if _global_loader is None: _global_loader = SchemaLoader() return _global_loader