5 Habilidades de IA que mantendran relevantes a los Cientificos de Datos en 2027

IA, RAG, LLM, Ciencia de Datos

Imagen principal de la publicacion

Hoy en dia, cualquiera puede construir un demo de LLM con una sola llamada a la API.

Pero lograr que esa misma funcionalidad sobreviva a usuarios reales, datos reales y una factura real es un trabajo totalmente diferente.

Ese trabajo se resume en cinco habilidades que aparecen en casi todos los sistemas de produccion en los que he trabajado o revisado: retrieval, routing, guardrails, evals y agent loops.

Cada una responde a una pregunta que un modelo frontera por si solo no puede manejar.

En este articulo vamos a repasar estas 5 habilidades con codigo que puedes ejecutar hoy. Cada fragmento usa una funcion call_llm(), de modo que puedes apuntarla al modelo que ya uses, ya sea alojado o local.


Hola, soy Sara Nóbrega, ingeniera de IA enfocada en desplegar sistemas de machine learning en produccion.


Un wrapper para que el codigo funcione en cualquier lugar

Rellena una de las ramas de esta funcion con el SDK que tengas.

El resto del articulo nunca toca un proveedor directamente, asi que cambiar de modelo significa editar esta funcion y nada mas.

def call_llm(prompt: str, system: str = "", model: str = "default") -> str:
    """Envia un prompt al modelo que tengas y devuelve la respuesta de texto."""

    # --- Opcion A: Compatible con OpenAI (OpenAI, Together, un vLLM local, Ollama) ---
    from openai import OpenAI
    client = OpenAI()  # lee OPENAI_API_KEY; pasa base_url=... para apuntar a un servidor local
    resp = client.chat.completions.create(
        model="gpt-4o-mini" if model == "default" else model,
        messages=[
            {"role": "system", "content": system},
            {"role": "user", "content": prompt},
        ],
    )
    return resp.choices[0].message.content

    # --- Opcion B: Anthropic (borra la Opcion A de arriba si usas esta) ---
    # from anthropic import Anthropic
    # client = Anthropic()  # lee ANTHROPIC_API_KEY
    # resp = client.messages.create(
    #     model="claude-haiku-4-5" if model == "default" else model,
    #     max_tokens=1024,
    #     system=system,
    #     messages=[{"role": "user", "content": prompt}],
    # )
    # return resp.content[0].text

Todo lo que sigue usa call_llm. Cambia el cuerpo una vez y todos los ejemplos siguen funcionando.


Habilidad #1: RAG, anclar el modelo a tus propios datos

Un modelo base no conoce los documentos de tu empresa, y afinarlo cada vez que cambian es lento y costoso.

La generacion aumentada por recuperacion (RAG) obtiene los documentos relevantes en el momento de la consulta y se los pasa al modelo como contexto.

Sigue siendo el patron de LLM mas comun en produccion, y normalmente es lo primero que ensena un curriculo.

El trabajo interesante esta en la recuperacion (retrieval).

Si devuelves el fragmento correcto depende de como dividas los documentos (chunking), de si combinas busqueda por palabras clave y busqueda semantica (retrieval hibrido), y de si reordenas los resultados antes de que lleguen al modelo (reranking).

Aqui tienes un pequeno pipeline que se ejecuta en CPU sin ningun servicio externo:

# pip install sentence-transformers numpy
import numpy as np
from sentence_transformers import SentenceTransformer

embedder = SentenceTransformer("all-MiniLM-L6-v2")  # pequeno, corre en una laptop

# En la vida real estos vienen de tus propios documentos, divididos en fragmentos.
docs = [
    "Nuestra politica de reembolsos permite devoluciones dentro de los 30 dias posteriores a la compra.",
    "El envio estandar tarda de 3 a 5 dias habiles.",
    "El plan Pro cuesta 49 dolares al mes e incluye soporte prioritario.",
    "El restablecimiento de contrasena se hace desde la pagina de configuracion de la cuenta.",
]

# Incrusta la base de conocimiento una sola vez, por adelantado.
doc_vectors = embedder.encode(docs, normalize_embeddings=True)

def retrieve(question: str, k: int = 2) -> list[str]:
    """Devuelve los k documentos mas cercanos en significado a la pregunta."""
    q = embedder.encode([question], normalize_embeddings=True)[0]
    scores = doc_vectors @ q             # similitud por coseno (los vectores estan normalizados)
    top = np.argsort(scores)[::-1][:k]   # primero los de mayor puntaje
    return [docs[i] for i in top]

def answer(question: str) -> str:
    context = "\n".join(retrieve(question))
    prompt = f"Responde usando solo el contexto siguiente.\n\nContexto:\n{context}\n\nPregunta: {question}"
    return call_llm(
        prompt,
        system="Eres un asistente de soporte. Si el contexto no responde la pregunta, dimelo.",
    )

Observa que la pregunta dice “enviar algo de vuelta” y el documento que coincide habla de “devoluciones”. La busqueda por palabras clave no detectaria esa coincidencia. La coincidencia por embeddings la captura porque ambas frases estan cerca en el espacio vectorial.

Como aprender RAG

Construyelo sobre tus propias notas y luego rompelo a proposito. Haz una pregunta que el recuperador responda mal y observa como la respuesta se degrada. Arreglalo cambiando el tamano del fragmento (chunk). Anade la busqueda por palabras clave BM25 junto a los embeddings (retrieval hibrido) y mide si las respuestas mejoran. Anade un reranker, como un cross-encoder o una API de reranking alojada, y compara los 3 mejores resultados antes y despues.

Cuando superes un array de numpy, pasa a una base de datos vectorial (FAISS para velocidad, Qdrant o Pinecone para filtrado) y a un framework como LlamaIndex o LangChain que gestiona el almacenamiento y la busqueda por ti. Para medir la calidad en lugar de fiarte de la intuicion, RAGAS te da metricas especificas de RAG: fidelidad, relevancia de la respuesta, precision del contexto y recuperacion del contexto.


Habilidad #2: Enrutamiento de modelos - pagar por el modelo que la tarea necesita

Enviar cada solicitud a un modelo frontera es la forma mas rapida de conseguir una factura que nadie puede explicar.

El enrutamiento (routing) envia las solicitudes faciles y de alto volumen a un modelo pequeno o local, y reserva el modelo caro para las dificiles.

Anadir una cache para los prompts repetidos se reporta que reduce entre 40% y 70% las facturas de inferencia en produccion, por eso el enrutamiento paso de ser un truco avanzado a una expectativa basica.

La habilidad consiste en puntuar una solicitud y elegir un nivel de servicio. Esta es la forma que tiene:

BARATO, MEDIO, FRONTERA = "local-pequeno", "nivel-medio", "frontera"

def route(tipo_tarea: str, tokens_entrada: int) -> str:
    # Las tareas de alto volumen y estrechas van a un modelo pequeno, normalmente local.
    if tipo_tarea in {"clasificar", "extraer", "etiquetar"} and tokens_entrada < 2000:
        return BARATO
    # El contexto largo o el razonamiento abierto merecen el modelo frontera.
    if tipo_tarea in {"razonar", "planificar", "revisar-codigo"} or tokens_entrada > 8000:
        return FRONTERA
    return MEDIO  # el valor predeterminado sensato en el medio

Un enrutador solo rinde si puedes ver cuanto gastas. Envuelve tus llamadas para que cada una quede registrada con su tipo de tarea, cantidad de tokens y costo.

import collections

# Precio aproximado por millon de tokens. Actualizalos para tu propio proveedor.
PRECIO = {"local-pequeno": 0.0, "nivel-medio": 0.60, "frontera": 12.0}
registro_llamadas = []  # en produccion esto va a una base de datos o a tu herramienta de tracing

def llamada_conseguida(prompt: str, tipo_tarea: str) -> str:
    nivel_modelo = route(tipo_tarea, tokens_entrada=len(prompt.split()))
    tokens = len(prompt.split()) * 1.3   # estimacion burda; usa un tokenizador real en produccion
    costo = tokens / 1_000_000 * PRECIO[nivel_modelo]
    registro_llamadas.append({"tarea": tipo_tarea, "nivel": nivel_modelo, "costo": costo})
    return call_llm(prompt, model=nivel_modelo)

# Despues de un lote de trabajo, ve a donde fue a parar el dinero:
gasto = collections.Counter()
for c in registro_llamadas:
    gasto[c["nivel"]] += c["costo"]
print(dict(gasto))

Como aprender el enrutamiento de modelos

Registra cada llamada de LLM en un proyecto que ya tengas y luego observa a donde va el dinero. Toma la tarea de mayor volumen y mas simple y enviala a un modelo mas barato, y mide honestamente la caida de calidad. Anade una cache para los prompts repetidos. Cuando el codigo manual se vuelva tedioso, prueba una libreria de enrutamiento como RouteLLM o un gateway como LiteLLM que se situa frente a muchos proveedores. Anota el costo antes y despues.


