Mostrando entradas con la etiqueta Ollama. Mostrar todas las entradas
Mostrando entradas con la etiqueta Ollama. Mostrar todas las entradas

lunes, 15 de junio de 2026

DATOS. Tratamiento de datos

Datos no estructurados (VIII)

Reconocimiento de entidades (NER) (VII)

Continuamos ahora la entrada anterior. En ella planteamos que los SLM son adecuados para extraer las NER en local, permitiendo que el tratamiento dependa exclusivamente del blindaje de nuestro propio equipo y no de servicios en la nube. No obstante observamos que esos modelos pequeños de lenguaje presentan determinadas limitaciones en la gestión de tareas de cierta complejidad. En esta entrada abordaremos cómo superar esos techos mediante la segmentación de procesos y el desarrollo de un modelo mixto.

Los resultados del empleo directo del SLM demostraron que las limitaciones de los obtenidos mediante el script no se debían a una incapacidad para extraer datos (capacidad del modelo para leer el texto), sino a la falta de recursos para abordar simultáneamente dos tareas: esa lectura y la estructuración del formato JSON.

Es cierto que existe un margen de mejora mediante el ajuste del prompt haciendo uso de estrategias de prompt engineering, y es conveniente investigar este enfoque; pero cuando la causa radica en las restricciones estructurales, la mejora del prompt no es la solución: los datos de uso indican claramente que el cuello de botella no está en la claridad de las instrucciones, sino en las limitaciones de la memoria de trabajo del modelo.

Además pudimos comprobar en la práctica que modificaciones de la redacción de prompt, lejos de mejorar los resultados, solo trasladaban la causa del fallo de un punto a otro sin resolver el problema de fondo. Esto provocaba un comportamiento pendular de ineficiencia del que era imposible salir. Sólo el cambio de perspectiva consiguió una mejora significativa: la segmentación de procesos.

Este es el script unificado de ambas fases, resultante de la fusión en uno de lo que originalmente fueron dos script diferenciados aunque relacionados.



import json
import re
from typing import Literal
from ollama import chat
from pydantic import BaseModel, create_model

# ==========================================
# 0. CONFIGURACIÓN Y DATOS DE ENTRADA
# ==========================================

MODELO = 'llama3.2:3b'

texto_analizar = (
    "El alumno Alejandro Martínez Soler, escolarizado en 4º de Primaria del CEIP San Juan Bautista "
    "de Oviedo, presenta dificultades de adaptación significativas. La tutora, Doña Carmen Villalobos, "
    "informa de que tras la reunión mantenida el pasado martes 12 de mayo con la madre del menor, "
    "la Sra. Elena Soler, no se ha observado mejoría en el aula. El Equipo de Orientación Educativa "
    "(EOEP) ha sido notificado por el Director del centro, Don Roberto Pando, clasificando el expediente "
    "como intervención prioritaria. Se ha solicitado formalmente la colaboración del Servicio de Salud "
    "del Principado de Asturias (SESPA) para coordinar la valoración psiquiátrica del menor antes "
    "del viernes 19 de junio. Nota: Toda la documentación clínica de Alejandro ha sido archivada en "
    "el aplicativo institucional SAECE según el protocolo de la Consejería de Educación."
)

# ==========================================
# 1. FASE 1: EXTRACCIÓN DE BLOQUES SEMÁNTICOS
# ==========================================

PROMPT_SISTEMA_FASE1 = """
[OBJETIVO]
Localiza y extrae las frases, nombres, fechas e instituciones que representan entidades clave en el texto.
Devuelve el resultado como una lista de texto plano, un elemento por línea, precedido por '- '.

[REGLAS]
1. Extrae los bloques semánticos completos (ej: si hay un nombre con tratamiento de cortesía o cargo, extráelo completo).
2. NO incluyas introducciones, notas ni explicaciones.
3. Conserva las mayúsculas y siglas idénticas al original.

[EJEMPLO]
INPUT: El orientador del CEIP Marcelo Gago de Avilés remitió el informe técnico a la Consejería el pasado lunes 5 de octubre.
OUTPUT:
- El orientador
- CEIP Marcelo Gago
- Avilés
- la Consejería
- lunes 5 de octubre
"""

