Skip to content

fix(feishu): classify native voice messages as VOICE for auto-transcription - #29235

Closed
wuli666 wants to merge 1 commit into
NousResearch:mainfrom
wuli666:fix/feishu-voice-note-transcription
Closed

fix(feishu): classify native voice messages as VOICE for auto-transcription#29235
wuli666 wants to merge 1 commit into
NousResearch:mainfrom
wuli666:fix/feishu-voice-note-transcription

Conversation

@wuli666

@wuli666 wuli666 commented May 20, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Feishu (Lark) native voice messages were never transcribed. Lark's top-level audio msg_type is an in-app voice recording (uploaded audio files arrive as file/media), but _resolve_normalized_message_type resolved the audio preferred type to MessageType.AUDIO. The gateway treats AUDIO as a non-transcribed file attachment:

# gateway/run.py
# MessageType.AUDIO = audio file attachment — never STT
# MessageType.VOICE = voice message (Opus/OGG) — always STT
if event.message_type == MessageType.AUDIO:
    audio_file_paths.append(path)      # not transcribed
elif event.message_type == MessageType.VOICE or ...:
    audio_paths.append(path)           # transcribed

So a Feishu voice note reached the agent as an untranscribable attachment — the user's spoken message silently never became text.

Feishu was the only platform getting this wrong. Telegram, Discord, Slack, WhatsApp, Signal, Matrix, WeChat, WeCom, DingTalk, QQ, BlueBubbles, Mattermost, and Yuanbao all classify native voice notes as MessageType.VOICE. This is the follow-up to #28993, which added native voice-note transcription for Discord + DingTalk but did not cover Feishu.

Fix

Return MessageType.VOICE for the audio branch in _resolve_normalized_message_type. That branch is reached only for Lark's top-level audio msg_type (the normalizer maps file uploads to document), so VOICE is unconditionally correct here — no risk of auto-transcribing an uploaded music/audio file.

Changes Made

  • gateway/platforms/feishu.pyaudio branch returns MessageType.VOICE instead of resolving to AUDIO via mime.
  • tests/gateway/test_feishu.pytest_extract_audio_message_downloads_and_caches asserted the old AUDIO behavior on a fixture literally named voice.ogg; updated to expect VOICE (the corrected classification).
  • tests/gateway/test_feishu_voice_message_type.py (new) — focused regression: audio → VOICE (with and without mime); photo/document/text unaffected.

How to Test

pytest tests/gateway/test_feishu_voice_message_type.py -v   # 3 passed
pytest tests/gateway/test_feishu.py -q                      # 198 passed

Verified the new voice tests fail when the branch resolves to AUDIO and pass with VOICE; the photo/document/text cases are unaffected either way.

Scope honesty: classification is mock-tested here; the downstream STT pipeline is the shared, already-proven path from #28993. End-to-end verification on a live Feishu account would be a welcome confirmation — I don't have one.

Related Issue

No tracking issue — sibling-gap found while reviewing #28993 (Feishu was the platform left uncovered). Happy to file one if preferred.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests
  • ♻️ Refactor
  • 🎯 New skill

Checklist

Code

  • I've read the Contributing Guide
  • Conventional Commits (fix(feishu): ...)
  • Searched existing PRs — no PR addresses Feishu voice-note classification (fix(gateway): transcribe native voice notes (Discord + DingTalk) #28993 covered Discord + DingTalk only)
  • Only changes related to this fix — single commit, 3 files
  • pytest tests/ -q — ran the full tests/gateway/test_feishu.py (198 passed) + new tests; did not run the whole repo suite locally, relying on CI
  • Added tests with verified regression discipline
  • Tested on: Ubuntu 24.04, Python 3.13.5

Documentation & Housekeeping

  • Docs — inline comment explains the Lark audio-is-voice rationale; N/A elsewhere
  • cli-config.yaml.example — N/A
  • CONTRIBUTING/AGENTS — N/A
  • Cross-platform — pure classification logic; platform-agnostic
  • Tool schemas — N/A

…iption

Lark's native "audio" msg_type is an in-app voice recording — uploaded
audio files arrive as "file"/"media". But _resolve_normalized_message_type
resolved the "audio" preferred type to MessageType.AUDIO, which the gateway
treats as a non-transcribed file attachment (run.py: AUDIO -> audio_file_paths,
"never STT"; VOICE -> audio_paths, "always STT"). Result: a Feishu voice
note reached the agent as an untranscribable audio attachment and was
silently ignored — the user's spoken message never became text.

Every other platform that receives native voice notes (Telegram, Discord,
Slack, WhatsApp, Signal, Matrix, WeChat, WeCom, DingTalk, QQ, BlueBubbles,
Mattermost, Yuanbao) classifies them as MessageType.VOICE. Feishu was the
only one classifying them as AUDIO. This is the follow-up to NousResearch#28993, which
added native voice-note transcription for Discord + DingTalk but did not
cover Feishu.

Return MessageType.VOICE for the "audio" branch. The branch is reached only
for Lark's top-level audio msg_type (set in the normalizer; file uploads map
to "document"), so VOICE is unconditionally correct here — no risk of
auto-transcribing an uploaded music/audio file.

