NSF-RAG-Codex / rag_corrector.py
Alexander Sanchez
added mixtral-8x7b
c64e5be
"""
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("'")