El context sweep
diseñarás y ejecutarás un context sweep —misma tarea, semántica fija, longitud creciente— y construirás la curva calidad-vs-longitud de TU agente con el harness. Importa porque "falla a veces en runs largos" no te deja priorizar nada; una curva sí.
4.1El problema
Tienes una frase en tu cabeza sobre Magallanes: "falla a veces en runs largos". Es verdad. Y no te sirve para nada.
Intenta priorizar con ella. ¿A partir de cuántos tokens empieza a fallar? ¿Cuánta calidad pierde: un 5% o un 60%? Tu último cambio al system prompt, ¿lo mejoró o lo empeoró? La frase no responde ninguna de esas preguntas. Es una anécdota, no un dato.
Quizá pienses en buscar la respuesta en los benchmarks públicos. RULER, NoLiMa, LongMemEval miden la degradación con la longitud en muchos modelos. Son sólidos. Pero miden modelos, no tu agente: no usan tus tools, tu corpus de documentos ni tu loop ReAct. Te dicen que el fenómeno existe; no te dicen dónde se rompe Magallanes.
Necesitas un instrumento que mida tu sistema concreto. En esta lección lo construyes. Vas a coger el mismo encargo, lanzarlo a longitudes crecientes y dibujar una curva: calidad en el eje vertical, longitud del contexto en el horizontal. Esa curva convierte la anécdota en una decisión de ingeniería.
4.2Qué vas a poder hacer
Al terminar serás capaz de:
- Diseñar un context sweep que varíe solo la longitud, con la semántica de la tarea fija y reproducible.
- Ejecutar el sweep sobre Magallanes a ≥4 longitudes y evaluar cada run con el harness.
- Construir e interpretar la curva calidad-vs-longitud, localizando su punto de inflexión.
- Criticar un sweep mal diseñado y arreglar sus defectos.
Necesitas saber antes:
- De N0·L3 (El presupuesto de tu agente): qué partidas componen el contexto y cuál crece por turno. Lo recuperamos en 4.3.
- Del apéndice A (El harness en 30 minutos) o del curso de evals: qué tres piezas tiene una eval. También en 4.3.
- El esqueleto congelado de Magallanes: el grafo de LangGraph, sus 3 tools (
buscar,leer,escribir_seccion) y su flag de longitud parametrizable.
4.3Recupera
Antes de seguir, responde de memoria. Esto reactiva lo anterior y engancha lo nuevo.
- Una eval tiene tres piezas. ¿Cuáles son? (Pista: una entra, una juzga, una resume el veredicto.)
- De L3: en Magallanes, ¿qué partida del presupuesto crece más rápido por turno y por qué?
- ¿Qué tool de Magallanes es la que infla el contexto a propósito al meter un documento entero?
Comprueba tu respuesta
- Dataset (los casos de entrada con su contexto), grader (el juez que puntúa cada salida) y score (el número resultante). El apéndice A monta las tres con promptfoo. · fuente: apéndice A (
/cursos/context-engineering/diagnostico/el-harness-en-30-minutos). - El historial y los tool results dentro de él. El loop ReAct es append-only: todo resultado de tool se acumula en
messagesy se queda. La pendiente por turno la domina ese arrastre. · fuente: corpus A.6 (ACON, contexto append-only). leer(doc_id)— devuelve el documento completo, grande a propósito. Cadaleer()mete un bloque entero al contexto. · fuente: esqueleto congelado de Magallanes (N0·L3).
4.4El concepto
Qué es un context sweep
Aquí está el instrumento. Un context sweep es un experimento que evalúa la misma tarea a longitudes de contexto crecientes, manteniendo fija la semántica de la tarea. Variar una sola cosa —la longitud— y medir qué le pasa a la calidad.
La analogía: es un barrido de frecuencias en un amplificador. Inyectas el mismo tono a volúmenes crecientes y anotas a partir de dónde distorsiona. El tono no cambia; solo su nivel. Dónde falla la analogía: el volumen de audio es un escalar limpio; la "longitud" de contexto arrastra contenido, y ese contenido puede interferir activamente (distractores). Lo veremos en 4.5.
Este patrón no es invención del curso. LOCA-bench (arXiv:2602.07962, feb 2026) lo automatiza. Controla los estados del entorno "to regulate the agent's context length… while keeping the underlying task semantics fixed". · fuente: corpus A.7.
Tres plantillas replicables del corpus muestran cómo se varía esa longitud:
- LongMemEval (arXiv:2410.10813): embebe la pregunta en un historial sintético multi-sesión escalable. La degradación frente al oráculo cae un 30–60% según la variante. · fuente: corpus A.5/A.7.
- arXiv:2510.05381: varía el contexto de 0 a 30K con relleno irrelevante, o incluso con espacios en blanco, para aislar el efecto de la longitud del efecto del retrieval. Reporta degradación del 13,9%–85% con la longitud, incluso con retrieval perfecto. · fuente: corpus A.5.
- arXiv:2512.04307: inyecta trayectorias irrelevantes entre subtareas dependientes y mide tanto el éxito como las categorías de fallo. · fuente: corpus A.7.
Tu sweep de Magallanes copia la idea de 2510.05381: misma tarea, inyectar documentos irrelevantes para hacer crecer el contexto.
Las decisiones de diseño
Un sweep no es "correrlo a dos longitudes y ya". Cuatro decisiones lo hacen válido o lo invalidan.
Decisión 1 — al menos 4 longitudes. Con dos puntos solo puedes trazar una recta, y una recta esconde la forma. La degradación con la longitud no es lineal ni plana: tiene un codo. Necesitas ≥4 puntos para ver dónde está ese codo. Nuestro sweep usa 0, 10, 25 y 50 documentos irrelevantes inyectados.
Decisión 2 — qué relleno usas. Aquí hay una elección de fondo. Puedes rellenar con texto neutro (relleno "blanco", sin significado) o con distractores semánticos: documentos que se parecen al tema pero no contienen la respuesta. No dan lo mismo. Chroma observó que un solo distractor ya reduce el rendimiento frente al baseline, y cuatro lo componen: la degradación se acumula. · fuente: corpus A.1. El relleno blanco mide el coste bruto de la longitud; los distractores miden además la interferencia. Empezamos con documentos irrelevantes del corpus (que actúan como distractores suaves); en la práctica probarás ambos.
Decisión 3 — reproducibilidad: seed y encargo fijos. Un agente con LLM tiene aleatoriedad. Si cada run usa una semilla distinta, no sabrás si la curva bajó por la longitud o por el azar. Fijas el seed y mantienes el mismo encargo literal en las cuatro longitudes. Así la única variable que cambia es la que mides.
Decisión 4 — el punto de inflexión. No buscas un número, buscas una forma. El punto de inflexión es la longitud a partir de la cual la calidad se desploma: el codo de la curva. Es lo que convierte "falla a veces" en "se rompe en torno a los N documentos inyectados". Esa frase sí prioriza.
El error nº1: cambiar la tarea al cambiar la longitud
Normaliza esto, porque es el fallo más común y el más traicionero. Al alargar el contexto, mucha gente alarga también el encargo: pasa de "investiga 3 subtemas" a "investiga 12 subtemas". Parece razonable. Está mal.
Si haces eso, mezclas dos variables: la longitud del contexto y la dificultad de la tarea. Cuando la curva baje, no sabrás cuál de las dos la tiró. Estarás midiendo dificultad disfrazada de longitud.
La regla que lo evita: la semántica de la tarea se queda congelada. El encargo es idéntico —los mismos subtemas, la misma instrucción— en todas las longitudes. Lo único que crece es el relleno irrelevante que inyectas alrededor. Esa es la línea que separa un sweep que mide contexto de uno que mide otra cosa.
Lo que NO es un sweep
Un sweep no es correr Magallanes diez veces con encargos distintos y comparar. Eso mide qué encargos son difíciles, no cómo responde a la longitud. Tampoco es lanzar un único run muy largo y mirar si "se siente peor". Sin puntos intermedios no hay curva, y sin curva no hay codo que localizar.
4.5Míralo funcionar
Vamos a montar sweep.py: corre Magallanes con el mismo encargo a 4 longitudes, mide cada run con dos métricas deterministas y emite la curva. Es código con varias piezas encadenadas.
Antes de leerlo línea a línea, lee el bloque entero una vez de corrido. Lo difícil no es ninguna línea suelta; es ver el flujo. Un bucle fija la longitud, invoca el grafo, recoge la salida y la mide con dos comprobaciones de strings. Primero hazte una idea de la forma.
El harness: el provider de promptfoo
El grader del sweep es el harness del apéndice A. promptfoo llama a un provider Python que envuelve a Magallanes. La firma es exacta (corpus F.8): call_api recibe el prompt y devuelve un dict con output.
1# my_provider.py — el provider que promptfoo invoca por cada caso
2# Firma EXACTA de promptfoo (corpus F.8): call_api(prompt, options, context) -> {"output": ...}
3import json
4from magallanes import construir_grafo # el grafo congelado de N0·L3
5
6def call_api(prompt: str, options: dict, context: dict) -> dict:
7 # 'context' trae las vars del caso de test (cuántos irrelevantes inyectar, el seed).
8 cfg = context.get("vars", {})
9 n_irrelevantes = int(cfg.get("n_irrelevantes", 0))
10 seed = int(cfg.get("seed", 42))
11
12 # El flag del lab: el grafo inyecta n documentos irrelevantes del corpus alrededor
13 # del MISMO encargo (semántica fija). El seed fija la aleatoriedad.
14 graph = construir_grafo(n_irrelevantes=n_irrelevantes, seed=seed)
15 result = graph.invoke(
16 {"messages": [{"role": "user", "content": prompt}]},
17 config={"configurable": {"thread_id": f"sweep-{n_irrelevantes}"}},
18 )
19
20 informe = result["messages"][-1].content # el informe final de Magallanes
21 return {"output": informe} # 'output' es el único campo requeridoTres cosas de este provider:
construir_grafo(n_irrelevantes=, seed=)es la envoltura de laboratorio que expone el flag de longitud parametrizable — el mismo grafo congelado con la inyección de documentos irrelevantes activada.- El mismo
promptllega a las 4 longitudes. La semántica no cambia: lo único que varía esn_irrelevantes. call_apidevuelve{"output": informe}. promptfoo aplicará a ese output las assertions del config.
El config de promptfoo: las 4 longitudes y las métricas
1# promptfooconfig.yaml — un caso por longitud; provider Python; assertions del lab
2providers:
3 - id: 'file://my_provider.py'
4
5prompts:
6 - 'Investiga los subtemas del encargo y redacta el informe: {{encargo}}'
7
8# Las 3 métricas del lab: citas a doc_id (contains) + cobertura y coherencia (llm-rubric).
9defaultTest:
10 assert:
11 - type: contains
12 value: 'doc-' # ¿cita al menos un doc_id real del corpus?
13 - type: llm-rubric
14 value: >
15 El informe cubre los subtemas del encargo, no se contradice entre secciones,
16 y solo cita documentos que existen en el corpus. Penaliza repeticiones y datos inventados.
17
18tests:
19 - vars: { encargo: "El mismo encargo de 3 subtemas", n_irrelevantes: 0, seed: 42 }
20 - vars: { encargo: "El mismo encargo de 3 subtemas", n_irrelevantes: 10, seed: 42 }
21 - vars: { encargo: "El mismo encargo de 3 subtemas", n_irrelevantes: 25, seed: 42 }
22 - vars: { encargo: "El mismo encargo de 3 subtemas", n_irrelevantes: 50, seed: 42 }Mira el detalle que importa: el encargo es idéntico en los cuatro casos. Lo único que sube es n_irrelevantes. Ese es el control que separa medir contexto de medir dificultad. El seed fijo da reproducibilidad.
Las assertions son las del apéndice A: contains comprueba de forma barata que hay una cita a un doc_id, y llm-rubric juzga cobertura y coherencia. Son las firmas verificadas de promptfoo (corpus F.8) — sin flags inventados.
El driver: ejecutar el sweep y emitir la curva
El driver invoca el grafo él mismo y calcula DOS métricas deterministas en Python puro. La primera es cobertura de subtemas: ¿aparecen los strings esperados en el informe? La segunda son citas: ¿cita doc_id reales del corpus? No parsea ningún JSON externo, así que no se rompe entre versiones de otras herramientas.
1# sweep.py — corre Magallanes a 4 longitudes y dibuja la curva calidad-vs-longitud
2from magallanes import construir_grafo # el grafo congelado de N0·L3
3
4LONGITUDES = [0, 10, 25, 50] # nº de documentos irrelevantes inyectados (≥4 puntos)
5ENCARGO = "Investiga los subtemas del encargo y redacta el informe: ..."
6
7# Semántica congelada: lo esperado NO cambia con la longitud.
8SUBTEMAS_ESPERADOS = ["ruta", "tripulación", "financiación"] # los 3 subtemas del encargo
9DOC_IDS_CORPUS = ["doc-001", "doc-014", "doc-027"] # doc_ids reales del corpus del lab
10
11def correr_un_run(n_irrelevantes: int, seed: int = 42) -> str:
12 # Invoca el grafo directamente (sin proceso externo). El flag inyecta los irrelevantes.
13 graph = construir_grafo(n_irrelevantes=n_irrelevantes, seed=seed)
14 result = graph.invoke(
15 {"messages": [{"role": "user", "content": ENCARGO}]},
16 config={"configurable": {"thread_id": f"sweep-{n_irrelevantes}"}},
17 )
18 return result["messages"][-1].content # el informe final de Magallanes
19
20def medir(informe: str) -> float:
21 # Métrica 1 — cobertura: fracción de subtemas esperados presentes en el informe.
22 cubiertos = sum(1 for s in SUBTEMAS_ESPERADOS if s.lower() in informe.lower())
23 cobertura = cubiertos / len(SUBTEMAS_ESPERADOS)
24 # Métrica 2 — citas: ¿cita al menos un doc_id real del corpus?
25 cita_real = 1.0 if any(d in informe for d in DOC_IDS_CORPUS) else 0.0
26 return 0.5 * cobertura + 0.5 * cita_real # score determinista [0,1]
27
28def dibujar_curva(curva: dict[int, float]) -> None:
29 print("\nlongitud (docs irrelevantes) | calidad")
30 print("-" * 44)
31 for n in sorted(curva):
32 s = curva[n]
33 barra = "#" * int(round(s * 30)) # 30 columnas = score 1.0
34 print(f"{n:>3} docs | {s:0.2f} | {barra}")
35
36if __name__ == "__main__":
37 curva = {n: medir(correr_un_run(n)) for n in LONGITUDES}
38 dibujar_curva(curva)Léelo de arriba abajo. correr_un_run invoca el grafo congelado con n_irrelevantes y devuelve el informe. medir calcula el score determinista con dos comprobaciones de strings. dibujar_curva imprime la curva en ASCII. Es Python estándar —comprensiones de string, un par de bucles— sin depender de la estructura interna de ninguna salida ajena.
Cada métrica es una comprobación de strings que tú controlas: in sobre el informe y sobre los doc_id reales del corpus del lab. La curva del sweep usa estas dos métricas deterministas — los valores relativos entre longitudes son el objetivo, no la cifra absoluta de cada punto.
La verificación complementaria la corres aparte con promptfoo y su llm-rubric, como enseña el apéndice A:
1npx promptfoo eval -c promptfooconfig.yamlLee el veredicto en la tabla que promptfoo imprime —cobertura y coherencia juzgadas por el rúbrica—. El driver mide la curva; promptfoo aporta el juicio cualitativo. No mezclamos las dos: nada de leer el JSON de promptfoo desde Python.
El resultado: la curva
El sweep imprime una tabla y su curva. Estos números son ilustrativos —tu run dará los suyos—; lo que importa es la forma:
longitud (docs irrelevantes) | calidad
--------------------------------------------
0 docs | 0.92 | ###########################
10 docs | 0.85 | #########################
25 docs | 0.61 | ##################
50 docs | 0.34 | ##########
calidad
1.0 | *
| *
| *
0.5 |
| *
0.0 +----+----+----+----+----
0 10 25 50 docs irrelevantes
Lee la forma, no los dígitos. Entre 0 y 10 documentos, la calidad apenas se mueve: el contexto aún cabe sin daño. Entre 10 y 25 cae con fuerza: ahí está el punto de inflexión, el codo. De 25 a 50, ya rota, sigue cayendo. Tu agente no "falla a veces"; se rompe en torno a los 10–25 documentos inyectados. Eso ya prioriza.
Self-explanation
Antes de leer la respuesta, intenta tú: ¿por qué inyectamos documentos irrelevantes en vez de acortar el encargo en los runs cortos?
Razónalo y comprueba
Porque acortar el encargo cambiaría la tarea, no solo la longitud. Recuerda el error nº1 de 4.4. Si el run corto investiga 1 subtema y el largo investiga 12, la calidad podría caer por la dificultad extra, no por la longitud. No sabrías cuál fue la causa.
Inyectar irrelevantes mantiene la semántica congelada: el encargo es el mismo en las 4 longitudes. Lo único que cambia es cuánto material inútil rodea a la información que sí importa. Así aíslas el efecto de la longitud. Es lo que hace 2510.05381: varía el contexto con relleno irrelevante o blanco para separar el efecto de la longitud del de retrieval. · fuente: corpus A.5.
Si pensaste "para que el agente trabaje más", revisa. El objetivo no es más trabajo: es el mismo trabajo rodeado de ruido.
4.6Hazlo tú
Andamiaje decreciente: amplías el sweep con un punto y una variante de relleno, y comparas las dos curvas.
Ejercicio — una 5ª longitud y un relleno distinto
Tu sweep tiene 4 longitudes con documentos irrelevantes del corpus. Haz dos cambios:
- Añade una 5ª longitud: 100 documentos irrelevantes inyectados. Un punto más allá del codo confirma si la curva se aplana abajo o sigue cayendo.
- Crea una variante de relleno: además de los documentos irrelevantes (distractores suaves), corre el sweep con relleno blanco —texto neutro sin tema, al estilo de los espacios en blanco de 2510.05381—. Compara las dos curvas.
Pista de implementación: pasa una var nueva (p. ej. tipo_relleno: "distractor" | "blanco") en los tests del config, y léela en call_api para decidir qué inyecta construir_grafo. No hay solución cerrada; reutiliza el patrón de 4.5.
Elaborative interrogation — antes de correrlo, predice: ¿qué curva caerá antes, la de distractores o la de relleno blanco? ¿Por qué?
Comprueba tu predicción
Esperas que la curva de distractores caiga antes y más fuerte. El relleno blanco solo añade tokens: mide el coste bruto de la longitud. Los distractores añaden tokens que compiten por la atención del modelo, porque se parecen al tema sin contener la respuesta.
La evidencia: Chroma observó que un solo distractor ya baja el rendimiento frente al baseline, y cuatro lo componen. La degradación con distractores es mayor cuanto menor es la similitud semántica entre la información buscada y el ruido. · fuente: corpus A.1. Y 2510.05381 muestra que incluso el relleno blanco degrada —la longitud sola hace daño—, así que ninguna de las dos curvas será plana. · fuente: corpus A.5.
La diferencia entre ambas curvas es justo lo que separa "demasiados tokens" de "demasiada interferencia". Las dos importan; los distractores duelen más.
Feedback: si predijiste que el relleno blanco no afectaría nada, revisa A.5: la longitud por sí sola ya degrada, aun con retrieval perfecto. La curva blanca baja; la de distractores baja más.
4.7Comprueba
Sin pistas. Gate de maestría: criticar y arreglar un sweep mal diseñado.
Un compañero te pasa este sweep para validar Magallanes. Tiene tres defectos que lo invalidan. Encuéntralos y arréglalos.
1# sweep_de_tu_companero.yaml
2providers:
3 - id: 'file://my_provider.py'
4tests:
5 - vars: { encargo: "Investiga 2 subtemas y redacta", n_irrelevantes: 0 }
6 - vars: { encargo: "Investiga 12 subtemas y redacta", n_irrelevantes: 60 }- Identifica los tres defectos.
- Explica qué medirías mal con cada uno.
- Arregla el sweep.
Criterio de corrección + feedback
Los tres defectos:
-
El encargo cambia con la longitud. El run corto investiga 2 subtemas; el largo, 12. Es el error nº1: mezcla longitud y dificultad. Si la calidad cae, no sabes cuál la tiró — mides dificultad, no contexto. Arreglo: congelar la semántica — el mismo encargo idéntico en todas las longitudes; solo varía
n_irrelevantes. -
Solo 2 puntos. Con dos longitudes solo trazas una recta, y la recta esconde el codo. No puedes localizar el punto de inflexión. Arreglo: al menos 4 longitudes (p. ej. 0, 10, 25, 50) para ver la forma.
-
No hay seed. Sin semilla fija, la aleatoriedad del LLM contamina la comparación: no sabrás si la curva bajó por la longitud o por el azar. Arreglo: fijar
seed(el mismo en todos los casos).
El sweep arreglado:
1providers:
2 - id: 'file://my_provider.py'
3tests:
4 - vars: { encargo: "Investiga 3 subtemas y redacta", n_irrelevantes: 0, seed: 42 }
5 - vars: { encargo: "Investiga 3 subtemas y redacta", n_irrelevantes: 10, seed: 42 }
6 - vars: { encargo: "Investiga 3 subtemas y redacta", n_irrelevantes: 25, seed: 42 }
7 - vars: { encargo: "Investiga 3 subtemas y redacta", n_irrelevantes: 50, seed: 42 }(Faltan las assertions; añádelas en defaultTest con contains + llm-rubric, como en 4.5.)
Feedback formativo: si encontraste el defecto 1 (el encargo que cambia), dominas lo esencial — es el que invalida el experimento de raíz. Si te faltaron el 2 o el 3, repasa las decisiones de diseño en 4.4: el nº de puntos y el seed no son adornos; son lo que hace que la curva sea legible y comparable. Gate: necesitas el defecto 1 identificado y arreglado para superar este punto.
4.8Conecta
Acabas de construir un instrumento, no de hacer un ejercicio. El sweep ES el instrumento del checkpoint C0.
La curva que produces es el corazón del diagnóstico de C0. Ya no entregas "Magallanes falla en runs largos": entregas su curva con su punto de inflexión. Reproducible, con seed fijo y semántica congelada. Eso es lo que la rúbrica C0 te pide demostrar.
Y va más allá del diagnóstico. Esta curva es el baseline que los próximos niveles deben batir. En N1 atacas el historial sin gestión; en N3, las tools y los tool results; en N4, el encargo que excede una ventana. Cada palanca que apliques se mide contra esta curva: ¿movió el codo a la derecha? ¿levantó la cola? Sin el baseline de hoy, "mejoró" sería otra anécdota.
Lo que acabas de medir, ningún benchmark público te lo da. RULER, NoLiMa y LongMemEval miden modelos. Tu curva mide tu agente con tus tools sobre tu corpus. Esa es la diferencia entre saber que el fenómeno existe y saber dónde se rompe tu sistema.
Cierra el arco que abrimos en 4.1: tenías una frase inútil. Ahora tienes una curva con un codo y un número de inflexión. La anécdota es un dato.
4.9Reflexiona
Tómate un minuto. Responder esto por escrito consolida lo aprendido mejor que releer.
- ¿Qué aprendiste? Resume en una frase por qué congelar la semántica de la tarea es lo que hace válido un sweep.
- ¿Qué sigue sin estar claro? ¿Tienes claro la diferencia entre relleno blanco y distractores semánticos, y por qué Chroma importa aquí? Si no, vuelve a 4.4, decisión 2.
- ¿Qué harías distinto? La próxima vez que alguien diga "el agente falla en contextos largos", ¿qué tres preguntas le harías antes de aceptar la afirmación?
Esto requiere práctica. La intuición de "qué variable muevo de verdad" llega corriendo sweeps, no leyendo. En L5 integrarás esta curva con los modos de fallo de L2 y el presupuesto de L3 en el informe de C0.