From b422eb27475bfb01db54209252d691fda803563f Mon Sep 17 00:00:00 2001 From: barckcode Date: Mon, 19 Oct 2026 12:10:45 +0200 Subject: [PATCH 1/3] docs(models): document the reasoning token budget (thinking_budget / thinking_token_budget) Both names are accepted since nan-devops#895 and each model receives the one it reads. Values and behaviour measured on prod 2026-10-19: 64 caps reasoning at 64 tokens on qwen3.8-flash, 0 turns it off on qwen3.8-flash and qwen3.6, values above 32768 are lowered on NaN, and combining a budget with reasoning_effort behaves differently per model. Co-Authored-By: Claude Opus 5.5 --- src/content/docs-es/models.mdx | 39 ++++++++++++++++++++++++++++++++++ src/content/docs/models.mdx | 39 ++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/src/content/docs-es/models.mdx b/src/content/docs-es/models.mdx index 27e615f..13d55c7 100644 --- a/src/content/docs-es/models.mdx +++ b/src/content/docs-es/models.mdx @@ -455,6 +455,45 @@ const chat = await client.chat.completions.create({ }); ``` +### Limitar el razonamiento con un presupuesto de tokens. + +En `qwen3.6`, `gemma4` y `qwen3.8-flash` también puedes limitar la fase de +razonamiento a un número exacto de tokens, en Chat Completions. Envía +`thinking_token_budget` o `thinking_budget`: son dos nombres del mismo +parámetro, así que usa el que ya envíe tu código. Cada modelo +recibe el nombre que entiende. Ponlo en el primer nivel del cuerpo o dentro de +`extra_body`. + +- Un entero de `1` a `32768` limita el razonamiento a ese número de tokens. Es + un tope estricto: el modelo deja de razonar ahí y escribe su respuesta. +- `0` apaga el razonamiento, igual que `reasoning_effort: "none"`. +- En NaN, un valor mayor (o que no sea un entero) se baja a `32768`. + +Envía un presupuesto o `reasoning_effort`, no los dos: combinados, el +resultado depende del modelo. El resto de modelos no admite presupuesto; usa +`reasoning_effort`. + +```bash +curl https://api.nan.builders/v1/chat/completions \ + -H "Authorization: Bearer $NAN_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "qwen3.8-flash", + "thinking_budget": 2048, + "messages": [{"role": "user", "content": "How many primes are there below 200?"}] + }' +``` + +Con el SDK de OpenAI va por `extra_body`: + +```python +chat = client.chat.completions.create( + model="qwen3.8-flash", + extra_body={"thinking_budget": 2048}, + messages=[{"role": "user", "content": "How many primes are there below 200?"}], +) +``` + ## Respuestas largas: usa streaming. Una petición sin `stream: true` no devuelve nada hasta que la respuesta diff --git a/src/content/docs/models.mdx b/src/content/docs/models.mdx index eebc628..709cff4 100644 --- a/src/content/docs/models.mdx +++ b/src/content/docs/models.mdx @@ -452,6 +452,45 @@ const chat = await client.chat.completions.create({ }); ``` +### Capping reasoning with a token budget. + +On `qwen3.6`, `gemma4` and `qwen3.8-flash` you can also cap the reasoning +phase at an exact number of tokens, on Chat Completions. Send +`thinking_token_budget` or `thinking_budget`: they are two names for the same +parameter, so use whichever your code already sends. Each model +receives the name it reads. Put it at the top level of the body or inside +`extra_body`. + +- An integer from `1` to `32768` caps reasoning at that many tokens. It is a + hard cap: the model stops reasoning there and writes its answer. +- `0` turns reasoning off, like `reasoning_effort: "none"`. +- On NaN, a larger value (or one that is not an integer) is lowered to `32768`. + +Send either a budget or `reasoning_effort`, not both: combined, the result +differs by model. The other models do not support a budget; use +`reasoning_effort` there. + +```bash +curl https://api.nan.builders/v1/chat/completions \ + -H "Authorization: Bearer $NAN_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "qwen3.8-flash", + "thinking_budget": 2048, + "messages": [{"role": "user", "content": "How many primes are there below 200?"}] + }' +``` + +With the OpenAI SDK it goes through `extra_body`: + +```python +chat = client.chat.completions.create( + model="qwen3.8-flash", + extra_body={"thinking_budget": 2048}, + messages=[{"role": "user", "content": "How many primes are there below 200?"}], +) +``` + ## Long outputs: stream them. A request without `stream: true` returns nothing until the whole answer is ready, and it has to be ready within **120 seconds**. Past that, the connection is closed with a `524` (or an error a few seconds earlier), whatever the model. At `deepseek-v4-flash` speeds that is roughly 10,000 to 15,000 output tokens; slower models, and reasoning models that think before they answer, fit less. From ab37735815dfe62071d3741d583ea54dc268e929 Mon Sep 17 00:00:00 2001 From: barckcode Date: Mon, 19 Oct 2026 12:13:36 +0200 Subject: [PATCH 2/3] docs(models): budget wording matches the evidence (gemma4 measured, contract wording, Responses) Co-Authored-By: Claude Opus 5.5 --- src/content/docs-es/models.mdx | 10 ++++++---- src/content/docs/models.mdx | 8 +++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/content/docs-es/models.mdx b/src/content/docs-es/models.mdx index 13d55c7..28ece00 100644 --- a/src/content/docs-es/models.mdx +++ b/src/content/docs-es/models.mdx @@ -458,7 +458,7 @@ const chat = await client.chat.completions.create({ ### Limitar el razonamiento con un presupuesto de tokens. En `qwen3.6`, `gemma4` y `qwen3.8-flash` también puedes limitar la fase de -razonamiento a un número exacto de tokens, en Chat Completions. Envía +razonamiento a un número de tokens, en Chat Completions. Envía `thinking_token_budget` o `thinking_budget`: son dos nombres del mismo parámetro, así que usa el que ya envíe tu código. Cada modelo recibe el nombre que entiende. Ponlo en el primer nivel del cuerpo o dentro de @@ -467,11 +467,13 @@ recibe el nombre que entiende. Ponlo en el primer nivel del cuerpo o dentro de - Un entero de `1` a `32768` limita el razonamiento a ese número de tokens. Es un tope estricto: el modelo deja de razonar ahí y escribe su respuesta. - `0` apaga el razonamiento, igual que `reasoning_effort: "none"`. -- En NaN, un valor mayor (o que no sea un entero) se baja a `32768`. +- En NaN, un valor mayor (o que no sea un entero) se reduce a `32768`. Envía un presupuesto o `reasoning_effort`, no los dos: combinados, el -resultado depende del modelo. El resto de modelos no admite presupuesto; usa -`reasoning_effort`. +resultado depende del modelo. En el resto de modelos el presupuesto no forma +parte de este contrato (según el modelo se rechaza con un `400` o se ignora); +usa `reasoning_effort`. En Responses el presupuesto no está garantizado; usa +`reasoning: {"effort": ...}`. ```bash curl https://api.nan.builders/v1/chat/completions \ diff --git a/src/content/docs/models.mdx b/src/content/docs/models.mdx index 709cff4..693cc09 100644 --- a/src/content/docs/models.mdx +++ b/src/content/docs/models.mdx @@ -455,7 +455,7 @@ const chat = await client.chat.completions.create({ ### Capping reasoning with a token budget. On `qwen3.6`, `gemma4` and `qwen3.8-flash` you can also cap the reasoning -phase at an exact number of tokens, on Chat Completions. Send +phase at a number of tokens, on Chat Completions. Send `thinking_token_budget` or `thinking_budget`: they are two names for the same parameter, so use whichever your code already sends. Each model receives the name it reads. Put it at the top level of the body or inside @@ -467,8 +467,10 @@ receives the name it reads. Put it at the top level of the body or inside - On NaN, a larger value (or one that is not an integer) is lowered to `32768`. Send either a budget or `reasoning_effort`, not both: combined, the result -differs by model. The other models do not support a budget; use -`reasoning_effort` there. +differs by model. On the other models a budget is not part of this contract +(depending on the model it is rejected with a `400` or ignored); use +`reasoning_effort` there. On Responses the budget is not guaranteed to apply; +use `reasoning: {"effort": ...}` there. ```bash curl https://api.nan.builders/v1/chat/completions \ From b35ec880ac80f52637d750e0de388f4377fcf31f Mon Sep 17 00:00:00 2001 From: barckcode Date: Mon, 19 Oct 2026 12:15:26 +0200 Subject: [PATCH 3/3] test(docs): pin the reasoning token budget section (en + es) Co-Authored-By: Claude Opus 5.5 --- src/tests/lib/docsReasoning.test.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/tests/lib/docsReasoning.test.ts b/src/tests/lib/docsReasoning.test.ts index b53adc2..b9fbd4a 100644 --- a/src/tests/lib/docsReasoning.test.ts +++ b/src/tests/lib/docsReasoning.test.ts @@ -211,6 +211,32 @@ describe('models page: the per-model table is the contract (en + es)', () => { } }); +describe('models page: the reasoning token budget (en + es)', () => { + // Measured live 2026-10-19 (nan-devops#895): both names reach every model + // under the name it reads; 64 caps reasoning on qwen3.6, gemma4 and + // qwen3.8-flash, 0 turns it off, NaN lowers anything above 32768. + for (const locale of ['', '-es'] as const) { + const text = section(locale); + it(`${locale || 'en'}: names both parameters, the three models, the range and the off value`, () => { + expect(text).toContain('`thinking_token_budget`'); + expect(text).toContain('`thinking_budget`'); + for (const model of ['`qwen3.6`', '`gemma4`', '`qwen3.8-flash`']) expect(text).toContain(model); + expect(text).toContain('`32768`'); + expect(text).toMatch(locale === '' ? /`0` turns reasoning off/ : /`0` apaga el razonamiento/); + }); + + it(`${locale || 'en'}: says not to combine it with reasoning_effort, and what the other models do`, () => { + expect(text).toMatch(locale === '' ? /not both/ : /no los dos/); + expect(text).toMatch(locale === '' ? /a budget is not part of this contract/ : /el presupuesto no forma\s+parte de este contrato/); + }); + + it(`${locale || 'en'}: shows it top level and through extra_body`, () => { + expect(text).toContain('"thinking_budget": 2048'); + expect(text).toContain('extra_body={"thinking_budget": 2048}'); + }); + } +}); + describe('model cards agree with the table (en + es)', () => { for (const locale of ['', '-es'] as const) { it(`${locale || 'en'}: every chat card says how reasoning is controlled`, () => {