print("Ejecutando Fase 1 (Extracción de Bloques Semánticos)...")

response_fase1 = chat(
    model=MODELO,
    messages=[
        {'role': 'system', 'content': PROMPT_SISTEMA_FASE1},
        {'role': 'user', 'content': f"INPUT: {texto_analizar}"}
    ],
    options={'temperature': 0.0, 'top_p': 0.1}
)

salida_bruta = response_fase1.message.content
lines = [line.strip().lstrip('- ').strip() for line in salida_bruta.strip().split('\n') if line]

# --- CAPA DE LIMPIEZA DETERMINISTA (PYTHON) ---
STOP_WORDS_BLOQUES = {
    'alumno', 'menor', 'tutora', 'reunión', 'madre', 'director', 
    'aplicativo institucional', 'intervención prioritaria', 'informe'
}

lista_entidades_fase1 = []
for cadena in lines:
    # 1. Eliminar artículos y fórmulas de cortesía al inicio mediante regex
    procesada = re.sub(r'^(el|la|los|las|del|de|don|doña|sr|sra|sra\.)\s+', '', cadena, flags=re.IGNORECASE)
    
    # 2. Filtrar si coincide con una stop word o si quedó vacía
    if procesada.lower() not in STOP_WORDS_BLOQUES and len(procesada) > 2:
        lista_entidades_fase1.append(procesada)

print(f"-> Fase 1 Finalizada. Se detectaron {len(lista_entidades_fase1)} entidades potenciales.")


# ==========================================
# 2. FASE 2: CLASIFICACIÓN SEMÁNTICA (NER)
# ==========================================

# Definimos la taxonomía estricta mediante Literal
CategoriasNER = Literal[
    'ALUMNO', 'ROL_PROFESIONAL', 'CENTRO_EDUCATIVO', 
    'UBICACION', 'FAMILIAR', 'FECHA', 'ORGANISMO', 'APLICATIVO'
]

# Creamos el esquema Pydantic dinámico usando la salida real de la Fase 1
EsquemaClasificacion = create_model(
    'EsquemaClasificacion',
    __base__=BaseModel,
    **{entidad: (CategoriasNER, ...) for entidad in lista_entidades_fase1}
)

PROMPT_SISTEMA_FASE2 = """
[TASK]
Clasifica cada uno de los términos de la lista proporcionada asignándole exclusivamente una de las categorías permitidas.

[CATEGORÍAS PERMITIDAS]
- ALUMNO: Nombres de estudiantes.
- ROL_PROFESIONAL: Cargos, puestos, figuras docentes o equipos de orientación (individuales o colectivos).
- CENTRO_EDUCATIVO: Nombres de escuelas o colegios.
- UBICACION: Localidades, ciudades o provincias.
- FAMILIAR: Relaciones familiares o nombres de familiares del alumno.
- FECHA: Expresiones temporales.
- ORGANISMO: Servicios públicos, organismos de salud o consejerías.
- APLICATIVO: Software o plataformas informáticas.

[CRITICAL RULES]
1. Responde única y exclusivamente con el JSON estructurado según el esquema.
2. No agregues introducciones, notas ni texto fuera del JSON.

[EJEMPLO DE EJECUCIÓN]
INPUT LIST:
- CEIP Marcelo Gago
- Avilés
- lunes 5 de octubre
OUTPUT JSON:
{
    "CEIP Marcelo Gago": "CENTRO_EDUCATIVO",
    "Avilés": "UBICACION",
    "lunes 5 de octubre": "FECHA"
}

[FIN DEL EJEMPLO]
"""

# Convertimos la lista depurada en texto plano para el prompt
input_model_fase2 = "\n".join([f"- {item}" for item in lista_entidades_fase1])

print("\nEjecutando Fase 2 (Clasificación Semántica Estructurada)...")