Habilidad #3: Guardarrailes - entrada y salida que puedas defender

Los guardarrailes pasaron de ser un extra a algo que los entrevistadores esperan que tengas.

La inyeccion de prompts ha sido el riesgo numero 1 en el OWASP Top 10 para Aplicaciones LLM durante 2 ediciones seguidas.

La razon por la que funciona es simple: un LLM lee las instrucciones y los datos en el mismo canal, asi que un atacante puede escribir una entrada que el modelo trate como una nueva instruccion y la siga, porque no puede distinguir entre ambas.

El trabajo abarca la validacion de entrada y salida, la deteccion de inyecciones y jailbreaks, el enmascaramiento de los datos personales (PII) antes de que lleguen al modelo o a tus registros, y mantener el modelo dentro de su ambito permitido.

Aqui tienes un filtro inicial que puedes ejecutar sin dependencias.

import re

PATRONES_INYECCION = [
    r"ignore (all|the|previous|above) instructions",
    r"disregard .*(system|prompt)",
    r"you are now",
    r"reveal your (system )?prompt",
]

def parece_inyeccion(texto: str) -> bool:
    return any(re.search(p, texto, re.IGNORECASE) for p in PATRONES_INYECCION)

PATRONES_PII = {
    "correo": r"[\w.+-]+@[\w-]+\.[\w.-]+",
    "telefono": r"\+?\d[\d\s().-]{7,}\d",
}

def enmascarar(texto: str) -> str:
    for etiqueta, patron in PATRONES_PII.items():
        texto = re.sub(patron, f"[{etiqueta}]", texto)
    return texto

def respuesta_protegida(entrada_usuario: str) -> str:
    if parece_inyeccion(entrada_usuario):
        return "Rechazado: esto parece un intento de cambiar mis instrucciones."
    limpio = enmascarar(entrada_usuario)
    return call_llm(
        limpio,
        system="Responde solo preguntas sobre nuestro producto. Rechaza cualquier otra cosa.",
    )

Una advertencia: trata las expresiones regulares como un primer filtro solamente. Un atacante decidido sortea una lista de patrones. La propia orientacion de OWASP es la defensa en profundidad: validacion de entrada mas filtrado de salida, herramientas con minimos privilegios, aprobacion humana para acciones sensibles y pruebas adversarias periodicas. Los sistemas de produccion recurren a Microsoft Presidio para la PII y a Guardrails AI o NeMo Guardrails para la validacion, en lugar del regex anterior.

Como aprender guardarrailes

Lee el OWASP Top 10 para Aplicaciones LLM una vez, de principio a fin. Luego toma el prompt del sistema de tu propio proyecto e intenta romperlo. Consigue que el modelo ignore sus instrucciones y luego tapona el agujero. Anade validacion de entrada y salida con una de las librerias anteriores. Anade deteccion de PII antes de que algo llegue al modelo o a los registros. La pregunta de entrevista suele ser “realiza un red teaming sobre tu propio prompt del sistema y nombra los vectores de inyeccion”, asi que practica escribir un ataque y su defensa de memoria.


Habilidad #4: Evals y observabilidad - saber si funciona

Cambiar un prompt sin un eval es adivinar. Cuando un agente de multiples pasos falla sin observabilidad, ves la respuesta final equivocada y ninguno de los pasos que la produjeron.

Empieza con un conjunto de eval pequeno. No necesitas un framework para empezar. Necesitas de 20 a 50 entradas reales con las respuestas que aceptarias. Incluso 8 ejemplos te muestran la forma.

conjunto_eval = [
    {"pregunta": "Cual es la capital de Francia?", "esperado": "Paris"},
    {"pregunta": "Cuantos dias tiene un anio bisiesto?", "esperado": "366"},
    {"pregunta": "Que significa CPU?", "esperado": "unidad central de procesamiento"},
    # ... amplia esto a 20-50 casos reales de tu propio uso
]

def puntuacion_ok(salida: str, esperado: str) -> bool:
    """Una comprobacion flexible: la respuesta aceptada aparecio en la respuesta?"""
    return esperado.lower() in salida.lower()

def ejecutar_eval(prompt_sistema: str) -> float:
    aprobados = 0
    for fila in conjunto_eval:
        salida = call_llm(fila["pregunta"], system=prompt_sistema)
        aprobados += puntuacion_ok(salida, fila["esperado"])
    puntaje = aprobados / len(conjunto_eval)
    print(f"aprobados {aprobados}/{len(conjunto_eval)} = {puntaje:.0%}")
    return puntaje

