Files
Toolbox/dev_test_scripts/debug/debug_dashscope_image_models.js
T

233 lines
9.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ============================================================
* debug_dashscope_image_models.js —— 探测百炼(DashScope)可用的文生图模型
*
* 目的:项目此前只用了 chat/completions 与 TTS,没有文生图调用。
* 本脚本枚举候选模型与候选端点,找出「本账号实际能用」的组合,
* 并把生成的图落盘到临时目录做 MD5 比对,确认不同 prompt 出的是不同的图。
*
* 运行:node dev_test_scripts/debug/debug_dashscope_image_models.js
* 说明:只读探测,不写入仓库任何文件(产物落在系统临时目录)。
* ============================================================ */
const fs = require('fs')
const path = require('path')
const crypto = require('crypto')
// 载入本机私有凭据(与项目其他模块同一份文件,不进仓库)
const envPath = path.join(process.env.USERPROFILE || 'C:\\Users\\Administrator', 'Toolbox_local_creds.env.local')
if (fs.existsSync(envPath)) {
const text = fs.readFileSync(envPath, 'utf8')
for (const line of text.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 KEY = process.env.DASHSCOPE_API_KEY || ''
const MaaS = (process.env.DASHSCOPE_API_HOST_FOR_OPENAI || '').replace(/\/+$/, '')
const PUBLIC_BASE = 'https://dashscope.aliyuncs.com'
const OUT = path.join(process.env.TEMP || 'C:\\Windows\\Temp', 'tc_img_probe')
fs.mkdirSync(OUT, { recursive: true })
const promptA = 'coastal embankment above tidal flat, wind turbines, autumn afternoon'
const promptB = 'old Chinese town stone lane with grey brick houses and red lanterns'
console.log('[env] KEY 已加载:', !!KEY, KEY ? '(len=' + KEY.length + ')' : '')
console.log('[env] MaaS host:', MaaS ? MaaS.replace(/\/compatible-mode.*$/, '/…') : '(空)')
console.log('[out]', OUT)
const get = async (url, headers) => {
const r = await fetch(url, { headers, signal: AbortSignal.timeout(25000) })
const t = await r.text()
return { status: r.status, body: t }
}
/* ---------- 1. 先看这个 MaaS 端点声明了哪些模型 ---------- */
async function listModels() {
console.log('\n======== 1. 列出 MaaS 端点的模型 ========')
if (!MaaS) { console.log('无 MaaS host,跳过'); return [] }
try {
const r = await get(MaaS + '/models', { Authorization: 'Bearer ' + KEY })
console.log('status=', r.status)
if (r.status !== 200) { console.log(r.body.slice(0, 400)); return [] }
let j = null
try { j = JSON.parse(r.body) } catch { console.log(r.body.slice(0, 400)); return [] }
const ids = (j.data || []).map((m) => m.id)
console.log('模型数 =', ids.length)
const likely = ids.filter((id) => /image|t2i|wanx|wan2|flux|sd|diffus/i.test(id))
console.log('疑似文生图相关:', likely.length ? likely.join(', ') : '(无)')
console.log('全部模型(前 60):\n ' + ids.slice(0, 60).join('\n '))
return ids
} catch (e) { console.log('ERR', e.message) ; return [] }
}
/* ---------- 2. 探 text2image/image-synthesis 端点 ---------- */
const WANX_MODELS = [
'wanx2.1-t2i-turbo',
'wanx2.1-t2i-plus',
'wanx2.0-t2i-turbo',
'wanx-v1',
'wan2.2-t2i-flash',
'wan2.2-t2i-plus',
'wan2.5-t2i-preview',
'wan2.7-t2i-plus'
]
async function tryImageSynthesis(model) {
const url = PUBLIC_BASE + '/api/v1/services/aigc/text2image/image-synthesis'
try {
const r = await fetch(url, {
method: 'POST',
headers: {
Authorization: 'Bearer ' + KEY,
'Content-Type': 'application/json',
'X-DashScope-Async': 'enable'
},
body: JSON.stringify({
model,
input: { prompt: promptA },
parameters: { size: '1024*576', n: 1 }
}),
signal: AbortSignal.timeout(25000)
})
const t = await r.text()
let j = null
try { j = JSON.parse(t) } catch {}
if (r.status === 200 && j && j.output && j.output.task_id) {
console.log(' ✓ ' + model + ' task=' + j.output.task_id)
return j.output.task_id
}
const msg = (j && (j.message || (j.output && j.output.task_status))) || t.slice(0, 160)
console.log(' ✗ ' + model + ' HTTP ' + r.status + ' ' + String(msg).slice(0, 160))
return null
} catch (e) {
console.log(' ✗ ' + model + ' ERR ' + e.message)
return null
}
}
async function poll(taskId, maxTry) {
const url = PUBLIC_BASE + '/api/v1/tasks/' + taskId
for (let i = 0; i < (maxTry || 20); i++) {
await new Promise((r) => setTimeout(r, 3000))
const r = await get(url, { Authorization: 'Bearer ' + KEY })
let j = null
try { j = JSON.parse(r.body) } catch {}
const st = j && j.output && j.output.task_status
if (st === 'SUCCEEDED') {
const results = (j.output.results || []).map((x) => x.url).filter(Boolean)
return { status: st, urls: results }
}
if (st === 'FAILED' || st === 'UNKNOWN') {
return { status: st, msg: j.message || (j.output && j.output.message) }
}
}
return { status: 'TIMEOUT' }
}
/* ---------- 3. 探多模态生成端点(qwen-image 走 messages) ---------- */
const QWEN_IMAGE_MODELS = ['qwen-image', 'qwen-image-plus', 'qwen-image-max']
async function tryMultimodal(model) {
const url = PUBLIC_BASE + '/api/v1/services/aigc/multimodal-generation/generation'
try {
const r = await fetch(url, {
method: 'POST',
headers: { Authorization: 'Bearer ' + KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
model,
input: { messages: [{ role: 'user', content: [{ text: promptA }] }] },
parameters: { size: '1024*576', n: 1 }
}),
signal: AbortSignal.timeout(40000)
})
const t = await r.text()
let j = null
try { j = JSON.parse(t) } catch {}
const content = j && j.output && j.output.choices && j.output.choices[0] &&
j.output.choices[0].message && j.output.choices[0].message.content
const urls = Array.isArray(content) ? content.map((c) => c.image).filter(Boolean) : []
if (r.status === 200 && urls.length) { console.log(' ✓ ' + model + ' 直接返回图片'); return { status: 'SUCCEEDED', urls } }
if (r.status === 200 && j && j.output && j.output.task_id) { console.log(' ✓ ' + model + ' task=' + j.output.task_id); return { status: 'TASK', task: j.output.task_id } }
const msg = (j && j.message) || t.slice(0, 160)
console.log(' ✗ ' + model + ' HTTP ' + r.status + ' ' + String(msg).slice(0, 160))
return null
} catch (e) { console.log(' ✗ ' + model + ' ERR ' + e.message); return null }
}
/* ---------- 4. 下载两张图,比对 MD5 ---------- */
async function download(url, name) {
const r = await fetch(url, { signal: AbortSignal.timeout(60000) })
if (!r.ok) throw new Error('HTTP ' + r.status)
const buf = Buffer.from(await r.arrayBuffer())
const file = path.join(OUT, name)
fs.writeFileSync(file, buf)
return { file, size: buf.length, md5: crypto.createHash('md5').update(buf).digest('hex') }
}
async function main() {
const available = await listModels()
console.log('\n======== 2. text2image/image-synthesis 候选模型 ========')
const hit = []
for (const m of WANX_MODELS) {
const t = await tryImageSynthesis(m)
if (t) hit.push({ model: m, task: t, kind: 'synthesis' })
}
console.log('\n======== 3. multimodal-generation 候选模型 ========')
for (const m of QWEN_IMAGE_MODELS) {
if (available.length && available.indexOf(m) < 0) { console.log(' - ' + m + ' 不在 MaaS 模型列表,仍试一次'); }
const r = await tryMultimodal(m)
if (r && r.status === 'SUCCEEDED') hit.push({ model: m, urls: r.urls, kind: 'multimodal' })
else if (r && r.status === 'TASK') hit.push({ model: m, task: r.task, kind: 'multimodal' })
}
if (!hit.length) { console.log('\n结论:没有可用的文生图模型组合。'); return }
const first = hit[0]
console.log('\n======== 4. 用可用模型 ' + first.model + ' 跑两张不同 prompt,比对 MD5 ========')
let urls = first.urls
if (!urls) {
const p = await poll(first.task, 30)
console.log('poll:', p.status, p.msg || '')
urls = p.urls || []
}
if (!urls.length) { console.log('未拿到图片 URL,无法比对'); return }
console.log('URL 数 =', urls.length, urls[0].slice(0, 100))
const a1 = await download(urls[0], 'probe_A.jpg')
console.log('A:', a1.size, a1.md5)
// 换 prompt 再生成一张
let taskB = null
if (first.kind === 'synthesis') taskB = await tryImageSynthesisWith(first.model, promptB)
if (taskB) {
const p = await poll(taskB, 30)
if (p.urls && p.urls.length) {
const b1 = await download(p.urls[0], 'probe_B.jpg')
console.log('B:', b1.size, b1.md5)
console.log('\n两张图 MD5 是否相同:', a1.md5 === b1.md5 ? '❌ 相同(模型没区分 prompt)' : '✓ 不同(模型正常按 prompt 出图)')
} else console.log('B 图未生成:', p.status)
}
}
async function tryImageSynthesisWith(model, prompt) {
const url = PUBLIC_BASE + '/api/v1/services/aigc/text2image/image-synthesis'
try {
const r = await fetch(url, {
method: 'POST',
headers: { Authorization: 'Bearer ' + KEY, 'Content-Type': 'application/json', 'X-DashScope-Async': 'enable' },
body: JSON.stringify({ model, input: { prompt }, parameters: { size: '1024*576', n: 1 } }),
signal: AbortSignal.timeout(25000)
})
const j = JSON.parse(await r.text())
return j && j.output && j.output.task_id ? j.output.task_id : null
} catch { return null }
}
main().catch((e) => console.error('FATAL', e && e.message))