Corregir lo que reportó ruff, que nunca se había ejecutado

ruff estaba configurado en pyproject.toml desde el primer commit y jamás se
había corrido. Tenía 15 hallazgos. Dos importan más allá del estilo:

- zip() sin strict= trunca en silencio al más corto. En las comparaciones de
  lotes eso significa que un test podía pasar sin haber comparado todo. Donde
  los largos deben coincidir ahora es strict=True; donde difieren a propósito
  (pares consecutivos) queda strict=False, que documenta la intención.
- Un import sin usar delataba algo peor: BraveBackend se había escrito sin una
  sola prueba. Se agregan ocho, contra una respuesta con la forma que devuelve
  la API, incluidas la limpieza de etiquetas, el caso de límite de tasa —que
  tiene que distinguirse de 'no respondió'— y que la credencial viaje en la
  cabecera y nunca en la URL. El respaldo tiene que funcionar justo cuando el
  primario ya falló; merecía la misma cobertura.

El resto es orden de imports, collections.abc y líneas largas.
This commit is contained in:
2026-07-28 07:25:51 -03:00
parent ba61125a3b
commit 4048936067
11 changed files with 185 additions and 64 deletions
+4 -1
View File
@@ -107,7 +107,10 @@ def test_rechaza_gqa_incoherente(tmp_path):
def test_rechaza_schedule_que_no_entra(tmp_path):
_espera_error(
_raiz(tmp_path, train={"max_steps": 100, "schedule": {"warmup_steps": 80, "decay_steps": 80}}),
_raiz(
tmp_path,
train={"max_steps": 100, "schedule": {"warmup_steps": 80, "decay_steps": 80}},
),
"no quedaría fase estable",
)
+5 -9
View File
@@ -8,8 +8,9 @@ no el SDK de Anthropic.
from __future__ import annotations
import json
from collections.abc import Iterator
from pathlib import Path
from typing import Any, Iterator
from typing import Any
import pytest
@@ -105,8 +106,7 @@ def trabajo(tmp_path: Path) -> Trabajo:
def _peticiones(n: int) -> list[Peticion]:
return [
Peticion(system="Sos ENLACE.", user=f"consulta {i}", meta={"semilla": i})
for i in range(n)
Peticion(system="Sos ENLACE.", user=f"consulta {i}", meta={"semilla": i}) for i in range(n)
]
@@ -229,9 +229,7 @@ def test_collect_espera_a_los_lotes_en_curso(trabajo, config):
cliente = ClienteFalso(rondas_hasta_terminar=2)
submit(trabajo, _peticiones(3), cliente, config)
dormidas = []
resumen = collect(
trabajo, cliente, config, ahora=lambda: 0.0, dormir=dormidas.append
)
resumen = collect(trabajo, cliente, config, ahora=lambda: 0.0, dormir=dormidas.append)
assert resumen["ok"] == 3
assert dormidas # efectivamente esperó
@@ -241,9 +239,7 @@ def test_collect_respeta_el_tope_de_espera(trabajo, config):
submit(trabajo, _peticiones(3), cliente, config)
reloj = iter([0.0, 1e9, 1e9])
with pytest.raises(DistillError, match="max_wait_hours"):
collect(
trabajo, cliente, config, ahora=lambda: next(reloj), dormir=lambda _: None
)
collect(trabajo, cliente, config, ahora=lambda: next(reloj), dormir=lambda _: None)
def test_collect_sin_lotes_da_un_error_util(trabajo, config):
+2 -2
View File
@@ -46,7 +46,7 @@ def test_restaurar_el_estado_continua_la_misma_secuencia(texto_es):
b.load_state_dict(estado)
obtenido = [b.next_batch("train")[0] for _ in range(3)]
for e, o in zip(esperado, obtenido):
for e, o in zip(esperado, obtenido, strict=True):
assert torch.equal(e, o)
@@ -62,7 +62,7 @@ def test_evaluar_no_altera_la_secuencia_de_entrenamiento(texto_es):
b.next_batch("val")
lotes.append(b.next_batch("train")[0])
for e, o in zip(esperado, lotes):
for e, o in zip(esperado, lotes, strict=True):
assert torch.equal(e, o)
+2 -1
View File
@@ -144,9 +144,10 @@ def test_z_loss_penaliza_logits_grandes():
def test_el_modelo_del_plan_pesa_lo_esperado():
"""tiny-50m tiene que estar cerca de 50M: es lo que entra en la 2060."""
from enlace.config.load import load_config
from pathlib import Path
from enlace.config.load import load_config
cfg = load_config(Path(__file__).resolve().parents[1] / "configs/runs/pretrain-2060.yaml")
model = Transformer(cfg.model, "math")
assert 45e6 < model.num_parameters() < 55e6
+4 -6
View File
@@ -7,9 +7,7 @@ from enlace.train.schedules import build_lr_fn
def _lr_fn(max_steps=1000, warmup=100, decay=200, min_ratio=0.0, base=1e-3):
cfg = ScheduleConfig(
kind="wsd", warmup_steps=warmup, decay_steps=decay, min_lr_ratio=min_ratio
)
cfg = ScheduleConfig(kind="wsd", warmup_steps=warmup, decay_steps=decay, min_lr_ratio=min_ratio)
return build_lr_fn(cfg, base, max_steps)
@@ -28,7 +26,7 @@ def test_la_fase_estable_es_plana():
def test_el_decay_baja_de_forma_monotona_hasta_cero():
lr = _lr_fn()
valores = [lr(s) for s in range(800, 1000)]
assert all(a >= b for a, b in zip(valores, valores[1:]))
assert all(a >= b for a, b in zip(valores, valores[1:], strict=False))
assert valores[0] == 1e-3
assert valores[-1] < 1e-4
@@ -52,5 +50,5 @@ def test_extender_la_corrida_mueve_el_inicio_del_decay():
"""
corto = _lr_fn(max_steps=1000)
largo = _lr_fn(max_steps=2000)
assert corto(850) < corto(700) # ya está decayendo
assert largo(850) == largo(700) # todavía en la fase estable
assert corto(850) < corto(700) # ya está decayendo
assert largo(850) == largo(700) # todavía en la fase estable
+102
View File
@@ -7,6 +7,7 @@ no en producción. Nada en esta suite sale a internet.
from __future__ import annotations
import json
from pathlib import Path
import pytest
@@ -322,3 +323,104 @@ def test_la_config_del_repo_declara_brave_de_respaldo():
cfg = load_agent_config()
assert cfg.search.backend == "duckduckgo"
assert "brave" in cfg.search.fallbacks
# --- parser de Brave -------------------------------------------------------
#
# El respaldo tiene que funcionar justo cuando el primario ya falló, así que su
# parser merece la misma cobertura. La respuesta de abajo reproduce la forma que
# devuelve la API de Brave.
_BRAVE = {
"web": {
"results": [
{
"title": "Home Assistant",
"url": "https://www.home-assistant.io/",
"description": "Domótica <strong>libre</strong> y con privacidad.",
},
{
"title": "Home Assistant &mdash; Wikipedia",
"url": "https://es.wikipedia.org/wiki/Home_Assistant",
"description": "Software de automatización del hogar.",
},
{"title": "Sin url", "url": "", "description": "no debería aparecer"},
]
}
}
class _RespuestaFalsa:
def __init__(self, payload: dict) -> None:
self._cuerpo = json.dumps(payload).encode()
def read(self) -> bytes:
return self._cuerpo
def __enter__(self):
return self
def __exit__(self, *_):
return False
def _brave_con(payload, monkeypatch):
backend = BraveBackend(api_key="clave-de-prueba")
monkeypatch.setattr("urllib.request.urlopen", lambda *a, **k: _RespuestaFalsa(payload))
return backend
def test_brave_extrae_titulo_url_y_descripcion(monkeypatch):
resultados = _brave_con(_BRAVE, monkeypatch).search("home assistant", 5)
assert len(resultados) == 2 # el que no tiene url se descarta
assert resultados[0].title == "Home Assistant"
assert resultados[0].url == "https://www.home-assistant.io/"
def test_brave_limpia_etiquetas_y_entidades(monkeypatch):
resultados = _brave_con(_BRAVE, monkeypatch).search("q", 5)
assert "<strong>" not in resultados[0].snippet
assert "&mdash;" not in resultados[1].title
# Los espacios múltiples se colapsan, igual que en el parser de DuckDuckGo.
assert " " not in resultados[1].snippet
def test_brave_respeta_el_maximo(monkeypatch):
assert len(_brave_con(_BRAVE, monkeypatch).search("q", 1)) == 1
def test_brave_sin_resultados_devuelve_lista_vacia(monkeypatch):
assert _brave_con({"web": {"results": []}}, monkeypatch).search("q", 5) == []
def test_brave_respuesta_sin_la_clave_web_no_rompe(monkeypatch):
assert _brave_con({"type": "search"}, monkeypatch).search("q", 5) == []
def test_brave_distingue_el_limite_de_tasa(monkeypatch):
"""Si el respaldo también está limitado, el mensaje tiene que decirlo: es
un diagnóstico distinto de 'no respondió'."""
import urllib.error
def falla_429(*a, **k):
raise urllib.error.HTTPError("u", 429, "Too Many Requests", {}, None)
monkeypatch.setattr("urllib.request.urlopen", falla_429)
with pytest.raises(SearchError, match="límite de tasa"):
BraveBackend(api_key="x").search("q", 5)
def test_brave_manda_la_credencial_en_la_cabecera(monkeypatch):
"""La clave va en X-Subscription-Token, nunca en la URL: una credencial en
la query string termina en los registros de todos los intermediarios."""
capturado = {}
def espia(request, *a, **k):
capturado["url"] = request.full_url
capturado["headers"] = request.headers
return _RespuestaFalsa({"web": {"results": []}})
monkeypatch.setattr("urllib.request.urlopen", espia)
BraveBackend(api_key="secreta-123").search("q", 5)
assert "secreta-123" not in capturado["url"]
assert capturado["headers"]["X-subscription-token"] == "secreta-123"