DSPy es el framework que Stanford NLP lanzó para dejar de escribir prompts a mano y empezar a programar los LLMs como si fueran funciones tipadas de Python. En este tutorial de agosto de 2026 vamos a montar, paso a paso, un pipeline completo: instalación con uv, conexión a Claude Opus 5 y GPT-5.6, primera Signature tipada, ChainOfThought, un módulo RAG compuesto, métrica de evaluación, optimización automática con MIPROv2, guardado del programa compilado y despliegue en FastAPI. Al terminar tendrás un servicio en producción que se ha auto-optimizado sobre tus propios datos de evaluación, con mejoras medidas del 10 al 40 por ciento frente al prompting manual.
Por qué programar LLMs en lugar de escribir prompts a mano
La práctica habitual en 2024 era pegar cadenas gigantes de texto en un archivo, iterar a ojo hasta que el resultado parecía decente y rezar para que el modelo siguiente no rompiera todo. DSPy propone lo contrario: declaras la firma de la tarea (entradas y salidas tipadas), compones módulos que representan pasos de razonamiento y dejas que un optimizador (MIPROv2, BootstrapFewShot, COPRO) genere el prompt real a partir de un conjunto de datos de evaluación. El resultado se comporta como código: se versiona, se testea, se mide y se mejora sin tocar cadenas mágicas.
Este cambio de paradigma se aceleró en 2026, con DSPy 3.2 y la llegada de la rama 3.3 estable a comienzos de agosto. Los benchmarks internos de Stanford y varios equipos de producto (Databricks, Zenoml, Faire) reportan mejoras del 10 al 40 por ciento en tareas estructuradas como QA multi-hop, clasificación, extracción y generación RAG cuando se pasa de prompts escritos a mano a programas DSPy compilados con MIPROv2. Y todo eso con la misma API subyacente: sigues llamando a Claude Opus 5, GPT-5.6 o modelos locales vía Ollama.
Si vienes del ecosistema de agentes, este tutorial se complementa con nuestra guía del Claude Agent SDK (agentes autónomos con herramientas) y con el tutorial de LangGraph (grafos de estado para orquestación). Para exponer tu programa como servidor MCP consumible por Claude Desktop, revisa FastMCP en producción.
Requisitos previos y hardware recomendado
Antes de lanzarnos al código, revisa esta lista mínima: Python 3.10 o superior (recomendado 3.12), gestor de dependencias uv (mucho más rápido que pip), una clave API de Anthropic para Claude Opus 5 (o de OpenAI, o de Google), y un entorno virtual limpio. Si vas a compilar programas con MIPROv2 sobre datasets de más de 200 ejemplos, prepara la tarjeta bancaria: la fase de optimización puede lanzar cientos de llamadas al LM juez.
El hardware no es crítico para DSPy en sí (el trabajo pesado lo hace el proveedor del LLM), pero si tu flujo es intensivo (compilaciones repetidas, evaluaciones cruzadas, notebooks) agradecerás una estación de trabajo cómoda. Estas son mis recomendaciones probadas en 2026:
- Apple MacBook Pro 14" con chip M4: la máquina de referencia para consultoría móvil; corre modelos locales de 7B vía Ollama sin despeinarse y compila programas DSPy en minutos.
- Mini PC Beelink SER8 con Ryzen 7 8845HS: 32 GB RAM, ideal como host permanente para DSPy en tu red doméstica, ejecutando MIPROv2 durante la noche.
- Monitor LG UltraGear 32" 4K: espacio de sobra para dos paneles de código, MLflow y la terminal del compilador MIPROv2 en paralelo.
- Keychron K8 Pro mecánico: layout tenkeyless retroiluminado, hot-swap para probar switches, ideal para maratones de tuning de prompts.
- Logitech MX Mechanical Mini: alternativa mecánica low-profile con multi-device por Bluetooth.
- Ratón Logitech MX Master 3S: silencioso, con scroll magnético y perfiles por aplicación (imprescindible en flujos multi-monitor).
- Sony WH-1000XM5 con cancelación activa: sesiones de trabajo profundo sin distracciones; la XM6 aún escasea en stock.
- Silla ergonómica Herman Miller Sayl: la referencia si programas seis horas al día; el respaldo suspendido evita la fatiga lumbar.
Este artículo contiene enlaces afiliados a Amazon con el tag webmasteroson-21. Si compras a través de estos enlaces, Arkaia puede recibir una comisión sin coste adicional para ti. Nuestra valoración editorial es independiente y no varía según los ingresos por afiliación.
Paso 1: instalación con uv (y el porqué de no usar pip global)
El primer paso es crear un entorno reproducible. Usamos uv porque instala DSPy y sus 40 dependencias transitivas en menos de 3 segundos, y bloquea el archivo uv.lock con hashes SHA256 verificables. Evita instalar DSPy en el Python del sistema: la librería arrastra torch y tokenizers, y romper el intérprete global es sólo cuestión de tiempo.
uv init dspy-lab
cd dspy-lab
uv add dspy-ai openai anthropic pydantic python-dotenv mlflow fastapi uvicorn
# Verificar la version instalada (agosto 2026: 3.3.x estable)
uv run python -c "import dspy; print(dspy.__version__)"
Salida esperada: 3.3.0 o superior. Si obtienes 3.2.1, fuerza el upgrade con uv add "dspy-ai>=3.3". La rama 3.3 introduce la nueva API de dspy.LM unificada y la migración completa a ReActV2, que estamos usando en producción.
Trampa común: el paquete se llama dspy-ai en PyPI pero se importa como dspy. Muchos tutoriales viejos siguen mostrando pip install dspy, que apunta a un proyecto abandonado sin relación con Stanford NLP.
Paso 2: configuración del LM (Claude Opus 5 o GPT-5.6)
DSPy delega el transporte de red a LiteLLM, así que soporta prácticamente cualquier proveedor: Anthropic, OpenAI, Google Gemini, Cohere, Mistral, Ollama local, vLLM, TogetherAI... La API es la misma; sólo cambia el string del modelo. Vamos a configurar Claude Opus 5 como LM por defecto (el rey en razonamiento multi-hop en agosto 2026, con precios de 5 dólares por millón de tokens de entrada y 25 dólares por millón de salida).
import os
import dspy
from dotenv import load_dotenv
load_dotenv() # Lee ANTHROPIC_API_KEY del entorno
# Modelo principal para inferencia
lm = dspy.LM(
"anthropic/claude-opus-5",
api_key=os.environ["ANTHROPIC_API_KEY"],
max_tokens=4096,
temperature=0.2,
)
# Modelo economico para el optimizador (juez de MIPROv2)
task_lm = dspy.LM("openai/gpt-5.6-mini", max_tokens=2048)
dspy.configure(lm=lm)
La distinción entre lm y task_lm es clave: durante la compilación, MIPROv2 lanza cientos de llamadas para proponer instrucciones y evaluar candidatas. Usar el modelo caro para todo puede disparar la factura. La convención de la comunidad es reservar Claude Opus 5 o GPT-5.6 para la inferencia final y delegar el juez interno a un modelo mini o flash.
Trampa común: si defines la clave API en un fichero .env pero olvidas llamar a load_dotenv(), DSPy fallará con un error de LiteLLM del tipo AuthenticationError: 401. Verifica siempre con echo $ANTHROPIC_API_KEY antes de ejecutar.
Paso 3: primera Signature tipada
Una Signature es el contrato de una llamada al LLM: describe qué campos entran, cuáles salen y cómo se llaman. DSPy usa las anotaciones de tipo de Python para generar automáticamente el prompt de sistema. Es el equivalente a definir una función Pydantic, pero para el modelo de lenguaje.
import dspy
class SummarizeText(dspy.Signature):
"""Resume un texto en 3 frases claras y neutras."""
# Campos de entrada (in)
texto: str = dspy.InputField(desc="Texto original en espanol")
audiencia: str = dspy.InputField(
desc="Perfil del lector: tecnico, general, ejecutivo"
)
# Campos de salida (out)
resumen: str = dspy.OutputField(
desc="Resumen final en 3 frases separadas por saltos de linea"
)
palabras_clave: list[str] = dspy.OutputField(
desc="Entre 3 y 5 palabras clave del texto"
)
Ojo al detalle: DSPy respeta el tipo declarado. Si dices list[str], el framework parsea la salida del modelo y devuelve una lista nativa de Python; si el modelo se sale del formato, la librería reintenta con instrucciones adicionales. Este contrato es el que MIPROv2 aprovecha después para reescribir el prompt sin romper tu código cliente.
Paso 4: Predict simple (la primera inferencia)
Con la Signature declarada, ejecutar la primera predicción es una línea:
from pprint import pprint
predictor = dspy.Predict(SummarizeText)
resultado = predictor(
texto=(
"DSPy 3.3 introduce ReActV2, mejoras en MIPROv2 y soporte nativo "
"para tool calls paralelos con Claude Opus 5. La nueva API dspy.LM "
"unifica todos los proveedores gracias a LiteLLM."
),
audiencia="tecnico",
)
pprint(resultado.resumen)
pprint(resultado.palabras_clave)
Salida esperada (aproximada; el modelo puede variar la redacción):
'DSPy 3.3 anade ReActV2 y mejoras en el optimizador MIPROv2.\n'
'Soporta tool calls paralelos con Claude Opus 5.\n'
'La API dspy.LM unifica proveedores mediante LiteLLM.'
['DSPy', 'ReActV2', 'MIPROv2', 'Claude Opus 5', 'LiteLLM']
Fíjate en dos cosas: no has escrito ningún prompt (DSPy lo generó a partir del docstring y las descripciones) y las palabras clave llegan ya como list[str] lista para pasar a tu lógica de negocio. Si quisieras ver el prompt real, ejecuta dspy.inspect_history(n=1) tras la llamada.
Paso 5: ChainOfThought (razonamiento explícito)
El módulo ChainOfThought es un drop-in de Predict que añade un campo intermedio reasoning generado por el modelo antes de la respuesta final. Aumenta la latencia y el coste, pero mejora sustancialmente la precisión en tareas de razonamiento, aritmética y clasificación ambigua.
cot = dspy.ChainOfThought(SummarizeText)
salida = cot(
texto="La Reserva Federal recorto tipos 25 puntos basicos en julio.",
audiencia="ejecutivo",
)
print("Razonamiento:", salida.reasoning)
print("Resumen:", salida.resumen)
Regla práctica: usa Predict para tareas mecánicas (extracción de datos, formateo, traducciones cortas) y ChainOfThought cuando el modelo tenga que decidir entre varias interpretaciones. En benchmarks de la comunidad de agosto 2026, CoT mejora F1 entre 3 y 12 puntos en clasificación legal, médica y financiera.
Paso 6: composición de Modules (pipeline RAG completo)
Los Modules son la unidad de composición en DSPy: agrupan varias Signatures dentro de una clase que hereda de dspy.Module. Vamos a montar un pipeline RAG mínimo con tres etapas: recuperar candidatos, reordenarlos con un mini-clasificador y generar la respuesta final. Este mismo patrón es el que MIPROv2 optimizará después de forma conjunta (no cada paso por separado).
import dspy
class RetrieveTopK(dspy.Signature):
"""Selecciona los k pasajes mas relevantes para la pregunta."""
pregunta: str = dspy.InputField()
pasajes: list[str] = dspy.InputField(desc="Corpus candidato")
top_k: list[str] = dspy.OutputField(desc="3 pasajes mas relevantes")
class Answer(dspy.Signature):
"""Responde en espanol basandote solo en los pasajes."""
pregunta: str = dspy.InputField()
contexto: list[str] = dspy.InputField()
respuesta: str = dspy.OutputField()
class RAG(dspy.Module):
def __init__(self):
super().__init__()
self.rerank = dspy.ChainOfThought(RetrieveTopK)
self.answer = dspy.ChainOfThought(Answer)
def forward(self, pregunta: str, pasajes: list[str]):
seleccion = self.rerank(pregunta=pregunta, pasajes=pasajes)
salida = self.answer(pregunta=pregunta, contexto=seleccion.top_k)
return dspy.Prediction(
respuesta=salida.respuesta,
contexto=seleccion.top_k,
)
rag = RAG()
resp = rag(
pregunta="Que introduce DSPy 3.3?",
pasajes=[
"DSPy 3.3 anade ReActV2 y mejoras en MIPROv2.",
"El plato del dia son macarrones con tomate.",
"La API dspy.LM se unifica gracias a LiteLLM.",
],
)
print(resp.respuesta)
El truco de DSPy es que rag.parameters() expone automáticamente los dos módulos internos como parámetros optimizables. Cuando pasemos este objeto al optimizador, MIPROv2 propondrá instrucciones y few-shots de forma conjunta para rerank y answer, no como pipelines independientes.
Paso 7: métrica de evaluación
Sin una métrica, DSPy es sólo una librería más de tipado. La métrica es la función que MIPROv2 intentará maximizar. Debe recibir gold (ejemplo esperado), pred (predicción del programa) y opcionalmente trace (traza interna). Devuelve un float o un booleano.
import dspy
from dspy.evaluate import Evaluate
def metric(gold, pred, trace=None) -> float:
"""Media entre exact match y overlap semantico."""
exact = float(gold.respuesta.strip().lower() in pred.respuesta.lower())
# Juez LLM binario: 1 si la respuesta es correcta, 0 si no
juez = dspy.Predict("pregunta, referencia, candidata -> es_correcta: bool")
veredicto = juez(
pregunta=gold.pregunta,
referencia=gold.respuesta,
candidata=pred.respuesta,
)
return 0.4 * exact + 0.6 * float(veredicto.es_correcta)
trainset = [
dspy.Example(
pregunta="Que optimizador propone DSPy?",
pasajes=["DSPy incluye MIPROv2, BootstrapFewShot y COPRO."],
respuesta="MIPROv2, BootstrapFewShot y COPRO.",
).with_inputs("pregunta", "pasajes"),
# ...anadir al menos 20 ejemplos mas para que MIPROv2 sea util
]
evaluator = Evaluate(devset=trainset, metric=metric, num_threads=4)
print("Score base:", evaluator(rag))
Trampa común: muchas métricas fallan en producción por comparar strings crudos. Usa siempre normalización (.lower(), .strip()), y cuando el dominio es ambiguo delega el veredicto final a un juez LLM (patrón LLM-as-a-judge). Con 20-50 ejemplos el optimizador ya obtiene ganancias visibles; a partir de 200 empieza a saturar.
Paso 8: optimización automática con MIPROv2
Ahora la magia. MIPROv2 (Multiprompt Instruction PRoposal Optimizer v2) trabaja en tres fases: bootstrapea few-shot examples aceptados por tu métrica, propone candidatos de instrucción con un modelo grounded en tu tarea, y busca la mejor combinación instrucción + demos mediante optimización bayesiana (TPE vía Optuna). Todo eso lo lanza una sola llamada:
from dspy.teleprompt import MIPROv2
optimizer = MIPROv2(
metric=metric,
prompt_model=task_lm, # LM economico para proponer prompts
task_model=lm, # LM final que ejecutara el programa
num_candidates=10, # Instrucciones candidatas por predictor
init_temperature=0.7,
auto="medium", # light / medium / heavy
)
compiled_rag = optimizer.compile(
student=rag,
trainset=trainset,
max_bootstrapped_demos=4,
max_labeled_demos=4,
requires_permission_to_run=False,
)
print("Score compilado:", evaluator(compiled_rag))
Salida esperada tras 8-15 minutos de compilación (según tamaño del trainset y auto=medium):
Score base: 62.5
Score compilado: 84.3
Ese salto de unos 22 puntos es típico en el primer ciclo. Las mejoras marginales después son menores, pero MIPROv2 también reduce la varianza (el programa compilado responde de forma mucho más consistente ante variaciones del input). Con auto="heavy" puedes exprimir 5-8 puntos más a costa de multiplicar por 3-4 el gasto en tokens del juez.
Paso 9: guardar y cargar programas compilados
Un programa compilado es un artefacto: instrucciones + demos + metadatos. Se guarda como JSON y se recarga sin tener que volver a pagar la compilación. En equipos de datos, este JSON entra al repositorio git como si fuera un modelo entrenado.
from pathlib import Path
compiled_rag.save("artifacts/rag_v1.json")
# Mas tarde, en produccion
rag_prod = RAG()
rag_prod.load("artifacts/rag_v1.json")
# La API es identica a la del programa original
resp = rag_prod(pregunta="...", pasajes=["..."])
Buena práctica: versiona los artefactos siguiendo la convención rag_vMAYOR.MENOR.json y registra en tu tabla de despliegue el hash SHA256 del fichero. Si el score cae después de un cambio de modelo (por ejemplo, Anthropic saca Claude Opus 5.1), recompila y compara antes de sustituir el artefacto en producción.
Paso 10: deploy en producción con FastAPI + MLflow
Para servir el programa compilado, envuelve DSPy en un endpoint FastAPI. Como bonus, registramos cada llamada en MLflow para trazabilidad y comparativa de versiones. Este es el patrón mínimo aceptado en producción (agosto 2026):
import dspy, mlflow
from fastapi import FastAPI
from pydantic import BaseModel
dspy.settings.configure(
lm=dspy.LM("anthropic/claude-opus-5"),
cache=True, # Cache disco para peticiones repetidas
)
mlflow.set_tracking_uri("http://mlflow:5000")
mlflow.dspy.autolog()
rag_prod = RAG()
rag_prod.load("artifacts/rag_v1.json")
app = FastAPI(title="DSPy RAG Service")
class Req(BaseModel):
pregunta: str
pasajes: list[str]
@app.post("/ask")
def ask(req: Req):
with mlflow.start_run():
out = rag_prod(pregunta=req.pregunta, pasajes=req.pasajes)
mlflow.log_param("n_pasajes", len(req.pasajes))
mlflow.log_metric("resp_len", len(out.respuesta))
return {"respuesta": out.respuesta, "contexto": out.contexto}
Lanza el servicio con uv run uvicorn main:app --host 0.0.0.0 --port 8000. La caché en disco de DSPy evita recomputar respuestas idénticas y reduce el coste hasta un 70 por ciento en tráfico repetitivo. El autolog de MLflow captura la traza completa: prompts finales, tokens gastados, latencia y score si adjuntas la métrica.
Trampa común: si despliegas en un contenedor Docker sin volumen persistente, la caché se pierde en cada reinicio. Monta un volumen en ~/.cache/dspy o desactiva la caché explícitamente con cache=False.
Comparativa: DSPy vs LangChain vs LlamaIndex vs Instructor
DSPy no reemplaza a las demás librerías; ocupa un hueco propio. Esta es la comparativa que uso en propuestas comerciales de 2026:
| Framework | Enfoque | Punto fuerte | Limitación clave |
|---|---|---|---|
| DSPy 3.3 | Programas tipados + optimización automática | Mejora medible (10-40 por ciento) con MIPROv2 | Curva de aprendizaje conceptual; requiere dataset |
| LangChain | Cadenas y agentes generalistas | Ecosistema masivo de integraciones | Prompts a mano; deuda técnica en versiones antiguas |
| LlamaIndex | RAG y data ingestion | Conectores a más de 200 fuentes de datos | Menos flexible para agentes complejos |
| Instructor | Salidas estructuradas con Pydantic | Validación estricta y reintentos automáticos | No optimiza prompts ni compone módulos |
En 2026 lo habitual es combinar: LlamaIndex para ingesta, DSPy para el núcleo de razonamiento optimizado, Instructor cuando la salida estructurada es crítica (facturación, compliance) y LangGraph o el Claude Agent SDK para la orquestación multi-agente. Ninguno es una isla.
Casos de uso reales en producción
DSPy pasa la prueba de fuego cuando hay dataset y métrica clara. Estos son los tres patrones que veo repetirse en clientes:
- QA multi-hop: agentes de soporte que consultan 3-4 fuentes antes de responder. MIPROv2 mejora especialmente el paso de síntesis final.
- Extracción estructurada: pasar facturas o contratos a JSON. Combina
dspy.Predictcon Pydantic y define la métrica como F1 por campo. - Clasificación legal o médica: taxonomías con 50-200 clases. Con 300 ejemplos etiquetados MIPROv2 alcanza al fine-tuning tradicional a una fracción del coste.
"Con DSPy dejamos de tratar los prompts como manuscritos y empezamos a tratarlos como código compilable. Es la misma revolución que trajo el compilador C frente al ensamblador" — Omar Khattab, autor original de DSPy en Stanford NLP.
Recursos, libros y siguiente paso
Si quieres profundizar más allá del tutorial oficial, estos son los libros que llevo recomendando todo 2026 para el stack Python + LLMs:
- Fluent Python (2 edición) de Luciano Ramalho: imprescindible para dominar los tipos, decoradores y descriptores que DSPy usa internamente.
- Hands-On Machine Learning (3 edición) de Aurélien Géron: base sólida de ML clásico y deep learning para entender qué hay bajo los LLMs.
- AI Engineering de Chip Huyen: el mejor libro de 2025-2026 sobre diseñar sistemas LLM en producción (evaluación, RAG, guardrails, cost control).
- Prompt Engineering for LLMs (O'Reilly): aunque DSPy busca automatizar el prompting, entender los principios subyacentes ayuda a diagnosticar cuándo el optimizador se atasca.
Preguntas frecuentes (FAQ)
¿Necesito muchos datos para usar MIPROv2 con DSPy?
No. Con 20-50 ejemplos ya obtienes mejoras notables. A partir de 200 el retorno marginal decrece. La calidad del dataset importa más que el volumen: ejemplos diversos superan siempre a cientos de duplicados.
¿Cuánto cuesta compilar un programa con MIPROv2?
Depende del tamaño del trainset, del preset (light/medium/heavy) y del modelo. Un caso típico (30 ejemplos, auto=medium, prompt_model=gpt-5.6-mini) ronda los 3-8 dólares. Con auto=heavy y Claude Opus 5 como juez puede subir a 40-80 dólares por compilación.
¿DSPy funciona con modelos locales vía Ollama?
Sí. Basta con instanciar dspy.LM("ollama/llama3.3") o el modelo que tengas descargado. Rinde bien para inferencia; para MIPROv2 conviene usar un modelo mayor (mixtral 8x22b o claude) como prompt_model para que las instrucciones propuestas sean de calidad.
¿Puedo usar DSPy con Claude Opus 5 y GPT-5.6 simultáneamente?
Sí. LiteLLM (el transporte que usa DSPy) permite mezclar proveedores en el mismo programa. Un patrón habitual es asignar el LM caro sólo a los módulos críticos con with dspy.context(lm=lm_opus): y dejar el resto en un modelo económico.
¿Cómo se compara MIPROv2 frente a BootstrapFewShot?
BootstrapFewShot sólo optimiza demos (few-shot examples); MIPROv2 optimiza demos e instrucciones conjuntamente con búsqueda bayesiana. En tareas simples pueden empatar; en pipelines multi-módulo (RAG, agentes) MIPROv2 gana sistemáticamente 5-15 puntos F1.
¿Puedo desplegar un programa DSPy sin FastAPI?
Sí. Cualquier framework de servidor Python vale (Flask, Litestar, aiohttp). También puedes exportar el programa como servidor MCP con FastMCP y consumirlo desde Claude Desktop o cualquier cliente MCP.
¿Cómo versiono los programas compilados en git?
Guarda el JSON generado por compiled.save() como cualquier artefacto: súbelo al repositorio, referencia el hash SHA256 en tu tabla de releases y automatiza la evaluación en CI. Si el score cae más de un umbral, bloquea el merge.
¿DSPy es adecuado para agentes con muchas herramientas?
Sí, mediante dspy.ReAct (o ReActV2 en la rama 3.3). Cada tool se declara como función Python normal y DSPy la convierte en dspy.Tool. Para orquestaciones muy complejas conviene combinar DSPy con LangGraph o con el Claude Agent SDK.
Conclusión
Programar LLMs con DSPy en agosto de 2026 significa cambiar la mentalidad: en lugar de pelearte con cadenas de texto, defines contratos tipados, compones módulos, escribes una métrica y dejas que MIPROv2 encuentre el mejor prompt sobre tus datos. Las mejoras del 10 al 40 por ciento respecto al prompting manual no son marketing: aparecen incluso con datasets modestos (20-50 ejemplos) y son reproducibles. Combinado con Claude Opus 5 para la inferencia final y un modelo mini como juez, obtienes un pipeline óptimo en coste y calidad.
El siguiente paso natural: exporta tu programa compilado como servidor MCP (revisa nuestro tutorial de FastMCP), intégralo con un agente autónomo del Claude Agent SDK y orquesta varios agentes con LangGraph. En un mes puedes tener un sistema que se auto-mejora en producción cada vez que llega nueva anotación humana.
Comentarios
Inicia sesion para dejar un comentario
Acceder