diff --git a/src/server/providers/catalog-activity.test.ts b/src/server/providers/catalog-activity.test.ts index a799ec4e..09df38ed 100644 --- a/src/server/providers/catalog-activity.test.ts +++ b/src/server/providers/catalog-activity.test.ts @@ -389,10 +389,22 @@ describe('issue #109 listModels', () => { if (url.endsWith('/capabilities/reasoning.md')) { return new Response( ` -| Model | Default | \`reasoning_effort\` | Disable reasoning | Availability | -| - | - | - | - | - | -| [\`gpt-oss-120b\`](/models/openai-oss) | \`medium\` | \`low\`, \`medium\`, \`high\` | Not supported | Shared Inference | -| \`kimi-k2.7-code\` | Always enabled | Accepted but ignored | Not supported | Customer trials only | +| Model | Default effort | Availability | +| - | - | - | +| [GPT OSS 120B](#gpt-oss-120b) | \`medium\` | Shared and Dedicated Inference | + +## Model behavior + +### GPT OSS 120B + +[\`gpt-oss-120b\`](/models/openai-oss) always enables reasoning. Use \`reasoning_effort\` to select one of the following levels. + +| Value | Behavior | +| - | - | +| \`low\` | Selects low reasoning effort | +| \`medium\` (default) | Selects medium reasoning effort | +| \`high\` | Selects high reasoning effort | +| \`none\` | Not supported | `, { status: 200 }, ) diff --git a/src/server/providers/cerebras-reasoning.test.ts b/src/server/providers/cerebras-reasoning.test.ts index 3cab3195..7f8d6c23 100644 --- a/src/server/providers/cerebras-reasoning.test.ts +++ b/src/server/providers/cerebras-reasoning.test.ts @@ -1,3 +1,5 @@ +import { readFileSync } from 'node:fs' + import { describe, expect, it } from 'vitest' import { @@ -7,18 +9,29 @@ import { parseCerebrasReasoning, } from './cerebras-reasoning.ts' -/** Rows from https://inference-docs.cerebras.ai/capabilities/reasoning.md */ -const REASONING = ` -| Model | Default | \`reasoning_effort\` | Disable reasoning | Availability | -| - | - | - | - | - | -| [\`qwen-3.8-27b\`](/models/qwen-3.8-27b) | \`high\` | \`none\`, \`low\`, \`medium\`, \`high\` | Set \`reasoning_effort\` to \`none\` | Shared Inference | -| \`kimi-k2.7-code\` | Always enabled | Accepted but ignored | Not supported | Customer trials only | -| [\`gpt-oss-120b\`](/models/openai-oss) | \`medium\` | \`low\`, \`medium\`, \`high\` | Not supported | Shared Inference | -| [\`gemma-4-31b\`](/dedicated/overview#supported-models) | Disabled | \`none\`, \`low\`, \`medium\`, \`high\` | Set \`reasoning_effort\` to \`none\` | Dedicated Inference | - -| Model | Default response behavior | \`raw\` | \`hidden\` | -| - | - | - | - | -| \`qwen-3.8-27b\` | Reasoning returned separately | Does not change the separated response format | Not supported | +/** https://inference-docs.cerebras.ai/capabilities/reasoning.md on 2026-11-13. */ +const REASONING = readFileSync( + new URL('./fixtures/cerebras-reasoning.md.txt', import.meta.url), + 'utf8', +) + +/** One model whose section never says whether reasoning can be turned off. */ +const UNSTATED = ` +| Model | Default effort | Availability | +| - | - | - | +| [GPT OSS 120B](#gpt-oss-120b) | \`medium\` | Shared | + +## Model behavior + +### GPT OSS 120B + +[\`gpt-oss-120b\`](/models/openai-oss) supports reasoning. + +| Value | Behavior | +| - | - | +| \`low\` | Selects low reasoning effort | +| \`medium\` (default) | Selects medium reasoning effort | +| \`high\` | Selects high reasoning effort | ` /** The chat-spec sentences the request map is allowed to trust. */ @@ -39,32 +52,74 @@ const SPEC = ` A message in the conversation. Developer messages are supported only by \`gpt-oss-120b\`. ` -describe('cerebras reasoning table', () => { +describe('cerebras reasoning page', () => { const parsed = parseCerebrasReasoning(REASONING) - it('reads effort lists and whether reasoning can be disabled', () => { + it('reads each model section for efforts and whether reasoning can be disabled', () => { expect(parsed.configured.get('qwen-3.8-27b')).toEqual({ mode: 'effort', mandatory: false, efforts: ['none', 'low', 'medium', 'high'], }) + expect(parsed.configured.get('kimi-k3')).toEqual({ + mode: 'effort', + mandatory: true, + efforts: ['low', 'high', 'max'], + }) + expect(parsed.configured.get('kimi-k3.1-pro')).toEqual({ + mode: 'effort', + mandatory: true, + efforts: ['low', 'medium', 'high', 'max'], + }) + expect(parsed.configured.get('zai-glm-5.3-flash')).toEqual({ + mode: 'effort', + mandatory: true, + efforts: ['low', 'high', 'max'], + }) expect(parsed.configured.get('gpt-oss-120b')).toEqual({ mode: 'effort', mandatory: true, efforts: ['low', 'medium', 'high'], }) - expect(parsed.configured.get('gemma-4-31b')?.mandatory).toBe(false) + expect(parsed.configured.get('gemma-4-31b')).toEqual({ + mode: 'effort', + mandatory: false, + efforts: ['none', 'low', 'medium', 'high'], + }) expect(parsed.ignored).toEqual(['kimi-k2.7-code']) + expect([...parsed.configured.keys()]).toEqual([ + 'qwen-3.8-27b', + 'kimi-k3', + 'kimi-k3.1-pro', + 'zai-glm-5.3-flash', + 'gpt-oss-120b', + 'gemma-4-31b', + ]) + }) + + it('throws when a behavior cell is not accept, disable, or unsupported', () => { + expect(() => + parseCerebrasReasoning( + REASONING.replace( + '| `medium` (default) | Selects medium reasoning effort |', + '| `medium` (default) | Sometimes |', + ), + ), + ).toThrow(/gpt-oss-120b unread behavior/) + }) + + it('throws when the section never says whether reasoning can be disabled', () => { + expect(() => parseCerebrasReasoning(UNSTATED)).toThrow( + /does not state whether reasoning can be disabled/, + ) }) - it('throws when a disable cell is neither none nor not supported', () => { + it('throws when the page has no model behavior section', () => { expect(() => - parseCerebrasReasoning(` -| Model | Default | \`reasoning_effort\` | Disable reasoning | Availability | -| - | - | - | - | - | -| \`gpt-oss-120b\` | \`medium\` | \`low\`, \`high\` | Sometimes | Shared Inference | -`), - ).toThrow(/unread disable cell/) + parseCerebrasReasoning( + REASONING.replace('## Model behavior', '## Notes'), + ), + ).toThrow(/no model behavior section/) }) }) diff --git a/src/server/providers/cerebras-reasoning.ts b/src/server/providers/cerebras-reasoning.ts index 3630ce6d..bf1750df 100644 --- a/src/server/providers/cerebras-reasoning.ts +++ b/src/server/providers/cerebras-reasoning.ts @@ -1,17 +1,20 @@ /** * Reasoning controls and the chat request map, read on every poll. * - * The reasoning table - * (https://inference-docs.cerebras.ai/capabilities/reasoning.md) names each - * model's `reasoning_effort` values and whether reasoning can be disabled. - * A value list is effort mode. "Accepted but ignored" is not a control. + * The reasoning page + * (https://inference-docs.cerebras.ai/capabilities/reasoning.md) lists each + * model under "Model behavior". That section's value table is the effort + * list. A behavior of "Disables reasoning" is the off value; "Not supported" + * is rejected; "always enables reasoning" or a rejected `none` is + * `mandatory`. A section that says the API ignores `reasoning_effort` is + * not a control. `mandatory` stays unset unless that section states it. * * The chat spec * (https://inference-docs.cerebras.ai/api-reference/openapi.yaml) names * `max_completion_tokens` (`max_tokens` is its alias) and says developer * messages are supported only by the ids in that sentence. */ -import { assertParsed, cachedDocs, markdownTableRows } from './model-facts.ts' +import { assertParsed, cachedDocs } from './model-facts.ts' import { reasoningFactsFor } from './reasoning-config.ts' import type { ChatRequestMap, EffortLevelMap } from './request-map.ts' import { fetchText, sha256Text } from './types.ts' @@ -34,91 +37,384 @@ export interface CerebrasChatSpec { developerIds: Array } -function column( - header: Array, - cells: Array, - name: string, -): string { - const index = header.findIndex((cell) => - cell.toLowerCase().includes(name.toLowerCase()), - ) - return index < 0 ? '' : (cells[index] ?? '') +interface SummaryRow { + name: string + /** Null when the summary says the effort is not configurable. */ + defaultEffort: string | null } -function modelId(cell: string): string { - const id = cell.match(/`([a-z0-9][a-z0-9.-]*)`/)?.[1] - if (!id) throw new Error(`cerebras reasoning: model cell has no id: ${cell}`) - return id +function tableCells(line: string): Array | null { + const trimmed = line.trim() + if (!trimmed.startsWith('|') || !trimmed.endsWith('|')) return null + return trimmed + .slice(1, -1) + .split('|') + .map((cell) => cell.trim()) +} + +function isSeparator(cells: Array): boolean { + return cells.every((cell) => /^:?-+:?$/.test(cell)) } -/** `true` when the cell says reasoning cannot be disabled. */ -function mandatoryFrom(cell: string): boolean { - const notSupported = /not supported/i.test(cell) - const canDisable = /set `reasoning_effort` to `none`/i.test(cell) - if (notSupported === canDisable) { - throw new Error(`cerebras reasoning: unread disable cell: ${cell}`) - } - return notSupported +function markdownTables(markdown: string): Array>> { + const tables: Array>> = [] + let current: Array> | null = null + for (const line of markdown.split('\n')) { + const cells = tableCells(line) + if (!cells) { + if (current) tables.push(current) + current = null + continue + } + if (isSeparator(cells)) continue + current ??= [] + current.push(cells) + } + if (current) tables.push(current) + return tables } -function effortValues(cell: string): Array | 'ignored' { - if (/accepted but ignored/i.test(cell)) { - if (/`[^`]+`/.test(cell)) { +function summaryName(cell: string): string { + const name = cell.match(/^\[([^\]]+)\]\(#[^)]+\)$/)?.[1] + if (!name) { + throw new Error(`cerebras reasoning: model cell has no anchor: ${cell}`) + } + return name +} + +function summaryDefault(cell: string): string | null { + if (/^not configurable$/i.test(cell)) return null + const token = cell.match( + /^`([a-z][a-z0-9]*)`(?:\s*\((?:disabled|default)\))?$/, + )?.[1] + if (!token) { + throw new Error(`cerebras reasoning: unread default effort: ${cell}`) + } + return token +} + +/** The supported-models table. Its default has to match the model section. */ +function supportedModels(markdown: string): Array { + const tables = markdownTables(markdown).filter((table) => { + const header = table[0] + return ( + header?.length === 3 && + header[0]?.toLowerCase() === 'model' && + header[1]?.toLowerCase() === 'default effort' && + header[2]?.toLowerCase() === 'availability' + ) + }) + if (tables.length === 0) { + throw new Error('cerebras reasoning: no supported-models table') + } + if (tables.length > 1) { + throw new Error('cerebras reasoning: more than one supported-models table') + } + const rows = tables[0]?.slice(1) ?? [] + if (rows.length === 0) { + throw new Error('cerebras reasoning: supported-models table is empty') + } + return rows.map((cells) => { + if (cells.length !== 3) { throw new Error( - `cerebras reasoning: ignored effort cell also lists values: ${cell}`, + `cerebras reasoning: short summary row: ${cells.join(' | ')}`, ) } - return 'ignored' + return { + name: summaryName(cells[0] ?? ''), + defaultEffort: summaryDefault(cells[1] ?? ''), + } + }) +} + +function modelSections( + markdown: string, +): Array<{ heading: string; body: string }> { + const lines = markdown.split('\n') + const start = lines.findIndex((line) => line.trim() === '## Model behavior') + if (start < 0) { + throw new Error('cerebras reasoning: no model behavior section') + } + let end = lines.length + for (let index = start + 1; index < lines.length; index++) { + if (/^## [^#]/.test(lines[index] ?? '')) { + end = index + break + } + } + const parts = lines + .slice(start + 1, end) + .join('\n') + .split(/^### /m) + if (/^\|/m.test(parts[0] ?? '')) { + throw new Error('cerebras reasoning: unread table before the first model') } - const values = [...cell.matchAll(/`([^`]+)`/g)].map((match) => match[1] ?? '') - if (values.length === 0 || values.some((value) => value === '')) { - throw new Error(`cerebras reasoning: unread effort cell: ${cell}`) + const sections = parts.slice(1).map((part) => { + const breakAt = part.indexOf('\n') + const heading = (breakAt < 0 ? part : part.slice(0, breakAt)).trim() + if (heading === '') { + throw new Error('cerebras reasoning: empty model heading') + } + return { heading, body: breakAt < 0 ? '' : part.slice(breakAt + 1) } + }) + if (sections.length === 0) { + throw new Error('cerebras reasoning: no model sections') } - return values + return sections } -/** The first reasoning table. Any other disable or effort wording throws. */ -export function parseCerebrasReasoning(markdown: string): CerebrasReasoningDoc { - const configured = new Map() - const ignored: Array = [] - let header: Array | null = null - for (const cells of markdownTableRows(markdown)) { - if ( - cells[0] === 'Model' && - cells.some((cell) => cell.includes('reasoning_effort')) - ) { - if (!cells.some((cell) => /disable reasoning/i.test(cell))) { +/** The first backtick is the model id. A later `reasoning_effort` is not. */ +function sectionModelId(heading: string, body: string): string { + const id = body.match(/`([^`]+)`/)?.[1] + if (!id || !/^[a-z0-9][a-z0-9.-]*$/.test(id)) { + throw new Error(`cerebras reasoning: ${heading} names no model id`) + } + return id +} + +function valueRows( + id: string, + body: string, +): Array<{ value: string; behavior: string }> { + const rows: Array<{ value: string; behavior: string }> = [] + let open = false + let tables = 0 + for (const line of body.split('\n')) { + const cells = tableCells(line) + if (!cells) { + open = false + continue + } + if (isSeparator(cells)) continue + const header = + cells.length === 2 && + cells[0]?.toLowerCase() === 'value' && + cells[1]?.toLowerCase() === 'behavior' + if (header) { + tables += 1 + if (tables > 1) { throw new Error( - 'cerebras reasoning: table has no Disable reasoning column', + `cerebras reasoning: ${id} has more than one effort table`, ) } - header = cells + open = true continue } - if (!header || cells.length !== header.length) continue - const id = modelId(column(header, cells, 'Model')) + if (!open || cells.length !== 2) { + throw new Error(`cerebras reasoning: ${id} has an unread table`) + } + rows.push({ value: cells[0] ?? '', behavior: cells[1] ?? '' }) + } + if (tables === 0) { + throw new Error(`cerebras reasoning: ${id} has no effort table`) + } + if (rows.length === 0) { + throw new Error(`cerebras reasoning: ${id} effort table is empty`) + } + return rows +} + +function ignoresEffort(body: string): boolean { + return ( + /accepts `reasoning_effort`[\s\S]{0,160}?ignores its value/i.test(body) || + /accepted but ignored/i.test(body) + ) +} + +function readIgnored( + id: string, + rows: Array<{ value: string; behavior: string }>, +): void { + for (const row of rows) { + if (row.value.startsWith('`')) { + throw new Error( + `cerebras reasoning: ${id} ignores reasoning_effort but lists ${row.value}`, + ) + } + if (!/^(?:omitted|any accepted value)\b/i.test(row.value)) { + throw new Error( + `cerebras reasoning: ${id} unread ignored value: ${row.value}`, + ) + } + if (!/keeps reasoning enabled/i.test(row.behavior)) { + throw new Error( + `cerebras reasoning: ${id} unread ignored behavior: ${row.behavior}`, + ) + } + } +} + +function behaviorKind( + id: string, + cell: string, +): 'disable' | 'accept' | 'reject' { + if (/^disables reasoning\.?$/i.test(cell)) return 'disable' + if (/^not supported\.?$/i.test(cell)) return 'reject' + if (/^selects\b/i.test(cell) || /^enables reasoning\b/i.test(cell)) { + return 'accept' + } + throw new Error(`cerebras reasoning: ${id} unread behavior: ${cell}`) +} + +function effortTokens( + id: string, + cell: string, +): { tokens: Array; defaultToken: string | null } { + const parts = cell + .split(',') + .map((part) => part.trim()) + .filter((part) => part !== '') + if (parts.length === 0) { + throw new Error(`cerebras reasoning: ${id} unread effort cell: ${cell}`) + } + const tokens: Array = [] + let defaultToken: string | null = null + for (const part of parts) { + const match = part.match(/^`([a-z][a-z0-9]*)`(?:\s*\((default)\))?$/) + const token = match?.[1] + if (!token || tokens.includes(token)) { + throw new Error(`cerebras reasoning: ${id} unread effort cell: ${cell}`) + } + tokens.push(token) + if (match[2]) { + if (defaultToken) { + throw new Error(`cerebras reasoning: ${id} marks two defaults`) + } + defaultToken = token + } + } + return { tokens, defaultToken } +} + +function reasoningFrom( + id: string, + body: string, + rows: Array<{ value: string; behavior: string }>, + summaryDefaultEffort: string, +): ModelReasoning { + const efforts: Array = [] + const rejected: Array = [] + let defaultToken: string | null = null + let disables = false + for (const row of rows) { + const kind = behaviorKind(id, row.behavior) + const parsed = effortTokens(id, row.value) + if (parsed.defaultToken) { + if (kind === 'reject') { + throw new Error( + `cerebras reasoning: ${id} default ${parsed.defaultToken} is not supported`, + ) + } + if (defaultToken) { + throw new Error(`cerebras reasoning: ${id} marks two defaults`) + } + defaultToken = parsed.defaultToken + } + for (const token of parsed.tokens) { + if (efforts.includes(token) || rejected.includes(token)) { + throw new Error(`cerebras reasoning: ${id} lists ${token} twice`) + } + if (kind === 'reject') rejected.push(token) + else efforts.push(token) + } + if (kind === 'disable') disables = true + } + if (efforts.length === 0) { + throw new Error(`cerebras reasoning: ${id} lists no effort values`) + } + if (!defaultToken || !efforts.includes(defaultToken)) { + throw new Error(`cerebras reasoning: ${id} marks no accepted default`) + } + if (defaultToken !== summaryDefaultEffort) { + throw new Error( + `cerebras reasoning: ${id} default is ${defaultToken} but the summary says ${summaryDefaultEffort}`, + ) + } + const always = /always enables reasoning/i.test(body) + if (disables && always) { + throw new Error( + `cerebras reasoning: ${id} always enables reasoning but a value disables it`, + ) + } + const proseOff = /set `reasoning_effort` to `none`/i.test(body) + // `none` that disables, or a stated always-on / rejected `none`. Nothing + // else sets mandatory: a bare effort list states no off switch. + const mandatory = disables + ? false + : always || rejected.includes('none') + ? true + : null + if (mandatory === null) { + throw new Error( + `cerebras reasoning: ${id} does not state whether reasoning can be disabled`, + ) + } + if (proseOff && (mandatory || !efforts.includes('none'))) { + throw new Error( + `cerebras reasoning: ${id} says to send none but the table disagrees`, + ) + } + if (mandatory && efforts.includes('none')) { + throw new Error( + `cerebras reasoning: ${id} cannot be disabled but lists none`, + ) + } + if (!mandatory && !efforts.includes('none')) { + throw new Error( + `cerebras reasoning: ${id} can be disabled but lists no none`, + ) + } + return { mode: 'effort', mandatory, efforts } +} + +/** + * Per-model sections under "Model behavior". The summary table only checks + * that every listed model has a section and that its default matches. + */ +export function parseCerebrasReasoning(markdown: string): CerebrasReasoningDoc { + const text = markdown.replace(/```[\s\S]*?```/g, '') + const summary = supportedModels(text) + const sections = modelSections(text) + if (sections.length !== summary.length) { + throw new Error( + `cerebras reasoning: ${sections.length} model sections for ${summary.length} summary rows`, + ) + } + const configured = new Map() + const ignored: Array = [] + for (let index = 0; index < sections.length; index++) { + const section = sections[index] + const row = summary[index] + if (!section || !row || section.heading !== row.name) { + throw new Error( + `cerebras reasoning: section ${section?.heading ?? '(missing)'} is not summary row ${row?.name ?? '(missing)'}`, + ) + } + const id = sectionModelId(section.heading, section.body) if (configured.has(id) || ignored.includes(id)) { throw new Error(`cerebras reasoning: duplicate id ${id}`) } - const efforts = effortValues(column(header, cells, 'reasoning_effort')) - if (efforts === 'ignored') { + const values = valueRows(id, section.body) + if (ignoresEffort(section.body)) { + readIgnored(id, values) + if (row.defaultEffort !== null) { + throw new Error( + `cerebras reasoning: ${id} ignores reasoning_effort but the summary default is ${row.defaultEffort}`, + ) + } ignored.push(id) continue } - const mandatory = mandatoryFrom(column(header, cells, 'Disable reasoning')) - if (mandatory && efforts.includes('none')) { - throw new Error( - `cerebras reasoning: ${id} cannot be disabled but lists none`, - ) - } - if (!mandatory && !efforts.includes('none')) { + if (row.defaultEffort === null) { throw new Error( - `cerebras reasoning: ${id} can be disabled but lists no none`, + `cerebras reasoning: ${id} summary says not configurable but the section lists efforts`, ) } - configured.set(id, { mode: 'effort', mandatory, efforts }) + configured.set( + id, + reasoningFrom(id, section.body, values, row.defaultEffort), + ) } - if (!header) throw new Error('cerebras reasoning: no reasoning_effort table') return { configured, ignored } } diff --git a/src/server/providers/fixtures/cerebras-reasoning.md.txt b/src/server/providers/fixtures/cerebras-reasoning.md.txt new file mode 100644 index 00000000..6fff200f --- /dev/null +++ b/src/server/providers/fixtures/cerebras-reasoning.md.txt @@ -0,0 +1,315 @@ +> ## Documentation Index +> Fetch the complete documentation index at: https://inference-docs.cerebras.ai/llms.txt +> Use this file to discover all available pages before exploring further. + +# Reasoning + +> Set reasoning effort, choose a response format, and manage reasoning across conversation turns. + +Reasoning models generate intermediate thinking tokens before their final answer. Use `reasoning_effort` to select a model's reasoning level and `reasoning_format` to control how the API returns its reasoning. + +Reasoning tokens count toward `max_completion_tokens` and the completion-token usage reported by the API. Effort levels select model-specific reasoning modes; they don't reserve or guarantee an exact token budget. + +## Supported models + +The following models support reasoning. Select a model to see its supported effort levels and an example request. + +| Model | Default effort | Availability | +| - | - | - | +| [Qwen 3.8 27B](#qwen-3-8-27b) | `high` | Shared and Dedicated Inference | +| [Kimi K2.7 Code](#kimi-k2-7-code) | Not configurable | Dedicated Inference | +| [Kimi K3](#kimi-k3) | `max` | Dedicated Inference | +| [Kimi K3.1 Pro](#kimi-k3-1-pro) | `max` | Dedicated Inference | +| [GLM 5.3 Flash](#glm-5-3-flash) | `max` | Dedicated Inference | +| [GPT OSS 120B](#gpt-oss-120b) | `medium` | Shared and Dedicated Inference | +| [Gemma 4 31B](#gemma-4-31b) | `none` (disabled) | Dedicated Inference | + +For Shared Inference, use the model ID listed in the [model catalog](https://inference-docs.cerebras.ai/models/overview). For [Dedicated Inference](https://inference-docs.cerebras.ai/dedicated/overview.md#supported-models), use the endpoint ID assigned to your organization as the request's `model` value. + +## Send a reasoning request + +Install the Cerebras Python SDK and set `CEREBRAS_API_KEY` as described in the [Quickstart](https://inference-docs.cerebras.ai/quickstart.md). This example selects `medium` effort for GPT OSS 120B and prints the reasoning separately from the final answer. + +```python theme={null} +from cerebras.cloud.sdk import Cerebras + +client = Cerebras() + +response = client.chat.completions.create( + model="gpt-oss-120b", + messages=[ + {"role": "user", "content": "Review this retry policy: retry failed jobs immediately."} + ], + reasoning_effort="medium", + reasoning_format="parsed", +) + +print("Reasoning:", response.choices[0].message.reasoning) +print("Answer:", response.choices[0].message.content) +``` + +For streaming responses, read reasoning from `choices[0].delta.reasoning` and final-answer text from `choices[0].delta.content`. + +The remaining Python examples reuse `client`. In Dedicated Inference examples, replace `YOUR_ENDPOINT_ID` with your assigned endpoint ID for that model. + +## Reasoning format + +Use `reasoning_format` to select how the API returns reasoning. Support varies by model. + +| Value | Behavior | +| - | - | +| `parsed` | Returns reasoning in `choices[0].message.reasoning` and the final answer in `choices[0].message.content` | +| `raw` | Includes reasoning in the final-answer content when supported | +| `hidden` | Omits reasoning text while still generating and counting reasoning tokens | +| `none` | Uses the model's default response format | + +The following table summarizes format support. A value of `none` for `reasoning_format` doesn't disable reasoning. To disable reasoning on a model that supports it, use `reasoning_effort="none"`. + +| Model | Default response behavior | `raw` | `hidden` | +| - | - | - | - | +| [Qwen 3.8 27B](#qwen-3-8-27b) | Reasoning returned separately | Keeps reasoning separate | Not supported | +| [Kimi K2.7 Code](#kimi-k2-7-code) | `parsed` | Supported | Not supported | +| [Kimi K3](#kimi-k3) | `parsed` | Not supported | Not supported | +| [Kimi K3.1 Pro](#kimi-k3-1-pro) | `parsed` | Not supported | Not supported | +| [GLM 5.3 Flash](#glm-5-3-flash) | `parsed` | Supported | Supported | +| [GPT OSS 120B](#gpt-oss-120b) | `parsed` | Supported | Supported | +| [Gemma 4 31B](#gemma-4-31b) | Reasoning disabled until enabled | Not supported | Not supported | + +## Model behavior + +Each section lists the model's reasoning effort values, shows an example request, and describes its format restrictions. + +### Qwen 3.8 27B + +[`qwen-3.8-27b`](https://inference-docs.cerebras.ai/models/qwen-3.8-27b) enables reasoning by default. Use `reasoning_effort` to select one of the following modes. + +| Value | Behavior | +| - | - | +| `none` | Disables reasoning | +| `low` | Selects Qwen's low reasoning mode | +| `medium` | Selects Qwen's medium reasoning mode | +| `high` (default) | Selects Qwen's native `xhigh` reasoning mode | + +This example selects `high` effort. To disable reasoning, set `reasoning_effort` to `none`. + +```python theme={null} +response = client.chat.completions.create( + model="qwen-3.8-27b", + messages=[ + {"role": "user", "content": "Review this retry policy: retry failed jobs immediately."} + ], + reasoning_effort="high", + reasoning_format="parsed", +) +``` + +**Response format:** Reasoning is returned separately in `message.reasoning` or streaming `delta.reasoning`. Setting `reasoning_format` to `raw` doesn't change this behavior. The `hidden` format isn't supported. + +**Conversation history:** Use `clear_thinking` to preserve or remove historical assistant reasoning. See [Multi-turn reasoning](#multi-turn-reasoning-and-clear_thinking). + +Do not send Qwen-native parameters such as `disable_reasoning`, `enable_thinking`, `preserve_thinking`, or `thinking_budget`. Use `reasoning_effort` and `clear_thinking` instead. + +### Kimi K2.7 Code + +[`kimi-k2.7-code`](https://inference-docs.cerebras.ai/dedicated/overview#supported-models) always enables reasoning. The API accepts `reasoning_effort` for client compatibility but ignores its value. + +| Value | Behavior | +| - | - | +| Omitted | Keeps reasoning enabled | +| Any accepted value, including `none` | Keeps reasoning enabled; doesn't change reasoning effort | + +This example requests parsed reasoning and omits `reasoning_effort` because it has no effect. + +```python theme={null} +response = client.chat.completions.create( + model="YOUR_ENDPOINT_ID", + messages=[ + {"role": "user", "content": "Review this retry policy: retry failed jobs immediately."} + ], + reasoning_format="parsed", +) +``` + +**Response format:** The default is `parsed`. The `raw` format is supported; `hidden` isn't supported. + +**Conversation history:** `clear_thinking` isn't implemented. Do not send it. + +> [!WARNING] +> +> Do not combine `reasoning_format="raw"` with `response_format` types `json_object` or `json_schema` for Kimi K2.7 Code. + +### Kimi K3 + +[`kimi-k3`](https://inference-docs.cerebras.ai/dedicated/overview#supported-models) always enables reasoning. Use `reasoning_effort` to select one of the following levels. + +| Value | Behavior | +| - | - | +| `low` | Selects low reasoning effort | +| `high` | Selects high reasoning effort | +| `max` (default) | Selects maximum reasoning effort | +| `medium`, `none` | Not supported | + +This example selects `high` effort and parsed reasoning. + +```python theme={null} +response = client.chat.completions.create( + model="YOUR_ENDPOINT_ID", + messages=[ + {"role": "user", "content": "Review this retry policy: retry failed jobs immediately."} + ], + reasoning_effort="high", + reasoning_format="parsed", +) +``` + +**Response format:** Only `parsed` is supported. Omit `reasoning_format` or set it to `parsed`. Do not send `raw` or `hidden`. + +**Conversation history:** Replay the complete assistant response, including reasoning and tool calls. Kimi K3 preserves reasoning history. Do not send `clear_thinking`. + +### Kimi K3.1 Pro + +[`kimi-k3.1-pro`](https://inference-docs.cerebras.ai/dedicated/overview#supported-models) always enables reasoning. Use `reasoning_effort` to select one of the following levels. + +| Value | Behavior | +| - | - | +| `low` | Selects low reasoning effort | +| `medium` | Selects medium reasoning effort | +| `high` | Selects high reasoning effort | +| `max` (default) | Selects maximum reasoning effort | +| `none` | Not supported | + +This example selects `medium` effort and parsed reasoning. + +```python theme={null} +response = client.chat.completions.create( + model="YOUR_ENDPOINT_ID", + messages=[ + {"role": "user", "content": "Review this retry policy: retry failed jobs immediately."} + ], + reasoning_effort="medium", + reasoning_format="parsed", +) +``` + +**Response format:** Only `parsed` is supported. Omit `reasoning_format` or set it to `parsed`. Do not send `raw` or `hidden`. + +**Conversation history:** Replay the complete assistant response, including reasoning and tool calls. Kimi K3.1 Pro preserves reasoning history. Do not send `clear_thinking`. + +### GLM 5.3 Flash + +[`zai-glm-5.3-flash`](https://inference-docs.cerebras.ai/dedicated/overview#supported-models) always enables reasoning. Use `reasoning_effort` to select one of the following levels. + +| Value | Behavior | +| - | - | +| `low` | Selects low reasoning effort | +| `high` | Selects high reasoning effort | +| `max` (default) | Selects maximum reasoning effort | +| `medium`, `none` | Not supported | + +This example selects `high` effort and parsed reasoning. + +```python theme={null} +response = client.chat.completions.create( + model="YOUR_ENDPOINT_ID", + messages=[ + {"role": "user", "content": "Review this retry policy: retry failed jobs immediately."} + ], + reasoning_effort="high", + reasoning_format="parsed", +) +``` + +**Response format:** The default is `parsed`. Both `raw` and `hidden` are supported. The `hidden` format omits reasoning text while the model still generates and counts reasoning tokens. + +**Conversation history:** Use `clear_thinking` to preserve historical assistant reasoning or remove reasoning before the latest user message. See [Multi-turn reasoning](#multi-turn-reasoning-and-clear_thinking). + +### GPT OSS 120B + +[`gpt-oss-120b`](https://inference-docs.cerebras.ai/models/openai-oss) always enables reasoning. Use `reasoning_effort` to select one of the following levels. + +| Value | Behavior | +| - | - | +| `low` | Selects low reasoning effort | +| `medium` (default) | Selects medium reasoning effort | +| `high` | Selects high reasoning effort | +| `none` | Not supported | + +This example selects `medium` effort and parsed reasoning. + +```python theme={null} +response = client.chat.completions.create( + model="gpt-oss-120b", + messages=[ + {"role": "user", "content": "Review this retry policy: retry failed jobs immediately."} + ], + reasoning_effort="medium", + reasoning_format="parsed", +) +``` + +**Response format:** The default is `parsed`. Both `raw` and `hidden` are supported. With `raw`, reasoning and final-answer content are concatenated without separators. + +### Gemma 4 31B + +[`gemma-4-31b`](https://inference-docs.cerebras.ai/dedicated/overview#supported-models) disables reasoning by default. Use `reasoning_effort` to enable or disable reasoning. + +| Value | Behavior | +| - | - | +| `none` (default) | Disables reasoning | +| `low`, `medium`, `high` | Enables reasoning; all three values behave equivalently | + +This example enables reasoning with `medium` effort and requests parsed reasoning. + +```python theme={null} +response = client.chat.completions.create( + model="YOUR_ENDPOINT_ID", + messages=[ + {"role": "user", "content": "Review this retry policy: retry failed jobs immediately."} + ], + reasoning_effort="medium", + reasoning_format="parsed", +) +``` + +**Response format:** The `raw` and `hidden` formats aren't supported. + +**Conversation history:** `clear_thinking` isn't supported. Do not send it. + +## Multi-turn reasoning and `clear_thinking` + +Chat Completions is stateless. Resend conversation history with each request, including any assistant reasoning and tool calls required by the model. For tool calls, include the full assistant response followed by a matching result for each tool-call ID. See [Tool Calling](https://inference-docs.cerebras.ai/capabilities/tool-use.md). + +For Qwen 3.8 27B and GLM 5.3 Flash, `clear_thinking` controls whether the model receives historical assistant reasoning. It doesn't remove the assistant's final-answer content. + +| Model | `false` or omitted (default) | `true` | +| - | - | - | +| Qwen 3.8 27B | Preserves historical assistant reasoning | Removes historical assistant reasoning | +| GLM 5.3 Flash | Preserves historical assistant reasoning | Removes assistant reasoning before the latest user message | + +This example makes a Qwen request, includes its reasoning in the conversation history, and removes that reasoning from the follow-up prompt with `clear_thinking=True`. To preserve the reasoning instead, omit `clear_thinking` or set it to `False`. + +```python theme={null} +messages = [{"role": "user", "content": "Design a retry strategy for a payment queue."}] +first = client.chat.completions.create( + model="qwen-3.8-27b", + messages=messages, +) + +assistant = first.choices[0].message +messages.extend([ + assistant.model_dump(exclude_none=True), + {"role": "user", "content": "Now account for duplicate delivery."}, +]) + +follow_up = client.chat.completions.create( + model="qwen-3.8-27b", + messages=messages, + clear_thinking=True, +) + +print(follow_up.choices[0].message.content) +``` + +## OpenAI client parameters + +`reasoning_effort` is a standard OpenAI client parameter. Pass Cerebras-specific parameters such as `reasoning_format` and `clear_thinking` through `extra_body` when you use an OpenAI client. See [OpenAI Compatibility](https://inference-docs.cerebras.ai/resources/openai.md#pass-non-standard-parameters).