Skip to content

fix(docs): los restos de la migracion, los techos por minuto, la deprecacion del web search y la politica de flux - #75

Open
borjaperfra wants to merge 8 commits into
mainfrom
fix/docs-pre-launch
Open

borjaperfra wants to merge 8 commits into
mainfrom
fix/docs-pre-launch

Conversation

@borjaperfra

@borjaperfra borjaperfra commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Empezo como triaje de una revision externa antes de lanzar y acabo siendo cuatro cosas, tres de ellas cifras publicadas que eran falsas.

1. Los techos por minuto estaban mal los cuatro

cloud-api no guarda un TPM: modelRateLimits guarda ventana de contexto y FillsPerMin, y el techo es el producto.

modelo ventana x fills real publicaba
deepseek-v4-flash 1.048.576 x 6 6,3M 1.5M
mimo-v2.5 1.050.000 x 4 4,2M 1.5M
qwen3.6 262.144 x 12 3,1M 1.5M
gemma4 262.144 x 4 1,0M 1.5M
glm5.3-flash 1.048.576 x 11 11,5M no aparecia
qwen3.8-flash 1.048.576 x 6 6,3M no aparecia

gemma4 prometia de mas: su techo real esta por debajo del anunciado, asi que quien planificara contra la pagina se comia 429 que la pagina decia imposibles. Y rerank no tiene 1000 rpm: esta en rateLimitExemptModels, sin limite por minuto propio.

Ahora se guardan los dos factores y el producto se deriva. El extractor de /api/docs tenia el mismo supuesto dentro: metia los cuatro en una fila y cogia tpm[0].

2. La busqueda web y el servidor MCP, deprecados

Sin fecha todavia. Y como la busqueda web es la unica herramienta que el MCP tiene, el servidor se va con ella: no tiene sentido avisar de media.

Seis superficies: las dos paginas /docs/mcp, la seccion de Ejemplos, /search y /mcp en la spec con deprecated: true (lo que Scalar pinta como badge y lo que recoge un cliente generado), los dos tags, y los enlaces cruzados desde intro, agent-setup, claude-code, gentle-ai y agents.

3. La politica de flux, que estaba publicada y era falsa

Dos sorpresas. Flux no pasa por LiteLLM: lo sirve cloud-api contra Workers AI, asi que los 60/min y las 5 a la vez de la key no le aplican. Y ya estaba publicada, mal: Ejemplos decia "20 peticiones por minuto", cifra que no aparece en ninguna parte de la plataforma. Lo real, de imagegen.NewRateLimiter(1, 3) y MonthlyQuota: 1 peticion por segundo con rafaga de 3, 100 al mes natural, hasta 4 imagenes por peticion y una peticion que pida varias sigue costando una.

No hizo falta tocar cloud-api: flux ya tenia politica propia y deliberada, solo que en otro subsistema.

4. La web, preparada para leer la politica

Cableado todo menos la URL. LIMITS_POLICY_URL sin poner, asi que se usa la tabla interna y no se hace ninguna peticion - verificado sirviendo la pagina. Cuatro reglas: un fallo nunca se ve, rechaza un documento mas nuevo del que entiende, todo o nada (nunca parchea un hueco desde la tabla interna), y publica solo los modelos del catalogo, porque la politica incluye glm5.2 y esta web no lo documenta.

Falta decidir de donde lee: /api/internal/limits/policy va detras del CreditsHookSecret, y la respuesta estrecha es una ruta publica de solo lectura, porque nada de ese documento es secreto.

Y los cuatro restos de la migracion con los que empezo esto

Las dos rutas de Windows del CLI con el \n y el \t ya gastados; la pagina diciendo dos veces que en Windows hay que compilar cuando v0.1.19 publica binarios; /rerank vendido como compatible con OpenAI en el cuerpo; y el chrome de /es/docs en ingles.

Los tests

Los 1150 pasaban con todo esto puesto. Ahora 1294. Cada regla vista en rojo antes, y las que mas importan comprobadas rompiendolas a proposito de varias formas:

  • ningun tabulador literal, ningun span de codigo inline sin cerrar - las dos marcas que deja un escape ya interpretado;
  • una pagina que instala una plataforma desde binario publicado no puede decirle que compile;
  • los factores anclados a cloud-api, no el producto, y todo modelo del catalogo contabilizado en algun sitio;
  • flux anclado a su propio limitador y obligado a no aparecer en las tablas de LiteLLM, porque una fila alli afirmaria que los 60/min le alcanzan;
  • si una pagina enlaza a algo deprecado, lo dice - buscando por escaneo, no por lista, porque el caso que no se puede cubrir a mano es la pagina que aun no existe;
  • el lector de politica, sobre todo por sus formas de fallar.

Lo que la revision externa decia y no era

La ES de Primeros pasos esta bien. Lo que leyeron -LiteLLM, qwen3.6, @ai-sdk/openai- existio, pero git log -S'LiteLLM' lo situa antes del #56: leyeron cache. Igual los "60 rpm" de la portada, que salen de un bloque i18n muerto. La navegacion ES no esta stale (misma fuente, paridad perfecta, ya habia test). Lo de MiMo y Pi ya estaba escrito.

🤖 Generated with Claude Code

borjaperfra and others added 5 commits September 15, 2026 11:01
…tados

`%LOCALAPPDATA%\Programs\nan` llegaba a la pagina partido en dos por un
salto de linea de verdad ("Programs" / "an") y `-InstallDir "C:\tools"`
con un tabulador donde iba la barra ("C:<TAB>ools"). Los dos en ingles y
en espanol, desde el #60.

Ninguno es un error de sintaxis en ningun sitio: la linea de PowerShell
corre perfectamente, solo que instala donde no es. Y una ruta de Windows
es justo la cadena que nadie de este equipo vuelve a pegar, asi que
sobrevivieron a la revision y a la traduccion.

Las dos rutas estan comprobadas contra el install.ps1 que se sirve hoy:
$InstallDir = Join-Path $env:LOCALAPPDATA 'Programs\nan', y -InstallDir
es un parametro suyo de verdad.

Los 1150 tests pasaban con el bug puesto. docsSnippets EJECUTA los
snippets, y un snippet que corre es todo lo que pide; estas dos reglas
nuevas miran el residuo en vez del resultado:

- ningun tabulador literal en las paginas, que es en lo que se queda un
  \t cuando alguien lo ha interpretado por el camino;
- ningun span de codigo inline sin cerrar, que es la marca que deja un
  \n al convertirse en salto de linea: parte el span y deja las dos
  lineas con un numero impar de comillas invertidas.

Vistas en rojo antes con los cuatro fallos exactos y ni un falso
positivo en las otras 20 paginas.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…mpilar

La pagina se trajo el instalador de PowerShell en el #60 y se quedo con
todo lo que estaba escrito de cuando Windows no tenia binario. Resultado:
ofrece `irm https://nan.builders/install.ps1 | iex` arriba, dice "hay
binarios para macOS, Linux y Windows" un poco mas abajo, y dos pantallas
despues manda a instalar Go.

- "Es la via para Windows" en Compilar desde el codigo. La seccion se
  queda, que es la respuesta buena para quien va a tocar el CLI; lo que
  no puede ser es la via para una plataforma que ya publicamos.
- El known issue entero, "En Windows hay que compilar. Las versiones
  publicadas traen binarios de macOS y Linux".

Lo zanja la release: v0.1.19 publica windows_amd64.zip y
windows_arm64.zip. Y de paso el known issue que si sigue en pie ofrecia
solo el `.tar.gz` para bajarlo a mano, que en Windows tampoco existe.

El test no persigue estas cuatro frases sino la forma del fallo: una
pagina que instala una plataforma desde un binario publicado y encima le
dice que compile. La siguiente plataforma va a llegar igual, anadiendo el
texto nuevo y dejando el viejo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…cuerpo

Cerraba con "el endpoint es compatible con OpenAI tanto en la
autenticacion como en el formato del cuerpo". La mitad es verdad y util
-la cabecera Bearer es la misma-, y la otra mitad manda al lector a
buscar una especificacion que no existe: OpenAI no define ningun
/rerank, asi que no hay cuerpo con el que ser compatible. El cuerpo es
nuestro.

