Cookbook renovation (#26885)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
zijiexia
2026-06-07 22:04:54 -07:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 6365d6faee
commit d1777d1f6d
16 changed files with 6692 additions and 1693 deletions
@@ -0,0 +1,220 @@
# Cookbook config reference (fields · cells · playground · MDX)
Loaded on demand by the `cookbook-add-model` skill. This is the field-by-field
contract for when the clone needs more than a rename. The two engine files are
the canonical specs — read their headers first:
- [`_deployment.jsx`](../../../../docs_new/src/snippets/_deployment.jsx) — the 5-dim matrix widget; lists every config field.
- [`_playground.jsx`](../../../../docs_new/src/snippets/_playground.jsx) — the diff-based override widget; lists the `playgroundFeatures` axes + the `AXIS_HANDLERS` interface.
Engine extension (adding a new playground axis) lives in [engine-axis.md](engine-axis.md).
---
## 2.1 Create the config file
**Path**: `docs_new/src/snippets/configs/<vendor>/<model>.jsx`. The vendor folder is
the HuggingFace org (`deepseek-ai`, `Qwen`, `moonshotai`, ...); the file
name is a short hyphenated model id (`deepseek-v4`, `qwen3.5`, ...).
**Shape**: must be a single `export const config = { ... }` literal. Do not
use function calls, spreads, fragment refs, or IIFE — Mintlify re-evaluates
this export at hydration time with module-level identifiers out of scope,
and any non-literal value crashes with `ReferenceError`.
**Required fields** (engine reads these — see the `_deployment.jsx` header for
the full contract):
| Field | Type | Purpose |
|---|---|---|
| `modelName` | string | Display label only. Not used for HF slug — see `modelNames`. |
| `supportedHardware` | `string[]` | Which hw ids appear in the catalog. Subset of `HARDWARE_CATALOG` (in `_deployment.jsx`) ∪ `config.hardware`. Listing an id makes its button appear; if no cell uses it, the engine greys it out automatically. |
| `hardware` | `{id,label,vram,vendor}[]` | Optional. GPUs the shared `HARDWARE_CATALOG` doesn't carry (workstation / desktop / future chips, e.g. RTX PRO 6000). The engine merges these into the catalog, so a model-specific GPU is config data — **no engine-catalog edit**. Also add the id to `supportedHardware`. |
| `variants` | `{id, label, subtitle?}[]` | 2nd-dim option list. Use `default` / single-element if the model has no variant axis. |
| `quantizations` | `{id, label}[]` | 3rd-dim option list. |
| `strategies` | `{id, label}[]` | 4th-dim option list. Common ids: `low-latency`, `balanced`, `high-throughput`. |
| `nodesOptions` | `{id, label}[]` | 5th-dim option list. The `id` MUST be `single` or `multi-N` — the engine parses N from the id for `--nnodes`. |
| `cells` | `{match, verified?, env, flags}[]` | One per supported (hw × variant × quant × strategy × nodes) combination. See §2.2. |
| `modelNames` | `{[key]: string}` | HF slug lookup. Keys are either `hw\|variant\|quant` (most specific) or `variant\|quant` (fallback). |
| `placeholders` | `{[key]: {target, label, default?}}` | `{{KEY}}` interpolation map for command + curl. `target` is `'command'` or `'curl'`. Editable through the Env modal. |
| `curl` | string | cURL template. Uses `{{MODEL_NAME}}` + placeholder keys. |
**Optional fields**:
| Field | Type | Purpose |
|---|---|---|
| `multiNodeHints` | `{[hwId]: string[]}` | Lines prepended as `# ...` comments to multi-node commands (env-var hints). Per-hw, and only for hw whose **cluster fabric needs manual NIC config** (e.g. `gb200` NVL72/MNNVL → NVSHMEM/Gloo hints). NOT every multi-N hw needs an entry — standard-IB DeepEP (h200) auto-detects the HCA, and Marlin multi-node (h100) uses no DeepEP/NVSHMEM at all. |
| `dockerImages` | `{[hwId]: string}` | Per-hw image name for `docker run` framing. **Ask the user which sglang build the recipes ran on; don't guess a supporting release.** Falls back to `lmsysorg/sglang:dev` if missing — also the sensible default when unsure. |
| `playgroundFeatures` | `{[axisId]: {...}}` | Opts into the Playground widget. See §2.3. |
| `benchmarkCommands` | `{speed: string, accuracy: {[accKey]: string \| {[variant]: string}}, numPromptsByConc?: {[c]: number}}` | Powers the benchmark card's **"⚡ Reproduce"** modal. `speed` is ONE `bench_serving` template; the engine fills `{{DATASET}}`/`{{ISL}}`/`{{OSL}}` from each cell's `speed[].workload`, the chip-picked `{{MAX_CONCURRENCY}}`, and `{{NUM_PROMPTS}}` (resolved `workload.num_prompts ?? numPromptsByConc[c] ?? max(c*2, 200)`). `accuracy` maps an accuracy field (e.g. `gsm8k_pct`) to a per-eval template — a string, OR a `{flash, pro, …}` object keyed by variant when the command differs per variant (e.g. GPQA/AIME `--max-tokens`). The modal renders a chip per eval (one command area, like Speed). Both also use `{{MODEL_NAME}}` + `{{CURL_HOST}}`/`{{CURL_PORT}}` like `curl`. Optional; the button only appears when this AND `benchmarks` are present. |
| `defaultAccuracy` | `{[variant]: {[accKey]: number}}` | Model-level accuracy applied to **every** cell of a variant (e.g. GPQA Diamond / AIME25 — hardware-independent). Merged UNDER each cell's measured `accuracy` (a per-cell value wins), so you set a variant's score once instead of copying it onto every benchmark entry. Keys must match `ACCURACY_LABELS` + `benchmarkCommands.accuracy`. |
| `github` | `{owner?, repo?, issueTemplate?, cookbookModel?}` | Overrides for the "Submit verified cell" CTA in the playground. Defaults: `sgl-project/sglang` + `3-playground-verified-cell.yml` + `"deepseek-ai/deepseek-v4"`. Set `cookbookModel` to the value that matches the `model` dropdown in your issue template so it's pre-selected when the issue opens. |
## 2.2 Author the 5-dim matrix (`cells[]`)
Each cell describes one verified (or auto-estimated) launch recipe.
```js
{
match: { hw: "b200", variant: "flash", quant: "fp4",
strategy: "low-latency", nodes: "single" },
verified: true, // green "Verified" badge; absence = yellow
env: [
"SGLANG_DEEPEP_NUM_MAX_DISPATCH_TOKENS_PER_RANK=1024",
],
flags: [
"--trust-remote-code",
"--model-path {{MODEL_NAME}}", // {{MODEL_NAME}} resolves from modelNames
"--tp 4",
"--moe-runner-backend flashinfer_mxfp4",
"--host {{HOST_IP}}",
"--port {{PORT}}",
],
},
```
**Rules**:
- `match` MUST contain exactly the 5 keys: `hw`, `variant`, `quant`,
`strategy`, `nodes`. The engine looks up cells by tuple equality.
- `env` and `flags` are FLAT literals. The engine does NOT expand
fragments, aliases, or templates — it consumes them verbatim
(only `{{PLACEHOLDER}}` substitutions happen at render time).
- DO NOT include `--nnodes` / `--node-rank` / `--dist-init-addr` in
`cell.flags` for multi-node cells. The renderer injects them
automatically from `match.nodes` (`multi-N` → N nodes).
- DO NOT include `--host` / `--port` literally — use `{{HOST_IP}}` /
`{{PORT}}` placeholders so users can override through the Env modal.
- Order flags as: `--model-path` first (after any `--trust-remote-code`),
then parallelism (`--tp`, `--dp`, `--enable-dp-attention`), then MoE
flags, then tuning knobs, with `--host` / `--port` last. The playground
engine assumes this ordering when inserting overrides (its anchors target
`--model-path` / `--tp` / etc., and inserts before the `--host` tail).
**Cells are denormalized on purpose** — common flags repeat across cells.
This makes each cell self-contained and easy to verify. When sweeping a
common change, edit every cell.
**Avoid premature cells**: only add a cell for a (hw × variant × quant ×
strategy × nodes) combination if you have a recipe that has been tested or
at least sanity-checked. The engine greys out un-listed combinations
automatically.
## 2.3 Configure `playgroundFeatures` (optional)
The Playground widget is opt-in per axis. Add only the axes that make sense
for this model. Recognised axis keys and their schemas (full reference in
the `_playground.jsx` header):
| Axis key | Widget | Use when |
|---|---|---|
| `attention` | TP / CP / DP-Attention sub-knobs (DP-Attention is a combined knob: its value is the DP degree AND toggles `--enable-dp-attention`) | Model exposes parallelism knobs in its cells (§2.2) and you want users to override them. |
| `moe` | Backend select + EP knob | Model is MoE and supports multiple `--moe-*-backend` choices. |
| `parsers` | Multi-toggle | Model has reasoning / tool-call parsers. |
| `speculative` | Single-select chip group | Model has spec-decoding presets you want to expose. |
| `pdDisagg` | Mode + transfer backend (+ optional per-backend env via `envWhen` hw-gate) + IB device + optional `router{port, command}` | Model supports prefill/decode disaggregation. When a PD role is active and `router` is set, the playground shows the router (SGLang Model Gateway) launch command as a separate companion block and retargets the cURL modal to `router.port` (clients hit the router, not the role servers). |
| `hicache` | Enable + storage + write policy | Model is large enough that hierarchical KV cache matters. |
| `hisparse` | Enable + host-ratio select; whole card gated on the live PD-Disagg mode being `decode` | DSA-style model (DeepSeek-V3.2 / V4, GLM-5) that supports decode-side hierarchical sparse attention. |
| `megamoe` | Single-select with hw/strategy gating | Blackwell-only kernel fusion variant. |
**Per-chip constraints**: any chip entry in any axis can be wrapped with
`hide` / `disable` constraint objects:
```js
{ value: 16, disable: { nodes: ["single"] },
disableReason: "TP=16 requires 16 ranks — switch the Deploy panel's Nodes to Multi-Nodes first." }
```
- `hide` — chip omitted entirely (use for hard impossibilities).
- `disable` — chip greyed out with tooltip (soft warning).
- Constraints are AND across keys, OR within each key's array.
- Bare `disabled: true` / `disable: true` is a static always-disabled form
(used for "Coming soon" chips).
## 2.4 Create the MDX page
Path: `docs_new/cookbook/<category>/<Vendor>/<Model>.mdx`. Import both widgets and
the per-model config, render them inside the relevant sections:
```mdx
## Deployment
import { Deployment } from "/src/snippets/_deployment.jsx";
import { config } from "/src/snippets/configs/<vendor>/<model>.jsx";
import { benchmarks } from "/src/snippets/configs/<vendor>/<model>-benchmarks.jsx";
{/* Install is a PREREQUISITE — keep it compact + collapsed at the top of the
Deploy section (NOT a numbered section). Tabs mirror the widget's
Python/Docker toggle. */}
<a id="install" />
<Accordion title="Install SGLang">
<Tabs>
<Tab title="Python (pip / uv)">…pip / uv install…</Tab>
<Tab title="Docker">…docker pull + a `docker run … sglang serve` example…</Tab>
</Tabs>
</Accordion>
<Deployment config={config} benchmarks={benchmarks} />
[model-specific tuning notes, caveats, links]
## Playground
import { Playground } from "/src/snippets/_playground.jsx";
<Playground config={config} />
```
**Heading slugs matter** — the two widgets cross-link by scrolling to each
other's section id (Mintlify auto-slugs headings: lowercase, spaces →
hyphens, punctuation dropped). The engines look up:
- the **Deploy** panel by id `deployment` (falls back to `deploy`) — used
by the Playground's "↑ Switch base" button and by deep-link scroll-on-
load. Title the section `## Deployment` (or `## Deploy`).
- the **Playground** by id `playground` — used by `_deployment.jsx`'s
"Open the Playground →" link. Title the section `## Playground`.
Avoid numbered headings like `## 3. Model Deployment` (slug
`3-model-deployment`) for these two sections — the cross-links would break.
The Playground reads the Deploy selection live via the URL hash + the
`sglang-deploy-sel` custom event, so the two can live in different parts
of the page.
The `benchmarks` prop is **optional**. It points at a sibling
`<model>-benchmarks.jsx` file (one entry per cell, keyed by the same
`match` tuple) that renders an accuracy + speed sub-card under the command
box; omit the import and the prop if the cookbook has no measured numbers
yet. See the `_deployment.jsx` header and `deepseek-v4-benchmarks.jsx` for
the full speed/accuracy schema.
To let users *reproduce* those numbers, add a `benchmarkCommands` block to
the config (§2.1, next to `curl`). When present alongside `benchmarks`, the
benchmark card grows a **"⚡ Reproduce"** button that opens a modal listing
the runnable commands for the current cell — one `bench_serving` command for
Speed (with concurrency chips that rewrite `--max-concurrency`) plus an
Accuracy command with a chip per eval. No separate benchmark section needed.
---
## Pitfalls (authoring)
**Stale URL hash hydration** — If a user shares a link from an old cell
catalog and the hash names an impossible combination, `_deployment.jsx`'s
`validateSelection` snaps to the nearest real cell. The Playground reads
the hash too — make sure cookbook removals don't leave dangling shared
links pointing at hardware/quant combos that no longer exist.
**Mintlify constraints** — Module-level statements are stripped. The config
MUST be a single `export const config = { ... }` literal — no function calls,
spreads, fragment refs, or IIFE (Mintlify re-evaluates the export at hydration
with module-level identifiers out of scope; any non-literal crashes with
`ReferenceError`). In MDX, capitalized JSX tags get rebound — use the built-in
Mintlify components (`<Accordion>`, `<Tabs>`, `<Card>`, ...) as documented.
Avoid `!(x in y)` anywhere (Mintlify's AST walker crashes on it) — use
`obj.key === undefined`.
**Per-cell denormalization** — Cells repeat common flags on purpose. Do
not factor them into a shared `commonFlags` array — Mintlify will fail
to inline the reference. If you need to sweep a flag across cells, do it
with a global find-replace in the config file.
@@ -0,0 +1,242 @@
# Engine extension: add a new playground feature axis
Loaded on demand by the `cookbook-add-model` skill. **Rare** — adding a model
cookbook is data-only and never needs this. The current 8 built-in axes
(`attention`, `moe`, `parsers`, `speculative`, `pdDisagg`, `hicache`,
`hisparse`, `megamoe`) already cover the SGLang feature surface most cookbooks
need. Only add a new axis if a real cookbook needs it and the feature does not
fit any existing axis. Touches `_playground.jsx` only.
For the per-model config/cells/MDX reference see [authoring-reference.md](authoring-reference.md).
---
## 3.1 Decide
Before touching the engine, confirm:
- The feature is a STABLE part of the SGLang CLI surface (will appear in
multiple cookbooks, not one-off).
- The feature cannot be expressed as a new option inside an existing axis
(e.g. a new MoE backend belongs in `moe.backend.options`, not a new
axis).
- The feature has a clean strip-prefix → emit-flag pattern.
If unsure, add it as data first (in one cookbook's config under an
existing axis) before promoting it to a built-in axis.
## 3.2 Pick the axis id and state shape
The axis id is the key in both `config.playgroundFeatures` and the
internal `deltas` object. Use camelCase, descriptive but short:
`mambaCache`, `attentionBackend`, `kvCacheDtype`.
The state shape is whatever `initState` returns. Common shapes:
- Single-select: a string sentinel (e.g. `"disabled"` / `"current"` / an
option id).
- Multi-toggle: `{[itemId]: bool}`.
- Sub-knobs: `{[knobId]: value | null}`.
- Compound (axis with its own internal sub-state, like PD-Disagg's
`{mode, ibDevice}`): a plain object.
Pick ONE "inherit base" sentinel and document it in the handler comment.
## 3.3 Implement the handler
Add one entry to `AXIS_HANDLERS` in `_playground.jsx`.
The handler owns everything: state init, apply (strip+insert), hidden-revert,
AND the JSX render. Engine main loop iterates `AXIS_HANDLERS` and calls each
method by name — adding a new axis is genuinely a one-place change.
Template:
```js
// ---- Axis: <Title> ----------------------------------------------------
// <one-paragraph description of what this axis controls and why it
// exists. Mention the SGLang feature it wraps and the strip/insert
// policy.>
<axisId>: {
initState: (fc) => /* initial state value */,
// Called when base cell changes. Return new value if the picked option
// is now hidden by a constraint; otherwise return value unchanged.
// Disabled picks are intentionally NOT auto-reverted (soft warning).
revertHidden: (value, fc, base, h) => {
// ... return value or a new value
return value;
},
// Pure function. Receives the current (flags, env) and returns the next
// (flags, env). Do NOT mutate inputs. The `value` argument is whatever
// initState returned. The `fc` argument is config.playgroundFeatures[axisId].
// The `sel` argument is the current base cell selection. The `h`
// argument is the helpers bundle (strip/insert primitives + anchors).
apply: ({ flags, env, value, fc, sel, h, derived }) => {
if (/* value is the inherit-base sentinel */) return { flags, env };
flags = h.stripFlagsByFirstToken(flags, [/* prefixes this axis owns */]);
if (/* an option is picked */) {
flags = h.insertAfter(flags, h.ANCHOR_NEAR_<X>, [/* new flags */]);
// or: flags = h.insertBeforeTail(flags, [/* new flags */]);
// if the axis mutates env:
// env = h.stripEnvByPrefix(env, fc.stripEnv || []);
// env = [...env, /* additional env vars */];
}
return { flags, env };
},
// Optional: read the base cell's flag array back into the same shape
// initState/apply use. Render shows this as the default selection
// (dropdown option or checked chip) when the state slot is the inherit
// sentinel — so the user sees the cell's actual --tp / MoE backend /
// spec preset instead of an opaque "Auto." When derive returns a real
// value, the inherit-sentinel option is hidden from the control. Apply
// also receives the derived
// value (as `derived`) and may use it as a no-op shortcut when the
// user's pick matches base. Skip when your axis owns flags that never
// appear in base cells (PD-Disagg / HiCache / MegaMoE).
// deriveFromBase: (cell, fc, h) => ({ ... }) | null,
// Optional: hints for the renderer. Currently only pdDisagg uses this
// to report its role banner. Omit if not needed.
// getRenderHints: (value, fc) => ({ pdMode: ... }) | null,
// Returns the axis card JSX. The outer div MUST have key={axisId} so
// React can track it in the engine's map loop. Return null for
// axis-level gating (e.g. MegaMoE on Hopper). Lay out as a single
// compact horizontal row: title on the left, fields after.
render: ({ axisId, value, setValue, fc, base, s, h, renderChip, renderSelect, derived }) => {
if (/* axis-level gating fails */) return null;
return (
<div key={axisId} style={s.card}>
<div style={s.compactRow}>
<span style={s.axisTitle}>Axis Title</span>
{/* For multi-option fields, use renderSelect(...) — the default.
For on/off toggles or single-select chip groups, use
renderChip instead (see "Control choice" in the conventions
below). Read state from `value`; write via `setValue(next)`
(replaces the whole axis slot). */}
<span style={s.field}>
<span style={s.fieldLabel}>Field</span>
{renderSelect(value.slot, fc.entries, (v) =>
setValue({ ...value, slot: v }), base)}
</span>
</div>
</div>
);
},
},
```
**Important conventions**:
- Insert the entry in the position you want it rendered. `AXIS_HANDLERS`
is iterated in insertion order for both render and apply.
- Use `h.ANCHOR_NEAR_*` constants for insertion. Add a new anchor to the
helpers bundle if your axis needs to land somewhere new in the flag
block.
- Use lowercase HTML JSX tags only. Capitalized tags get rebound by
Mintlify.
- Inside `render`, read state via `value` (the slice for this axis).
Write state via `setValue(next)` (replaces the whole slice). For
compound axes, do `setValue({ ...value, [k]: nextK })`.
- Layout: one `s.compactRow` per axis card, `s.axisTitle` for the
leading label, one `s.field` per (label + input) pair.
- Control choice — `renderSelect` vs `renderChip`:
- `renderSelect(current, entries, onPick, base, labelFor?, opts?)` is
the **default** compact control (a `<select>` dropdown). It filters
hidden chips and disables greyed-out ones internally — no per-chip
`evaluateChip` loop needed in the render body. Most axes use it
(attention, moe, pdDisagg, hisparse, hicache, megamoe). Pass
`{ hideValues: [<sentinel>] }` when your `deriveFromBase` resolved to
a real value, so the inherit-sentinel ("Auto" / "Inherited" /
"current") doesn't clutter the dropdown.
- `renderChip(label, current, value, onPick, { disabled?, disabledReason? })`
renders a **button** instead of a dropdown row. Use it for a chip
group when you want the options laid out as buttons. It serves two
shapes:
- **Multi-toggle** (Parsers) — one independent on/off chip per item;
`current` is that item's effective bool, `value` is `true`, so the
chip is "checked" when the item is on.
- **Single-select** (Speculative) — a radio-style group; pass the
group's effective value as `current` and each option's id as
`value`, so exactly one chip is checked (`current === value`).
Chip groups own their visibility/disable filtering: loop
`h.evaluateChip(opt, base)` in the render body, skip `c.hidden`,
filter the inherit-sentinel yourself when `deriveFromBase` resolved
to a real value, and forward `c.disabled` / `c.disableReason` into
`renderChip`'s opts (this is what surfaces a disabled chip's tooltip,
e.g. a "Coming soon" entry).
- Selected chips use the same terracotta (`#D45D44`) as the Deploy
panel's selected button, so both widgets read as one
visual system. Don't introduce a per-axis accent color.
- Default-from-base: if your axis can be read out of base cells'
flags, implement `deriveFromBase` and have your render show the
derived value when state is the sentinel (e.g.
`const eff = value.tp !== null ? value.tp : (derived && derived.tp)`).
This is what makes a fresh playground load show the user's actual
recipe instead of "auto." Flag-parsing helpers on `h`:
`parseIntFlag`, `hasFlag`, `findFlagArg`.
- **Avoid the `in` operator wrapped in unary** (`!(x in y)`). Mintlify's
AST walker crashes on it (`TypeError: this[e] is not a function`). Use
`obj.key === undefined` or `obj.id !== undefined` instead. Bare
`if (key in obj)` (no surrounding `!`) is fine.
## 3.4 Document the per-cookbook schema
Edit the file header in `_playground.jsx` to add your new axis to the
"Recognised keys" list, with a one-line description of its schema.
Optionally add a paragraph below explaining its strip/insert policy.
Update the §2.3 axis table in [authoring-reference.md](authoring-reference.md) to list the new axis.
## 3.5 Migrate cookbooks that need it
For each cookbook that should expose this axis, add a
`playgroundFeatures.<axisId>` entry to its config. Verify the chip group
renders, options apply correctly, and the diff matches expectations.
---
## Pitfalls (engine work)
**Insertion anchor misses** — `insertAfter` falls back to right-after
`--model-path` if none of its anchor prefixes are present. If your axis
emits flags that should land somewhere specific, include the most likely
anchor prefixes in your call. Order doesn't matter (set semantics).
**Conditional strips** — Some axes strip ONLY when overridden
(`attention.tp`, `moe.backend`, `speculative`, `megamoe`). Others strip
UNCONDITIONALLY whenever declared (`parsers`, `pdDisagg`, `hicache`). The
header comment in `AXIS_HANDLERS` documents which policy each axis uses;
follow the same pattern when adding a new axis. If unsure, prefer
conditional strip — it preserves base behavior when the user does not
opt in.
**Closure of `AXIS_HANDLERS`** — Inside a handler method, you can
reference `AXIS_HANDLERS.<otherAxis>` for cross-handler calls (megamoe
does this for `_gateOpen`). This works because `AXIS_HANDLERS` is in
lexical scope. Do NOT use this for general logic — it tightly couples
handlers. Reserve it for one handler's helpers shared between its own
`render` and `revertHidden`.
---
## Review checklist for a new-axis PR
- [ ] `AXIS_HANDLERS` is the ONLY place that mentions the new axis id
(apart from per-cookbook config). No `if (axisId === '<new>')`
branches anywhere in the engine.
- [ ] `initState` is deterministic and idempotent (does not depend on
the base cell).
- [ ] `apply` is pure — does not mutate inputs.
- [ ] `revertHidden` returns the same reference when nothing changed
(avoids unnecessary re-renders).
- [ ] `render` returns `null` when axis-level gating fails (whole card
hidden) — does not render an empty placeholder.
- [ ] `render` sets `key={axisId}` on its outer element.
- [ ] No `!(x in y)` patterns introduced (Mintlify AST walker crashes).
- [ ] File header lists the new axis in "Recognised keys".
- [ ] The §2.3 table in `authoring-reference.md` lists the new axis.
- [ ] One existing cookbook config is updated to consume the new axis,
and visual verification shows the diff is correct.
@@ -0,0 +1,107 @@
# MDX authoring rules (Mintlify) + invocation-example patterns
Loaded on demand by the `cookbook-add-model` skill (Phase 5, writing the page prose).
These are model-agnostic Mintlify hygiene rules — the most common review findings.
The cookbook is **Mintlify**, not Docusaurus.
## Mintlify syntax
**Allowed components**: `<Card>`, `<CardGroup>`, `<Note>`, `<Tip>`, `<Warning>`,
`<Info>`, `<Accordion>`, `<AccordionGroup>`, `<Steps>`, `<Step>`, `<Tabs>`, `<Tab>`,
`<CodeGroup>`, `<Frame>`, `<Icon>`.
**Forbidden** (flag every occurrence):
- Docusaurus admonitions (`:::note` / `:::warning` / …) — use `<Note>` / `<Warning>`.
- `@site/...` / `@theme/...` imports — use absolute `/src/snippets/...`.
- GitHub alert blocks (`> [!NOTE]`, `> [!WARNING]`).
- **Markdown pipe tables** on new pages — use JSX `<table>` (see below).
- Inline `<details>` / `<summary>` — use `<Accordion>`.
- Unknown / non-Mintlify components.
- `<CardGroup>` / `<Card>` on individual model pages — those are for category `intro.mdx` only.
**Code fences**: always labeled — ` ```python Example `, ` ```bash Command `,
` ```shell Command `, ` ```text Output `. When nesting a fenced block inside another,
the **outer** fence uses four backticks.
**Internal links**: root-relative, no extension (`/cookbook/<category>/<Vendor>/<Model>`);
`docs.sglang.io` is canonical. Flag `.md`/`.mdx` extensions and `../`-relative page links
in body prose. (Existing cookbook pages do use `../../../docs/...` for cross-links into
the non-cookbook docs tree — that's the established exception; don't introduce new ones.)
## JSX tables (required for all tables on new pages)
```jsx
<table style={{width: "100%", borderCollapse: "collapse", tableLayout: "fixed"}}>
<thead>
<tr style={{borderBottom: "2px solid #d55816"}}>
<th style={{textAlign: "left", padding: "10px 12px", fontWeight: 700}}>Col</th>
</tr>
</thead>
<tbody>
<tr><td style={{padding: "9px 12px"}}>cell</td></tr>
</tbody>
</table>
```
Alternate column background colors (`rgba(255,255,255,0.02)` / `0.05`) for readability;
adjust `<colgroup>` widths for 3- or 5-column tables. The DeepSeek-V4 page §1 variants
table is a live reference.
## Invocation-example patterns (§3 Advanced Usage)
- **Reasoning-parser output shape must match the example**:
- *Separate-field* parsers (most qwen/glm, `kimi_k2`, `deepseek-v4`): thinking lands
in `message.reasoning_content`, answer in `message.content` — print both.
- *Inline-tag* parsers (e.g. `minimax-append-think`): thinking is wrapped in
`<think>...</think>` **inside** `message.content` — the client parses the tags; for
streaming, buffer and split on the markers.
Pick the pattern from the model card / SGLang docs for that specific parser.
- **Hybrid reasoning models**: show both thinking-on (default) and thinking-off
(`extra_body={"chat_template_kwargs": {"thinking": False}}` or `enable_thinking: False`).
- **Tool-call follow-up on thinking models**: the final assistant turn may put text in
`reasoning_content` instead of (or with) `content` — print both so the output isn't a
misleading `None`.
- **Every runnable block** is immediately followed by `**Output Example:**` + a
` ```text Output ` block with **real** server output (verbatim, not paraphrased).
`Pending update...` is acceptable only with the user's explicit acknowledgement.
- **Do not hardcode sampling params** (`temperature`, `top_p`) in sample code — SGLang
uses `generation_config.json` defaults. Listing "Recommended generation" in §1 is fine.
- Format raw API objects (`ChatCompletionMessage(...)`) into readable Reasoning /
Content / Tool Calls blocks.
## Frontmatter
- **Top-level `description:`** is the canonical field — it sets the page's SEO meta
description (`og:`/`twitter:description` fall back to it) AND renders as the visible
**subtitle** under the title, filling the header band before the first heading. Give every
page a one-line top-level `description` (a lede / value prop) — without it, a page that
opens straight into `## Deployment` shows an empty gap under the title (the title and
`## Deployment` are the same size, so they read as two bare headings). Do **not** put the
description inside a `metatags` block — `metatags` is for other/custom tags, and
`metatags.description` is redundant with (and non-canonical vs) the top-level field.
- **Write it for SEO** (it doubles as the search-result snippet): front-load the exact
model name + intent — e.g. `Deploy <Model> with SGLang — …` — aim for ~150–160 chars, and
pack secondary keywords (variants + sizes, `Mixture-of-Experts` / architecture, target
GPUs). Phrase it as a value prop, not a generic "`<Model>` is a … model" intro.
- Frontmatter MUST be the first thing in the file — no comment or blank line before the
opening `---`.
## Commands & ports
- **Deploy/launch** commands use `sglang serve --model-path …` — never
`python -m sglang.launch_server` / `python3 -m sglang.launch_server` (deprecated).
- **Benchmark workload** commands use `python3 -m sglang.bench_serving …` (never bare
`python -m`); built-in accuracy scripts use `python3 benchmark/...`.
- Port **30000** everywhere on a page — launch, curl, client `base_url`, and bench must
agree. Keep one canonical deploy command (the Deploy widget) and don't re-paste launch
commands across sections; the documented command must match the widget's output for the
same selection (doc ↔ config parity).
## Factual hygiene
- License must match the actual HuggingFace license (don't copy from another model).
- HF URLs resolve to a real model; Docker images from `lmsysorg/sglang`.
- No Google-Drive image links (they don't render); host images in the repo.
- Shell placeholders are `export VAR=<value>`, not `export VAR=${VAR}` (a bash no-op).
- `tag: NEW` is sparing — at most one per `<category>/<Vendor>/` dir (the newest); strip
it from siblings when adding a new NEW page.
@@ -0,0 +1,76 @@
# Vendor card logo (new brand only)
A new vendor/brand in the cookbook landing grid needs a card logo at
`docs_new/cards/logos/<org-slug>.png`. **Ask the user for the brand's logo, then generate
the conforming PNG** — never invent, copy, or hallucinate one, and never ship a
non-conforming file. Reference: PR #27400 (added `tencent.png` + `poolside.png`).
If the org already has a card/logo, do nothing here — only update the `<Card href>`.
## Spec (match the existing logos exactly)
| Property | Value |
|---|---|
| Path | `docs_new/cards/logos/<org-slug>.png` — lowercase, matches the `img=` in the `<Card>` |
| Canvas | **940 × 525** px |
| Mode | **RGBA**, fully **transparent** background |
| Content | **Icon-only** — the brand glyph/mark (the "swirl"), **no wordmark text** |
| Placement | centered; glyph ≈ 0.33 × width and ≈ 0.50 × height of the canvas |
Why icon-only + transparent: cards render on both light and dark backgrounds, so a baked-in
(usually black) wordmark vanishes on dark. `deepseek.png` / `ernie.png` are 940×525 RGBA
exemplars — eyeball your output against them.
## 1. Get the source
Ask the user for the brand logo (SVG preferred → crisp + already transparent; else a high-res
transparent PNG, or a link to the official press/brand asset). Prefer an **icon-only** source;
if they only have a full lockup, ask them to crop the glyph, or crop it yourself.
If the user **pasted** an image inline, it may not be on disk — recover the base64 `image`
block from the session transcript (`~/.claude/projects/<slug>/*.jsonl`) and decode it to a file.
## 2. Generate (Pillow)
There's no system Pillow — use a venv:
```bash
python3 -m venv /tmp/logo-venv && /tmp/logo-venv/bin/pip install -q Pillow
```
```python
from PIL import Image
src = Image.open("SOURCE").convert("RGBA") # icon-only, already transparent
W, H = 940, 525
target_h = round(H * 0.50) # glyph ≈ half the canvas height
scale = target_h / src.height
glyph = src.resize((round(src.width * scale), target_h), Image.LANCZOS)
canvas = Image.new("RGBA", (W, H), (0, 0, 0, 0)) # transparent
canvas.paste(glyph, ((W - glyph.width) // 2, (H - glyph.height) // 2), glyph)
canvas.save("docs_new/cards/logos/<org-slug>.png")
```
Notes:
- **Wordmark present?** Crop to the glyph first (or ask the user for an icon-only asset). Don't
ship text in the logo.
- **Solid background?** Don't auto-strip it (risky) — ask the user for a transparent source.
- **SVG source?** Rasterize at high res first (`cairosvg` / `rsvg-convert`), then run the above.
- If the glyph is much wider than tall, cap by width instead (≈ 0.33 × W) so it doesn't overflow.
## 3. Verify
```bash
sips -g pixelWidth -g pixelHeight -g hasAlpha docs_new/cards/logos/<org-slug>.png
# → pixelWidth: 940 pixelHeight: 525 hasAlpha: yes
```
## 4. Wire + track + validate
```bash
# Card in the landing grid (keep card order aligned with the docs.json nav order):
# <Card title="<NavGroup>" mode="card"
# href="/cookbook/<category>/<Vendor>/<Model>"
# img="/cards/logos/<org-slug>.png" />
git add -f docs_new/cards/logos/<org-slug>.png # root .gitignore ignores *.png repo-wide
cd docs_new && mint validate && mint broken-links # confirms the card href + img resolve
```