| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197 |
- /**
- * TTS 请求日志完整性验证脚本
- *
- * 目的:
- * 1. 直接发 N 次 TTS API 请求(MiniMax + 阿里云)
- * 2. 验证每次请求都写入了 AiCallLog 数据库表
- * 3. 对比日志文件中的请求次数与数据库记录
- * 4. 检测是否有异常的大量请求或日志遗漏
- *
- * 用法:npx ts-node scripts/test-tts-logging.ts
- */
- import axios from 'axios';
- import { prisma } from '../src/models';
- import { withAiLog } from '../src/services/ai-call-logger';
- import * as fs from 'fs';
- import * as path from 'path';
- // ============ 配置 ============
- const TEST_COUNT = 10; // 每个供应商发多少条请求
- const LOG_DIR = path.join(process.cwd(), '..', '..');
- const LOG_FILE = path.join(process.cwd(), '..', 'tts-debug.log');
- // 从 models.json 读取 API Key
- const modelsConfig = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'src', 'config', 'models.json'), 'utf-8'));
- const minimaxVendor = modelsConfig.vendors?.minimax;
- const bailianVendor = modelsConfig.vendors?.bailian;
- const MINIMAX_API_KEY = minimaxVendor?.apiKey || '';
- const BAILIAN_API_KEY = bailianVendor?.apiKey || '';
- // ============ 主流程 ============
- async function main() {
- console.log('╔══════════════════════════════════════════╗');
- console.log('║ TTS 请求日志完整性验证脚本 ║');
- console.log('╚══════════════════════════════════════════╝\n');
- // ---- Step 1: 记录测试前的数据库基线 ----
- const beforeCount = await prisma.aiCallLog.count();
- console.log(`[基线] 测试前 AiCallLog 总记录数: ${beforeCount}`);
- // ---- Step 2: 发 MiniMax TTS 请求(createTask)----
- console.log(`\n[测试] 发送 ${TEST_COUNT} 次 MiniMax createTask 请求...`);
- const miniMaxResults: string[] = [];
- for (let i = 0; i < TEST_COUNT; i++) {
- try {
- await withAiLog(
- () => axios.post('https://api.minimaxi.com/v1/t2a_async_v2', {
- model: 'speech-2.8-hd',
- text: `测试文本_${i + 1},用于验证TTS请求日志完整性。`.repeat(5),
- voice_setting: { voice_id: 'audiobook_female_1', speed: 1, vol: 1, pitch: 1 },
- audio_setting: { audio_sample_rate: 32000, bitrate: 128000, format: 'mp3', channel: 1 },
- }, {
- headers: { 'Authorization': `Bearer ${MINIMAX_API_KEY}`, 'Content-Type': 'application/json' },
- timeout: 10000,
- }),
- { callType: 'tts_test_create', provider: 'minimax', model: 'speech-2.8-hd', textLen: 200 }
- );
- miniMaxResults.push(`#${i + 1}: 成功`);
- } catch (err: any) {
- const msg = err?.message?.substring(0, 60) || '未知错误';
- miniMaxResults.push(`#${i + 1}: 失败(${msg})`);
- }
- }
- // ---- Step 3: 发阿里云 TTS 请求(synthesize)----
- console.log(`\n[测试] 发送 ${TEST_COUNT} 次阿里云 synthesize 请求...`);
- const aliyunResults: string[] = [];
- for (let i = 0; i < TEST_COUNT; i++) {
- try {
- await withAiLog(
- () => axios.post('https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation', {
- model: 'qwen3-tts-instruct-flash',
- input: { text: `测试文本_${i + 1}`.repeat(10), voice: 'Cherry', language_type: 'Chinese' },
- }, {
- headers: { 'Authorization': `Bearer ${BAILIAN_API_KEY}`, 'Content-Type': 'application/json' },
- timeout: 10000,
- }),
- { callType: 'tts_test_synthesize', provider: 'bailian', model: 'qwen3-tts-instruct-flash', textLen: 100 }
- );
- aliyunResults.push(`#${i + 1}: 成功`);
- } catch (err: any) {
- const msg = err?.message?.substring(0, 60) || '未知错误';
- aliyunResults.push(`#${i + 1}: 失败(${msg})`);
- }
- }
- // ---- Step 4: 等异步写入落库 ----
- console.log('\n⏳ 等待异步日志写入...');
- await new Promise(r => setTimeout(r, 3000));
- // ---- Step 5: 数据库验证 ----
- const afterCount = await prisma.aiCallLog.count();
- const newRecords = afterCount - beforeCount;
-
- const miniMaxDbCount = await prisma.aiCallLog.count({
- where: { callType: 'tts_test_create' }
- });
- const aliyunDbCount = await prisma.aiCallLog.count({
- where: { callType: 'tts_test_synthesize' }
- });
- const totalExpected = TEST_COUNT * 2; // MiniMax + 阿里云
- const totalActual = miniMaxDbCount + aliyunDbCount;
- console.log('\n╔══════════════════════════════════════════╗');
- console.log('║ 📊 测试结果报告 ║');
- console.log('╚══════════════════════════════════════════╝');
- console.log(`\n 发送请求总数: ${totalExpected} (MiniMax ${TEST_COUNT} + 阿里云 ${TEST_COUNT})`);
- console.log(` DB 新增记录: ${newRecords}`);
- console.log(` MiniMax 入库: ${miniMaxDbCount} / ${TEST_COUNT}`);
- console.log(` 阿里云 入库: ${aliyunDbCount} / ${TEST_COUNT}`);
- console.log(` 实际入库总数: ${totalActual} / ${totalExpected}`);
- console.log(` 遗漏率: ${totalExpected - totalActual > 0 ? ((totalExpected - totalActual) / totalExpected * 100).toFixed(1) + '%' : '0%'}`);
- // ---- 详细信息 ----
- const recentLogs = await prisma.aiCallLog.findMany({
- where: { callType: { in: ['tts_test_create', 'tts_test_synthesize'] } },
- orderBy: { createdAt: 'desc' },
- take: 5,
- select: { id: true, callType: true, provider: true, success: true, errorMsg: true, duration: true, createdAt: true }
- });
-
- console.log(`\n 最近 5 条测试日志:`);
- for (const log of recentLogs) {
- const status = log.success ? '✅' : '❌';
- const err = log.errorMsg ? ` (${log.errorMsg.substring(0, 40)})` : '';
- console.log(` ${status} #${log.id} ${log.callType} | ${log.provider} | ${log.duration}ms${err}`);
- }
- // ---- Step 6: 检查是否有异常大量的历史 TTS 请求 ----
- const last24h = new Date(Date.now() - 24 * 60 * 60 * 1000);
- const last24hTtsCalls = await prisma.aiCallLog.count({
- where: {
- callType: { startsWith: 'tts' },
- createdAt: { gte: last24h },
- }
- });
-
- const ttsBreakdown = await prisma.aiCallLog.groupBy({
- by: ['callType', 'provider'],
- where: {
- callType: { startsWith: 'tts' },
- createdAt: { gte: last24h },
- },
- _count: { id: true },
- });
- ttsBreakdown.sort((a, b) => b._count.id - a._count.id);
- console.log(`\n\n╔══════════════════════════════════════════╗`);
- console.log(`║ 📊 过去 24h TTS 请求统计 ║`);
- console.log(`╚══════════════════════════════════════════╝`);
- console.log(`\n 24h TTS 总请求数: ${last24hTtsCalls}`);
- console.log(`\n 按类型/供应商拆分:`);
- for (const item of ttsBreakdown.slice(0, 15)) {
- console.log(` ${item.callType.padEnd(25)} | ${(item.provider || '-').padEnd(15)} | ${String(item._count.id).padStart(6)} 次`);
- }
- // ---- Step 7: 对比日志文件 ----
- let logFileCount = 0;
- try {
- if (fs.existsSync(LOG_FILE)) {
- const logContent = fs.readFileSync(LOG_FILE, 'utf-8');
- logFileCount = (logContent.match(/\[TTS\]/g) || []).length;
- console.log(`\n tts-debug.log 文件中的 [TTS] 标记数: ${logFileCount}`);
- } else {
- console.log(`\n ⚠️ tts-debug.log 文件不存在`);
- }
- } catch (e) {
- console.log(`\n ⚠️ 无法读取 tts-debug.log: ${(e as Error).message}`);
- }
- // ---- 结论 ----
- console.log(`\n\n╔══════════════════════════════════════════╗`);
- console.log(`║ 🎯 结论 ║`);
- console.log(`╚══════════════════════════════════════════╝`);
-
- if (totalActual === totalExpected) {
- console.log(` ✅ 日志记录完整:${totalActual}/${totalExpected} 全部入库,0%遗漏`);
- } else if (totalActual >= totalExpected * 0.9) {
- console.log(` ⚠️ 轻微遗漏:${totalExpected - totalActual} 条未入库(${((1 - totalActual/totalExpected)*100).toFixed(1)}%)`);
- } else {
- console.log(` ❌ 严重遗漏:${totalExpected - totalActual} 条未入库(${((1 - totalActual/totalExpected)*100).toFixed(1)}%)`);
- }
- if (last24hTtsCalls > 1000) {
- console.log(` ⚠️ 过去24h有 ${last24hTtsCalls} 次TTS请求,次数可能异常偏高,请检查死循环`);
- } else {
- console.log(` ✅ 过去24h TTS请求 ${last24hTtsCalls} 次,在正常范围内`);
- }
- // 清理
- await prisma.$disconnect();
- }
- main().catch(err => {
- console.error('脚本异常:', err);
- prisma.$disconnect().then(() => process.exit(1));
- });
|