105 lines
5.5 KiB
JavaScript
105 lines
5.5 KiB
JavaScript
/* ============================================================
|
|
* test_travel_citytour_generate_integration.js
|
|
* 「游山玩水」生成链路集成测试:不依赖服务重启,直接调用 lab 内的 ai.js
|
|
*
|
|
* 验证目标:
|
|
* 1. prompt 组装 + 百炼调用是否通
|
|
* 2. qwen-plus-latest 能否产出符合 output_schema 的整份行程(会不会被截断)
|
|
* 3. 结构校验(validate.js)出来多少 errors / warnings
|
|
* 4. 端到端耗时(决定前端要不要长 loading 提示)
|
|
*
|
|
* 运行:node dev_test_scripts/integration/test_travel_citytour_generate_integration.js
|
|
* 产物:payload 落在系统临时目录(不进仓库),仅用于人工查看
|
|
* ============================================================ */
|
|
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 LAB = path.join(process.cwd(), 'src', 'server', 'thought_lab', 'labs', 'travel_citytour')
|
|
|
|
const SKILL_ID = process.argv[2] || 'sh_2d_car_hike'
|
|
const DEST = process.argv[3] || '南通如东'
|
|
const MODEL_OVERRIDE = process.argv[4] || ''
|
|
const MAXTOK_OVERRIDE = Number(process.argv[5]) || 0
|
|
|
|
// 技能全栈自包含:引擎三件套与技能包都在 skills/<skill_id>/ 内
|
|
const SKILL_DIR = path.join(LAB, 'skills', SKILL_ID)
|
|
const skillIndex = require(path.join(SKILL_DIR, 'index.js'))
|
|
const { validatePayload } = require(path.join(SKILL_DIR, 'validate.js'))
|
|
const { repairPayload } = require(path.join(SKILL_DIR, 'repair.js'))
|
|
const ai = require(path.join(SKILL_DIR, 'ai.js'))
|
|
// 红线能力(callLLM / 北京时间 / JSON 提取)由 lab 层提供,测试直接复用同一实现,避免漂移
|
|
const { makeCtx } = require(path.join(LAB, 'index.js'))
|
|
const ctx = makeCtx(SKILL_ID)
|
|
|
|
const main = async () => {
|
|
const cfg = JSON.parse(fs.readFileSync(path.join(SKILL_DIR, 'config.json'), 'utf8'))
|
|
const aiConfig = Object.assign({}, cfg.ai || {})
|
|
if (MODEL_OVERRIDE) aiConfig.model = MODEL_OVERRIDE
|
|
if (MAXTOK_OVERRIDE) aiConfig.max_tokens = MAXTOK_OVERRIDE
|
|
const bundle = skillIndex.loadBundle()
|
|
const dest = skillIndex.findDestination(bundle, DEST)
|
|
const answers = {
|
|
destination: DEST,
|
|
departure_origin: bundle.skill.base_origin || '',
|
|
departure_date: '2026-09-19',
|
|
departure_time: '06:40',
|
|
vehicle_type: 'fuel',
|
|
headcount: 1
|
|
}
|
|
|
|
console.log('===== 生成链路集成测试 =====')
|
|
console.log('skill :', bundle.skill.id, 'v' + bundle.skill.version, '(' + (bundle.skill.anchors || []).length + ' 槽位)')
|
|
console.log('skill_dir :', SKILL_DIR)
|
|
console.log('destination:', DEST, dest ? '(技能包内有覆盖数据)' : '(首次生成,无覆盖数据)')
|
|
console.log('model :', aiConfig.model, ' temp=', aiConfig.temperature, ' max_tokens=', aiConfig.max_tokens)
|
|
console.log('ai_ready :', ctx.aiReady() ? '已配置(密钥由 lab 层持有)' : '缺失!')
|
|
console.log('system 长度:', ai.buildMessages(bundle, answers, dest)[0].content.length, '字符')
|
|
console.log('开始生成…\n')
|
|
|
|
const t0 = Date.now()
|
|
let result = null
|
|
try {
|
|
result = await ai.generate({ bundle, answers, dest, aiConfig, ctx })
|
|
} catch (e) {
|
|
console.log('❌ 生成失败:' + String(e.message || e))
|
|
if (e.raw) console.log('原始输出前 1200 字:\n' + String(e.raw).slice(0, 1200))
|
|
process.exit(1)
|
|
}
|
|
|
|
// 与服务端一致:先无损修复,再校验
|
|
const repaired = repairPayload(result.payload, bundle)
|
|
const check = validatePayload(repaired.payload, bundle)
|
|
const outDir = path.join(process.env.TEMP || 'C:\\Windows\\Temp', 'travel_citytour_out')
|
|
fs.mkdirSync(outDir, { recursive: true })
|
|
const outFile = path.join(outDir, 'payload_' + SKILL_ID + '.json')
|
|
fs.writeFileSync(outFile, JSON.stringify(repaired.payload, null, 2), 'utf8')
|
|
|
|
console.log('✅ 生成成功')
|
|
console.log('耗时 :', result.elapsed_ms + ' ms(含 ' + result.attempts + ' 次尝试)')
|
|
console.log('总墙钟耗时 :', (Date.now() - t0) + ' ms')
|
|
console.log('finish_reason:', result.finish_reason || '(空)')
|
|
console.log('usage :', JSON.stringify(result.usage || {}))
|
|
console.log('\n结构规模 :', JSON.stringify(check.stats))
|
|
console.log('无损修复 :', repaired.fixes.length, repaired.fixes.length ? '\n - ' + repaired.fixes.slice(0, 12).join('\n - ') : '')
|
|
console.log('errors :', check.errors.length, check.errors.length ? '\n - ' + check.errors.slice(0, 12).join('\n - ') : '')
|
|
console.log('warnings :', check.warnings.length, check.warnings.length ? '\n - ' + check.warnings.slice(0, 12).join('\n - ') : '')
|
|
console.log('\npayload 落盘:', outFile, '(' + (fs.statSync(outFile).size / 1024).toFixed(1) + ' KB)')
|
|
console.log('summary 标题:', (repaired.payload.summary || {}).title || '(无)')
|
|
console.log('\n' + (check.errors.length === 0 ? '✓ 结构校验通过,可直接渲染' : '✗ 结构校验未通过,需按上面 errors 调整规则或 prompt'))
|
|
process.exit(check.errors.length === 0 ? 0 : 2)
|
|
}
|
|
|
|
main().catch((e) => { console.error('FATAL', e && e.message); process.exit(1) }) |