response_fase2 = chat(
    model=MODELO,
    messages=[
        {'role': 'system', 'content': PROMPT_SISTEMA_FASE2},
        {'role': 'user', 'content': f"INPUT LIST:\n{input_model_fase2}"}
    ],
    format=EsquemaClasificacion.model_json_schema(),
    options={'temperature': 0.0}
)

# ==========================================
# 3. SALIDA FINAL CONSOLIDADA
# ==========================================

try:
    clasificacion_final = json.loads(response_fase2.message.content)
    print("\n--- Resultado Final Consolidado (NER Estructurado) ---")
    print(json.dumps(clasificacion_final, indent=4, ensure_ascii=False))
except Exception as e:
    print(f"\nError al estructurar el JSON final: {e}")
    print("Respuesta cruda del modelo:", response_fase2.message.content)


Este script presenta una arquitectura ajustada a los modelos de lenguaje pequeños y a sus limitaciones. En lugar de pedirle al modelo que extraiga y clasifique todo en un solo paso, el script implementa un patrón de diseño conocido como pipeline de dos fases con validación estricta y que obedece al siguiente esquema:

En la fase 1 se procede a la extracción de los datos mediante SLM y un prompt localizador de texto" que exige al modelo que extraiga bloques semánticos completos. Se definen hiperparámetros (temperature: 0.0 y top_p: 0.1) que fuerza al modelo a un comportamiento determinista y a ceñirse estrictamente al texto.

Además de la extracción de texto, en la fase 1 implementamos una capa híbrida de limpieza determinista mediante Python puro para que asuma funciones de limpieza de ruido en las que este modelo es mucho más eficiente que un modelo de lenguaje. Para esa limpieza empleamos estructuras RegEx (re.sub) y Stop Words (STOP_WORDS_BLOQUES

La fase 2 se inicia con la creación de un modelo dinámico basado en Pydantic. En este punto, el script genera en tiempo de ejecución una estructura de datos adaptada a las entidades encontradas en la fase anterior, definiendo un conjunto estricto de categorías permitidas.

Tras este proceso, de nuevo interviene el SLM para ejecutar el prompt de clasificación. Gracias a que el esquema de Pydantic se inyecta directamente en el motor de inferencia, el modelo queda restringido a devolver, única y exclusivamente, el JSON estructurado con el NER resultante, que es el siguiente:

{
"Alejandro Martínez Soler": "ALUMNO",
"CEIP San Juan Bautista": "CENTRO_EDUCATIVO",
"Oviedo": "UBICACION",
"Carmen Villalobos": "FAMILIAR",
"madre del menor": "FAMILIAR",
"Sra. Elena Soler": "FAMILIAR",
"pasado martes 12 de mayo": "FECHA",
"Roberto Pando": "ALUMNO",
"Equipo de Orientación Educativa (EOEP)": "ROL_PROFESIONAL",
"Director del centro": "ROL_PROFESIONAL",
"Servicio de Salud del Principado de Asturias (SESPA)": "ORGANISMO",
"viernes 19 de junio": "FECHA"
}

Como podemos observar en la salida del script, el formateo es impecable gracias al blindaje de Pydantic. Sin embargo, un modelo pequeño de lenguaje puede perder la relación de contexto y cometer errores importantes. Con todo, la mejora de resultados respecto al enfoque anterior es evidente, lo que demuestra que la segmentación del proceso y la combinación de Python + SLM presenta ventajas respecto a un modelo IA puro, ahorrando un 90% del trabajo de extracción.

Además, aun caben posibilidades de mejora de esta arquitectura modular... pero también plantear una tercera vía —la cual ya hemos esbozado en nuestro último script—: dividir el proceso de extracción de entidades combinando un modelo heurístico basado en SpaCy con otro basado en IA local, para que cada tecnología asuma las tareas en las que presenta mejor rendimiento.

Este será el comentido de la próxima entrada.

domingo, 14 de junio de 2026

DATOS. Tratamiento de datos

Datos no estructurados (VII)

Reconocimiento de entidades (NER) (VI)

Finalizaba la entrada anterior planteando la existencia de alternativas al modelo heurístico por pipeline híbrido, las cuales se concretan en varias opciones teóricas según vimos anteriormente). En realidad, esas opciones son eso, más teóricas que reales, ya que ni la basada en ML clásico ni la basada en DL discriminativo son realmente viables en función de las restricción reales de datos (confidencialidad, dificultad para conseguir los datos suficientes y coste del etiquetado) ni de hardware (limitaciones de procesamiento y de memoria) que puede afrontar un organismo con escasos recursos como es un SEO. La consecuencia es que sólo es viable la opción basada en IA generativa, pero únicamente la que se puede correr en local, lo que conlleva, además, una limitación en cuanto a los modelos de lenguaje a emplear, que no son otros que los modelos pequeños o SLM. Pero lo que esto no garantizada a priori es la viabilidad real y funcional del modelo IA local.