# Ahora puedes comparar dos prompts con un numero en lugar de con intuiciones:
ejecutar_eval("Responde en una sola palabra.")
ejecutar_eval("Responde solo con el dato, sin oracion.")

La primera vez que cambias un prompt y puedes demostrar que el puntaje se mantuvo, esto deja de sentirse como un trabajo pesado.

Calificar respuestas abiertas con un juez LLM

La coincidencia exacta falla con las respuestas abiertas donde la redaccion varia. La solucion habitual es que un segundo modelo califique al primero. La orientacion de DeepEval sobre los jueces destaca un punto que vale la pena recordar: un APROBADO o REPROBADO binario es mas fiable que pedirle a un juez una puntuacion de 0 a 100. Asi que mantengamos el veredicto binario.

PROMPT_JUEZ = """Calificas la respuesta de un asistente frente a una referencia.
Responde exactamente con APROBADO o REPROBADO en la primera linea, luego una razon corta.

Pregunta: {q}
Respuesta de referencia: {ref}
Respuesta del asistente: {ans}

La respuesta APROBADO si es factualmente coherente con la referencia,
incluso cuando la redaccion es diferente."""

def juez(pregunta: str, referencia: str, respuesta_asistente: str) -> bool:
    veredicto = call_llm(PROMPT_JUEZ.format(q=pregunta, ref=referencia, ans=respuesta_asistente))
    return veredicto.strip().upper().startswith("APROBADO")

Comprueba al juez contra ti mismo

Un juez sin comprobar es un numero en el que no puedes confiar. Los jueces LLM muestran sesgos conocidos: favorecen respuestas mas largas, favorecen la respuesta que aparece primero y tienden a ser demasiado indulgentes. Asi que antes de confiar en los numeros del juez, etiqueta unas 20 respuestas tu mismo y mide con que frecuencia el juez esta de acuerdo contigo.

# Tu calificas estas 20 a mano: True = buena respuesta, False = mala respuesta.
etiquetas_humanas = [True, True, False, True, False, True]  # ... tus etiquetas
etiquetas_juez = [juez(r["q"], r["ref"], r["ans"]) for r in respuestas_calificadas]

acuerdo = sum(h == j for h, j in zip(etiquetas_humanas, etiquetas_juez)) / len(etiquetas_humanas)
print(f"el juez coincide conmigo {acuerdo:.0%} de las veces")

# Por debajo de ~80%? Reescribe la rubrica del juez antes de confiar en una sola puntuacion.

Ver el interior de una ejecucion fallida

La observabilidad es la otra mitad. Cuando algo se rompe, quieres toda la secuencia de pasos que produjo la respuesta. Un pequeño decorador te permitira empezar: registra cada paso, su estado y cuanto tiempo tardo.

import time, functools

traza = []

def trazado(fn):
    @functools.wraps(fn)
    def envoltorio(*args, **kwargs):
        inicio = time.time()
        estado = "ok"
        try:
            return fn(*args, **kwargs)
        except Exception as e:
            estado = f"error: {e}"
            raise
        finally:
            traza.append({
                "paso": fn.__name__,
                "estado": estado,
                "segundos": round(time.time() - inicio, 3),
            })
    return envoltorio

@trazado
def paso_recuperar(q): return retrieve(q)

@trazado
def paso_responder(q): return answer(q)

paso_responder("Cuanto tiempo tengo para devolver algo?")

print(traza)  # -> una fila por paso, con tiempos y estado

En produccion no haces esto a mano. La industria se decanto por las convenciones semanticas GenAI de OpenTelemetry como el esquema estandar para las trazas de LLM, y OpenTelemetry se graduo en la CNCF en 2026, lo que la convierte en una eleccion segura a largo plazo. Herramientas como Langfuse, LangSmith, Arize Phoenix y Braintrust se conectan a ella y te dan una interfaz sobre cada paso.

Como aprender evals

Empieza escribiendo a mano el conjunto de 20 ejemplos anterior para un proyecto que ya tengas. Hacerlo a mano te obliga a decidir que es una buena respuesta, una decision que ninguna herramienta tomara por ti. Cuando quieras mas que una coincidencia exacta, elige un framework:

Para el monitoreo en produccion, anade encima una de Langfuse, LangSmith, Phoenix o Braintrust.


