Skip to content

LivekitObserve.export() raises TypeError by awaiting a dict (sync function awaited) #4

@0xNuru

Description

@0xNuru

Bug: LivekitObserve.export() raises TypeError: object dict can't be used in 'await' expression

Description

Calling await soundflare.export(session_id) during a LiveKit agent shutdown callback raises:

TypeError: object dict can't be used in 'await' expression

Root Cause

In soundflare/__init__.py (line ~815), the export() method is defined as async def and uses await on send_session_to_soundflare():

async def export(self, session_id, recording_url=""):
    extra_data = {}
    if hasattr(self, 'spans_data') and self.spans_data:
        extra_data['telemetry_spans'] = self.spans_data

    return await send_session_to_soundflare(  # <-- BUG: this is a sync function that returns a dict
        session_id,
        recording_url,
        apikey=self.apikey,
        api_url=self.host_url,
    )

However, send_session_to_soundflare() in soundflare/soundflare.py (line ~634) is a synchronous function that returns a dict. You cannot await a plain dict.

Suggested Fix

import asyncio

async def export(self, session_id, recording_url=""):
    """Export session data to Soundflare"""
    extra_data = {}
    if hasattr(self, 'spans_data') and self.spans_data:
        extra_data['telemetry_spans'] = self.spans_data

    return await asyncio.to_thread(
        send_session_to_soundflare,
        session_id,
        recording_url,
        apikey=self.apikey,
        api_url=self.host_url,
        **extra_data,  # <-- also forward the telemetry spans
    )

Using asyncio.to_thread() properly offloads the blocking HTTP call to a thread pool, keeping the event loop responsive during agent shutdown.

Reproduction

from soundflare import LivekitObserve

soundflare = LivekitObserve(
    agent_id="test-agent",
    apikey="your-key",
)

# In a LiveKit agent entrypoint:
session_id = soundflare.start_session(session=session)

async def on_shutdown():
    await soundflare.export(session_id)  # <-- TypeError here

ctx.add_shutdown_callback(on_shutdown)

Current Workaround

Bypassing export() and calling the underlying sync function directly:

from soundflare.soundflare import send_session_to_soundflare
async def export_soundflare_data():
    await asyncio.to_thread(
        send_session_to_soundflare,
        session_id,
        apikey=soundflare.apikey,
        api_url=soundflare.host_url,
    )

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions