Ctrl K
ComunidadGuías…

scripts/test-models.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * Model availability test script.
 * Tests all chat_capable models against their provider APIs.
 * Uses a 1-token prompt to minimize cost.
 */

const OPENAI_KEY    = process.env.OPENAI_KEY
const ANTHROPIC_KEY = process.env.ANTHROPIC_KEY
const GEMINI_KEY    = process.env.GEMINI_KEY

if (!OPENAI_KEY || !ANTHROPIC_KEY || !GEMINI_KEY) {
  console.error('Missing keys. Set OPENAI_KEY, ANTHROPIC_KEY, GEMINI_KEY env vars.')
  process.exit(1)
}

// ── No-temperature models (same logic as playground-runner.ts) ────────────────
const NO_TEMP = new Set(['chat-latest', 'gpt-5', 'gpt-5-mini', 'gpt-5-nano', 'gpt-5.5'])
function skipTemp(model) { return /^o\d/.test(model) || NO_TEMP.has(model) }

// ── Models to test ────────────────────────────────────────────────────────────
const OPENAI_MODELS = [
  'gpt-5.5', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.4-nano',
  'gpt-5', 'gpt-5.1', 'gpt-5.2', 'gpt-5-mini', 'gpt-5-nano',
  'chat-latest',
  'o4-mini', 'o3', 'o3-mini', 'o1',
  'gpt-4o', 'gpt-4o-mini', 'gpt-4o-2024-05-13',
  'gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano',
  'gpt-4-turbo', 'gpt-4-turbo-2024-04-09',
  'gpt-4', 'gpt-4-0613',
  'gpt-3.5-turbo', 'gpt-3.5-turbo-0125', 'gpt-3.5-turbo-1106',
]

const ANTHROPIC_MODELS = [
  'claude-opus-4-7', 'claude-opus-4-6',
  'claude-opus-4-5', 'claude-opus-4-5-20251101',
  'claude-opus-4-1', 'claude-opus-4-1-20250805',
  'claude-opus-4', 'claude-opus-4-0', 'claude-opus-4-20250514',
  'claude-sonnet-4-6',
  'claude-sonnet-4-5', 'claude-sonnet-4-5-20250929',
  'claude-sonnet-4', 'claude-sonnet-4-0', 'claude-sonnet-4-20250514',
  'claude-haiku-4-5', 'claude-haiku-4-5-20251001',
  'claude-3-5-sonnet-20241022', 'claude-3-5-haiku-20241022',
  'claude-3-opus-20240229', 'claude-3-haiku-20240307',
]

const GEMINI_MODELS = [
  'gemini-3.5-flash',
  'gemini-3.1-pro-preview', 'gemini-3.1-pro-preview-customtools',
  'gemini-3.1-flash-lite', 'gemini-3.1-flash-lite-preview',
  'gemini-3-flash-preview',
  'gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.5-flash-lite',
  'gemini-2.5-flash-lite-preview-09-2025',
  'gemini-2.5-computer-use-preview-10-2025',
  'gemini-2.0-flash', 'gemini-2.0-flash-lite',
  'gemini-1.5-pro', 'gemini-1.5-flash',
]

// ── Test functions ────────────────────────────────────────────────────────────
async function testOpenAI(model) {
  const body = {
    model,
    messages: [{ role: 'user', content: 'Hi' }],
    max_completion_tokens: 5,
    ...(skipTemp(model) ? {} : { temperature: 0.7 }),
  }
  const res = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${OPENAI_KEY}` },
    body: JSON.stringify(body),
  })
  if (!res.ok) {
    const text = await res.text().catch(() => '')
    let msg = text
    try { msg = JSON.parse(text).error?.message ?? text } catch {}
    return { ok: false, error: msg }
  }
  return { ok: true }
}

async function testAnthropic(model) {
  const res = await fetch('https://api.anthropic.com/v1/messages', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': ANTHROPIC_KEY,
      'anthropic-version': '2023-06-01',
    },
    body: JSON.stringify({
      model,
      max_tokens: 5,
      messages: [{ role: 'user', content: 'Hi' }],
    }),
  })
  if (!res.ok) {
    const text = await res.text().catch(() => '')
    let msg = text
    try { msg = JSON.parse(text).error?.message ?? text } catch {}
    return { ok: false, error: msg }
  }
  return { ok: true }
}

async function testGemini(model) {
  const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${GEMINI_KEY}`
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      contents: [{ role: 'user', parts: [{ text: 'Hi' }] }],
      generationConfig: { maxOutputTokens: 5 },
    }),
  })
  if (!res.ok) {
    const text = await res.text().catch(() => '')
    let msg = text
    try { msg = JSON.parse(text).error?.message ?? text } catch {}
    return { ok: false, error: msg }
  }
  return { ok: true }
}

// ── Runner ────────────────────────────────────────────────────────────────────
async function runBatch(label, models, testFn, concurrency = 3) {
  console.log(`\n${'─'.repeat(60)}`)
  console.log(`${label} (${models.length} models)`)
  console.log('─'.repeat(60))

  const ok = [], fail = []
  for (let i = 0; i < models.length; i += concurrency) {
    const batch = models.slice(i, i + concurrency)
    const results = await Promise.all(batch.map(m => testFn(m).then(r => ({ model: m, ...r }))))
    for (const r of results) {
      const icon = r.ok ? '✓' : '✗'
      const line = r.ok ? `  ${icon} ${r.model}` : `  ${icon} ${r.model}\n      → ${r.error}`
      console.log(line)
      if (r.ok) ok.push(r.model); else fail.push({ model: r.model, error: r.error })
    }
    // small delay between batches to avoid rate limits
    if (i + concurrency < models.length) await new Promise(r => setTimeout(r, 500))
  }

  console.log(`\n  PASS ${ok.length} / FAIL ${fail.length}`)
  return fail
}

// ── Main ──────────────────────────────────────────────────────────────────────
const allFailed = []

const oaiFail  = await runBatch('OpenAI',    OPENAI_MODELS,    testOpenAI,    3)
const antFail  = await runBatch('Anthropic', ANTHROPIC_MODELS, testAnthropic, 3)
const gemFail  = await runBatch('Gemini',    GEMINI_MODELS,    testGemini,    3)

allFailed.push(...oaiFail.map(f => ({ provider: 'openai', ...f })))
allFailed.push(...antFail.map(f => ({ provider: 'anthropic', ...f })))
allFailed.push(...gemFail.map(f => ({ provider: 'gemini', ...f })))

if (allFailed.length > 0) {
  console.log('\n\n══ FAILED MODELS (need chat_capable = FALSE) ══')
  for (const f of allFailed) {
    console.log(`  ${f.provider} / ${f.model}`)
    console.log(`    ${f.error}`)
  }
} else {
  console.log('\n\n✓ All models passed!')
}