88 lines
3.5 KiB
JavaScript
88 lines
3.5 KiB
JavaScript
/* ============================================================
|
||
* 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)) |