Skip to content

fix(gateway/weixin): route voice messages through Hermes STT instead of trusting Tencent Cloud's text - #47125

Closed
Kewe63 wants to merge 2 commits into
NousResearch:mainfrom
Kewe63:fix/27300-weixin-voice-stt-routing
Closed

Kewe63 wants to merge 2 commits into
NousResearch:mainfrom
Kewe63:fix/27300-weixin-voice-stt-routing

Conversation

@Kewe63

@Kewe63 Kewe63 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

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.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.


Changes

  • gateway/platforms/weixin.py:
    • _download_voice(): drop the if voice_item.text: return None
      short-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.py now produces the body
  • tests/gateway/test_weixin.py:
    • New TestWeixinVoiceAlwaysDownloaded class (5 tests): primary repro
      for both functions, _collect_media integration path, and a regression
      guard for the text-item path

How to Test

# Scoped
python3 -m pytest \
  tests/gateway/test_weixin.py::TestWeixinVoiceAlwaysDownloaded -v
# ✅ 5/5 passed

# Full file
python3 -m pytest tests/gateway/test_weixin.py
# ✅ 74/74 passed

Checklist

  • Tests pass — 74/74 in file, 5/5 new
  • ruff check — PASS, 0 warnings
  • Follows Conventional Commits
  • Changes scoped to this fix only (2 files, 151+/5-)

Risk & Impact

Low. Two single-function changes in one platform adapter with explanatory
comments at each edit site. Public API surface is unchanged; _download_voice
and _extract_text return shapes are the same. Behavior only changes for the
previously-broken case (voice_item.text set); the fall-through path is
exercised by the existing happy path (voice_item.text absent).

Type: 🐛 Bug fix
Closes: #27300

…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.
@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery platform/wecom WeCom / WeChat Work adapter P2 Medium — degraded but workaround exists labels Jun 16, 2026
@teknium1

Copy link
Copy Markdown
Collaborator

Thanks for the focused fix. Current main still has both reported guards: gateway/platforms/weixin.py:963-966 returns Tencent's voice_item.text, and gateway/platforms/weixin.py:1646-1650 skips the raw-audio download when that field is present. The existing runner path will transcribe the resulting audio/silk voice attachment (gateway/platforms/weixin.py:1587-1591, gateway/run.py:10454-10458), so the production change matches the stated goal.

Suggested changes

  • Add one integration-level regression test covering the final routing contract: an inbound Weixin voice item with Tencent text should construct a VOICE event with audio/silk media and reach _enrich_message_with_transcription. The new tests cover the adapter prerequisites, but not that gateway-runner handoff.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 2026
…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.
teknium1 pushed a commit that referenced this pull request Jul 28, 2026
…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.
teknium1 pushed a commit that referenced this pull request Jul 28, 2026
…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.
teknium1 pushed a commit that referenced this pull request Jul 28, 2026
…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.
@teknium1

Copy link
Copy Markdown
Collaborator

Merged into main via consolidated salvage PR #73515 (merge c911a5f10f). Your Weixin fix routing voice through Hermes STT instead of trusting Tencent's voice_item.text was cherry-picked with your authorship.

Your contribution is credited to you in git history. Thank you! Closing this PR as merged-via-salvage.

@teknium1 teknium1 closed this Jul 29, 2026
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…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.
prmartinow pushed a commit to prmartinow/hermes-agent that referenced this pull request Aug 26, 2026
…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.
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/wecom WeCom / WeChat Work adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants