4 Commits

Author SHA1 Message Date
msaldain 59b7c3acb6 Un respaldo de búsqueda sin credencial se omite en vez de romper la carga
Python / calidad (push) Successful in 3s
Python / tests (push) Successful in 11s
YAML / yaml (push) Successful in 4s
El CI, apenas empezó a ejecutarse de verdad, hizo fallar dos de sus tres
trabajos. La causa es un defecto de diseño, no del CI: la config del agente
declara a Brave como respaldo, y la validación exigía la credencial para poder
*leer* el archivo. Como la config vive en el repositorio y la credencial no, un
clon limpio quedaba sin poder cargar su propia configuración. Mis pruebas en
Gigastar no lo veían porque acá el archivo .env existe.

Ahora se distingue por rol, que es lo que corresponde:

- El primario sin credencial sigue siendo error fatal. Sin él no queda ninguna
  búsqueda en pie, y descubrirlo en la primera consulta real es tarde.
- Un respaldo sin credencial se cae de la cadena y el primario sigue andando.
  Degradarse es la respuesta correcta: tener red de seguridad es mejor que no
  tenerla, pero no tenerla es mejor que no arrancar.

Omitir no es esconder. La propiedad respaldos_omitidos deja el motivo a la
vista, y build_backend emite un aviso al construir la cadena, que es el punto
por el que pasa cualquier entrypoint que use búsqueda.

Verificado escondiendo .env y corriendo la suite y el validador como lo haría
un clon limpio: 124 tests en verde con credencial y sin ella.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 07:48:29 -03:00
msaldain 4048936067 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.
2026-07-28 07:25:51 -03:00
msaldain 36040d7cc8 Brave como respaldo de DuckDuckGo, y arreglo de la guardia de bf16
Cadena de búsqueda con respaldo. DuckDuckGo sigue de primario porque no exige
credenciales: en el camino habitual ningún tercero se entera de qué busca la
familia. Pero su endpoint lite no es una API con contrato, así que cuando
limite por tasa o cambie el HTML, Brave responde.

Dos matices del encadenado:

- Cero resultados NO dispara el respaldo. Si el primario respondió bien y no
  encontró nada, esa es la respuesta correcta; encadenar gastaría cuota y
  devolvería resultados peores. Solo se avanza ante un error real.
- La cadena recuerda quién respondió: al depurar una respuesta rara, lo
  primero que hay que saber es de dónde salió.

Un respaldo sin credencial falla al arrancar y no en la primera consulta —
que es justo cuando el primario ya falló y el respaldo tiene que funcionar.

Aparte, verificando torch en la 2060 apareció un defecto real en backends.py:
torch.cuda.is_bf16_supported() usa including_emulation=True por defecto, así
que devuelve True en Turing, donde bf16 no existe en silicio. La guardia no
habría atrapado el perfil de la 5090 corriendo en la 2060: el entrenamiento
seguía, emulado y silencioso. Medido en la placa: 4,49 ms por matmul en bf16
contra 1,75 ms en fp16, unas 2,6x más lento. Ahora se consulta con
including_emulation=False, con respaldo por compute capability.

11 tests nuevos (113 en total).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 00:39:13 -03:00
msaldain 179062a5d4 Búsqueda web con DuckDuckGo, sin API key
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>
2026-07-27 23:15:13 -03:00