Skip to content

Fix: Handle local file paths in audio content conversion#466

Closed
sidonsoft wants to merge 1 commit intoagentscope-ai:mainfrom
sidonsoft:fix/audio-local-file-path-handling
Closed

Fix: Handle local file paths in audio content conversion#466
sidonsoft wants to merge 1 commit intoagentscope-ai:mainfrom
sidonsoft:fix/audio-local-file-path-handling

Conversation

@sidonsoft
Copy link
Copy Markdown

Summary

This PR fixes a bug in message_to_agentscope_msg() where audio content with a local file path (e.g., /tmp/voice.ogg) would be incorrectly wrapped as Base64Source instead of being converted to a proper file:// URL.

Related PR: agentscope-ai/QwenPaw#1896


The Bug

In src/agentscope_runtime/adapters/agentscope/message.py, the audio handling logic has this branching:

elif cnt_type == "audio":
    if value and isinstance(value, str) and value.startswith("data:"):
        # Case 1: data: URI → Base64Source ✅
        ...
    else:
        parsed_url = urlparse(value)
        if parsed_url.scheme and parsed_url.netloc:
            # Case 2: Full URL → URLSource ✅
            ...
        else:
            # Case 3: Everything else → Base64Source ❌
            audio_extension = getattr(cnt, "format")
            base64_source = Base64Source(
                type="base64",
                media_type=f"audio/{audio_extension}",  # Could be "audio/None"
                data=value,  # Could be a file path!
            )

The problem: Case 3 assumes that non-URL strings are raw base64 data. But if value is a local file path like /tmp/voice.ogg:

  1. It's not a data: URI → skipped Case 1
  2. urlparse("/tmp/voice.ogg") gives scheme="", netloc="" → skipped Case 2
  3. Falls through to Case 3, where it's treated as base64 data (it's not)

The Fix

Added a local file path check using os.path.isfile() before falling back to base64:

else:
    audio_extension = getattr(cnt, "format", None)

    if value and isinstance(value, str) and os.path.isfile(value):
        # Local file path → convert to file:// URL
        url_source = URLSource(
            type="url",
            url=Path(value).as_uri(),
            media_type=f"audio/{audio_extension}" if audio_extension else None,
        )
        msg_content.append(block_cls(type=cnt_type, source=url_source))
    else:
        # Fall back to base64 (existing behavior)
        base64_source = Base64Source(
            type="base64",
            media_type=f"audio/{audio_extension}" if audio_extension else None,
            data=value,
        )
        msg_content.append(block_cls(type=cnt_type, source=base64_source))

Why os.path.isfile() is Appropriate

The check is strict — it returns False if the file doesn't exist on disk. This is correct because:

  1. The runner processes freshly downloaded Telegram/DingTalk audio files, so they should exist at conversion time
  2. If the file doesn't exist, something else is broken upstream
  3. Falling back to base64 for non-existent paths would produce file:// URLs that fail downstream with confusing errors

Additional Fixes

Issue Before After
getattr(cnt, "format") Could raise AttributeError getattr(cnt, "format", None) with default
media_type=f"audio/{audio_extension}" Produces "audio/None" if format missing None if extension missing
Style - Uses os.path.isfile() for consistency with CoPaw

Behavior Changes

Input Before After
/tmp/audio.ogg (existing file) Base64Source(data="/tmp/audio.ogg", media_type="audio/None") URLSource(url="file:///tmp/audio.ogg")
data:audio/ogg;base64,... Base64Source(...) unchanged ✅
https://example.com/audio.ogg URLSource(url="...") unchanged ✅
T2dnUw... (raw base64) Base64Source(...) unchanged ✅

Coordination with CoPaw PR

This PR fixes the root cause in agentscope-runtime.

CoPaw PR #1896 adds downstream defensive handling in message_processing.py. That fix should remain as a safety net for:

  1. Backwards compatibility with older runtime versions
  2. Defense-in-depth against similar issues in other code paths

Both PRs are complementary:

  • This PR → Fixes the source of the malformed blocks
  • CoPaw PR → Defensive handling downstream

Audio content with local file paths (e.g., /tmp/voice.ogg) were
incorrectly wrapped as Base64Source instead of being converted to
file:// URLs.

Changes:
- Add os.path.isfile() check before falling back to base64
- Convert local file paths to file:// URLs using Path.as_uri()
- Fix getattr(cnt, 'format') to use default None to prevent AttributeError
- Guard media_type construction to avoid 'audio/None' strings

Related: agentscope-ai/QwenPaw#1896
@sidonsoft sidonsoft requested a review from a team March 20, 2026 11:40
@cla-assistant
Copy link
Copy Markdown

cla-assistant Bot commented Mar 20, 2026

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Russell Stephens seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant