/* ============================================================ * debug_probe_max_tokens.js —— 探测 chat 模型可接受的 max_tokens 上限 * * 背景:「游山玩水」整份行程 JSON 约 1.2-1.6 万 output token, * 用 qwen-plus-latest + max_tokens=8000 会截断(finish_reason=length)。 * 本脚本用极短的 prompt 试不同 max_tokens,找出模型实际能接受的上限, * 顺便列出可用 chat 模型。 * * 运行:node dev_test_scripts/debug/debug_probe_max_tokens.js * 说明:只读探测,用极短 prompt(输出 1-2 个 token),几乎不产生费用。 * ============================================================ */ const fs = require('fs') const path = require('path') const envPath = path.join(process.env.USERPROFILE || 'C:\\Users\\Administrator', 'Toolbox_local_creds.env.local') if (fs.existsSync(envPath)) { for (const line of fs.readFileSync(envPath, 'utf8').split(/\r?\n/)) { if (!line || /^\s*#/.test(line)) continue const i = line.indexOf('=') if (i < 0) continue const k = line.slice(0, i).trim() const v = line.slice(i + 1).trim().replace(/^["']|["']$/g, '') if (k && !(k in process.env)) process.env[k] = v } } const HOST = (process.env.DASHSCOPE_API_HOST_FOR_OPENAI || '').replace(/\/+$/, '') const KEY = process.env.DASHSCOPE_API_KEY || '' const URL = HOST ? HOST + '/chat/completions' : '' const MODELS = [ 'qwen-plus-latest', 'qwen-max-latest', 'qwen3-max', 'qwen3.5-plus', 'qwen3.5-plus-latest', 'qwen-turbo-latest' ] const BUDGETS = [8192, 16384, 32000] const tryOne = async (model, maxTokens) => { try { const r = await fetch(URL, { method: 'POST', headers: { Authorization: 'Bearer ' + KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ model, messages: [{ role: 'user', content: '只回复两个字:好的' }], max_tokens: maxTokens }), signal: AbortSignal.timeout(60000) }) const t = await r.text() let j = null try { j = JSON.parse(t) } catch {} if (r.status === 200 && j && j.choices) { const u = j.usage || {} return { ok: true, note: 'completion=' + (u.completion_tokens === undefined ? '?' : u.completion_tokens) } } return { ok: false, note: 'HTTP ' + r.status + ' ' + String((j && j.message) || t).slice(0, 130) } } catch (e) { return { ok: false, note: 'ERR ' + e.message } } } const main = async () => { console.log('host:', HOST ? '已配置' : '缺失', ' key:', KEY ? '已加载' : '缺失') console.log('\n===== 可用 chat 模型(MaaS 声明) =====') try { const r = await fetch(HOST + '/models', { headers: { Authorization: 'Bearer ' + KEY } }) const j = JSON.parse(await r.text()) const ids = (j.data || []).map((m) => m.id) const chat = ids.filter((id) => /qwen|deepseek|glm|kimi|llama|gpt/i.test(id) && !/image|vl|audio|asr|tts|voice|video|t2i|t2v|embed/i.test(id)) console.log('chat 类候选共 ' + chat.length + ' 个:\n ' + chat.slice(0, 50).join('\n ')) } catch (e) { console.log('列模型失败:' + e.message) } console.log('\n===== max_tokens 上限探测 =====') for (const m of MODELS) { const line = [] for (const b of BUDGETS) { const r = await tryOne(m, b) line.push(b + ':' + (r.ok ? '✓' : '✗')) if (!r.ok) { line.push('(' + r.note.slice(0, 90) + ')'); break } } console.log(' ' + m.padEnd(20) + ' ' + line.join(' ')) } } main().catch((e) => console.error('FATAL', e && e.message))