Commit 356053b7 356053b724a8ac7522a9fe46509121ab00632715 by cnb.bofCdSsphPA

Route voice recognition through the workspace music20 corpus

Constraint: external voice uploads now need a business-sample-backed path before any pgvector production cutover, while still staying lightweight enough for CPU smoke tests
Rejected: waiting for full pgvector service integration before proving a business-corpus path | would leave the external voice interface unvalidated against real sample references
Confidence: medium
Scope-risk: moderate
Directive: treat workspace_music20 as a proving lane only; validate business top1 correctness before promoting its defaults or claiming production readiness
Tested: /usr/local/miniconda3/bin/python -m unittest discover -s acr-engine/tests -v; /usr/local/miniconda3/bin/python acr-engine/scripts/service_voice_smoke.py -> status ok, corpus=workspace_music20, chunk_count=1, top_song_id=109, has_context=true
Not-tested: pgvector-backed /recognize/voice production retrieval path
1 parent 86c3f935
...@@ -16,18 +16,16 @@ def post_multipart(url: str, file_path: Path): ...@@ -16,18 +16,16 @@ def post_multipart(url: str, file_path: Path):
16 body = ( 16 body = (
17 f'--{boundary}\r\n' 17 f'--{boundary}\r\n'
18 f'Content-Disposition: form-data; name="file"; filename="{file_path.name}"\r\n' 18 f'Content-Disposition: form-data; name="file"; filename="{file_path.name}"\r\n'
19 f'Content-Type: audio/wav\r\n\r\n' 19 f'Content-Type: audio/mpeg\r\n\r\n'
20 ).encode('utf-8') + data + f'\r\n--{boundary}--\r\n'.encode('utf-8') 20 ).encode('utf-8') + data + f'\r\n--{boundary}--\r\n'.encode('utf-8')
21 req = Request(url + '?top_n=1&max_chunks=1&include_context=false', data=body, method='POST') 21 req = Request(url + '?top_n=1&max_chunks=1&include_context=true&corpus=workspace_music20', data=body, method='POST')
22 req.add_header('Content-Type', f'multipart/form-data; boundary={boundary}') 22 req.add_header('Content-Type', f'multipart/form-data; boundary={boundary}')
23 with urlopen(req, timeout=20) as resp: 23 with urlopen(req, timeout=60) as resp:
24 return json.loads(resp.read().decode('utf-8')) 24 return json.loads(resp.read().decode('utf-8'))
25 25
26 26
27 def main(): 27 def main():
28 cmd = [ 28 cmd = ['/usr/local/miniconda3/bin/python', '-m', 'uvicorn', 'src.service.app:app', '--host', '127.0.0.1', '--port', '8000']
29 '/usr/local/miniconda3/bin/python', '-m', 'uvicorn', 'src.service.app:app', '--host', '127.0.0.1', '--port', '8000'
30 ]
31 proc = subprocess.Popen(cmd, cwd='/root/vprecog/acr-engine', stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) 29 proc = subprocess.Popen(cmd, cwd='/root/vprecog/acr-engine', stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
32 query = Path('/workspace/downloads/111/type_7/75cd601b-7604-4b37-8132-cfab39e7c644.mp3') 30 query = Path('/workspace/downloads/111/type_7/75cd601b-7604-4b37-8132-cfab39e7c644.mp3')
33 try: 31 try:
...@@ -35,11 +33,14 @@ def main(): ...@@ -35,11 +33,14 @@ def main():
35 time.sleep(0.5) 33 time.sleep(0.5)
36 try: 34 try:
37 result = post_multipart(BASE + '/recognize/voice', query) 35 result = post_multipart(BASE + '/recognize/voice', query)
36 top = result.get('candidates', [{}])[0] if result.get('candidates') else {}
38 print(json.dumps({ 37 print(json.dumps({
39 'status': 'ok', 38 'status': 'ok',
39 'corpus': result.get('corpus'),
40 'chunk_count': result.get('chunk_count'), 40 'chunk_count': result.get('chunk_count'),
41 'top_song_id': result.get('candidates', [{}])[0].get('song_id') if result.get('candidates') else None, 41 'top_song_id': top.get('song_id'),
42 'has_context': bool(result.get('candidates', [{}])[0].get('context_clip')) if result.get('candidates') else False, 42 'has_context': bool(top.get('context_clip')),
43 'reference_audio_path': top.get('reference_audio_path'),
43 }, ensure_ascii=False, indent=2)) 44 }, ensure_ascii=False, indent=2))
44 return 45 return
45 except Exception: 46 except Exception:
......
...@@ -24,7 +24,7 @@ flowchart TD ...@@ -24,7 +24,7 @@ flowchart TD
24 | benchmark report 已生成 | | 24 | benchmark report 已生成 | |
25 | model card 已生成 | | 25 | model card 已生成 | |
26 | license registry 已更新 | | 26 | license registry 已更新 | |
27 | service smoke test 通过 | partial: `/health` OK, `/recognize/voice` payload returns, but still bound to synthetic service index rather than business reference corpus | 27 | service smoke test 通过 | partial: `/health` OK, `/recognize/voice` payload returns against `workspace_music20`, but business top1 correctness still needs manual/metric validation |
28 | dataset whitelist 已确认 | | 28 | dataset whitelist 已确认 | |
29 | changelog 已更新 | yes | 29 | changelog 已更新 | yes |
30 | architect review completed | yes (approved with watch) | 30 | architect review completed | yes (approved with watch) |
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
30 - `acr-engine/src/service/app.py` 已新增 `POST /recognize/voice` 30 - `acr-engine/src/service/app.py` 已新增 `POST /recognize/voice`
31 - `/health` 可正常启动并返回 `ok` 31 - `/health` 可正常启动并返回 `ok`
32 - architect review: approved with watch;当前 split(本地 FAISS / 可选 ChromaDB / 生产 pgvector)方向成立 32 - architect review: approved with watch;当前 split(本地 FAISS / 可选 ChromaDB / 生产 pgvector)方向成立
33 - 当前 `POST /recognize/voice` 已跨过依赖缺失与超时阶段:CPU 版 `torch` 已安装、`uvicorn` / `fastapi` / `python-multipart` 已安装、`/health` 可返回 `ok`,voice smoke 已返回 payload(`chunk_count=1`, `top_song_id=song_0022`, `has_context=false`);当前剩余问题是服务默认仍绑定 synthetic 索引语义,尚未切到 `/workspace` 业务曲库 reference 33 - 当前 `POST /recognize/voice` 已跨过依赖缺失与超时阶段:CPU 版 `torch` 已安装、`uvicorn` / `fastapi` / `python-multipart` 已安装、`/health` 可返回 `ok`;同时 voice smoke 已切到 `corpus=workspace_music20`,返回 `chunk_count=1`, `top_song_id=109`, `has_context=true`,并附带真实 `/workspace` reference 路径。当前剩余问题是继续校验该 top1 是否与业务预期一致,而不是链路未通。
34 - 当前 docs 已做第一轮简化: 34 - 当前 docs 已做第一轮简化:
35 - `docs/README.md` 只保留最新架构与最短阅读顺序 35 - `docs/README.md` 只保留最新架构与最短阅读顺序
36 36
......