From 477f090dfaf3f2b0512cf1ab6e57050045ff4b1b Mon Sep 17 00:00:00 2001 From: ydy0615 Date: Fri, 1 May 2026 20:55:19 +0800 Subject: [PATCH] refactor: improve markdown sanitization by collapsing excessive newlines --- .coverage | Bin 53248 -> 0 bytes backend/TEST_SUMMARY.md | 234 -------------------- backend/TTS_ASR_MACOS_FIX.md | 319 --------------------------- backend/main.py | 3 +- backend/tests/test_main_endpoints.py | 11 +- 5 files changed, 5 insertions(+), 562 deletions(-) delete mode 100644 .coverage delete mode 100644 backend/TEST_SUMMARY.md delete mode 100644 backend/TTS_ASR_MACOS_FIX.md diff --git a/.coverage b/.coverage deleted file mode 100644 index 1d9102e5ba1df83eb45975c35c7f416fee5cfc7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53248 zcmeI)%WoS+90%~-y7k76BL_v16>`X2kYm+xWmSmR0Rlv#q7oDW;)FZ4$M%AE*V#uN z2ZUTIl?(p?e`6&M91s_NznNXHoy1jdm6q=+cJ?tdJHOAo>~7m{pFDPBB}PH$$yhwJ zwk_MXJ`uvQtPOf?(JR{=+PRw@&~JNbf8Fke)%oL}t@`&?t^9*k|8eW>`cZ9M`)l*h z+WVF7HeIJ(>Cgpi5P$##AW#^A`IDN{*xR$8{SwRmRK=kjsL;G`efG`C@#%>;J^u9Z zi7@BHVO7v}bR>>N7+i>%3dP8sD&hJ=cOYZeAB%XRveZeWhP-CcIa;+iVMpiZy`DRy zT(KHc5wp^F&E68B8mUnE0~MKC9J<3+we{Pt zRj0ADW4};3CK(RsXLBVQI+2I+Xha1Kf^f*S?}ySKOjOhsauMMmpn6`$CfYFY9>jr| zy1ow6kKEV|e4#GYAc@uR)&Sd#mTiQ$91WHAlmmKda;EoM(#bR5k~2-7W*$-H{2*h^ zGxDt|v#ztBczq)ODe-39IF!TOI1bk3T$eWv(qLb^ZEk7HJl>V&%*eS#O|Hau&l0t0 z{IRrOx{S`I^wFs&lQ_`FJ!<@3m)>g6-`a2*2M6|x zGu=^`r`LDmXt7gS=`@SeYq}0!U*B(dl&p3V9!1ls2u6CyaBg85hkEn!n0b(4$dCt` z2-1X9(>L1NxUy6S(VqtW)xM^COr&Ul&|Huu8zugLhf2gT41#kN%CYLCSIxBWraKaK zoo=PvI5}9?brxNq*In#Z=X+(RvA=IWtLSc&wpRMfS?NMqOWrb#W|urAqa@5Gl2=WV zS&n>`%*IH1g~B;<%8ldwb>XDxdfjwqWA5B_8oRsp+>8jGm-uyWB?vx~4}oh7!RuFl zCgX{?S$9576>r`vH$K{3S8=XFugmY{=i7I5m47d3eUB!6ew9|L%xCf{f2ppH&gGP5 zlbLiw8nQG8n-xHC!l9(ZvL7U|?!SDfyC<(bd3{X*`Eh1XsaiTY4ezgmC~|38N;hM3s3W>H0ZMN zMLV7+{b9OZ@iJ^mrMaI6C*Q|RB9u`yX;4NqM6Z>Z6`Ee<87nKlGb0l&)ssUr+__Pf zO1zk;8^xK`=yMsX*!7g)B)T%XV_nEd3_?X0)3sDrjKn6_Yh{Z0g05t9sEMZ4EWN7l za1-~s{LbH!`P;KKKd_y~{rmPjNk_1moO%&`67XaeYkGKRrDt8cn0K?LI45~fB*y_ z009U<00Izz00i*-AAJA<5P$##AOHafKmY;|fB*y_P<#PA|1W+UBSHv300Izz00bZa M0SG_<0uX?}e_G5ST>t<8 diff --git a/backend/TEST_SUMMARY.md b/backend/TEST_SUMMARY.md deleted file mode 100644 index ca3eb81..0000000 --- a/backend/TEST_SUMMARY.md +++ /dev/null @@ -1,234 +0,0 @@ -# TTS/ASR模块修复完成总结 - -## 修复概览 - -本次修复彻底重构了`backend/tts_asr.py`,针对macOS和Apple Silicon (M1/M2/M3)进行了全面优化,并提供了完整的测试套件。 - -## 修复日期 - -**完成时间**: 2026-04-06 - -## 修改文件清单 - -### 核心修改 -- ✅ `backend/tts_asr.py` - 主要重构(~1150行) -- ✅ `backend/requirements.txt` - 添加新依赖 -- ✅ `README.md` - 更新文档 - -### 测试脚本(新增) -- ✅ `backend/tests/test_tts_asr_unit.py` - 单元测试 -- ✅ `backend/tests/test_tts_asr_integration.py` - 集成测试 -- ✅ `backend/tests/simulate_macos.py` - macOS环境模拟工具 -- ✅ `backend/tests/run_tests.py` - 测试运行器 -- ✅ `backend/tests/quick_verify.py` - 快速验证脚本 - -### 文档(新增) -- ✅ `backend/TTS_ASR_MACOS_FIX.md` - 详细修复说明 -- ✅ `backend/tests/TESTING_GUIDE.md` - 测试指南 - -## 核心改进汇总 - -### 1. 设备检测系统(DeviceCapabilities) - -**改进前**: -- 简单的MPS/CUDA检测 -- 缺少内存管理 -- 无Apple Silicon特殊处理 - -**改进后**: -- `DeviceCapabilities`数据类,结构化存储设备信息 -- 全面的MPS/CUDA可用性测试(1000x1000矩阵运算) -- Apple Silicon自动识别(Darwin + arm64) -- 动态内存管理(MPS内存限制为系统内存的60%) -- 智能设备降级策略 - -### 2. 模型加载优化 - -**改进前**: -- 固定使用large-v3-turbo模型 -- 无内存优化选项 -- 缺少离线模式支持 - -**改进后**: -- 6种模型大小可选(tiny/base/small/medium/large/turbo) -- Apple Silicon自动推荐`small`模型 -- INT8量化支持(减少内存占用) -- 离线模式(检查模型缓存) -- 环境变量驱动的配置 - -### 3. 音频处理鲁棒性 - -**改进前**: -- librosa.resample无回退 -- 缺少音频验证 - -**改进后**: -- `_validate_audio_data()`: 完整的音频数据验证 -- `_resample_audio_robust()`: 多重回退重采样 - - librosa.resample → torchaudio → NumPy线性插值 -- 所有音频操作都有完整的错误处理 - -### 4. 环境变量配置 - -| 变量名 | 说明 | 默认值 | -|--------|------|--------| -| `TTS_ASR_DEVICE` | 设备选择 | `auto` | -| `TTS_ASR_MODEL_SIZE` | ASR模型大小 | `auto` | -| `TTS_ASR_QUANTIZE` | INT8量化 | `false` | -| `TTS_ASR_OFFLINE_MODE` | 离线模式 | `false` | -| `TTS_ASR_WARMUP` | 启动预热 | `true` | -| `TTS_ASR_WARMUP_TIMEOUT` | 预热超时(秒) | `120` | -| `TTS_ASR_IDLE_TIMEOUT` | 空闲卸载(秒) | `0` | -| `TTS_ASR_MPS_MEMORY_LIMIT_MB` | MPS内存限制 | `8192` | - -### 5. 新增API端点 - -- `GET /v1/tts-asr/config`: 获取完整配置信息 -- 增强`/v1/tts-asr/status`: 包含设备能力、模型大小等 -- 增强`/v1/tts-asr/warmup`: 返回详细预热结果 - -## 测试套件概览 - -### 单元测试(test_tts_asr_unit.py) - -覆盖8个测试类,共20+测试用例: - -- `TestAppleSiliconDetection`: Apple Silicon检测 -- `TestEnvironmentVariables`: 环境变量解析 -- `TestModelSizeSelection`: 模型大小选择 -- `TestAudioValidation`: 音频验证 -- `TestAudioResampling`: 音频重采样 -- `TestDeviceCapabilities`: 设备能力 -- `TestModelCacheCheck`: 模型缓存 -- `TestRequestResponseModels`: API模型 - -### 集成测试(test_tts_asr_integration.py) - -需要运行后端服务,测试完整API流程: - -- 配置端点测试 -- 状态端点测试 -- 预热端点测试 -- TTS功能测试 -- ASR功能测试 -- API密钥验证 -- 长文本处理 -- 性能基准测试 - -### macOS模拟测试(simulate_macos.py) - -在非macOS环境下模拟Apple Silicon环境: - -- Apple Silicon环境模拟 -- MPS设备模拟 -- CUDA设备模拟 -- 内存管理测试 -- 完整环境变量测试 - -## 使用建议 - -### Apple Silicon推荐配置 - -**8GB内存**: -```bash -export TTS_ASR_MODEL_SIZE=small -export TTS_ASR_MPS_MEMORY_LIMIT_MB=4096 -``` - -**16GB+内存**: -```bash -export TTS_ASR_MODEL_SIZE=medium -export TTS_ASR_MPS_MEMORY_LIMIT_MB=8192 -``` - -**内存紧张**: -```bash -export TTS_ASR_MODEL_SIZE=tiny -export TTS_ASR_QUANTIZE=true -``` - -### 快速开始 - -```bash -# 1. 安装依赖 -pip install -r backend/requirements.txt - -# 2. 快速验证 -python backend/tests/quick_verify.py - -# 3. 运行单元测试 -pytest backend/tests/test_tts_asr_unit.py -v - -# 4. macOS模拟测试 -python backend/tests/simulate_macos.py --full-simulation - -# 5. 启动后端服务 -python backend/main.py - -# 6. 运行集成测试(另一终端) -python backend/tests/test_tts_asr_integration.py -``` - -## 向后兼容性 - -所有改动保持100%向后兼容: - -- ✅ 现有API端点未改变 -- ✅ 默认行为与原版一致 -- ✅ 新功能通过环境变量启用 -- ✅ 无需修改现有代码 - -## 已知限制 - -1. **MPS float16**: 默认使用float32以避免潜在问题 -2. **8-bit量化**: 仅在CPU和CUDA环境支持 -3. **Core ML**: 预留扩展点但未实现 - -## 性能影响 - -- **Apple Silicon**: 推荐使用small模型,性能更稳定 -- **MPS内存**: 自动限制为系统内存的60%,避免OOM -- **模型加载**: 支持预热和空闲卸载,优化内存使用 - -## 故障排查 - -### 模型加载失败 -1. 检查网络连接 -2. 关闭离线模式: `export TTS_ASR_OFFLINE_MODE=false` -3. 使用预热端点: `POST /v1/tts-asr/warmup` - -### MPS内存不足 -1. 使用更小模型: `export TTS_ASR_MODEL_SIZE=tiny` -2. 启用量化: `export TTS_ASR_QUANTIZE=true` -3. 降低内存限制: `export TTS_ASR_MPS_MEMORY_LIMIT_MB=4096` - -### 音频处理失败 -1. 检查音频格式(支持WAV) -2. 确保采样率≥8000Hz -3. 查看详细日志 - -## 未来改进方向 - -1. 集成Core ML作为备选推理后端 -2. 支持torch.compile (PyTorch 2.0+) -3. 实现模型下载进度显示 -4. 添加更多音频格式支持 - -## 验证状态 - -✅ 所有文件已创建 -✅ 核心函数已实现 -✅ 环境变量已配置 -✅ 测试脚本已编写 -✅ 文档已更新 - -## 联系方式 - -如有问题,请参考: -- [修复详细说明](./TTS_ASR_MACOS_FIX.md) -- [测试指南](./tests/TESTING_GUIDE.md) -- [README更新](../README.md) - ---- - -**修复完成确认**: 所有TTS/ASR模块修复已完成,代码已全面重构并优化,测试套件完整,文档齐全。 diff --git a/backend/TTS_ASR_MACOS_FIX.md b/backend/TTS_ASR_MACOS_FIX.md deleted file mode 100644 index 2a08e63..0000000 --- a/backend/TTS_ASR_MACOS_FIX.md +++ /dev/null @@ -1,319 +0,0 @@ -# TTS/ASR macOS适配修复说明 - -## 修复概述 - -本次修复彻底重构了`backend/tts_asr.py`,针对macOS和Apple Silicon (M1/M2/M3)进行了全面优化。 - -## 主要改进 - -### 1. 增强的设备检测 (`_detect_device_capabilities`) - -**改进前问题**: -- 简单的张量乘法测试不足以验证MPS设备实际可用性 -- 缺少内存限制检测 -- Apple Silicon没有特殊处理 - -**改进后**: -- 使用`DeviceCapabilities`结构化存储设备信息 -- 更全面的MPS测试(1000x1000矩阵运算) -- 自动检测Apple Silicon并调整内存限制 -- 根据系统内存动态设置MPS内存阈值(默认60%) -- 支持设备能力降级(MPS→CPU, CUDA→CPU) - -**验证方法**: -```python -# 在Python环境中测试 -from tts_asr import _detect_device_capabilities -caps = _detect_device_capabilities() -print(f"Device: {caps.device}") -print(f"MPS Available: {caps.mps_available}") -print(f"Apple Silicon: {_is_apple_silicon()}") -``` - -### 2. 模型大小选择和量化支持 - -**新增环境变量**: -- `TTS_ASR_MODEL_SIZE`: 选择Whisper模型大小 - - `tiny`: 最小模型,最快但准确度较低 - - `base`: 基础模型,平衡性能和准确度 - - `small`: 推荐用于Apple Silicon - - `medium`: 中等模型 - - `large`: 大模型,最高准确度 - - `turbo`: large-v3-turbo (原默认模型) - - `auto`: 自动选择(Apple Silicon默认small) - -- `TTS_ASR_QUANTIZE`: 启用INT8量化(减少内存占用) - -**Apple Silicon优化**: -- 自动检测并推荐`small`模型 -- 考虑MPS内存限制选择合适模型 - -**验证方法**: -```python -# 查看推荐的模型大小 -from tts_asr import _get_recommended_model_size -print(_get_recommended_model_size()) # Apple Silicon: "small" -``` - -### 3. 离线模式支持 - -**新增环境变量**: -- `TTS_ASR_OFFLINE_MODE`: 启用离线模式 - - 启动前检查模型是否已缓存 - - 缓存不存在时优雅失败而非崩溃 - -**验证方法**: -```bash -# 启用离线模式 -export TTS_ASR_OFFLINE_MODE=true -python backend/main.py - -# 检查模型缓存 -python -c "from tts_asr import _check_model_cached; print(_check_model_cached('openai/whisper-small'))" -``` - -### 4. 健壮的音频处理 - -**改进前问题**: -- `librosa.resample`失败时无回退 -- 缺少音频数据验证 - -**改进后**: -- `_validate_audio_data()`: 验证音频数据有效性 -- `_resample_audio_robust()`: 多重回退重采样 - 1. 优先使用`librosa.resample` - 2. 回退到`torchaudio.transforms.Resample` - 3. 最后使用NumPy线性插值 - -**验证方法**: -```python -import numpy as np -from tts_asr import _resample_audio_robust - -# 测试重采样 -audio = np.random.randn(16000).astype(np.float32) -resampled = _resample_audio_robust(audio, 16000, 48000) -print(f"Original: {len(audio)}, Resampled: {len(resampled)}") -``` - -### 5. 改进的错误处理和降级 - -**降级路径**: -``` -MPS推理失败 → 标记MPS不可用 → 清理MPS缓存 → 降级到CPU -CUDA推理失败 → 标记CUDA不可用 → 清理CUDA缓存 → 降级到CPU -``` - -**日志改进**: -- 详细记录设备检测过程 -- 明确标注降级原因 -- 显示模型大小、量化状态、离线模式等配置 - -### 6. 新增API端点 - -**GET /v1/tts-asr/config**: -```json -{ - "environment": { - "TTS_ASR_DEVICE": "auto", - "TTS_ASR_MODEL_SIZE": "auto", - "TTS_ASR_QUANTIZE": false, - "TTS_ASR_OFFLINE_MODE": false, - ... - }, - "device": { - "current": "mps", - "mps_available": true, - "cuda_available": false, - "is_apple_silicon": true, - "mps_memory_limit_mb": 8192 - }, - "model": { - "tts": "hexgrad/Kokoro-82M", - "asr_current_size": "small", - "asr_recommended_size": "small", - "available_sizes": ["tiny", "base", "small", "medium", "large", "turbo"] - } -} -``` - -## 环境变量完整列表 - -| 变量名 | 说明 | 默认值 | 示例 | -|--------|------|--------|------| -| `TTS_ASR_DEVICE` | 设备选择 | `auto` | `mps`, `cuda`, `cpu` | -| `TTS_ASR_MODEL_SIZE` | ASR模型大小 | `auto` | `tiny`, `base`, `small`, `medium`, `large`, `turbo` | -| `TTS_ASR_QUANTIZE` | INT8量化 | `false` | `true`, `false` | -| `TTS_ASR_OFFLINE_MODE` | 离线模式 | `false` | `true`, `false` | -| `TTS_ASR_WARMUP` | 启动预热 | `true` | `true`, `false` | -| `TTS_ASR_WARMUP_TIMEOUT` | 预热超时(秒) | `120` | `60`, `180` | -| `TTS_ASR_IDLE_TIMEOUT` | 空闲卸载(秒) | `0` | `300`, `600` | -| `TTS_ASR_MPS_MEMORY_LIMIT_MB` | MPS内存限制(MB) | `8192` | `4096`, `16384` | - -## macOS使用建议 - -### 推荐配置 - -**Apple Silicon (M1/M2/M3) 8GB内存**: -```bash -export TTS_ASR_MODEL_SIZE=small -export TTS_ASR_MPS_MEMORY_LIMIT_MB=4096 -``` - -**Apple Silicon (M1/M2/M3) 16GB+内存**: -```bash -export TTS_ASR_MODEL_SIZE=medium -export TTS_ASR_MPS_MEMORY_LIMIT_MB=8192 -``` - -**内存紧张时**: -```bash -export TTS_ASR_MODEL_SIZE=tiny -export TTS_ASR_QUANTIZE=true -``` - -### 性能优化建议 - -1. **首次运行**: 建议不使用离线模式,让模型自动下载 -2. **后续运行**: 启用离线模式避免网络延迟 - ```bash - export TTS_ASR_OFFLINE_MODE=true - ``` - -3. **长期运行服务**: 设置空闲超时自动卸载模型 - ```bash - export TTS_ASR_IDLE_TIMEOUT=600 # 10分钟后卸载 - ``` - -4. **调试模式**: 查看详细设备检测日志 - ```python - import logging - logging.getLogger("tts_asr").setLevel(logging.DEBUG) - ``` - -## 验证步骤(非Mac环境) - -由于你不在Mac环境下,可以使用以下方法验证代码逻辑: - -### 1. 代码静态检查 -```bash -# 检查Python语法 -python -m py_compile backend/tts_asr.py - -# 检查导入 -python -c "import backend.tts_asr" -``` - -### 2. 单元测试模拟 -```python -# 模拟Apple Silicon环境 -import os -import platform - -# 模拟Darwin/arm64 -original_system = platform.system -original_machine = platform.machine - -def mock_system(): - return "Darwin" - -def mock_machine(): - return "arm64" - -platform.system = mock_system -platform.machine = mock_machine - -# 测试Apple Silicon检测 -from tts_asr import _is_apple_silicon -assert _is_apple_silicon() == True - -# 恢复原始函数 -platform.system = original_system -platform.machine = original_machine -``` - -### 3. 环境变量测试 -```python -import os -os.environ['TTS_ASR_MODEL_SIZE'] = 'small' -os.environ['TTS_ASR_QUANTIZE'] = 'true' - -# 重新加载模块 -import importlib -import backend.tts_asr -importlib.reload(backend.tts_asr) - -from backend.tts_asr import TTS_ASR_MODEL_SIZE, TTS_ASR_QUANTIZE -assert TTS_ASR_MODEL_SIZE == 'small' -assert TTS_ASR_QUANTIZE == True -``` - -### 4. API端点测试(需要运行服务) -```bash -# 启动服务 -python backend/main.py - -# 测试配置端点(需要API Key) -curl -X GET "http://localhost:8001/v1/tts-asr/config" \ - -H "X-API-Key: your-secret-key-here" - -# 测试状态端点 -curl -X GET "http://localhost:8001/v1/tts-asr/status" \ - -H "X-API-Key: your-secret-key-here" -``` - -## 依赖更新 - -已在`backend/requirements.txt`中添加: -- `psutil`: 系统内存检测 -- `torchaudio`: 音频重采样备选方案 - -安装新依赖: -```bash -pip install -r backend/requirements.txt -``` - -## 向后兼容性 - -所有改动保持向后兼容: -- 现有API端点未改变 -- 默认行为与原版一致 -- 新功能通过环境变量启用 - -## 已知限制 - -1. **MPS float16**: 在某些操作上可能不稳定,代码默认使用float32 -2. **8-bit量化**: 仅在CPU和CUDA环境支持,MPS不支持 -3. **Core ML**: 预留了扩展点但未实现(需要额外依赖) - -## 未来改进方向 - -1. 集成Core ML作为备选推理后端 -2. 支持torch.compile (PyTorch 2.0+) -3. 实现模型自动下载的进度显示 -4. 添加更多音频格式支持 - -## 问题排查 - -### 模型加载失败 -1. 检查网络连接 -2. 尝试关闭离线模式: `export TTS_ASR_OFFLINE_MODE=false` -3. 查看详细日志: 设置`logging.getLogger("tts_asr").setLevel(logging.DEBUG)` - -### MPS内存不足 -1. 使用更小的模型: `export TTS_ASR_MODEL_SIZE=tiny` -2. 启用量化: `export TTS_ASR_QUANTIZE=true` -3. 降低内存限制: `export TTS_ASR_MPS_MEMORY_LIMIT_MB=4096` - -### 音频处理失败 -1. 检查音频格式(支持WAV) -2. 确保音频采样率≥8000Hz -3. 查看日志中的详细错误信息 - ---- - -**修复完成日期**: 2026-04-06 -**修改文件**: -- `backend/tts_asr.py` (主要重构) -- `backend/requirements.txt` (添加依赖) -- `README.md` (更新文档) diff --git a/backend/main.py b/backend/main.py index eedc60c..c8deae2 100644 --- a/backend/main.py +++ b/backend/main.py @@ -133,7 +133,8 @@ def _sanitize_converted_markdown(text: str) -> str: value = (text or "").replace("\r\n", "\n").replace("\r", "\n") value = IMAGE_MARKDOWN_RE.sub("", value) value = IMAGE_HTML_RE.sub("", value) - return value + value = re.sub(r"\n{3,}", "\n\n", value) + return value.strip() def get_client_ip(request: Request) -> str: diff --git a/backend/tests/test_main_endpoints.py b/backend/tests/test_main_endpoints.py index 98210bd..031927c 100644 --- a/backend/tests/test_main_endpoints.py +++ b/backend/tests/test_main_endpoints.py @@ -1,7 +1,6 @@ import os import sys import base64 -import types import pytest from unittest.mock import MagicMock from fastapi.testclient import TestClient @@ -11,10 +10,6 @@ BACKEND_DIR = os.path.abspath(os.path.join(CURRENT_DIR, "..")) if BACKEND_DIR not in sys.path: sys.path.insert(0, BACKEND_DIR) -fake_tts_asr = types.ModuleType("tts_asr") -fake_tts_asr.register_tts_asr_routes = lambda app: None -sys.modules.setdefault("tts_asr", fake_tts_asr) - import main # type: ignore API_KEY = main.API_KEY @@ -65,13 +60,13 @@ def test_sanitize_markdown_strips_img_tag(): assert "") -def test_sanitize_markdown_preserves_extra_newlines(): - assert main._sanitize_converted_markdown("a\n\n\nb\n\n\n\nc") == "a\n\n\nb\n\n\n\nc" +def test_sanitize_markdown_collapse_newlines(): + assert main._sanitize_converted_markdown("a\n\n\nb\n\n\n\nc") == "a\n\nb\n\nc" def test_sanitize_markdown_normalize_crlf(): result = main._sanitize_converted_markdown("line1\r\nline2\r\n") - assert result == "line1\nline2\n" + assert "line1\nline2" in result assert "\r" not in result