diff --git a/src/content/docs-es/examples.md b/src/content/docs-es/examples.md index 96144d42..9c8ae050 100644 --- a/src/content/docs-es/examples.md +++ b/src/content/docs-es/examples.md @@ -78,7 +78,7 @@ Instalación: `npm install openai` ### salida estructurada en deepseek-v4-flash -En `api.nan.builders`, `deepseek-v4-flash` rechaza `response_format` `json_schema` con un `400` (en `api.helmcode.com` se acepta), y `json_object` solo garantiza JSON válido, no su forma. Para obtener una salida que siga un esquema, define una única herramienta de tipo función cuyo `parameters` sea tu JSON Schema (raíz `"type": "object"`, `"strict": true`) y fuérzala con `tool_choice`. El modelo devuelve argumentos que siguen el esquema en `choices[0].message.tool_calls[0].function.arguments`, como una cadena JSON. `strict` pide que se ajusten exactamente; se aplica donde el modelo admite decodificación estricta, así que valida los argumentos si tu código depende de ellos. +`deepseek-v4-flash` acepta `response_format` `json_schema` en peticiones a `/chat/completions` sin streaming y sin tools. En `api.nan.builders` lo rechaza con un `400` cuando la petición usa streaming, lleva `tools` o va a `/responses` (también con `n` mayor que 1, un esquema de más de 16 KB, un esquema no válido o un esquema que el gateway no valida (un `$ref` fuera del esquema, `$dynamicRef` o `$anchor`, un `$id` anidado o demasiado grande al resolver sus `$ref`); en `api.helmcode.com` se acepta en todos esos casos), y `json_object` solo garantiza JSON válido, no su forma. Para obtener una salida que siga un esquema en esas peticiones, define una única herramienta de tipo función cuyo `parameters` sea tu JSON Schema (raíz `"type": "object"`, `"strict": true`) y fuérzala con `tool_choice`. El modelo devuelve argumentos que siguen el esquema en `choices[0].message.tool_calls[0].function.arguments`, como una cadena JSON. `strict` pide que se ajusten exactamente; se aplica donde el modelo admite decodificación estricta, así que valida los argumentos si tu código depende de ellos. ```bash curl https://api.nan.builders/v1/chat/completions \ diff --git a/src/content/docs-es/models.mdx b/src/content/docs-es/models.mdx index 4d87024a..2290390e 100644 --- a/src/content/docs-es/models.mdx +++ b/src/content/docs-es/models.mdx @@ -50,7 +50,7 @@ OpenAI y la misma `base URL`. tag="305B MoE" leftLabel="generación de texto, chat y visión" rightLabel="capacidades" - description={'Modelo MoE de 305B parámetros, servido en su variante Vision-Exp: acepta imágenes de entrada. Contexto de 1M tokens. Tool calling y razonamiento. Cuota de 3B tokens al mes por miembro. La salida estructurada depende del host: en api.helmcode.com se aceptan response_format json_schema y json_object; en api.nan.builders, json_object es compatible (el prompt debe contener la palabra JSON; si no, se rechaza con un 400) y json_schema se rechaza con un 400. json_object solo garantiza JSON sintácticamente válido, no su forma: describe el esquema en el prompt. Para salida restringida a un esquema en api.nan.builders, usa qwen3.6 o gemma4, donde json_schema con strict restringe campos, tipos y claves obligatorias; o, en este modelo, fuerza una única herramienta de tipo función cuyo parameters sea tu esquema (ver Ejemplos). Con el razonamiento activado, un max_tokens por debajo de 16384 se sube a 16384 para que quepa el razonamiento, así que un prompt a menos de 16384 tokens de la ventana de 1.048.576 se rechaza con un 400 aunque pidas un max_tokens pequeño (normalmente Context length exceeded, a veces un Invalid request genérico). Con el razonamiento apagado (reasoning_effort "none" o cualquiera de sus alias) tu max_tokens se usa tal cual.'} + description={'Modelo MoE de 305B parámetros, servido en su variante Vision-Exp: acepta imágenes de entrada. Contexto de 1M tokens. Tool calling y razonamiento. Cuota de 3B tokens al mes por miembro. Salida estructurada: response_format json_object es compatible (en api.nan.builders el prompt debe contener la palabra JSON; si no, se rechaza con un 400). json_schema se acepta en peticiones a /chat/completions sin streaming y sin tools: la respuesta se comprueba contra tu esquema y, si no lo cumple, normalmente la vuelve a servir un proveedor que lo impone; una respuesta que no se pudo comprobar, o un fallo que no se pudo reemplazar a tiempo, se devuelve tal cual (valídala si tu código depende de ella). En api.nan.builders el resto de peticiones json_schema se rechazan con un 400: con streaming, con tools, a /responses, con n mayor que 1, con un esquema de más de 16 KB, que no sea un JSON Schema válido o que el gateway no valida (un $ref fuera del esquema, $dynamicRef o $anchor, un $id anidado o demasiado grande al resolver sus $ref); en api.helmcode.com se aceptan. json_object solo garantiza JSON sintácticamente válido, no su forma: describe el esquema en el prompt. Para salida restringida a un esquema en esas otras peticiones, usa qwen3.6 o gemma4, donde json_schema con strict restringe campos, tipos y claves obligatorias; o, en este modelo, fuerza una única herramienta de tipo función cuyo parameters sea tu esquema (ver Ejemplos). Con el razonamiento activado, un max_tokens por debajo de 16384 se sube a 16384 para que quepa el razonamiento, así que un prompt a menos de 16384 tokens de la ventana de 1.048.576 se rechaza con un 400 aunque pidas un max_tokens pequeño (normalmente Context length exceeded, a veces un Invalid request genérico). Con el razonamiento apagado (reasoning_effort "none" o cualquiera de sus alias) tu max_tokens se usa tal cual.'} specs={[ { label: 'Tipo', value: 'MoE (305B total)' }, { label: 'Cuantización', value: 'FP8' }, @@ -63,7 +63,7 @@ OpenAI y la misma `base URL`. 'Tool calling', 'Control del razonamiento (reasoning_effort: none lo apaga · de minimal a max) · detalles', 'Visión (entrada de imagen)', - 'Modo JSON (json_object; en api.nan.builders el prompt debe mencionar JSON) · json_schema: 400 en api.nan.builders, aceptado en api.helmcode.com', + 'Modo JSON (json_object; en api.nan.builders el prompt debe mencionar JSON) · json_schema en chat sin streaming y sin tools (en api.nan.builders el resto, p. ej. con streaming, con tools o en /responses: 400)', 'Contexto de 1M tokens', 'Generación en streaming (SSE)', ]} diff --git a/src/content/docs/examples.md b/src/content/docs/examples.md index dae81c4f..13795c47 100644 --- a/src/content/docs/examples.md +++ b/src/content/docs/examples.md @@ -78,7 +78,7 @@ Install: `npm install openai` ### structured output on deepseek-v4-flash -On `api.nan.builders`, `deepseek-v4-flash` rejects `response_format` `json_schema` with a `400` (on `api.helmcode.com` it is accepted), and `json_object` only guarantees valid JSON, not its shape. To get output that follows a schema, define one function tool whose `parameters` is your JSON Schema (root `"type": "object"`, `"strict": true`) and force it with `tool_choice`. The model returns arguments that follow the schema in `choices[0].message.tool_calls[0].function.arguments`, as a JSON string. `strict` asks for an exact match; it is enforced where the model supports strict decoding, so validate the arguments if your code depends on them. +`deepseek-v4-flash` accepts `response_format` `json_schema` on non-streamed `/chat/completions` requests without tools. On `api.nan.builders` it rejects it with a `400` when the request streams, carries `tools` or goes to `/responses` (also with `n` above 1, a schema over 16 KB, an invalid schema, or a schema the gateway will not validate (a `$ref` outside the schema, `$dynamicRef` or `$anchor`, a nested `$id`, or too large once its `$ref`s are inlined); on `api.helmcode.com` it is accepted in all of them), and `json_object` only guarantees valid JSON, not its shape. To get output that follows a schema in those requests, define one function tool whose `parameters` is your JSON Schema (root `"type": "object"`, `"strict": true`) and force it with `tool_choice`. The model returns arguments that follow the schema in `choices[0].message.tool_calls[0].function.arguments`, as a JSON string. `strict` asks for an exact match; it is enforced where the model supports strict decoding, so validate the arguments if your code depends on them. ```bash curl https://api.nan.builders/v1/chat/completions \ diff --git a/src/content/docs/models.mdx b/src/content/docs/models.mdx index 10603a60..d528c2df 100644 --- a/src/content/docs/models.mdx +++ b/src/content/docs/models.mdx @@ -50,7 +50,7 @@ with the same `base URL`. tag="305B MoE" leftLabel="text generation, chat & vision" rightLabel="capabilities" - description={'305B parameter MoE model, served as the Vision-Exp variant: it takes images as input. 1M token context. Tool calling and reasoning. 3B token monthly quota per member. Structured output depends on the host: on api.helmcode.com, response_format json_schema and json_object are accepted; on api.nan.builders, json_object is supported (the prompt must contain the word JSON, otherwise it is rejected with a 400) and json_schema is rejected with a 400. json_object guarantees syntactically valid JSON only, not its shape: describe the schema in the prompt. For schema-constrained output on api.nan.builders, use qwen3.6 or gemma4, where json_schema with strict constrains fields, types and required keys; or, on this model, force one function tool whose parameters is your schema (see Examples). While reasoning is on, a max_tokens below 16384 is raised to 16384 so the reasoning fits, so a prompt within 16384 tokens of the 1,048,576-token window is rejected with a 400 even with a small max_tokens (usually Context length exceeded, sometimes a generic Invalid request). With reasoning off (reasoning_effort "none" or any of its aliases) your max_tokens is used as sent.'} + description={'305B parameter MoE model, served as the Vision-Exp variant: it takes images as input. 1M token context. Tool calling and reasoning. 3B token monthly quota per member. Structured output: response_format json_object is supported (on api.nan.builders the prompt must contain the word JSON, otherwise it is rejected with a 400). json_schema is accepted on non-streamed /chat/completions requests without tools: the answer is checked against your schema and, when it does not match, usually re-served by a provider that enforces it; an answer that could not be checked, or a miss the re-serve could not replace in time, is returned as is (validate it if your code depends on it). On api.nan.builders other json_schema requests are rejected with a 400: streamed, with tools, on /responses, with n above 1, with a schema over 16 KB, not a valid JSON Schema, or one the gateway will not validate (a $ref outside the schema, $dynamicRef or $anchor, a nested $id, or too large once its $refs are inlined); on api.helmcode.com they are accepted. json_object guarantees syntactically valid JSON only, not its shape: describe the schema in the prompt. For schema-constrained output in those other requests, use qwen3.6 or gemma4, where json_schema with strict constrains fields, types and required keys; or, on this model, force one function tool whose parameters is your schema (see Examples). While reasoning is on, a max_tokens below 16384 is raised to 16384 so the reasoning fits, so a prompt within 16384 tokens of the 1,048,576-token window is rejected with a 400 even with a small max_tokens (usually Context length exceeded, sometimes a generic Invalid request). With reasoning off (reasoning_effort "none" or any of its aliases) your max_tokens is used as sent.'} specs={[ { label: 'Type', value: 'MoE (305B total)' }, { label: 'Quantization', value: 'FP8' }, @@ -63,7 +63,7 @@ with the same `base URL`. 'Tool calling', 'Reasoning control (reasoning_effort: none turns it off · minimal to max) · details', 'Vision (image input)', - 'JSON mode (json_object; on api.nan.builders the prompt must mention JSON) · json_schema: 400 on api.nan.builders, accepted on api.helmcode.com', + 'JSON mode (json_object; on api.nan.builders the prompt must mention JSON) · json_schema on non-streamed chat without tools (on api.nan.builders the other cases, e.g. streamed, with tools or on /responses: 400)', '1M token context', 'Streaming generation (SSE)', ]} diff --git a/src/data/openapi.json b/src/data/openapi.json index 049f80a2..cd890eba 100644 --- a/src/data/openapi.json +++ b/src/data/openapi.json @@ -9385,7 +9385,7 @@ } }, "ResponseFormat": { - "description": "Force structured output. What each mode guarantees:\n\n- `json_object`: syntactically valid JSON only, NOT its shape. Describe the schema you want in the prompt. On `deepseek-v4-flash` on `api.nan.builders` the prompt must also contain the word JSON, otherwise `400`.\n- `json_schema` with `strict: true`: the output is constrained to the schema (fields, types and required keys).\n\n`json_schema` works on `qwen3.6` and `gemma4`. On `deepseek-v4-flash` it depends on the host: on the enterprise base URL (see the overview) it is accepted; on `api.nan.builders` it is rejected with a `400` before the request reaches the model, and `json_object` still works there. For output that follows a schema on `deepseek-v4-flash` there, define one function tool whose `parameters` is your JSON Schema (root `type: object`, `strict: true`), force it with `tool_choice: {\"type\": \"function\", \"function\": {\"name\": \"...\"}}` and read `choices[0].message.tool_calls[0].function.arguments`.", + "description": "Force structured output. What each mode guarantees:\n\n- `json_object`: syntactically valid JSON only, NOT its shape. Describe the schema you want in the prompt. On `deepseek-v4-flash` on `api.nan.builders` the prompt must also contain the word JSON, otherwise `400`.\n- `json_schema` with `strict: true`: the output is constrained to the schema (fields, types and required keys).\n\n`json_schema` works on `qwen3.6` and `gemma4`. On `deepseek-v4-flash` it is accepted on non-streamed `/chat/completions` requests without `tools`: the answer is checked against your schema and, when it does not match, usually re-served by a provider that enforces it; an answer that could not be checked, or a miss the re-serve could not replace in time, is returned as is (validate it if your code depends on it). Other `json_schema` requests on `deepseek-v4-flash` depend on the host: on the enterprise base URL (see the overview) they are accepted; on `api.nan.builders` a request that streams, carries `tools`, goes to `/responses`, sets `n` above 1, or whose schema is over 16 KB, invalid or one the gateway will not validate (a `$ref` outside the schema, `$dynamicRef` or `$anchor`, a nested `$id`, or too large once its `$ref`s are inlined) is rejected with a `400` before it reaches the model, and `json_object` still works there. For output that follows a schema on `deepseek-v4-flash` in those requests, define one function tool whose `parameters` is your JSON Schema (root `type: object`, `strict: true`), force it with `tool_choice: {\"type\": \"function\", \"function\": {\"name\": \"...\"}}` and read `choices[0].message.tool_calls[0].function.arguments`.", "oneOf": [ { "type": "object", diff --git a/src/lib/openapiSpec.test.ts b/src/lib/openapiSpec.test.ts index 660aa391..5841a19c 100644 --- a/src/lib/openapiSpec.test.ts +++ b/src/lib/openapiSpec.test.ts @@ -673,9 +673,14 @@ describe('getting-started guides: /usage parity', () => { /** * THE GATEWAY REJECTS TWO REQUEST SHAPES WITH A 400 BEFORE ROUTING: a tool * name outside ^[a-zA-Z0-9_-]{1,64}$ (any model) and `response_format` - * `json_schema` on `deepseek-v4-flash` on api.nan.builders (`json_object` still - * works there). On api.helmcode.com the same model accepts `json_schema` (and - * `json_object` without the word JSON), measured live 2026-10-15. + * `json_schema` on `deepseek-v4-flash` on api.nan.builders when the request + * streams, carries `tools` or goes to /responses (`json_object` still works + * there). Since 2026-10-16 a non-streamed /chat/completions json_schema request + * without tools IS served on api.nan.builders: the gateway checks the answer + * against the schema and re-serves a miss from a provider that enforces it + * (nan-devops, community json_schema wrapper). On api.helmcode.com the model + * accepts `json_schema` in every shape (and `json_object` without the word + * JSON), measured live 2026-10-15. * A member who hits either should find it in the reference and on the model * card, in both locales, not learn it from the error. */ @@ -701,11 +706,16 @@ describe('documented 400s: tool names and structured output', () => { } }); - it('says json_schema on deepseek-v4-flash depends on the host, and json_object works on both', () => { + it('says where json_schema on deepseek-v4-flash is served, where it is a 400, and that json_object works', () => { const description: string = schemas.ResponseFormat.description; expect(description).toContain( - 'On `deepseek-v4-flash` it depends on the host: on the enterprise base URL (see the overview) it is accepted; on `api.nan.builders` it is rejected with a `400` before the request reaches the model, and `json_object` still works there.', + 'On `deepseek-v4-flash` it is accepted on non-streamed `/chat/completions` requests without `tools`: the answer is checked against your schema and, when it does not match, usually re-served by a provider that enforces it; an answer that could not be checked, or a miss the re-serve could not replace in time, is returned as is (validate it if your code depends on it).', ); + expect(description).toContain( + 'Other `json_schema` requests on `deepseek-v4-flash` depend on the host: on the enterprise base URL (see the overview) they are accepted; on `api.nan.builders` a request that streams, carries `tools`, goes to `/responses`, sets `n` above 1, or whose schema is over 16 KB, invalid or one the gateway will not validate (a `$ref` outside the schema, `$dynamicRef` or `$anchor`, a nested `$id`, or too large once its `$ref`s are inlined) is rejected with a `400` before it reaches the model, and `json_object` still works there.', + ); + // The old blanket "rejected on api.nan.builders" must not come back. + expect(description).not.toContain('it depends on the host: on the enterprise base URL (see the overview) it is accepted'); expect(description).toContain('On `deepseek-v4-flash` on `api.nan.builders` the prompt must also contain the word JSON, otherwise `400`.'); // "accepted", never a strictness promise for that host. expect(description).not.toMatch(/enterprise base URL[^.]*(guarantee|constrained)/); @@ -724,14 +734,18 @@ describe('documented 400s: tool names and structured output', () => { return page.slice(start, page.indexOf('/>', start)); }; const en = card(''); - expect(en).toContain('Structured output depends on the host: on api.helmcode.com, response_format json_schema and json_object are accepted; on api.nan.builders, json_object is supported (the prompt must contain the word JSON, otherwise it is rejected with a 400) and json_schema is rejected with a 400.'); + expect(en).toContain('json_schema is accepted on non-streamed /chat/completions requests without tools: the answer is checked against your schema and, when it does not match, usually re-served by a provider that enforces it; an answer that could not be checked, or a miss the re-serve could not replace in time, is returned as is (validate it if your code depends on it). On api.nan.builders other json_schema requests are rejected with a 400: streamed, with tools, on /responses, with n above 1, with a schema over 16 KB, not a valid JSON Schema, or one the gateway will not validate (a $ref outside the schema, $dynamicRef or $anchor, a nested $id, or too large once its $refs are inlined); on api.helmcode.com they are accepted.'); + expect(en).toContain('on api.nan.builders the prompt must contain the word JSON, otherwise it is rejected with a 400'); expect(en).toContain('use qwen3.6 or gemma4'); - expect(en).toContain('json_schema: 400 on api.nan.builders, accepted on api.helmcode.com'); + expect(en).toContain('json_schema on non-streamed chat without tools (on api.nan.builders the other cases, e.g. streamed, with tools or on /responses: 400)'); + expect(en).not.toContain('json_schema is rejected with a 400.'); expect(en).not.toMatch(/not supported \(400\)|json_schema is not and/); const es = card('-es'); - expect(es).toContain('La salida estructurada depende del host: en api.helmcode.com se aceptan response_format json_schema y json_object; en api.nan.builders, json_object es compatible (el prompt debe contener la palabra JSON; si no, se rechaza con un 400) y json_schema se rechaza con un 400.'); + expect(es).toContain('json_schema se acepta en peticiones a /chat/completions sin streaming y sin tools: la respuesta se comprueba contra tu esquema y, si no lo cumple, normalmente la vuelve a servir un proveedor que lo impone; una respuesta que no se pudo comprobar, o un fallo que no se pudo reemplazar a tiempo, se devuelve tal cual (valídala si tu código depende de ella). En api.nan.builders el resto de peticiones json_schema se rechazan con un 400: con streaming, con tools, a /responses, con n mayor que 1, con un esquema de más de 16 KB, que no sea un JSON Schema válido o que el gateway no valida (un $ref fuera del esquema, $dynamicRef o $anchor, un $id anidado o demasiado grande al resolver sus $ref); en api.helmcode.com se aceptan.'); + expect(es).toContain('en api.nan.builders el prompt debe contener la palabra JSON; si no, se rechaza con un 400'); expect(es).toContain('usa qwen3.6 o gemma4'); - expect(es).toContain('json_schema: 400 en api.nan.builders, aceptado en api.helmcode.com'); + expect(es).toContain('json_schema en chat sin streaming y sin tools (en api.nan.builders el resto, p. ej. con streaming, con tools o en /responses: 400)'); + expect(es).not.toContain('json_schema se rechaza con un 400.'); expect(es).not.toMatch(/no soportado \(400\)|json_schema no y/); }); }); @@ -1000,9 +1014,21 @@ describe('documented structured output: guarantees and the deepseek-v4-flash rec expect(section).toMatch(locale ? /devuelve argumentos que siguen el esquema/ : /returns arguments that follow the schema/); expect(section).toMatch(locale ? /donde el modelo admite decodificación estricta/ : /where the model supports strict decoding/); expect(section).not.toMatch(/guaranteed|garantizad/); - // The 400 is host-specific: api.helmcode.com accepts json_schema on this model. - expect(section).toContain(locale ? 'En `api.nan.builders`, `deepseek-v4-flash` rechaza' : 'On `api.nan.builders`, `deepseek-v4-flash` rejects'); - expect(section).toContain(locale ? 'en `api.helmcode.com` se acepta' : 'on `api.helmcode.com` it is accepted'); + // Since 2026-10-16 json_schema is served on non-streamed chat without + // tools; the 400 is for streamed / tools / /responses requests, and only + // on api.nan.builders (api.helmcode.com accepts those too). + expect(section).toContain(locale + ? '`deepseek-v4-flash` acepta `response_format` `json_schema` en peticiones a `/chat/completions` sin streaming y sin tools.' + : '`deepseek-v4-flash` accepts `response_format` `json_schema` on non-streamed `/chat/completions` requests without tools.'); + expect(section).toContain(locale + ? 'En `api.nan.builders` lo rechaza con un `400` cuando la petición usa streaming, lleva `tools` o va a `/responses`' + : 'On `api.nan.builders` it rejects it with a `400` when the request streams, carries `tools` or goes to `/responses`'); + expect(section).toContain(locale ? 'en `api.helmcode.com` se acepta en todos esos casos' : 'on `api.helmcode.com` it is accepted in all of them'); + expect(section).toContain(locale ? 'un `$ref` fuera del esquema' : 'a `$ref` outside the schema'); + // Schemas the gateway refuses to validate (remote refs, dynamic refs, + // nested $id, ref explosion) are still a 400 on api.nan.builders. + expect(section).toContain(locale ? 'un esquema que el gateway no valida' : 'a schema the gateway will not validate'); + expect(section).toContain('`$dynamicRef`'); }); } });