- plugins/platforms/feishu/adapter.py: _resolve_normalized_message_type audio
  branch returns VOICE instead of resolving to AUDIO via mime.
- tests/gateway/test_feishu.py: test_extract_audio_message_downloads_and_caches
  asserted the old AUDIO behavior on a fixture literally named voice.ogg —
  updated to expect VOICE (the corrected classification).
- tests/gateway/test_feishu_voice_message_type.py: new focused regression
  tests (audio->VOICE with and without mime; photo/document/text unaffected).

Rebased onto current main: the Feishu platform moved from the single-file
gateway/platforms/feishu.py into the plugins/platforms/feishu/ package; the
fix applies to the same _resolve_normalized_message_type logic at its new home.

Verified the new voice tests fail when the branch resolves to AUDIO and pass
with VOICE, while the photo/document/text cases are unaffected either way.

Note: classification is mock-tested here; the downstream STT pipeline is the
shared, already-proven path (NousResearch#28993). End-to-end verification on a live
Feishu account would be a welcome confirmation.

Refs NousResearch#28993 (sibling-gap: Feishu was the platform left uncovered).
@wuli666
wuli666 force-pushed the fix/feishu-voice-note-transcription branch from 7b85002 to 4f9d402 Compare June 26, 2026 10:39
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused Feishu fix. Current main still has the reported defect: normalize_feishu_message() selects preferred_message_type="audio" for raw Feishu audio messages (plugins/platforms/feishu/adapter.py:870-877), while _resolve_normalized_message_type() maps that through the MIME helper to MessageType.AUDIO (plugins/platforms/feishu/adapter.py:3802-3824). The gateway then routes AUDIO to the non-STT attachment path and routes VOICE to _enrich_message_with_transcription() (gateway/run.py:10414-10458).

The PR changes the exact resolver branch and updates the existing voice.ogg extraction fixture plus adds MIME-independent resolver regression coverage. No verified correctness, completeness, or design-fit issue was found. GitHub currently reports the PR as cleanly mergeable.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 13, 2026

@GottZ GottZ left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during triage.

Summary

Two PRs address the same Feishu native voice-note misclassification by changing the audio resolver from MessageType.AUDIO to MessageType.VOICE, allowing the gateway to send native recordings through STT. #29235 applies the fix and regression coverage at the current plugin path, while #29295 implements the same underlying change against the removed pre-migration adapter path.

Related pull requests

  • #29235 related — (+60/-2) — merge: Changes the active plugins/platforms/feishu/adapter.py resolver branch and adds focused coverage showing that native audio messages become VOICE regardless of MIME while other normalized types remain unaffected; this matches the keep_open review, which found no correctness, completeness, or design-fit issue.
  • #29295 duplicate — (+58/-3) — close as duplicate of #29235: The diff makes the same AUDIO-to-VOICE correction and adds useful MIME/file-upload checks, but it targets the removed gateway/platforms/feishu.py path and is conflicting. Despite the keep_open review on #29295, its own review identifies that the change must be salvaged at the current plugin path, which #29235 already does cleanly.

Duplicates

#29235 and #29295 implement the same resolver correction for Feishu native audio messages; #29295 is effectively superseded by the current-path implementation in #29235.

Suggested consolidation

Merge #29235 because it fixes the reported cause in the active Feishu adapter and includes direct regression coverage. Close #29295 as a duplicate/superseded implementation: its substantive change is already represented by #29235, while its diff applies to a removed path and currently conflicts.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    subgraph Dup29235 ["PRs duplicating each other"]
        P29235["PR #29235 (open)"]
        P29295["PR #29295 (open)"]
    end
    class P29235 open
    class P29295 open
    class P29235 target
    click P29235 "https://github.com/NousResearch/hermes-agent/pull/29235"
    click P29295 "https://github.com/NousResearch/hermes-agent/pull/29295"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed or no verify verdict yet (state tag in the node label).

Cross-PR triage: Reviewed 2 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 10 kB of PR diffs, 6 kB of issue/PR text, 2 kB of discussion (3 comments), 1 verify verdict. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

@GottZ GottZ left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during triage.

Delta since our previous triage comment

Since the previous triage comment, @teknium1 closed #29295 as a stale-target implementation and confirmed that its intent is being salvaged by #29235 at the live plugin path. This new contributor decision confirms and extends our earlier analysis: #29295 cannot apply because it patches the deleted pre-migration adapter.

Changed pull requests

  • #29295 [closed] duplicate — (+58/-3) — closed as superseded by #29235: Despite the earlier keep_open review, @teknium1’s newer closure confirms that the diff targets deleted gateway/platforms/feishu.py; the equivalent AUDIO-to-VOICE fix remains relevant as the superseded reference implementation now covered at the active path by #29235.

Suggested consolidation

The recommendation is unchanged: merge #29235; #29295 is now closed as its stale-path duplicate.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    subgraph Dup29235 ["PRs duplicating each other"]
        P29235["PR #29235 (open)"]
        P29295["PR #29295 (closed)"]
    end
    class P29235 open
    class P29295 closed
    class P29235 target
    click P29235 "https://github.com/NousResearch/hermes-agent/pull/29235"
    click P29295 "https://github.com/NousResearch/hermes-agent/pull/29295"
    classDef changedSince stroke:#dc2626,stroke-width:3px,stroke-dasharray: 6 3
    class P29295 changedSince
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed or no verify verdict yet (state tag in the node label).
Dashed red border = changed since our previous triage comment (1 added/updated, 1 removed graph elements).

Cross-PR triage: Reviewed 2 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 10 kB of PR diffs, 6 kB of issue/PR text, 3 kB of discussion (4 comments), 1 verify verdict. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

teknium1 pushed a commit that referenced this pull request Jul 28, 2026
…dio path dedup

Trimmed cherry-pick of PR #40592 (duration + dedup hunks only; the
voice-classification hunk duplicates #29235 and the send_voice Opus
rewrite is out of scope for this inbound-focused PR):

- adapter.py: ffprobe duration (off-loop) attached to Feishu voice
  uploads via _build_file_upload_body(duration=...) and the audio
  message payload (#16524, #8300)
- gateway/run.py: TTS dedup narrowed to the current turn;
  _enrich_message_with_transcription dedups repeated audio paths

Refs #40592 #16524 #8300
teknium1 pushed a commit that referenced this pull request Jul 28, 2026
…dio path dedup

Trimmed cherry-pick of PR #40592 (duration + dedup hunks only; the
voice-classification hunk duplicates #29235 and the send_voice Opus
rewrite is out of scope for this inbound-focused PR):

- adapter.py: ffprobe duration (off-loop) attached to Feishu voice
  uploads via _build_file_upload_body(duration=...) and the audio
  message payload (#16524, #8300)
- gateway/run.py: TTS dedup narrowed to the current turn;
  _enrich_message_with_transcription dedups repeated audio paths

Refs #40592 #16524 #8300
teknium1 pushed a commit that referenced this pull request Jul 28, 2026
…dio path dedup

Trimmed cherry-pick of PR #40592 (duration + dedup hunks only; the
voice-classification hunk duplicates #29235 and the send_voice Opus
rewrite is out of scope for this inbound-focused PR):

- adapter.py: ffprobe duration (off-loop) attached to Feishu voice
  uploads via _build_file_upload_body(duration=...) and the audio
  message payload (#16524, #8300)
- gateway/run.py: TTS dedup narrowed to the current turn;
  _enrich_message_with_transcription dedups repeated audio paths

Refs #40592 #16524 #8300
@teknium1

Copy link
Copy Markdown
Contributor

Merged into main via consolidated salvage PR #73515 (merge c911a5f10f). Your Feishu fix classifying Lark native audio msg_type as MessageType.VOICE was cherry-picked with your authorship, with a dedicated regression test file.

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
…dio path dedup

Trimmed cherry-pick of PR NousResearch#40592 (duration + dedup hunks only; the
voice-classification hunk duplicates NousResearch#29235 and the send_voice Opus
rewrite is out of scope for this inbound-focused PR):

- adapter.py: ffprobe duration (off-loop) attached to Feishu voice
  uploads via _build_file_upload_body(duration=...) and the audio
  message payload (NousResearch#16524, NousResearch#8300)
- gateway/run.py: TTS dedup narrowed to the current turn;
  _enrich_message_with_transcription dedups repeated audio paths

Refs NousResearch#40592 NousResearch#16524 NousResearch#8300
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/feishu Feishu / Lark adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages tool/tts Text-to-speech and transcription type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants