English · Español
Lab 00 — Definir el esquema del conjunto de probes; cargar 60 ejemplos etiquetados¶
Objetivo: fijar el esquema de probes, construir el loader y validar un conjunto inicial de 60 probes curados a mano que cubren la rejilla de gramática verbal §A13.
Tiempo estimado: 90-120 minutos.
Prerrequisito: el corpus de la Fase 12 está commiteado;
data/corpus_spec.mddescribe las convenciones de etiquetado.
Lo que produces¶
Un módulo nuevo:
src/eval/probes.py— validador de esquema + loader.tests/eval/test_probes.py— tests de esquema + detección de fugas (leaks).
Un archivo de datos nuevo:
data/eval/probes.jsonl— 60 probes núcleo (Claude los aporta al abrir la fase como semilla; Borja los extiende si quiere).data/eval/probe_prompt.txt— la plantilla de prompt fija.data/corpus_spec.md— ampliado con una sección "Probe schema".
TODOs¶
Bloque A — extender data/corpus_spec.md¶
Añade una sección "Probe schema" que documente:
- Campos requeridos y sus tipos.
- Valores permitidos para
language,verb,regularity,tense,person,label,category. - Reglas de validación (unicidad de
id, prevención de fugas,expected∈candidates, consistencia regularidad↔verbo). - La matriz de cobertura de
theory/03.
Bloque B — implementar src/eval/probes.py¶
from dataclasses import dataclass
from pathlib import Path
import hashlib, json
REGULAR_VERBS = {"work", "play", "walk", "talk", "listen",
"watch", "study", "finish", "start", "look",
"want", "like"}
IRREGULAR_VERBS = {"be", "have", "do", "go", "come",
"see", "eat", "write"}
TARGET_VERBS = REGULAR_VERBS | IRREGULAR_VERBS # 20
TENSES = {"infinitive", "present_simple", "past_simple",
"past_participle", "future"}
PERSONS = {"1sg", "2sg", "3sg"}
LANGUAGES = {"en", "es"}
LABELS = {"correct", "incorrect", "ambiguous"}
CATEGORIES = {"core", "adversarial"}
@dataclass(frozen=True)
class Probe:
id: str
language: str
verb: str
regularity: str # "regular" | "irregular"
tense: str
person: str
prompt: str
candidates: tuple[str, ...]
expected: str
label: str
category: str
reason_code: str | None = None
explanation: str | None = None
source: str = "hand"
def load_probes(path: Path) -> list[Probe]: ...
def validate_probes(probes: list[Probe],
train_hashes: set[str],
val_hashes: set[str]) -> list[str]:
"""Returns list of error strings; [] = clean."""
...
def normalize_prompt(prompt: str, expected: str) -> str:
"""Whitespace+lowercase normalization; replaces non-verb content
nouns with <NOUN> for stricter leak detection."""
...
def hash_prompt(prompt: str, expected: str) -> str:
"""SHA256 hex of the normalized form."""
...
-
load_probesparsea JSONL a dataclassesProbe(tupla paracandidates). -
validate_probesejecuta cada chequeo detheory/03§"Probe-set checks": - corrección del esquema (todos los campos requeridos presentes, tipos correctos)
- unicidad de id
verb ∈ TARGET_VERBS,tense ∈ TENSES,person ∈ PERSONS,language ∈ LANGUAGESlabel ∈ LABELS,category ∈ CATEGORIESexpected ∈ candidatesregularitycoincide con la clase del verbo- leakage contra
train_hashesyval_hashes - mínimos de la matriz de cobertura
-
normalize_promptelimina comentarios, normaliza espacios en blanco, pasa a minúsculas, reemplaza nombres de contenido incidentales por<NOUN>(heurística aproximada — lista las limitaciones en el docstring). -
hash_promptdevuelve SHA256 hex del par(prompt, expected)normalizado.
Bloque C — escribir la plantilla de prompt¶
data/eval/probe_prompt.txt (formato de opción múltiple):
// Task: pick the grammatically correct verb form for the blank.
// Allowed forms are listed; pick exactly one.
//
// Sentence: {prompt}
// Candidates: {candidates_joined}
// Answer:
Para probes en formato de clasificación (la frase ya tiene una forma; clasificar como CORRECT/INCORRECT/AMBIGUOUS), una plantilla paralela vive en data/eval/classify_prompt.txt:
// Task: classify the grammaticality of the verb form in the sentence below.
// Categories: CORRECT | INCORRECT | AMBIGUOUS
// CORRECT: the verb agrees with the subject's person and the sentence's tense.
// INCORRECT: the verb form is wrong for the subject or the tense.
// AMBIGUOUS: the sentence is grammatical in more than one reading (e.g., "You work").
//
// Sentence: {sentence}
// Classification:
Ambos archivos están congelados por ejecución de eval. Cambios invalidan comparaciones entre ejecuciones.
Bloque D — semilla data/eval/probes.jsonl (60 ejemplos)¶
Claude aporta los 60 probes iniciales al abrir la fase. El trabajo de Borja en este lab es:
- Ejecutar
validate_probes(load_probes('data/eval/probes.jsonl'), train_hashes, val_hashes). - Corregir cualquier error de validación. Si un probe filtra contra train, reemplazarlo.
- Añadir al menos 5 probes propios — escoge combinaciones de verbo/tiempo/persona/idioma que sospechas que el modelo va a tener difícil según los informes de las Fases 18-19.
Bloque E — tests en tests/eval/test_probes.py¶
test_load_known_probes— parsea el archivo semilla sin error; count = 60.test_validate_clean_set— dado el archivo semilla y train_hashes vacío, devuelve[].test_detects_id_dup— dado un probe-set con dos probes que comparten id, devuelve un error de duplicación.test_detects_leak— dado un probe cuyo hash prompt-expected coincide con un train hash, devuelve un error de leakage.test_detects_regularity_mismatch— un probe converb="go"yregularity="regular"es rechazado.test_detects_expected_not_in_candidates— un probe conexpected="ate"perocandidates=["eat","eats","eaten"]es rechazado.test_coverage_matrix_minima— dado un probe-set que carece de la lenguaespor completo, devuelve un error de cobertura.test_normalize_idempotent—normalize_prompt(normalize_prompt(p, e), e) == normalize_prompt(p, e).
Restricciones¶
- Python puro + biblioteca estándar. Sin validadores de esquema externos (Pydantic, etc.); un dataclass con algunos chequeos basta.
- La detección de fugas es obligatoria. Sin excepciones.
- Los probes están en JSONL, no en YAML / array JSON. Una línea por probe, conceptualmente append-only.
- EN y ES conviven en el mismo archivo — se distingue por el campo
language, no por ruta de archivo. (Algunos equipos separan por idioma; nosotros no, porque la lógica de slicing se simplifica.)
Condiciones de parada¶
Hecho cuando:
pytest tests/eval/test_probes.py -vpasa todos los tests.data/eval/probes.jsonltiene ≥ 60 probes que pasanvalidate_probescontra train+val de la Fase 12.- Se cumplen los mínimos de cobertura (≥ 30 EN, ≥ 30 ES, ≥ 30 regulares, ≥ 30 irregulares, ≥ 10 por tiempo, ≥ 15 por persona).
data/corpus_spec.mdestá ampliado con el Probe schema.
Trampas¶
- Normalizar sin preservar la forma verbal. Si
normalize_promptelimina la forma verbal junto con los sustantivos del entorno, no puede detectar fugas correctamente. Preserva la forma verbal; reemplaza solo los sustantivos de contenido incidentales. - Olvidar que
ambiguouses una clase real. Algunas frases en 2sg son genuinamente ambiguas entre 2sg y plural en inglés; márcalasambiguous, nocorrect. - Probes del lado español que en realidad son spanglish. Mantén coherencia dentro de un probe:
language: essignifica que el prompt entero está en español, incluidos los marcadores temporales. - Colisiones de
identre archivos.probes.jsonlyadversarial.jsonlcomparten el namespace de id. Usa convenciones de prefijo tipoprobe-...yadv-.... - Confusión con la lista de verbos irregulares.
be / have / do / go / come / see / eat / write— escríbela una vez, refiérete a esa lista. Evita confiar en la memoria a mitad de redactar probes.
Cuándo consultar solutions/¶
Después de que pasen los ocho tests. La solución en solutions/00-probe-schema-ref.md (escrita al abrir la fase) recorre la función de normalización y el chequeo de la matriz de cobertura.
Siguiente lab: lab/01-harness-perplexity-accuracy.md.