SPB Git

Repository Public Research preview

build-knowledge.mjs 8.1 KB 82 lines javascript

1// Derives knowledge.json from the live probe matrix, the official pricing page and the official2// model pages. Usage: node probes/build-knowledge.mjs [--refetch]   (refetch re-reads model pages)3import { existsSync, readFileSync, writeFileSync } from "node:fs";4const matrix = JSON.parse(readFileSync(new URL("./matrix.json", import.meta.url)));5const pagesFile = new URL("./model-pages.json", import.meta.url);67// USD per 1M tokens, https://developers.openai.com/api/docs/pricing (read 2026-11-10).8// [input, output, cached input, batch input, batch output, fast input, fast output, long-context input, long-context output]9const P = {10  "gpt-6-astra": [10, 50, 1, 5, 25, 20, 100, 20, 75], "gpt-6.1-sol": [2, 10, 0.1, 1, 5, 4, 20, 4, 15], "gpt-6.1-terra": [1, 5, 0.05, 0.5, 2.5, 2, 10, 2, 7.5],11  "gpt-6-luna": [0.1, 0.5, 0.01, 0.05, 0.25, 0.2, 1, 0.2, 0.75], "gpt-6-sol": [2, 10, 0.2, 1, 5, 4, 20, 4, 15], "gpt-5.6-sol": [4, 20, 0.4, 2, 10, 8, 40, 8, 30],12  "gpt-5.6-terra": [2, 12, 0.2, 1, 6, 4, 24, 4, 18], "gpt-5.6-luna": [0.2, 1.2, 0.02, 0.1, 0.6, 0.4, 2.4, 0.4, 1.8], "gpt-5.5": [5, 30, 0.5, 2.5, 15, 12.5, 75, 10, 45],13  "gpt-5.5-pro": [30, 180, null, 15, 90, null, null, 60, 270], "gpt-5.4": [2.5, 15, 0.25, 1.25, 7.5, 5, 30, 5, 22.5], "gpt-5.4-pro": [30, 180, null, 15, 90, null, null, 60, 270],14  "gpt-5.4-mini": [0.75, 4.5, 0.075, 0.375, 2.25, 1.5, 9], "gpt-5.4-nano": [0.2, 1.25, 0.02, 0.1, 0.625], "gpt-5.2": [1.75, 14, 0.175, 0.875, 7, 3.5, 28],15  "gpt-5.2-pro": [21, 168, null, 10.5, 84], "gpt-5.1": [1.25, 10, 0.125, 0.625, 5, 2.5, 20], "gpt-5": [1.25, 10, 0.125, 0.625, 5, 2.5, 20],16  "gpt-5-mini": [0.25, 2, 0.025, 0.125, 1, 0.45, 3.6], "gpt-5-nano": [0.05, 0.4, 0.005, 0.025, 0.2], "gpt-5-pro": [15, 120, null, 7.5, 60],17  "gpt-4.1": [2, 8, 0.5, 1, 4, 3.5, 14], "gpt-4.1-mini": [0.4, 1.6, 0.1, 0.2, 0.8, 0.7, 2.8], "gpt-4o": [2.5, 10, 1.25, 1.25, 5, 4.25, 17],18  "gpt-4o-mini": [0.15, 0.6, 0.075, 0.075, 0.3, 0.25, 1], "o3-pro": [20, 80, null, 10, 40], "o3": [2, 8, 0.5, 1, 4, 3.5, 14],19};20const n = (x) => (x === undefined ? null : x);21const price = (id) => { const p = P[id]; if (!p) return null; return { input: p[0], output: p[1], cached_input: n(p[2]), batch_input: n(p[3]), batch_output: n(p[4]), fast_input: n(p[5]), fast_output: n(p[6]), long_context: p[7] != null ? { above_input_tokens: 272000, input: p[7], output: p[8] } : null }; };2223let pages = existsSync(pagesFile) ? JSON.parse(readFileSync(pagesFile)) : {};24if (process.argv.includes("--refetch") || Object.keys(pages).length === 0) {25  pages = {};26  for (const id of Object.keys(matrix.models)) {27    try {28      const res = await fetch(`https://developers.openai.com/api/docs/models/${id}.md`, { headers: { "User-Agent": "docs-fetch/1.0" } });29      if (!res.ok) { pages[id] = { found: false, status: res.status }; continue; }30      const t = (await res.text()).replace(/<[^>]*>/g, "");31      const num = (re) => { const m = re.exec(t); return m ? Number(m[1].replace(/,/g, "")) : null; };32      pages[id] = { found: true, context_window: num(/([\d,]+) context window/), max_output_tokens: num(/([\d,]+) max output tokens/), knowledge_cutoff: /- ([A-Z][a-z]{2} \d{1,2}, \d{4}) knowledge cutoff/.exec(t)?.[1] ?? null, deprecated: /deprecat/i.test(t.slice(0, 1500)) };33    } catch (e) { pages[id] = { found: false, error: String(e.message) }; }34  }35  writeFileSync(pagesFile, JSON.stringify(pages, null, 1));36}3738// A 400 whose text says generation ran out of tokens is a generation outcome, not a validation failure.39const accepted = (r) => r.ok || /Could not finish the message because max_tokens/.test(r.message ?? "");40const rejected = (r) => !accepted(r) && (r.status === 400 || r.status === 404 || r.status === 403);41const EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];42const models = {};43for (const [id, { probes: p }] of Object.entries(matrix.models)) {44  const levels = EFFORTS.filter((l) => accepted(p[`effort_${l}`]));45  const chat = !(p.chat_basic.status === 404);46  const pg = pages[id] ?? {};47  models[id] = {48    id, family: id.startsWith("o3") ? "o-series" : id.startsWith("gpt-4") ? "gpt-4" : id === "chat-latest" ? "chat" : id.split("-").slice(0, 2).join("-"),49    reasoning_model: levels.length > 0, context_window: pg.context_window ?? null, max_output_tokens: pg.max_output_tokens ?? null, knowledge_cutoff: pg.knowledge_cutoff ?? null,50    pricing_usd_per_mtok: price(id),51    responses: {52      effort_levels: levels,53      // Observed rule: reasoning models accept temperature/top_p only while the effective effort is "none".54      default_effort_is_none: levels.length > 0 && accepted(p.temperature),55      sampling_params: levels.length === 0 ? "always" : accepted(p.temperature_effort_none) ? "effort_none_only" : "never", verbosity_levels: ["low", "medium", "high"].filter((v) => v === "medium" || accepted(p[`verbosity_${v}`])),56      structured_outputs: accepted(p.structured), function_tools: accepted(p.tool_auto), forced_tool_choice: accepted(p.tool_required) && accepted(p.tool_forced),57      web_search: accepted(p.web_search), code_interpreter: accepted(p.code_interpreter) ? "yes" : /other than 'auto'/.test(p.code_interpreter.message ?? "") ? "auto_only" : "no",58      hosted_tools_need_auto_choice: /other than 'auto'/.test(p.web_search.message ?? ""),59      image_input: accepted(p.image_input), logprobs: accepted(p.top_logprobs), reasoning_summary: accepted(p.reasoning_summary), encrypted_reasoning: accepted(p.include_encrypted),60      background: accepted(p.background), flex_tier: accepted(p.service_tier_flex) || p.service_tier_flex.status === 429, priority_tier: accepted(p.service_tier_priority),61      prompt_cache_key: accepted(p.prompt_cache_key), cache_retention_24h: accepted(p.cache_retention_24h), min_max_output_tokens: 16,62    },63    chat_completions: chat ? {64      supported: true, max_tokens_legacy: accepted(p.chat_max_tokens_legacy),65      temperature: levels.length === 0 ? "always" : accepted(p.chat_temperature_effort_none) ? "effort_none_only" : "never", default_effort_is_none: levels.length > 0 && accepted(p.chat_temperature), stop: accepted(p.chat_stop), logprobs: accepted(p.chat_logprobs),66      reasoning_effort_param: accepted(p.chat_reasoning_effort), function_tools: accepted(p.chat_tools) ? "yes" : accepted(p.chat_tools_effort_none) ? "effort_none_only" : "no",67      structured_outputs: accepted(p.chat_json_schema), n: accepted(p.chat_n2), seed: accepted(p.chat_seed), developer_role: accepted(p.chat_developer_role), system_role: accepted(p.chat_system_role),68    } : { supported: false },69    observed_errors: Object.fromEntries(Object.entries(p).filter(([, v]) => rejected(v)).map(([k, v]) => [k, v.message])),70  };71}72const out = {73  schema: 1, observed_at: matrix.generated_at,74  sources: { behaviour: "Live Responses and Chat Completions probes (probes/probe.mjs)", pricing: "https://developers.openai.com/api/docs/pricing (read 2026-11-10)", limits: "https://developers.openai.com/api/docs/models/<id> (probes/model-pages.json)" },75  recommended: { flagship: "gpt-6-astra", balanced: "gpt-6.1-sol", fast: "gpt-6.1-terra", cheapest: "gpt-6-luna" },76  notes: ["Only models visible to the probing key were probed; dated snapshots are assumed to behave like their alias.", "Audio, image-generation, realtime, embedding and moderation models are listed by list_models but not rule-probed.", "Behaviour changes: re-run the probes before relying on an edge case."],77  other_prices_usd_per_mtok: { "text-embedding-3-small": { input: 0.02 }, "text-embedding-3-large": { input: 0.13 }, "text-embedding-ada-002": { input: 0.1 }, "omni-moderation-latest": { input: 0 } },78  models,79};80writeFileSync(new URL("../knowledge.json", import.meta.url), JSON.stringify(out, null, 1));81for (const m of Object.values(models)) console.log(m.id.padEnd(14), String(m.context_window ?? "?").padStart(8), String(m.max_output_tokens ?? "?").padStart(7), `effort:${m.responses.effort_levels.join("/") || "-"}`.padEnd(46), `samp:${m.responses.sampling_params}${m.responses.default_effort_is_none ? '(default none)' : ''} forced:${+m.responses.forced_tool_choice} chat:${m.chat_completions.supported ? (m.chat_completions.function_tools) : "NO"} $${m.pricing_usd_per_mtok?.input ?? "?"}`);82
Files main
Menu

Theme