La pagina ya lo sabia. Tres lineas mas arriba, en el comentario del
propio snippet, dice que el endpoint "is not part of the standard OpenAI
client". Y /search, el otro endpoint nuestro del mismo fichero, ya
cerraba bien: "manda el cuerpo JSON con tu key en el Bearer". Esto es
igualar rerank a como ya estaba escrito search.

El test es la pagina contra si misma: lo que ella marca como fuera del
cliente estandar no puede venderse dos parrafos despues como compatible
en su payload.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bajo prosa en espanol, el boton decia "Copy", el aviso "Copied to
clipboard", el buscador vacio "No results" y los aria-label "Search
documentation" y "Documentation".

Tiene una causa concreta y esta en el historial: docsCopyIsEnglish.test
existe porque paso lo contrario, el espanol se habia colado en las
paginas inglesas -"Copiar", "!Copiado al portapapeles!"- y se arreglo
escribiendo esas cadenas en ingles. Correcto para /docs, y deja /es/docs
justo asi. El layout ya tenia la tabla que lo resuelve, `T`, con su rama
`en` y su rama `es`; estas ocho nunca entraron porque viven en los
scripts de cliente, no en el markup.

El de busqueda ya iba con define:vars, asi que se le pasa `T` y ya esta.
El de los bloques de codigo esta empaquetado y tipado, asi que no puede
llevar define:vars sin perder el TypeScript: lee sus etiquetas del
dataset del propio toast, que este layout pinta siempre.

Sin fallback en ingles, a proposito: un fallback es exactamente como las
paginas espanolas acabaron diciendo "Copied to clipboard". El precio es
que un data-* que falte deja el boton mudo en vez de en otro idioma, que
tambien es silencioso, asi que hay test de que el toast lleva los cinco
atributos y de que algo lee los cinco.

La regla del test no es "en que idioma esta esto" sino "sale de la
tabla": un literal que el lector ve, fuera de `T`, tiene un solo idioma
sea el que sea.

Verificado renderizado en las dos rutas: /docs sirve "Copied to
clipboard" y /es/docs "Copiado al portapapeles", y de paso las dos rutas
de Windows salen enteras.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fui a mirar si a `glm5.3-flash` y `qwen3.8-flash` les faltaba el TPM y
resulta que el problema era mas grande: **ninguno de los cuatro numeros
publicados era correcto.**

cloud-api no guarda un numero de tokens por minuto. `modelRateLimits` en
usage_quota.go guarda la ventana de contexto y un FillsPerMin -cuantas
veces por minuto puedes rellenar esa ventana entera- y el techo es el
producto. El hook de rate limit lo replica en FALLBACK_MODEL_LIMITS y
las dos tablas estan obligadas a coincidir; coinciden.

Lo que sale de multiplicar, contra lo que decia la pagina:

  deepseek-v4-flash  1.048.576 x  6 =  6,3M   publicaba 1.5M
  mimo-v2.5          1.050.000 x  4 =  4,2M   publicaba 1.5M
  qwen3.6              262.144 x 12 =  3,1M   publicaba 1.5M
  gemma4               262.144 x  4 =  1,0M   publicaba 1.5M
  glm5.3-flash       1.048.576 x 11 = 11,5M   no aparecia
  qwen3.8-flash      1.048.576 x  6 =  6,3M   no aparecia

**El que importa es gemma4: su techo real esta POR DEBAJO del anunciado.**
Los otros tres se quedaban cortos, que es feo pero inofensivo; ese
prometia de mas, y un miembro que planificara contra la pagina se comia
429 que la pagina decia que no podian pasar.

Y `rerank` no tiene 1000 rpm. Esta en `rateLimitExemptModels` junto a
kokoro, whisper y qwen3-embedding: **no lleva limite por minuto propio.**
El 1000 no sale de ningun sitio del backend. Ahora se publican los cuatro
como lista, porque un endpoint ausente de la tabla no se distingue de uno
que se nos olvido medir - que es exactamente como el 1000 estuvo ahi
meses.