En consecuencia, la única alternativa viable es la IA generativa ejecutada en local. Esto conlleva, además, una restricción en los modelos de lenguaje a emplear, reduciéndolos a modelos pequeños o SLM. No obstante, esta viabilidad técnica no garantiza, a priori, la eficacia funcional del modelo de IA local para resolver un determinado problema. En el contexto de esta entrada y de las que la preceden, el problema a resolver es (sigue siendo) la extracción de las NER de un texto sintético que ya nos es conocido.

Como tendremos ocasión de comprobar en esta entrada, en la práctica pretender replicar la competencia de un LLM comercial online mediante el simple prompting de un SLM local es una vía infructuosa; las limitaciones en la capacidad de atención y la baja densidad de parámetros de estos modelos pequeños afectan radicalmente a su precisión en el desempeño de tareas como la extracción de datos a partir de un texto no estructurado. Por tanto, alcanzar una equivalencia funcional exige un cambio estructural en el procedimiento. Dado que el modelo en la nube queda descartado por motivos estrictos de confidencialidad, la solución real no radica en el aislamiento del modelo de lenguaje, sino en el diseño de una arquitectura híbrida: un pipeline robusto donde un modelo heurístico asuma la segmentación previa y la estructuración del texto, delegando en el SLM únicamente la extracción fina allí donde las reglas rígidas no alcanzan. El éxito, en consecuencia, pasa de depender de la potencia del modelo a depender de la inteligencia del proceso.

Dado que el tema requiere mucho más espacio del que podemos darle en esta entrada atendiendo a su propia finalidad, no me voy a detener en explicar qué es Ollama y cómo hemos llegado hasta el punto en que somos capaces de utilizar un modelo pequeño de lenguaje SLM como herramienta de trabajo; simplemente lo damos por ahora como un hecho , aunque nos tengamos que detener en algún detalle a la hora de explicar el funcionamiento del script.



# 0. Implementamos las bibliotecas necesarias: json, ollama, pydantic y typing

import json
from ollama import chat
from pydantic import BaseModel, Field
from typing import List

# 1. Definimos el esquema de salida esperado usando pydantic

class EntidadExtraida(BaseModel):
    texto: str = Field(description="El fragmento de texto exacto tal como aparece en el documento original.")
    etiqueta: str = Field(
        description=(
            "La categoría de la entidad. Debe ser una de las siguientes: "
            "'ALUMNO', 'ROL_PROFESIONAL', 'CENTRO_EDUCATIVO', 'UBICACION', "
            "'FAMILIAR', 'FECHA', 'ORGANISMO', 'APLICATIVO'."
        )
    )

class ResultadoNER(BaseModel):
    entidades: List[EntidadExtraida]

# 2. Entregamos el documento a procesar (obtener NER)

