56 lines
1.9 KiB
Python
56 lines
1.9 KiB
Python
"""Utilidades de texto para parseo de JSON embebido y construcción de schema."""
|
|
import re
|
|
from typing import Optional, Dict, Any
|
|
|
|
|
|
def extract_json_from_text(text: str) -> Optional[str]:
|
|
"""Extrae el primer bloque JSON de un texto, con o sin fence ```json.
|
|
|
|
Devuelve la cadena JSON o None si no se encuentra.
|
|
"""
|
|
# 1. Try markdown fences
|
|
match = re.search(r"```json\s*(.*?)\s*```", text, flags=re.DOTALL | re.IGNORECASE)
|
|
if match:
|
|
return _clean_json_string(match.group(1).strip())
|
|
|
|
match = re.search(r"```\s*(\{[\s\S]*?\})\s*```", text, flags=re.DOTALL)
|
|
if match:
|
|
return _clean_json_string(match.group(1).strip())
|
|
|
|
# 2. Try finding outer braces
|
|
start = text.find('{')
|
|
end = text.rfind('}')
|
|
if start != -1 and end != -1 and end > start:
|
|
candidate = text[start:end + 1].strip()
|
|
return _clean_json_string(candidate)
|
|
|
|
return None
|
|
|
|
|
|
def _clean_json_string(json_str: str) -> str:
|
|
"""Intenta limpiar errores comunes de JSON generado por LLM."""
|
|
# Eliminar comas finales en objetos/listas: , } -> } y , ] -> ]
|
|
# Nota: Esto es una heurística simple y puede fallar en strings que contienen esos patrones.
|
|
# Usamos regex conservador.
|
|
json_str = re.sub(r",\s*}", "}", json_str)
|
|
json_str = re.sub(r",\s*]", "]", json_str)
|
|
return json_str
|
|
|
|
|
|
def build_schema(data: Dict[str, Any], prefix: str = "") -> Dict[str, str]:
|
|
"""Construye un mapa ruta->tipo a partir de un dict anidado.
|
|
|
|
Para listas de dicts, usa la notación "/*/subcampo".
|
|
"""
|
|
schema: Dict[str, str] = {}
|
|
for k, v in data.items():
|
|
path = f"{prefix}/{k}" if prefix else k
|
|
if isinstance(v, dict):
|
|
schema.update(build_schema(v, path))
|
|
elif isinstance(v, list) and v and isinstance(v[0], dict):
|
|
for subkey, subval in v[0].items():
|
|
schema[f"{path}/*/{subkey}"] = type(subval).__name__
|
|
else:
|
|
schema[path] = type(v).__name__
|
|
return schema
|