Skip to content

fix(dingtalk): extract ASR recognition from 1:1 voice messages - #49546

Closed
zh-xl-kang wants to merge 1 commit into
NousResearch:mainfrom
zh-xl-kang:fix/dingtalk-audio-recognition
Closed

fix(dingtalk): extract ASR recognition from 1:1 voice messages#49546
zh-xl-kang wants to merge 1 commit into
NousResearch:mainfrom
zh-xl-kang:fix/dingtalk-audio-recognition

Conversation

@zh-xl-kang

@zh-xl-kang zh-xl-kang commented Jun 20, 2026

Copy link
Copy Markdown

What does this PR do?

DingTalk 1:1 voice messages arrive with msgtype="audio" and empty text/rich_text fields. The server-side ASR result lives in extensions.content.recognition (or extensions.recognition on some SDK versions). Without this fix, the empty-text gate in _on_message silently dropped voice messages in DM conversations.

This restores 1:1 voice support by reading the recognition text from extensions and classifying msgtype="audio" so the gateway can route through STT when the ASR result is empty. It complements — and does not overlap with — the earlier group-chat voice fix (see below).

Related Issue

Fixes #

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • plugins/platforms/dingtalk/adapter.py_extract_text(): pull recognition text from extensions when msgtype="audio" and text/rich_text are empty, with a three-level fallback:
    1. extensions.content.recognition (primary path, current SDK)
    2. extensions.recognition (fallback for some SDK versions)
    3. "[Voice message — transcription unavailable]" (placeholder so the message is not dropped)
  • plugins/platforms/dingtalk/adapter.py_extract_media(): classify msgtype="audio" as MessageType.AUDIO (not TEXT) so the gateway routes through STT when recognition is empty. downloadCode is intentionally not added to media_urls — it is an OSS code, not a local file path, and the STT pipeline would fail trying to open it.
  • tests/gateway/test_dingtalk.py — 9 new regression tests (see below).

Relationship to existing fix

Commit 93734c26e (fix(dingtalk): transcribe native voice notes) fixed voice messages arriving via rich-text type=voice items in group chats (where the bot is @mentioned). This PR fixes the separate path for 1:1 DM voice messages where msgtype="audio" and the ASR result is in extensions, not rich-text. The two fixes are complementary and do not overlap.

How to Test

  1. Send a 1:1 (DM) voice message to a DingTalk bot so it arrives with msgtype="audio" and empty text/rich_text.
  2. Before this fix: the empty-text gate in _on_message silently drops the message.
  3. After this fix: the recognition text is extracted from extensions (or a placeholder is used) and the message is classified as MessageType.AUDIO, so it is processed / routed through STT instead of dropped.

Automated: 9 new regression tests cover the primary extensions.content.recognition path, the extensions.recognition fallback, the no-recognition → placeholder fallback, edge cases (empty extensions, non-dict extensions, whitespace-only recognition), MessageType.AUDIO classification, and the downloadCode not-in-media_urls invariant (2 tests). All existing DingTalk tests (68) continue to pass — 77 passed total.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform:

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery platform/dingtalk DingTalk adapter P2 Medium — degraded but workaround exists labels Jun 20, 2026
@zh-xl-kang
zh-xl-kang force-pushed the fix/dingtalk-audio-recognition branch from 8b6fc6d to 9a82f15 Compare June 20, 2026 11:18
@zh-xl-kang
zh-xl-kang force-pushed the fix/dingtalk-audio-recognition branch 2 times, most recently from 80be470 to 1a4392f Compare June 25, 2026 12:45
@zh-xl-kang

Copy link
Copy Markdown
Author

Hi maintainers 👋 — just a gentle follow-up on this PR.

It's been open for 11 days with no review yet. This is a small, well-tested fix (+132 lines, 9 new regression tests, all 77 DingTalk tests passing) that addresses a real gap: 1:1 DM voice messages were silently dropped because the empty-text gate in _on_message didn't check extensions for ASR results.

The fix is complementary to the existing group-chat voice note handling (commit 93734c2) — it covers the separate msgtype=audio path that DM conversations use.

Would appreciate a look when you have a moment. cc @teknium1 @kevinskysunny

@zh-xl-kang
zh-xl-kang force-pushed the fix/dingtalk-audio-recognition branch from 1a4392f to 379f442 Compare July 1, 2026 06:26
@zh-xl-kang

Copy link
Copy Markdown
Author

Quick follow-up — this PR has been open for 12 days and the last bump was yesterday. Happy to provide additional context, split the change into smaller chunks, or address any concerns if there's something blocking the review. The change is isolated to the DingTalk adapter (no core/gateway changes) and fully tested. Any guidance on review timeline would be appreciated. 🙏

DingTalk 1:1 voice messages arrive with msgtype='audio' and empty
text/rich_text fields. The server-side ASR result lives in
extensions.content.recognition (or extensions.recognition on some SDK
versions). Without this fix, the empty-text gate in _on_message
silently dropped voice messages in DM conversations.

Changes:
- _extract_text: pull recognition text from extensions when
  msgtype='audio' and text/rich_text are empty, with three-level
  fallback (content.recognition → extensions.recognition → placeholder)
- _extract_media: classify msgtype='audio' as MessageType.AUDIO
  (not TEXT) so the gateway routes through STT when recognition is
  empty. downloadCode is intentionally NOT added to media_urls — it is
  an OSS code, not a local file path.
- Correct misleading STT comment in _extract_media.
- Fix 9 audio test imports: gateway.platforms.dingtalk →
  plugins.platforms.dingtalk.adapter (post-migration path from 5600105).
- Add test_never_returns_empty_string: verifies _extract_text never
  returns empty for audio messages across 4 fallback paths.

Adds 9 regression tests covering all fallback paths, edge cases
(non-dict extensions, whitespace-only recognition), and the
downloadCode-not-in-media_urls invariant.
@zh-xl-kang
zh-xl-kang force-pushed the fix/dingtalk-audio-recognition branch from 379f442 to 1798f2b Compare July 2, 2026 06:58

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for isolating the 1:1 DingTalk ASR path. The recognition extraction targets a real current-main gap: plugins/platforms/dingtalk/adapter.py:663-670 calls _extract_text() and drops an event with no text or media, while current _extract_text() (:724-749) never reads extensions.

Problems

  • The new MessageType.AUDIO classification does not route this message through STT. gateway/run.py:10414-10422 explicitly keeps AUDIO out of audio_paths, and this PR intentionally leaves media_urls empty. The TestAudioMessageTypeClassification docstring therefore asserts behavior the gateway does not provide.

Suggested changes

  • Retain the ASR-text extraction, but remove or justify the AUDIO classification; it is not needed for recognized text to clear the existing empty-message gate.
  • Add an adapter-level _on_message regression asserting the ASR text reaches handle_message, rather than only helper-level assertions.

This is an automated hermes-sweeper review.

# empty, the placeholder text "[Voice message — transcription
# unavailable]" is used; no secondary STT is attempted.
msg_type = MessageType.AUDIO

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

MessageType.AUDIO does not route through STT: gateway/run.py:10414-10422 explicitly excludes AUDIO from audio_paths, and this branch supplies no media URL. Please remove this classification or document a verified downstream use; the ASR text extraction already prevents the empty-message drop.

@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 14, 2026
@zh-xl-kang

Copy link
Copy Markdown
Author

Closing — these issues have been addressed in the latest upstream release. Thanks for the review feedback.

@zh-xl-kang zh-xl-kang closed this Jul 22, 2026
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/dingtalk DingTalk 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 type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants