Spaces:
Sleeping
Sleeping
| """ | |
| rag_corrector.py | |
| ββββββββββββββββ | |
| NΓΊcleo del sistema RAG. | |
| 1. Detecta posibles errores HTR y grafΓas modernas en el texto de entrada. | |
| 2. Recupera ejemplos similares del vector store (few-shot dinΓ‘mico). | |
| 3. Construye el prompt con reglas, ejemplos y alertas. | |
| 4. Llama a GPT-4o y devuelve el texto corregido + trazabilidad. | |
| Uso: | |
| from rag_corrector import RAGCorrector | |
| corrector = RAGCorrector(vector_store) | |
| result = corrector.correct("texto htr aqui") | |
| print(result["corrected"]) | |
| print(result["prompt"]) # para depuraciΓ³n | |
| """ | |
| import os | |
| from typing import List, Dict, Tuple | |
| from openai import OpenAI | |
| from dotenv import load_dotenv | |
| from knowledge_base import HTR_ERROR_PATTERNS, GRAFIA_PATTERNS | |
| load_dotenv() | |
| MODEL = os.getenv("OPENAI_MODEL", "gpt-4o") | |
| TOP_K = int(os.getenv("TOP_K", 5)) | |
| SYSTEM_PROMPT = """Eres un corrector especializado en documentos notariales y judiciales \ | |
| espaΓ±oles del siglo XVI (castellano antiguo). | |
| Tu ΓNICA tarea es corregir los errores introducidos por el proceso automΓ‘tico de \ | |
| reconocimiento de texto manuscrito (HTR). NO debes modernizar el texto bajo ninguna \ | |
| circunstancia. | |
| REGLAS ABSOLUTAS β incumplirlas invalida la correcciΓ³n: | |
| 1. Conserva SIEMPRE las grafΓas propias del s.XVI: | |
| fizo, fazer, hazer, merΓ§ed, vezino, mesmo, Γ§ibdad, escriuano, | |
| dho (=dicho), qΜ (=que), nro (=nuestro), vn/vna, etc. | |
| 2. NO conviertas fβh inicial latina (fizo β hizo, fazer β hacer). | |
| 3. Conserva abreviaturas y tildes voladas (qΜ, nro, dho, sr). | |
| 4. Corrige SOLO lo que claramente sea un error HTR (grafema confundido visualmente). | |
| 5. Si no estΓ‘s seguro de si algo es error HTR o forma s.XVI vΓ‘lida β conserva el original. | |
| 6. Responde ΓNICAMENTE con el texto corregido. Sin explicaciones, sin comillas.""" | |
| class RAGCorrector: | |
| def __init__(self, vector_store): | |
| self.vs = vector_store | |
| #self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) | |
| self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL", "https://api.x.ai/v1"),) | |
| # ββ API pΓΊblica ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def correct(self, htr_text: str, top_k: int = TOP_K, model: str = None) -> Dict: | |
| """ | |
| Corrige un texto HTR usando RAG. | |
| Retorna dict con: | |
| corrected : str β texto corregido | |
| prompt : str β prompt completo enviado al LLM | |
| retrieved : list β documentos recuperados del vector store | |
| htr_errors : list β patrones HTR detectados | |
| grafia_warns : list β grafΓas modernas detectadas (alertas) | |
| model : str β modelo usado | |
| """ | |
| retrieved = self.vs.retrieve(htr_text, k=top_k) | |
| htr_errors = self._detect_htr_errors(htr_text) | |
| grafia_warns = self._detect_grafias(htr_text) | |
| prompt = self._build_prompt(htr_text, retrieved, htr_errors, grafia_warns) | |
| corrected = self._call_llm(prompt, model=model or MODEL) | |
| return { | |
| "corrected": corrected, | |
| "prompt": prompt, | |
| "retrieved": retrieved, | |
| "htr_errors": htr_errors, | |
| "grafia_warns": grafia_warns, | |
| "model": model or MODEL, | |
| } | |
| # ββ DetecciΓ³n de patrones ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _detect_htr_errors(self, text: str) -> List[Dict]: | |
| found = [] | |
| for p in HTR_ERROR_PATTERNS: | |
| if p["htr"] in text: | |
| found.append(p) | |
| return found | |
| def _detect_grafias(self, text: str) -> List[Dict]: | |
| """Detecta formas modernas que NO deberΓan modernizarse.""" | |
| found = [] | |
| lower = text.lower() | |
| for p in GRAFIA_PATTERNS: | |
| if p["modern"].lower() in lower: | |
| found.append(p) | |
| return found | |
| # ββ Constructor de prompt ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _build_prompt( | |
| self, | |
| htr_text: str, | |
| retrieved: List[Dict], | |
| htr_errors: List[Dict], | |
| grafia_warns: List[Dict], | |
| ) -> str: | |
| sections = [] | |
| # Few-shot dinΓ‘mico: ejemplos recuperados | |
| if retrieved: | |
| examples = [] | |
| for i, doc in enumerate(retrieved, 1): | |
| corr = "; ".join(doc["corrections"]) if doc["corrections"] else "β" | |
| examples.append( | |
| f"Ejemplo {i} [{doc['type']}, {doc['region']}, {doc['date']}]" | |
| f"caligrafia: {doc['caligrafia']}" | |
| f" (similitud={doc['score']}):\n" | |
| f" HTR: \"{doc['htr']}\"\n" | |
| f" GT: \"{doc['gt']}\"\n" | |
| f" Correcciones aplicadas: {corr}" | |
| ) | |
| sections.append( | |
| "EJEMPLOS DEL CORPUS (similares al texto a corregir):\n" | |
| + "\n\n".join(examples) | |
| ) | |
| # Alertas de patrones HTR detectados | |
| if htr_errors: | |
| hints = "\n".join( | |
| f" β’ '{p['htr']}' puede ser '{p['gt']}': {p['context']} (ej: {p['example']})" | |
| for p in htr_errors | |
| ) | |
| sections.append(f"POSIBLES ERRORES HTR DETECTADOS EN ESTE TEXTO:\n{hints}") | |
| # Alertas de grafΓas modernas | |
| if grafia_warns: | |
| warns = "\n".join( | |
| f" β’ '{p['modern']}' β mantener como '{p['ancient']}': {p['rule']}" | |
| for p in grafia_warns | |
| ) | |
| sections.append( | |
| f"ALERTA β GRAFΓAS QUE NO DEBEN MODERNIZARSE:\n{warns}" | |
| ) | |
| context_block = "\n\n".join(sections) | |
| return ( | |
| f"{context_block}\n\n" | |
| f"TEXTO HTR A CORREGIR:\n\"{htr_text}\"" | |
| if context_block | |
| else f"TEXTO HTR A CORREGIR:\n\"{htr_text}\"" | |
| ) | |
| # ββ Llamada al LLM βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _call_llm(self, user_prompt: str, model: str = MODEL) -> str: | |
| response = self.client.chat.completions.create( | |
| model=model, # usa el modelo que llega, no el de .env | |
| temperature=0.1, # baja temperatura: reproducibilidad | |
| max_tokens=1024, | |
| messages=[ | |
| {"role": "system", "content": SYSTEM_PROMPT}, | |
| {"role": "user", "content": user_prompt}, | |
| ], | |
| ) | |
| return response.choices[0].message.content.strip().strip('"').strip("'") | |