Un patrón sin "/" inicial en .gitignore matchea cualquier directorio con ese
nombre en cualquier nivel. "data/" y "runs/" estaban excluyendo, además de lo
que se quería:
enlace/data/ los cargadores de datos (ByteStream, ShardStream)
configs/data/ corpus.yaml, smoke.yaml, distill.yaml
configs/runs/ todos los configs ejecutables
Es decir que el repo commiteado no contenía ni la capa de datos ni un solo
config con el que arrancar un entrenamiento. Como scripts/remote.sh sincroniza
por git push/pull, el servidor habría recibido un paquete que falla al
importar, y el síntoma habría aparecido recién allá.
Se anclan las cuatro rutas con "/" inicial y se documenta el porqué en el
propio archivo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Etapa 3 del plan. Se elige la Batch API sobre llamadas sueltas porque cuesta
la mitad y generar un dataset no es sensible a la latencia: es exactamente su
caso de uso. A cambio el trabajo es asincrónico y puede tardar horas, así que
todo el estado vive en disco y es reanudable — matar el proceso, perder la
conexión o reiniciar el servidor no pierde nada ni gasta de nuevo.
Cuatro decisiones que sostienen eso:
- El custom_id se deriva del hash del contenido del pedido. El trabajo queda
idempotente: re-generar la misma especificación produce los mismos IDs, así
que lo ya resuelto se reconoce y no se vuelve a pagar.
- Los resultados se indexan por custom_id, nunca por posición. La API los
devuelve en cualquier orden, y emparejarlos por índice mezcla las respuestas
en silencio; un dataset mal alineado es peor que uno vacío porque parece
correcto. El cliente falso de los tests los invierte a propósito.
- Los fallos se registran en errors.jsonl con su motivo en vez de descartarse,
y --retry-failed reenvía solo esos.
- El prompt de sistema compartido va marcado para caché: es idéntico entre
miles de pedidos, y a ~0,1x del precio de entrada deja de contar.
Los lotes se persisten después de cada envío y no al final, para que una caída
a mitad de camino no deje lotes huérfanos sin registrar.
El SDK de anthropic se importa de forma perezosa y queda como extra opcional:
el servidor de entrenamiento no lo necesita y el paquete importa sin él.
21 tests nuevos (92 en total), todos contra un cliente falso — lo que hay que
verificar es el harness, no el SDK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Solo se usa en la Etapa 3, y solo para la parte difícil del dataset SFT
(identidad y casos límite). El grueso sale de plantillas —cero modelo— y
de un 7-8B local en la 2060.
Se documenta además que una suscripción Pro/Max no sirve como credencial
programática: autentica claude.ai y Claude Code, no la API.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Primera tool del agente. No depende del modelo, así que se puede usar y
verificar de punta a punta antes de que exista el modelo propio.
Sobre el endpoint: la API oficial de DuckDuckGo ("Instant Answer") no sirve
para esto — responde definiciones y fichas de entidades, y para una consulta
normal devuelve 200 con el cuerpo vacío. Los resultados web reales solo están
en el endpoint lite, que no es una API con contrato: el HTML puede cambiar y
hay límite de tasa. Por eso el backend está detrás de una interfaz y queda
implementado también SearxNG autoalojado, que es un cambio de una línea de
config cuando haga falta.
Decisiones:
- Presupuesto de contexto explícito: con seq_len 2048 los resultados compiten
con la memoria recuperada y el turno del usuario, así que se devuelven cinco
recortados en vez de diez completos.
- Las redirecciones /l/?uddg= se desenvuelven al destino real: guardarlas
ensuciaría la memoria episódica, donde dos búsquedas a la misma página
parecerían páginas distintas.
- Caché con TTL, que es de comportamiento y no de rendimiento: en una casa las
mismas preguntas se repiten muchas veces por día.
- Sin resultados devuelve "Sin resultados." en vez de vacío, para que el modelo
pueda decir que no encontró nada en lugar de inventar.
Se quitan Brave y Tavily de .env.example: ya no hay credenciales que gestionar
ni un tercero al que informarle qué busca la familia.
23 tests nuevos (71 en total), contra una respuesta real guardada como fixture:
el parser es lo que se rompe cuando cambia el markup, y tiene que fallar en la
suite y no en producción. Ningún test sale a internet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Base del proyecto ENLACE: un modelo de lenguaje propio entrenado desde cero,
en español, para asistencia general y familiar. El plan completo está en
docs/PLAN.md.
Esta etapa establece el andamiaje y lo verifica de punta a punta:
- Configuración por capas (hardware × model × train × data) validada con
pydantic. Ningún hiperparámetro vive en el código y una config inválida
falla al arrancar, no a las tres horas de entrenamiento.
- Perfiles de hardware que aíslan el salto de GPU: la RTX 2060 (Turing) no
soporta bfloat16 ni FlashAttention-2, así que entrena en float16 con
GradScaler y backend mem_efficient; el perfil de la 5090 ya está escrito.
backends.py valida el perfil contra la GPU real antes de empezar.
- Transformer decoder-only estilo Llama: RMSNorm, SwiGLU, RoPE, GQA,
embeddings atados, QK-norm y z-loss. Los dos últimos son lo que mantiene
estable el entrenamiento en float16.
- Entrenador con schedule WSD, acumulación de gradiente, precisión mixta,
checkpointing atómico y reanudación exacta.
- Cargadores de datos con estado serializable: bytes para el smoke test y
shards uint16 para el corpus real.
48 tests, entre ellos el crítico: reanudar desde un checkpoint reproduce los
pesos de una corrida ininterrumpida, parámetro por parámetro.
Verificado en CPU: 300 pasos sobre texto en español, loss 3.07 -> 1.63.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>