Habilidad 5: Bucles agenicos - trabajo de multiples pasos que se recupera

Esta habilidad tiene la mayor hype y la mayor rotacion, asi que aprendela con los ojos bien abiertos.

La encuesta del Q1 2026 de Gartner encontro que solo el 17% de las organizaciones han desplegado agentes de IA, aunque mas del 60% espera hacerlo en dos anios. La advertencia que te mantiene creible: Gartner tambien espera que mas del 40% de los proyectos agenicos de IA sean descartados para 2027 por costo, valor poco claro o gobernanza debil.

La adopcion avanza por delante de la fiabilidad.

Un agente es un modelo mas herramientas mas un bucle que sigue llamando herramientas hasta que la tarea se completa. Construyelo a mano una vez para sentir donde se vuelve fragil.

import json

def herramienta_busqueda(consulta: str) -> str:
    """Sustituto de una llamada real de busqueda o base de datos."""
    conocimiento = {"clima en lisboa": "22C y soleado"}
    return conocimiento.get(consulta.lower().strip(), "")  # devuelve "" cuando no encuentra nada

HERRAMIENTAS = {"buscar": herramienta_busqueda}

SISTEMA_AGENTE = """Resuelves una tarea usando herramientas.
Para llamar a una herramienta, responde con JSON: {"tool": "buscar", "input": "tu consulta"}
Cuando tengas la respuesta final, responde con JSON: {"answer": "tu respuesta"}
Responde solo con JSON, nada mas."""

def ejecutar_agente(tarea: str, max_pasos: int = 5) -> str:
    historial = f"Tarea: {tarea}"
    for _ in range(max_pasos):
        respuesta = call_llm(historial, system=SISTEMA_AGENTE)
        try:
            accion = json.loads(respuesta)
        except json.JSONDecodeError:
            historial += "\nEso no era JSON valido. Responde solo con JSON."
            continue
        if "answer" in accion:
            return accion["answer"]
        herramienta = HERRAMIENTAS.get(accion.get("tool"))
        resultado = herramienta(accion["input"]) if herramienta else ""
        if resultado:
            historial += f"\nResultado de la herramienta: {resultado}"
        else:
            # La herramienta devolvio basura. Informa al agente en lugar de reventar.
            historial += "\nLa herramienta no devolvio nada util. Prueba otra consulta o responde con lo que sepas."
    return "Detenido tras demasiados pasos."

Ejecutalo unas cuantas veces y veras con que facilidad se descarrila: el modelo devuelve prosa en lugar de JSON, una herramienta regresa vacia, el bucle gira. Esa fragilidad es precisamente la razon por la que existen los frameworks. LangGraph y CrewAI te dan maquinas de estado y puntos de control (checkpointing), de modo que una ejecucion se reanuda desde el paso que fallo en lugar de reiniciar toda la tarea.

Como aprender bucles agenicos

Construye el bucle anterior a mano y siente donde se rompe. Reconstruye lo mismo en LangGraph o CrewAI y nota que es lo que el framework maneja por ti: estado, reintentos, puntos de control. Fuerza un fallo y haz que el agente se recupere. Dale una herramienta real y maneja el caso en que la herramienta devuelve basura. Luego lee un post-mortem de un proyecto de agentes que fue cancelado. Las lecciones de costo y gobernanza son la parte que ningun tutorial ensena, y conocerlas te convierte en la persona que pregunta si la tarea necesita un agente o basta con una buena llamada de funcion.


Conclusion

Aprende estas cinco habilidades y podras anclar un modelo en los datos de la empresa, reducir buena parte de la factura, evitar que el sistema filtra informacion o se le haga jailbreak, demostrar con numeros que funciona, y lograr que haga trabajo de multiples pasos sin colapsar.

No necesitas las cinco a la vez. Intentar aprenderlas en paralelo es como la gente se agota y no aprende ninguna. Elige la que toca tu trabajo actual y profundiza.

Nada de esto requiere un titulo nuevo. Solo requiere elegir una habilidad, construir algo pequeno que se rompa y arreglarlo. Luego la siguiente.

Abre un proyecto que ya tengas y anade el patron que le falta: retrieval si no puede ver tus datos, routing si la factura te asusta, evals si cambias prompts por corazonada.

Gracias por leer!


Mi nombre es Sara Nóbrega. Soy ingeniera de IA con formacion en fisica.

Fuente original: 5 AI Skills That Will Keep Data Scientists Relevant in 2027