Conversation
…of trusting Tencent Cloud's text
When WeChat (Weixin) returns a voice_item.text (Tencent Cloud's STT),
Hermes previously trusted that text as the user-visible message body and
skipped downloading the raw audio. For non-Chinese audio that text is
garbage — the original report was a Russian voice message that came
back as English phonemes — and the user sees nonsense as their own
message. International users on the WeChat gateway effectively can't
use voice.
Two short-circuits in gateway/platforms/weixin.py caused this:
- _download_voice() returned None whenever voice_item.text was set,
so the raw SILK/Opus audio was never fetched.
- _extract_text() returned voice_item.text verbatim as the body,
so even if the audio had been downloaded, the central STT
pipeline in gateway/run.py never had a chance to replace it.
Fix both: always download the raw audio (the central pipeline picks
it up via _collect_media()), and skip voice items in _extract_text()
so the body comes from Hermes' own mlx-whisper / whisper.cpp /
faster-whisper transcription instead of Tencent's. Behavior is
unchanged when voice_item.text is absent (the original happy path
where audio was already being downloaded).
Tests:
- 5 new tests in TestWeixinVoiceAlwaysDownloaded covering both
functions, the _collect_media integration path, and a
regression guard for the text-item path.
- 74/74 in tests/gateway/test_weixin.py pass.
|
Thanks for the focused fix. Current Suggested changes
Automated hermes-sweeper review. |
…arch#27300 voice routing Address teknium1 review on PR NousResearch#47125: the existing adapter-level tests cover the prerequisites (_download_voice / _collect_media / _extract_text) but not the gateway-runner handoff. Add TestWeixinVoiceGatewayHandoff covering the final routing contract: - An inbound Weixin voice item carrying Tencent Cloud text is surfaced as a VOICE MessageEvent whose media is audio/silk (the shape the runner keys off to enter Hermes' STT pipeline), exercised through the real _process_message. - That VOICE event's body does NOT leak Tencent's STT text, so the central transcript replaces it rather than being trusted. - A VOICE/audio/silk event reaches the real GatewayRunner _enrich_message_with_transcription (patched as a spy), proving the runner handoff the adapter-only tests missed.
…oice routing Address teknium1 review on PR #47125: the existing adapter-level tests cover the prerequisites (_download_voice / _collect_media / _extract_text) but not the gateway-runner handoff. Add TestWeixinVoiceGatewayHandoff covering the final routing contract: - An inbound Weixin voice item carrying Tencent Cloud text is surfaced as a VOICE MessageEvent whose media is audio/silk (the shape the runner keys off to enter Hermes' STT pipeline), exercised through the real _process_message. - That VOICE event's body does NOT leak Tencent's STT text, so the central transcript replaces it rather than being trusted. - A VOICE/audio/silk event reaches the real GatewayRunner _enrich_message_with_transcription (patched as a spy), proving the runner handoff the adapter-only tests missed.
…oice routing Address teknium1 review on PR #47125: the existing adapter-level tests cover the prerequisites (_download_voice / _collect_media / _extract_text) but not the gateway-runner handoff. Add TestWeixinVoiceGatewayHandoff covering the final routing contract: - An inbound Weixin voice item carrying Tencent Cloud text is surfaced as a VOICE MessageEvent whose media is audio/silk (the shape the runner keys off to enter Hermes' STT pipeline), exercised through the real _process_message. - That VOICE event's body does NOT leak Tencent's STT text, so the central transcript replaces it rather than being trusted. - A VOICE/audio/silk event reaches the real GatewayRunner _enrich_message_with_transcription (patched as a spy), proving the runner handoff the adapter-only tests missed.
…oice routing Address teknium1 review on PR #47125: the existing adapter-level tests cover the prerequisites (_download_voice / _collect_media / _extract_text) but not the gateway-runner handoff. Add TestWeixinVoiceGatewayHandoff covering the final routing contract: - An inbound Weixin voice item carrying Tencent Cloud text is surfaced as a VOICE MessageEvent whose media is audio/silk (the shape the runner keys off to enter Hermes' STT pipeline), exercised through the real _process_message. - That VOICE event's body does NOT leak Tencent's STT text, so the central transcript replaces it rather than being trusted. - A VOICE/audio/silk event reaches the real GatewayRunner _enrich_message_with_transcription (patched as a spy), proving the runner handoff the adapter-only tests missed.
|
Merged into main via consolidated salvage PR #73515 (merge Your contribution is credited to you in git history. Thank you! Closing this PR as merged-via-salvage. |
…arch#27300 voice routing Address teknium1 review on PR NousResearch#47125: the existing adapter-level tests cover the prerequisites (_download_voice / _collect_media / _extract_text) but not the gateway-runner handoff. Add TestWeixinVoiceGatewayHandoff covering the final routing contract: - An inbound Weixin voice item carrying Tencent Cloud text is surfaced as a VOICE MessageEvent whose media is audio/silk (the shape the runner keys off to enter Hermes' STT pipeline), exercised through the real _process_message. - That VOICE event's body does NOT leak Tencent's STT text, so the central transcript replaces it rather than being trusted. - A VOICE/audio/silk event reaches the real GatewayRunner _enrich_message_with_transcription (patched as a spy), proving the runner handoff the adapter-only tests missed.
…arch#27300 voice routing Address teknium1 review on PR NousResearch#47125: the existing adapter-level tests cover the prerequisites (_download_voice / _collect_media / _extract_text) but not the gateway-runner handoff. Add TestWeixinVoiceGatewayHandoff covering the final routing contract: - An inbound Weixin voice item carrying Tencent Cloud text is surfaced as a VOICE MessageEvent whose media is audio/silk (the shape the runner keys off to enter Hermes' STT pipeline), exercised through the real _process_message. - That VOICE event's body does NOT leak Tencent's STT text, so the central transcript replaces it rather than being trusted. - A VOICE/audio/silk event reaches the real GatewayRunner _enrich_message_with_transcription (patched as a spy), proving the runner handoff the adapter-only tests missed.
…arch#27300 voice routing Address teknium1 review on PR NousResearch#47125: the existing adapter-level tests cover the prerequisites (_download_voice / _collect_media / _extract_text) but not the gateway-runner handoff. Add TestWeixinVoiceGatewayHandoff covering the final routing contract: - An inbound Weixin voice item carrying Tencent Cloud text is surfaced as a VOICE MessageEvent whose media is audio/silk (the shape the runner keys off to enter Hermes' STT pipeline), exercised through the real _process_message. - That VOICE event's body does NOT leak Tencent's STT text, so the central transcript replaces it rather than being trusted. - A VOICE/audio/silk event reaches the real GatewayRunner _enrich_message_with_transcription (patched as a spy), proving the runner handoff the adapter-only tests missed.
Summary
When WeChat (Weixin) returned
voice_item.text(Tencent Cloud's STT output),Hermes trusted that text as the user-visible message body and skipped
downloading the raw audio. For non-Chinese audio that text is garbage — the
original report was a Russian voice message that came back as English phonemes
— and the user sees nonsense as their own message. International users on the
WeChat gateway effectively can't use voice.
Two short-circuits in
gateway/platforms/weixin.pycaused this:_download_voice()returnedNonewhenevervoice_item.textwas set, sothe raw SILK/Opus audio was never fetched.
_extract_text()returnedvoice_item.textverbatim as the body, so evenif the audio had been downloaded, the central STT pipeline in
gateway/run.pynever had a chance to replace it.
Fix both: always download the raw audio (the central pipeline picks it up via
_collect_media()), and skip voice items in_extract_text()so the bodycomes from Hermes' own mlx-whisper / whisper.cpp / faster-whisper transcription
instead of Tencent's. Behavior is unchanged when
voice_item.textis absent.Changes
gateway/platforms/weixin.py:_download_voice(): drop theif voice_item.text: return Noneshort-circuit so the raw audio is always downloaded
_extract_text(): voice item no longer returns Tencent's text;the central STT pipeline in
gateway/run.pynow produces the bodytests/gateway/test_weixin.py:TestWeixinVoiceAlwaysDownloadedclass (5 tests): primary reprofor both functions,
_collect_mediaintegration path, and a regressionguard for the text-item path
How to Test
Checklist
ruff check— PASS, 0 warningsRisk & Impact
Low. Two single-function changes in one platform adapter with explanatory
comments at each edit site. Public API surface is unchanged;
_download_voiceand
_extract_textreturn shapes are the same. Behavior only changes for thepreviously-broken case (
voice_item.textset); the fall-through path isexercised by the existing happy path (
voice_item.textabsent).Type: 🐛 Bug fix
Closes: #27300