feat: transcribe_voice 新增 OpenAI Whisper API 后端 (#66)
默认 local,零行为变化。opt-in 双因素:transcription_backend=openai 且 openai_api_key 都齐才生效;任一缺失静默回退 local + stderr 一行警告。 首次进入云路径会 stderr 警告"语音将上传至 OpenAI 服务器"。 新增 config.json 字段: - transcription_backend: "local" (默认) | "openai" - local_whisper_model: "base" (替换 mcp_server.py 里硬编码 DEFAULT_WHISPER_MODEL) - openai_api_key: "" (默认空;openai 包为 optional,按需 pip install) 关键技术选择: - _transcribe(wav, backend) 单一 if/else 分发,不引入插件/工厂层 (Rule of Three —— 只有一个云后端时不值得抽象) - 文件 > 25MB 在 OpenAI() 实例化之前提前拒绝,避免无谓上传 - 错误分类清晰: 缺 key / 缺 openai 包 / 401 / 429 / APIError 各自的提示 - PR #58 缓存 schema 自然扩展: 条目加 backend 字段,命中需 backend+model_size 都匹配 - 旧条目缺 backend 字段视为 "local",向前兼容 PR #58 已落盘的所有数据 - transcribe_chat.py 批量 CLI 与 MCP 工具共享同一份配置,保持一致 新增 2 个测试 (tests/test_openai_backend.py),只覆盖回归风险最高的两条: - 文件 > 25MB 必须在 SDK 实例化前拒绝(隐私契约的防线) - backend 不匹配的旧条目不命中(避免切后端时返回错后端结果) 其余路径要么琐碎(默认值读取)、要么坏掉时声音很大(SDK 错误、ImportError), 要么已被 PR #58 现有测试隐式覆盖(缺 backend 字段的旧条目),不再单独写测试。 顺手把 README 里 PR #53 漏掉的 voice 三件套(get_voice_messages / decode_voice / transcribe_voice)补进 MCP 工具表,并新增"⚠️ 语音转录隐私" 章节说清数据流向、成本(约 \$0.006/分钟)、25MB 上限、回退行为。 Closes ylytdeng/wechat-decrypt#59
This commit is contained in:
@@ -194,7 +194,7 @@ class TranscribeVoiceCacheHitTests(_CacheIsolationMixin, unittest.TestCase):
|
||||
"text": "缓存命中文本",
|
||||
"language": "zh",
|
||||
"create_time": 1700000000,
|
||||
"model_size": mcp_server.DEFAULT_WHISPER_MODEL,
|
||||
"model_size": mcp_server.LOCAL_WHISPER_MODEL,
|
||||
})
|
||||
|
||||
with patch.object(mcp_server, "resolve_username", return_value="wxid_test") as mock_resolve, \
|
||||
@@ -216,7 +216,7 @@ class TranscribeVoiceCacheHitTests(_CacheIsolationMixin, unittest.TestCase):
|
||||
self._seed(key, {
|
||||
"text": "历史条目",
|
||||
"language": "zh",
|
||||
"model_size": mcp_server.DEFAULT_WHISPER_MODEL,
|
||||
"model_size": mcp_server.LOCAL_WHISPER_MODEL,
|
||||
})
|
||||
|
||||
with patch.object(mcp_server, "resolve_username", return_value="wxid_test"), \
|
||||
@@ -233,7 +233,7 @@ class TranscribeVoiceCacheHitTests(_CacheIsolationMixin, unittest.TestCase):
|
||||
"text": "",
|
||||
"language": "zh",
|
||||
"create_time": 1700000000,
|
||||
"model_size": mcp_server.DEFAULT_WHISPER_MODEL,
|
||||
"model_size": mcp_server.LOCAL_WHISPER_MODEL,
|
||||
})
|
||||
|
||||
with patch.object(mcp_server, "resolve_username", return_value="wxid_test"), \
|
||||
@@ -244,7 +244,7 @@ class TranscribeVoiceCacheHitTests(_CacheIsolationMixin, unittest.TestCase):
|
||||
self.assertIn("(zh)", result)
|
||||
|
||||
def test_model_mismatch_is_treated_as_miss(self):
|
||||
# 缓存条目的 model_size 和当前 DEFAULT_WHISPER_MODEL 不一致时,
|
||||
# 缓存条目的 model_size 和当前 LOCAL_WHISPER_MODEL 不一致时,
|
||||
# 不应命中;进入 miss 路径(这里无 whisper 依赖,应落到"缺少依赖"分支)。
|
||||
key = mcp_server._voice_transcription_cache_key("wxid_test", 10)
|
||||
self._seed(key, {
|
||||
|
||||
Reference in New Issue
Block a user