SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
7.6 KB · 106 lines typescript
Raw Blame History
1// Probe 08: Responses API (stateless check), endpoints listing, ZDR list, header variants, :batch behaviour, prompt caching on a repeated prompt, reasoning_details round trip on Anthropic.2import { raw, rawSSE, save, short } from "./lib.ts";3import { OPENAI, ANTHROPIC, GOOGLE, byId } from "./models.ts";45const results: any = {};67// Responses API8{9  const r = await raw("/responses", { method: "POST", body: JSON.stringify({ model: OPENAI, input: "Say hi in 3 words.", max_output_tokens: 30, reasoning: { effort: "low" } }) });10  const b: any = r.body;11  results.responses_basic = { status: r.status, keys: Object.keys(b ?? {}), outputTypes: b?.output?.map((o: any) => o.type), usage: b?.usage, model: b?.model, provider: b?.provider, store: b?.store, error: b?.error };12  console.log(`[responses] ${r.status} keys=${Object.keys(b ?? {})} output=${b?.output?.map((o: any) => o.type)} usage=${JSON.stringify(b?.usage)} ${b?.error ? JSON.stringify(b.error) : ""}`);13  const r2 = await raw("/responses", { method: "POST", body: JSON.stringify({ model: OPENAI, input: "hi", max_output_tokens: 30, store: true }) });14  results.responses_store_true = { status: r2.status, body: r2.body };15  console.log(`[responses store:true] ${r2.status} ${short(r2.body, 300)}`);16  const r3 = await raw("/responses", { method: "POST", body: JSON.stringify({ model: ANTHROPIC, input: "hi", max_output_tokens: 30, previous_response_id: "resp_123" }) });17  results.responses_prev_id = { status: r3.status, body: r3.body };18  console.log(`[responses previous_response_id] ${r3.status} ${short(r3.body, 300)}`);19  const s = await rawSSE("/responses", { model: ANTHROPIC, input: "Say hi in 3 words.", max_output_tokens: 30, stream: true });20  results.responses_stream = { status: s.status, eventTypes: [...new Set(s.events.map((e) => e.data?.type ?? e.data))], eventNames: [...new Set(s.events.map((e) => e.event))], count: s.events.length, last: s.events[s.events.length - 2]?.data };21  console.log(`[responses stream] ${s.status} events=${s.events.length} types=${results.responses_stream.eventTypes} eventNames=${results.responses_stream.eventNames}`);22}2324// Endpoints for a model + ZDR list25{26  const slug = byId.get(ANTHROPIC)?.canonical_slug ?? ANTHROPIC;27  const r = await raw(`/models/${ANTHROPIC}/endpoints`);28  const b: any = r.body;29  results.endpoints = { status: r.status, keys: Object.keys(b?.data ?? {}), endpointKeys: Object.keys(b?.data?.endpoints?.[0] ?? {}), endpoints: b?.data?.endpoints?.map((e: any) => ({ name: e.name, provider_name: e.provider_name, tag: e.tag, context_length: e.context_length, max_completion_tokens: e.max_completion_tokens, pricing: e.pricing, supported_parameters: e.supported_parameters, status: e.status, quantization: e.quantization, uptime_last_30m: e.uptime_last_30m, supports_implicit_caching: e.supports_implicit_caching })) };30  console.log(`\n[endpoints ${ANTHROPIC}] ${r.status} keys=${results.endpoints.keys} endpointKeys=${results.endpoints.endpointKeys}`);31  console.log("  ", short(results.endpoints.endpoints, 800));32  const z = await raw("/endpoints/zdr");33  const zb: any = z.body;34  results.zdr = { status: z.status, count: zb?.data?.length, sample: zb?.data?.slice(0, 2), keys: Object.keys(zb ?? {}) };35  console.log(`[zdr] ${z.status} count=${zb?.data?.length} sample=${short(zb?.data?.[0], 300)}`);36  const p = await raw("/providers");37  const pb: any = p.body;38  results.providers = { status: p.status, count: pb?.data?.length, sample: pb?.data?.slice(0, 2) };39  console.log(`[providers] ${p.status} count=${pb?.data?.length} sample=${short(pb?.data?.[0], 300)}`);40}4142// Header variants: X-Title vs X-OpenRouter-Title, both accepted?43{44  for (const [name, headers] of Object.entries({ x_title: { "X-Title": "PolyLLM" }, x_openrouter_title: { "X-OpenRouter-Title": "PolyLLM" } })) {45    const r = await raw("/chat/completions", { method: "POST", headers, body: JSON.stringify({ model: OPENAI, messages: [{ role: "user", content: "hi" }], max_tokens: 5, reasoning: { effort: "none" } }) });46    results[`header_${name}`] = { status: r.status, id: (r.body as any)?.id };47    console.log(`[header ${name}] ${r.status}`);48  }49}5051// :batch variant behaviour (what does chat/completions do with it?)52{53  const ctrl = new AbortController();54  const t = setTimeout(() => ctrl.abort(), 60_000);55  const t0 = Date.now();56  try {57    const r = await raw("/chat/completions", { method: "POST", signal: ctrl.signal, body: JSON.stringify({ model: `${OPENAI}:batch`, messages: [{ role: "user", content: "hi" }], max_tokens: 5, usage: { include: true } }) });58    results.batch = { status: r.status, ms: Date.now() - t0, body: r.body };59    console.log(`\n[batch] ${r.status} ${Date.now() - t0}ms ${short(r.body, 500)}`);60  } catch (e: any) {61    results.batch = { error: String(e), ms: Date.now() - t0 };62    console.log(`\n[batch] aborted after ${Date.now() - t0}ms: ${e}`);63  } finally {64    clearTimeout(t);65  }66}6768// Prompt caching: Anthropic with cache_control on a ~1200-token system prompt, twice69{70  const big = "You are a helpful assistant. " + "The quick brown fox jumps over the lazy dog near the riverbank while the sun sets slowly. ".repeat(260);71  const body = {72    model: ANTHROPIC,73    messages: [74      { role: "system", content: [{ type: "text", text: big, cache_control: { type: "ephemeral" } }] },75      { role: "user", content: "Reply with the single word OK." },76    ],77    max_tokens: 5,78    usage: { include: true },79  };80  const a = await raw("/chat/completions", { method: "POST", body: JSON.stringify(body) });81  const bb = await raw("/chat/completions", { method: "POST", body: JSON.stringify(body) });82  results.cache = { first: (a.body as any)?.usage, second: (bb.body as any)?.usage, err: (a.body as any)?.error ?? (bb.body as any)?.error };83  console.log(`\n[cache anthropic] first=${JSON.stringify(results.cache.first)}\n  second=${JSON.stringify(results.cache.second)} ${results.cache.err ? JSON.stringify(results.cache.err) : ""}`);84  // OpenAI automatic caching with the same big prompt85  const body2 = { model: OPENAI, messages: [{ role: "system", content: big }, { role: "user", content: "Reply with the single word OK." }], max_tokens: 5, usage: { include: true }, reasoning: { effort: "none" } };86  const c = await raw("/chat/completions", { method: "POST", body: JSON.stringify(body2) });87  const d = await raw("/chat/completions", { method: "POST", body: JSON.stringify(body2) });88  results.cache_openai = { first: (c.body as any)?.usage, second: (d.body as any)?.usage };89  console.log(`[cache openai] first=${JSON.stringify(results.cache_openai.first)}\n  second=${JSON.stringify(results.cache_openai.second)}`);90}9192// Reasoning on Anthropic (max_tokens budget) and Google (mandatory reasoning) — reasoning_details shapes, non-streaming93{94  for (const model of [ANTHROPIC, GOOGLE]) {95    const r = await raw("/chat/completions", {96      method: "POST",97      body: JSON.stringify({ model, messages: [{ role: "user", content: "Is 17 prime? One sentence." }], max_tokens: 200, reasoning: { max_tokens: 1024 }, usage: { include: true } }),98    });99    const b: any = r.body;100    const m = b?.choices?.[0]?.message;101    results[`reasoning_${model}`] = { status: r.status, provider: b?.provider, reasoning: m?.reasoning, reasoning_details: m?.reasoning_details, usage: b?.usage, error: b?.error };102    console.log(`\n[reasoning ${model}] ${r.status} reasoning=${short(m?.reasoning, 100)} details=${short(m?.reasoning_details?.map((d: any) => ({ ...d, text: d.text?.slice(0, 40), data: d.data?.slice(0, 20), signature: d.signature?.slice(0, 20) })), 500)} usage=${JSON.stringify(b?.usage)} ${b?.error ? JSON.stringify(b.error) : ""}`);103  }104}105save("08-misc.json", results);106