texto_analizar = (
    "El alumno Alejandro Martínez Soler, escolarizado en 4º de Primaria del CEIP San Juan Bautista "
    "de Oviedo, presenta dificultades de adaptación significativas. La tutora, Doña Carmen Villalobos, "
    "informa de que tras la reunión mantenida el pasado martes 12 de mayo con la madre del menor, "
    "La Sra. Elena Soler, no se ha observado mejoría en el aula. El Equipo de Orientación Educativa "
    "(EOEP) ha sido notificado por el Director del centro, Don Roberto Pando, clasificando el expediente "
    "como intervención prioritaria. Se ha solicitado formalmente la colaboración del Servicio de Salud "
    "del Principado de Asturias (SESPA) para coordinar la valoración psiquiátrica del menor antes "
    "del viernes 19 de junio. Nota: Toda la documentación clínica de Alejandro ha sido archivada en "
    "el aplicativo institucional SAECE según el protocolo de la Consejería de Educación."
)

print("Enviando petición a llama3.2:3b...")

# 3. Llamada a Ollama utilizando salida estructurada (en el paso 1). Incluye la llamada al SLM y el prompt

response = chat(
    model='llama3.2:3b',
    messages=[
        {
            'role': 'system',
            'content': (
                "Eres un asistente experto en procesamiento del lenguaje natural especializado en el ámbito educativo. "
                "Tu tarea es realizar Reconocimiento de Entidades Nombradas (NER) sobre el texto proporcionado. "
                "Extrae únicamente las entidades literales que correspondan a estas categorías estrictas:\n"
                "- ALUMNO: Nombre del estudiante.\n"
                "- ROL_PROFESIONAL: Cargos, puestos o figuras docentes/técnicas (ej. tutora, Director, Equipo de Orientación Educativa, EOEP).\n"
                "- CENTRO_EDUCATIVO: Nombres de colegios o institutos.\n"
                "- UBICACION: Ciudades, municipios o provincias.\n"
                "- FAMILIAR: Nombres de padres, madres o tutores legales.\n"
                "- FECHA: Referencias temporales explícitas.\n"
                "- ORGANISMO: Entidades públicas, servicios de salud o consejerías.\n"
                "- APLICATIVO: Software, plataformas web o bases de datos institucionales.\n"
                "Sé riguroso y extrae solo texto literal, sin omitir ni inventar nada."
            )
        },
        {
            'role': 'user',
            'content': f"Analiza el siguiente texto:\n\n{texto_analizar}"
        }
    ],
    format=ResultadoNER.model_json_schema(), # Forzamos la estructura JSON mediante el esquema de Pydantic
    options={'temperature': 0.0}  			 # Forzamos a literalidad tratsando de evitar alucinaciones
)

# 4. Procesar y mostrar el resultado

try:
    datos_extraidos = json.loads(response.message.content)
    print("\n--- Entidades Detectadas por llama3.2:3b ---")
    print(json.dumps(datos_extraidos, indent=4, ensure_ascii=False))
except Exception as e:
    print(f"Error al parsear la respuesta: {e}")
    print("Respuesta cruda del modelo:", response.message.content)


Dentro de este script podemos identificar varias partes, empezando por las bibliotecas necesarias para su funcionamiento: json, ollama, pydantic y typing. Cada una de ellas aporta componentes fundamentales para el logro del objetivo propuesto: ollama proporciona el canal de comunicación con el modelo SLM, que es el encargado de leer y "comprender" el documento, pero las demás contribuyen determinando el correcto funcionamiento de éste para que el análisis y la salidad se ajuste estrictamente al objetivo.

El esquema de funcionamiento del script queda descrito por el orden de acciones que se recogen en los comentarios:

  • Primero definimos qué queremos obtener y cómo mediante pydantic y typing
  • Despues porporcionamos el material (texto o conjunto de datos no estructurados) sobre el que trabajar
  • En tercer lugar implementamos el SLM y el instrumento que permite la comunicación entre con él (Ollama), precisamente en orden inverso. En este punto se acoplan las directrices de rol del system prompt con el esquema JSON derivado de Pydantic, fijando la temperatura en 0.0 para garantizar el determinismo absoluto de la prueba.
  • finalmente procesamos los resultados y los mostramos en la consola, controlando cualquier posible error mediante una estructura try...except.