**Por que no se vio:** el numero era un `label` escrito a mano. Ahora se
guardan los dos factores y el producto se deriva; un producto que nadie
multiplica no puede salir mal. El extractor de /api/docs tenia el mismo
supuesto metido dentro -metia los cuatro modelos en una fila y cogia el
valor de `tpm[0]`-, una forma que solo puede ser correcta mientras todos
compartan cifra, y nunca la compartieron.

De paso, dos cosas que la prosa decia mal:

- "glm5.3 no se rige por minuto en absoluto" es falso: tiene techo como
  los demas (11,5M), lo que pasa es que la ventana movil muerde mucho
  antes (400M/4h son 1,67M/min de media). El bloque premium ya lo
  publica, porque "¿esto va por minuto?" merece respuesta.
- Faltaba la jerarquia, que es lo que hacia irresoluble lo de rerank:
  el limite que notas es el mas estricto de los tres que aplican, y los
  60/min de la key cuentan todo lo que llames.

El test ancla los FACTORES a cloud-api, no el producto, y exige que todo
modelo del catalogo este contabilizado en algun sitio - que es la regla
que habria cazado el hueco original. Visto en rojo de las dos formas:
cambiando un fills y quitando un modelo de la tabla.

Fuentes: helmcode/nan-cloud-api internal/handlers/usage_quota.go y
limits_policy.go; helmcode/nan-devops scripts/litellm-ratelimit-hook/
rate_limit_hook.py y docs/GLM52-PREMIUM-TIER.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@borjaperfra borjaperfra changed the title fix(docs): los cuatro restos de la migracion que si eran reales fix(docs): los restos de la migracion, y los techos por minuto que estaban mal los cuatro Sep 15, 2026
borjaperfra and others added 3 commits September 15, 2026 11:47
Se retira la busqueda web, por API y por MCP. Y como la busqueda web es
**la unica herramienta que el servidor MCP tiene hoy**, el servidor se va
con ella: no tiene sentido avisar solo de una mitad.

Sin fecha todavia. El aviso dice lo que se puede decir hoy -sigue
funcionando, se anunciara una fecha antes de que pare- y sobre todo dice
la decision que el lector tiene delante: no construyas nada nuevo encima.

Seis superficies, porque el hecho vive en seis sitios:

- `/docs/mcp` y `/es/docs/mcp`, con Callout arriba del todo y la
  descripcion del frontmatter cambiada, que es lo que se ve en la barra
  lateral y en los resultados de busqueda antes de abrir la pagina.
- La seccion `tool: web search` de Ejemplos, en los dos idiomas.
- `/search` y `/mcp` en la spec, con **`deprecated: true`**, que es lo que
  Scalar pinta como badge y lo que recoge un cliente generado. La prosa
  sola no vale: nada aguas abajo lee prosa.
- Los tags Search y MCP de la spec.
- Y los enlaces cruzados desde intro, agent-setup, claude-code, gentle-ai
  y agents, que es por donde llega la gente que no fue a buscarlo.

El test no persigue que alguien se olvide de deprecar. Persigue el fallo
que este repo ya ha tenido dos veces: **el cambio entra en ingles y la
pagina espanola sigue recomendandolo en voz baja.** Un miembro que lee
/es/docs y no se entera es justo el que construye encima.

Las paginas que enlazan se buscan escaneando, no por lista, porque el
caso que no se puede cubrir a mano es la pagina que todavia no existe.
Mira una ventana de lineas y no la linea suelta: en agent-setup el enlace
va en `href:` y lo que el lector lee esta en `note:`.

Dos cosas que se afinaron al verlo fallar: los marcadores son raices
(`deprecad`), porque el espanol flexiona y "deprecada" pasaba; y la
comprobacion ignora el frontmatter, porque mientras contaba se podia
borrar el Callout entero y todo seguia en verde.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fui a publicar la de flux creyendo que no tenia ninguna. Tenia dos
sorpresas.

**La primera: flux no pasa por LiteLLM.** Lo sirve cloud-api contra
Workers AI, en `internal/imagegen`, asi que no llega al rate_limit_hook
y **los 60/min y las 5 a la vez de la key no le aplican**. El comentario
de `cmd/server/main.go` lo dice con todas las letras. Mi conclusion
anterior -que caia al `defaultRateLimit` de los modelos de chat- estaba
mal, y tampoco hace falta ningun cambio en cloud-api: flux ya tiene
politica propia y deliberada, solo que en otro subsistema.

