test-tts-logging.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /**
  2. * TTS 请求日志完整性验证脚本
  3. *
  4. * 目的:
  5. * 1. 直接发 N 次 TTS API 请求(MiniMax + 阿里云)
  6. * 2. 验证每次请求都写入了 AiCallLog 数据库表
  7. * 3. 对比日志文件中的请求次数与数据库记录
  8. * 4. 检测是否有异常的大量请求或日志遗漏
  9. *
  10. * 用法:npx ts-node scripts/test-tts-logging.ts
  11. */
  12. import axios from 'axios';
  13. import { prisma } from '../src/models';
  14. import { withAiLog } from '../src/services/ai-call-logger';
  15. import * as fs from 'fs';
  16. import * as path from 'path';
  17. // ============ 配置 ============
  18. const TEST_COUNT = 10; // 每个供应商发多少条请求
  19. const LOG_DIR = path.join(process.cwd(), '..', '..');
  20. const LOG_FILE = path.join(process.cwd(), '..', 'tts-debug.log');
  21. // 从 models.json 读取 API Key
  22. const modelsConfig = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'src', 'config', 'models.json'), 'utf-8'));
  23. const minimaxVendor = modelsConfig.vendors?.minimax;
  24. const bailianVendor = modelsConfig.vendors?.bailian;
  25. const MINIMAX_API_KEY = minimaxVendor?.apiKey || '';
  26. const BAILIAN_API_KEY = bailianVendor?.apiKey || '';
  27. // ============ 主流程 ============
  28. async function main() {
  29. console.log('╔══════════════════════════════════════════╗');
  30. console.log('║ TTS 请求日志完整性验证脚本 ║');
  31. console.log('╚══════════════════════════════════════════╝\n');
  32. // ---- Step 1: 记录测试前的数据库基线 ----
  33. const beforeCount = await prisma.aiCallLog.count();
  34. console.log(`[基线] 测试前 AiCallLog 总记录数: ${beforeCount}`);
  35. // ---- Step 2: 发 MiniMax TTS 请求(createTask)----
  36. console.log(`\n[测试] 发送 ${TEST_COUNT} 次 MiniMax createTask 请求...`);
  37. const miniMaxResults: string[] = [];
  38. for (let i = 0; i < TEST_COUNT; i++) {
  39. try {
  40. await withAiLog(
  41. () => axios.post('https://api.minimaxi.com/v1/t2a_async_v2', {
  42. model: 'speech-2.8-hd',
  43. text: `测试文本_${i + 1},用于验证TTS请求日志完整性。`.repeat(5),
  44. voice_setting: { voice_id: 'audiobook_female_1', speed: 1, vol: 1, pitch: 1 },
  45. audio_setting: { audio_sample_rate: 32000, bitrate: 128000, format: 'mp3', channel: 1 },
  46. }, {
  47. headers: { 'Authorization': `Bearer ${MINIMAX_API_KEY}`, 'Content-Type': 'application/json' },
  48. timeout: 10000,
  49. }),
  50. { callType: 'tts_test_create', provider: 'minimax', model: 'speech-2.8-hd', textLen: 200 }
  51. );
  52. miniMaxResults.push(`#${i + 1}: 成功`);
  53. } catch (err: any) {
  54. const msg = err?.message?.substring(0, 60) || '未知错误';
  55. miniMaxResults.push(`#${i + 1}: 失败(${msg})`);
  56. }
  57. }
  58. // ---- Step 3: 发阿里云 TTS 请求(synthesize)----
  59. console.log(`\n[测试] 发送 ${TEST_COUNT} 次阿里云 synthesize 请求...`);
  60. const aliyunResults: string[] = [];
  61. for (let i = 0; i < TEST_COUNT; i++) {
  62. try {
  63. await withAiLog(
  64. () => axios.post('https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation', {
  65. model: 'qwen3-tts-instruct-flash',
  66. input: { text: `测试文本_${i + 1}`.repeat(10), voice: 'Cherry', language_type: 'Chinese' },
  67. }, {
  68. headers: { 'Authorization': `Bearer ${BAILIAN_API_KEY}`, 'Content-Type': 'application/json' },
  69. timeout: 10000,
  70. }),
  71. { callType: 'tts_test_synthesize', provider: 'bailian', model: 'qwen3-tts-instruct-flash', textLen: 100 }
  72. );
  73. aliyunResults.push(`#${i + 1}: 成功`);
  74. } catch (err: any) {
  75. const msg = err?.message?.substring(0, 60) || '未知错误';
  76. aliyunResults.push(`#${i + 1}: 失败(${msg})`);
  77. }
  78. }
  79. // ---- Step 4: 等异步写入落库 ----
  80. console.log('\n⏳ 等待异步日志写入...');
  81. await new Promise(r => setTimeout(r, 3000));
  82. // ---- Step 5: 数据库验证 ----
  83. const afterCount = await prisma.aiCallLog.count();
  84. const newRecords = afterCount - beforeCount;
  85. const miniMaxDbCount = await prisma.aiCallLog.count({
  86. where: { callType: 'tts_test_create' }
  87. });
  88. const aliyunDbCount = await prisma.aiCallLog.count({
  89. where: { callType: 'tts_test_synthesize' }
  90. });
  91. const totalExpected = TEST_COUNT * 2; // MiniMax + 阿里云
  92. const totalActual = miniMaxDbCount + aliyunDbCount;
  93. console.log('\n╔══════════════════════════════════════════╗');
  94. console.log('║ 📊 测试结果报告 ║');
  95. console.log('╚══════════════════════════════════════════╝');
  96. console.log(`\n 发送请求总数: ${totalExpected} (MiniMax ${TEST_COUNT} + 阿里云 ${TEST_COUNT})`);
  97. console.log(` DB 新增记录: ${newRecords}`);
  98. console.log(` MiniMax 入库: ${miniMaxDbCount} / ${TEST_COUNT}`);
  99. console.log(` 阿里云 入库: ${aliyunDbCount} / ${TEST_COUNT}`);
  100. console.log(` 实际入库总数: ${totalActual} / ${totalExpected}`);
  101. console.log(` 遗漏率: ${totalExpected - totalActual > 0 ? ((totalExpected - totalActual) / totalExpected * 100).toFixed(1) + '%' : '0%'}`);
  102. // ---- 详细信息 ----
  103. const recentLogs = await prisma.aiCallLog.findMany({
  104. where: { callType: { in: ['tts_test_create', 'tts_test_synthesize'] } },
  105. orderBy: { createdAt: 'desc' },
  106. take: 5,
  107. select: { id: true, callType: true, provider: true, success: true, errorMsg: true, duration: true, createdAt: true }
  108. });
  109. console.log(`\n 最近 5 条测试日志:`);
  110. for (const log of recentLogs) {
  111. const status = log.success ? '✅' : '❌';
  112. const err = log.errorMsg ? ` (${log.errorMsg.substring(0, 40)})` : '';
  113. console.log(` ${status} #${log.id} ${log.callType} | ${log.provider} | ${log.duration}ms${err}`);
  114. }
  115. // ---- Step 6: 检查是否有异常大量的历史 TTS 请求 ----
  116. const last24h = new Date(Date.now() - 24 * 60 * 60 * 1000);
  117. const last24hTtsCalls = await prisma.aiCallLog.count({
  118. where: {
  119. callType: { startsWith: 'tts' },
  120. createdAt: { gte: last24h },
  121. }
  122. });
  123. const ttsBreakdown = await prisma.aiCallLog.groupBy({
  124. by: ['callType', 'provider'],
  125. where: {
  126. callType: { startsWith: 'tts' },
  127. createdAt: { gte: last24h },
  128. },
  129. _count: { id: true },
  130. });
  131. ttsBreakdown.sort((a, b) => b._count.id - a._count.id);
  132. console.log(`\n\n╔══════════════════════════════════════════╗`);
  133. console.log(`║ 📊 过去 24h TTS 请求统计 ║`);
  134. console.log(`╚══════════════════════════════════════════╝`);
  135. console.log(`\n 24h TTS 总请求数: ${last24hTtsCalls}`);
  136. console.log(`\n 按类型/供应商拆分:`);
  137. for (const item of ttsBreakdown.slice(0, 15)) {
  138. console.log(` ${item.callType.padEnd(25)} | ${(item.provider || '-').padEnd(15)} | ${String(item._count.id).padStart(6)} 次`);
  139. }
  140. // ---- Step 7: 对比日志文件 ----
  141. let logFileCount = 0;
  142. try {
  143. if (fs.existsSync(LOG_FILE)) {
  144. const logContent = fs.readFileSync(LOG_FILE, 'utf-8');
  145. logFileCount = (logContent.match(/\[TTS\]/g) || []).length;
  146. console.log(`\n tts-debug.log 文件中的 [TTS] 标记数: ${logFileCount}`);
  147. } else {
  148. console.log(`\n ⚠️ tts-debug.log 文件不存在`);
  149. }
  150. } catch (e) {
  151. console.log(`\n ⚠️ 无法读取 tts-debug.log: ${(e as Error).message}`);
  152. }
  153. // ---- 结论 ----
  154. console.log(`\n\n╔══════════════════════════════════════════╗`);
  155. console.log(`║ 🎯 结论 ║`);
  156. console.log(`╚══════════════════════════════════════════╝`);
  157. if (totalActual === totalExpected) {
  158. console.log(` ✅ 日志记录完整:${totalActual}/${totalExpected} 全部入库,0%遗漏`);
  159. } else if (totalActual >= totalExpected * 0.9) {
  160. console.log(` ⚠️ 轻微遗漏:${totalExpected - totalActual} 条未入库(${((1 - totalActual/totalExpected)*100).toFixed(1)}%)`);
  161. } else {
  162. console.log(` ❌ 严重遗漏:${totalExpected - totalActual} 条未入库(${((1 - totalActual/totalExpected)*100).toFixed(1)}%)`);
  163. }
  164. if (last24hTtsCalls > 1000) {
  165. console.log(` ⚠️ 过去24h有 ${last24hTtsCalls} 次TTS请求,次数可能异常偏高,请检查死循环`);
  166. } else {
  167. console.log(` ✅ 过去24h TTS请求 ${last24hTtsCalls} 次,在正常范围内`);
  168. }
  169. // 清理
  170. await prisma.$disconnect();
  171. }
  172. main().catch(err => {
  173. console.error('脚本异常:', err);
  174. prisma.$disconnect().then(() => process.exit(1));
  175. });