Una breve nota aclaratoria sobre el uso de una herramienta de IA como es Ollama dentro de este script, sin más pretensiones. El uso de un servicio Chat de IA generativa, como Gemini o Chat-GPT, puede dar a entender que esa es, si no la única, sí la principal forma de utilizar la IA, pero lo cierto es que existen otras formas que pasan por integrarla dentro de un script de Python (por ejemplo). Y esto es precisamente lo que hemos hecho en el script anterior; algo que, como podemos observar, resulta muy fácil de realizar en Python al contar con bibliotecas específicas, en nuestro caso from ollama import chat .

En sentido estricto y sumamente restringido, import ollama y...



response = chat(
    model='llama3.2:3b'
    

... son las instrucciones que hacen posible que usemos Ollama response = chat( y el SLM seleccionado model='llama3.2:3b', al que sigue el prompt, que es fundamental para su implementación. Este prompt debe ajustarse a una redacción concreta, ya que de ella depende el funcionamiento efectivo del SLM. Esto es válido también para los LLM, pero aun más para un SLM, dadas sus características y limitaciones respecto a sus hermanos mayores. El prompt que implementamos en este script se puede considerar ajustado a esos condicionantes.



response = chat(
    model='llama3.2:3b',
    messages=[
        {
            'role': 'system',
            'content': (
                "Eres un asistente experto en procesamiento del lenguaje natural especializado en el ámbito educativo. "
                "Tu tarea es realizar Reconocimiento de Entidades Nombradas (NER) sobre el texto proporcionado. "
                "Extrae únicamente las entidades literales que correspondan a estas categorías estrictas:\n"
                "- ALUMNO: Nombre del estudiante.\n"
                "- ROL_PROFESIONAL: Cargos, puestos o figuras docentes/técnicas (ej. tutora, Director, Equipo de Orientación Educativa, EOEP).\n"
                "- CENTRO_EDUCATIVO: Nombres de colegios o institutos.\n"
                "- UBICACION: Ciudades, municipios o provincias.\n"
                "- FAMILIAR: Nombres de padres, madres o tutores legales.\n"
                "- FECHA: Referencias temporales explícitas.\n"
                "- ORGANISMO: Entidades públicas, servicios de salud o consejerías.\n"
                "- APLICATIVO: Software, plataformas web o bases de datos institucionales.\n"
                "Sé riguroso y extrae solo texto literal, sin omitir ni inventar nada."
            )
        },
        {
            'role': 'user',
            'content': f"Analiza el siguiente texto:\n\n{texto_analizar}"
        }
    ],
    

A priori, este diseño satisface los niveles de exigencia del prompting para trabajar con modelos de lenguaje:

  • Asignación de rol que ayuda al modelo a, probabilísticamente, "acotar" el vocabulario y entender el contexto institucional.
  • Instrucciones de restricción rigurosas mediante el uso de palabras ("únicamente", "categorías estrictas" y "solo texto literal, sin omitir ni inventar nada") que actuan como barreras efectivas contra las alucinaciones.
  • Taxonomía con ejemplos In-line, como en ROL_PROFESIONAL, donde se incluyen ejemplos del dominio (tutora, Director, EOEP), lo que permite acotar entidades (v.g. identificar si "Equipo de Orientación" es un rol o un organismo).

Hagamos ahora un sencillo ejercicio de comparación entre lo que nos revuelve el uso de este prompt con Gemini y su LLM...

ALUMNO: Alejandro Martínez Soler
ROL_PROFESIONAL: tutora, Carmen Villalobos, Equipo de Orientación Educativa, EOEP, Director, Roberto Pando
CENTRO_EDUCATIVO: CEIP San Juan Bautista
UBICACION: Oviedo
FAMILIAR: Elena Soler
FECHA: martes 12 de mayo, viernes 19 de junio
ORGANISMO: Servicio de Salud del Principado de Asturias, SESPA, Consejería de Educación
APLICATIVO: SAECE

... y el resultado de su empleo con Ollama + llama3.2:3b

- ALUMNO: Alejandro Martínez Soler
- ROL_PROFESIONAL:
* Tutora: Doña Carmen Villalobos
* Director: Don Roberto Pando
* Equipo de Orientación Educativa (EOEP)
- CENTRO_EDUCATIVO: CEIP San Juan Bautista de Oviedo
- UBICACION: Oviedo
- FAMILIAR:
* Elena Soler (madre del alumno)
* FECHA:
* pasado martes 12 de mayo
* viernes 19 de junio
- ORGANISMO: Servicio de Salud del Principado de Asturias (SESPA)
- APLICATIVO: SAECE

Resulta muy interesante comprobar que el modelo LLM no consigue mejores resultados que el SLM en la tarea propuesta (NER) y sobre un texto plano; esto refuerza la idea de que hoy en día es posible obtener buenos resultados en según que tareas trabajando con modelos pequeños en local, lo que hace posible desarrollar proyectos con estas tecnologías con confianza en los resultados que podemos obtener, facilitando así dar respuesta a condicionantes como son los derivados de la privacidad. no obstante estos modelos no son superiores, ni siquiera iguales, a los LLM en cuanto la tarea se complica o entramos en contextos más extensos o interconectados. Pero cada cosa a su tiempo. De momento nos podemos quedar con lo que hasta ahora nos resulta útil: para un NER, un SLM corriendo en local puede que no tenga mucho que envidiar a un LLM... incluso es posible que se inviertan los papeles...

Pero veamos qué obtenemos al aplicar nuestro script:

{
"entidades": [
{
"texto": "Alejandro Martínez Soler",
"etiqueta": "ALUMNO"
},
{
"texto": "Doña Carmen Villalobos",
"etiqueta": "ROL_PROFESIONAL"
},
{
"texto": "La Sra. Elena Soler",
"etiqueta": "FAMILIAR"
},
{
"texto": "Don Roberto Pando",
"etiqueta": "ROL_PROFESIONAL"
},
{
"texto": "CEIP San Juan Bautista de Oviedo",
"etiqueta": "CENTRO_EDUCATIVO"
},
{
"texto": "Oviedo",
"etiqueta": "UBICACION"
},
{
"texto": "SEPA",
"etiqueta": "ORGANISMO"
},
{
"texto": "SAECE",
"etiqueta": "APLICATIVO"
}
]
}

Es de destacar el cambio formal que se ha producido, ya que pasamos de un listado estructurado de elementos a la expresión formal de una estructura JSON. Pero además también podemos comprobar cómo el mismo SLM que antes extraía los datos solicitados, ahora comete muchos más errores y omisiones. La explicación hay que buscarla en la naturaleza del modelo (y sus limitaciones) enfrentada a las exigencias de la tarea: al ser texto y prompt los mismos, la diferencia estriba en la exigencia de devolver una estructura que obliga a prestar atención a muchos detalles de forma como es un JSON. Ante todo ello, un modelo pequeño de lenguaje es incapaz de ofrecer una respuesta de calidad, la misma que sí puede ofrecer si no se le exige más allá de sus posibilidades. Esto abre una segunda vía de trabajo o, si se prefiere, un diseño en el que el código Python se complemente eficientemente con la IA.

Hasta aquí esta entrada, que evidentemente queda inconclusa. Lo que resta, que no es poco, para la próxima.

NOTAS
1 Puede que sea necesario dedicar una entrada al tema, pero diré que, más por cacharrear que por otra cosa, lo cierto es que tengo instalado Ollama en mi ordenador y con este servicio una serie de modelos. La instalación de Ollama no supone ninguna dificultad y la instalación de los modelos tampoco, así que me voy a ahorrar en estos momentos.
2 Según Gemini, en sentido estricto, Ollama es un entorno de ejecución (runtime) y un gestor de modelos ligero y de código abierto, diseñado para empaquetar, desplegar y ejecutar Modelos de Lenguaje (LLMs y SLMs) de forma local. No es un modelo de inteligencia artificial en sí mismo, sino la infraestructura que permite interactuar con ellos sin dependencia de servicios en la nube.