**La segunda: ya estaba publicada, y mal.** Ejemplos decia "20 peticiones
por minuto", una cifra que **no aparece en ninguna parte de la
plataforma**. Lo real, de `imagegen.NewRateLimiter(1, 3)`,
`imagegen.MonthlyQuota` y `imagegen.MaxVariants`:

  1 peticion por segundo, rafaga de 3
  100 peticiones por mes natural
  hasta 4 imagenes por peticion, y una peticion que pida varias
  sigue costando una

Bloque propio en la card de limites y no una fila, porque lo primero que
hay que decir de esto es que los limites de la key no le llegan, y eso
una fila no lo puede decir. Mas el ritmo en la ficha del modelo y el
texto de Ejemplos corregido, en los dos idiomas.

El test ancla los tres numeros a su limitador y ademas **exige que flux
NO aparezca en las tablas de LiteLLM**: una fila alli afirmaria que los
60/min le alcanzan, y no es verdad. Visto en rojo de las dos formas.

Y de paso, `minimax-h3` deja de estar en la lista de "no publicados" por
la razon equivocada: es el modelo de video, con allowlist en cloud-api.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cloud-api publica la politica entera en un documento y su propio
comentario dice para que: el 400M/4h estaba copiado a mano en cuatro
sitios -el hook, el types.ts de cloud-ui, el rateLimits.ts de esta web y
la prosa de las docs- "sin que nada pudiera notar que habian divergido".
Esta web fue donde peor salio: publicaba un techo por minuto para cuatro
modelos y no acertaba ninguno.

**Esta todo cableado menos la URL.** `LIMITS_POLICY_URL` no esta puesta,
asi que `resolveRateLimitsConfig()` devuelve la tabla interna y no hace
ninguna peticion: la pagina renderiza hoy exactamente igual que antes de
que este fichero existiera, verificado sirviendola. Apuntando esa
variable a un endpoint que sirva el documento, la web pasa a seguir a la
plataforma.

El endpoint que leeria, `/api/internal/limits/policy`, va detras del
`CreditsHookSecret`, compartido con el hook. Dar eso a un worker publico
es decision de quien lleva la plataforma, y la respuesta estrecha es una
ruta publica de solo lectura: nada de ese documento es secreto, ya esta
publicado literalmente en /docs/models. `LIMITS_POLICY_TOKEN` esta ahi
para cualquiera de las dos.

Cuatro reglas, cada una con su motivo:

1. **Un fallo nunca se ve.** Cualquier error, timeout o forma que no
   reconozca, y se usa la tabla interna. Una card de limites que no
   renderiza es peor que una que va un deploy por detras.
2. **Rechaza un documento mas nuevo del que entiende**, igual que el
   hook: malinterpretar un documento reformado pone numeros MAL,
   declinarlo pone numeros un poco viejos.
3. **Todo o nada.** Un documento al que le falta un campo se rechaza
   entero, no se parchea desde la tabla interna. Media card mezclando
   dos fuentes en silencio es justo el fallo que esto viene a cerrar.
4. **Solo los modelos del catalogo.** La politica gobierna todo lo que
   sirve la plataforma, que no es el mismo conjunto: `glm5.2` esta ahi y
   esta web no lo documenta a proposito. La politica pone los numeros;
   modelos.json sigue decidiendo de que modelos habla la pagina.

Y no toca ni los limites por key (viven en la key de LiteLLM) ni la
generacion de imagenes (no pasa por LiteLLM), que el mapeo conserva
explicitamente.

Los tests cubren sobre todo las formas de fallar, que es lo que importa
en una pagina de documentacion. Comprobados en rojo tres: aceptar una
version desconocida, no filtrar por el catalogo -se cuela glm5.2- y
parchear un hueco en vez de rechazar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@borjaperfra borjaperfra changed the title fix(docs): los restos de la migracion, y los techos por minuto que estaban mal los cuatro fix(docs): los restos de la migracion, los techos por minuto, la deprecacion del web search y la politica de flux Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants