Skip to content

feat: add smallest ai asr and tts extensions#2203

Merged
plutoless merged 7 commits into
TEN-framework:mainfrom
harshitajain165:feat/smallest-ai-integration
Jul 18, 2026
Merged

feat: add smallest ai asr and tts extensions#2203
plutoless merged 7 commits into
TEN-framework:mainfrom
harshitajain165:feat/smallest-ai-integration

Conversation

@harshitajain165

@harshitajain165 harshitajain165 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

What

Adds two new vendor extensions for Smallest AI:

  • smallest_asr_python — real-time speech-to-text using the
    Pulse
    live WebSocket API (wss://api.smallest.ai/waves/v1/stt/live). 38 languages,
    64 ms time-to-first-transcript.
  • smallest_tts_python — text-to-speech using the
    Lightning
    SSE streaming endpoint (/waves/v1/tts/live). ~100 ms to first audio chunk,
    12 languages, voice cloning; lightning_v3.1 and lightning_v3.1_pro models.

Implementation notes

ASR (extends AsyncASRBaseExtension):

  • Streams raw PCM16 binary frames; interim results with final=false, finals on
    Pulse's is_final.
  • asr_finalize maps to Pulse's {"type": "finalize"} control message (session
    stays open).
  • word_timestamps=true by default so final results carry accurate
    start_ms/duration_ms.
  • Reconnect with exponential backoff (5 attempts) before reporting a fatal error.

TTS (extends AsyncTTS2HttpExtension):

  • SSE frames (data: {"audio": "<base64>"} / {"done": true}) decoded
    incrementally; a partial line is never split across chunk boundaries.
  • output_format pinned to pcm: Lightning's raw PCM is already signed 16-bit
    LE mono, matching the pcm_frame contract with no conversion, and skipping the
    container header keeps time-to-first-audio low.
  • 401/403 and invalid_api_key classified as INVALID_KEY_ERROR (fatal);
    everything else non-fatal.

Both authenticate via params.api_key / SMALLEST_API_KEY and send
X-Source: ten-framework for API-side attribution. All other params keys pass
through verbatim (query string for ASR, request body for TTS).

Testing

  • Extension-local mocked test suites included for both (no API key needed):
    ASR — result shape, finalize, dump, metrics, reconnect, vendor error, invalid
    params; TTS — basic/dump/flush, error classification, metrics, params/URL
    resolution, robustness, state machine.
  • python -m black --check --line-length 80 clean; syntax verified.
  • task asr-guarder-test EXTENSION=smallest_asr_python — 10/10 passed against the live API
  • task tts-guarder-test EXTENSION=smallest_tts_python — 15/15 passed (+1 optional skip) against the live API
  • End-to-end voice test — live conversation via the playground on the voice_assistant_smallest graph (Pulse STT + GPT-4o + Lightning TTS over Agora RTC), including barge-in/interruption

Supersedes #2200 (auto-closed by a force-push during a history cleanup).

@harshitajain165
harshitajain165 force-pushed the feat/smallest-ai-integration branch from 63273f3 to e2f2b42 Compare July 7, 2026 07:11
@harshitajain165
harshitajain165 marked this pull request as ready for review July 7, 2026 08:43
@harshitajain165

Copy link
Copy Markdown
Contributor Author

@halajohn @plutoless
Requesting review whenever you find a chance, thanks in advance!

@harshitajain165

Copy link
Copy Markdown
Contributor Author

Hi @plutoless 👋

The earlier CI failure was in smallest_tts_python's test_headers_configuration (tests/test_params.py) — the client now sets a default X-Source: ten-framework header, so the header-count assertions were off by one. I've fixed the assertions and pushed the update.

The workflow runs are currently sitting in "action_required" (the fork-PR approval gate), so they haven't started. Could you approve/re-run the CI workflows when you get a chance? And a review whenever you have time would be much appreciated. Thanks a lot!

Comment thread ai_agents/agents/ten_packages/extension/smallest_asr_python/extension.py Outdated
@harshitajain165
harshitajain165 force-pushed the feat/smallest-ai-integration branch 2 times, most recently from d17c5d9 to 3f8eb1a Compare July 14, 2026 09:32
diyuyi-agora
diyuyi-agora previously approved these changes Jul 16, 2026
@diyuyi-agora

Copy link
Copy Markdown
Contributor

format check failed, please fix

harshitajain165 and others added 6 commits July 17, 2026 17:57
Add two new vendor extensions for Smallest AI:

- smallest_asr_python: real-time speech-to-text via the Pulse live
  WebSocket API (binary PCM16 in, interim/final transcripts out,
  finalize control message, reconnect with exponential backoff).
- smallest_tts_python: text-to-speech via the Lightning SSE streaming
  endpoint (base64 PCM16 chunks decoded on the fly, ~100 ms to first
  audio, output_format pinned to pcm).

Both include mocked extension-local test suites, test configs, and
README docs. Configured via params.api_key or SMALLEST_API_KEY.
…sults

- guard _send_finalize against a down websocket and complete the
  finalize handshake locally so asr_finalize_end is always emitted
  and the turn cannot stall
- reset the reconnect retry budget on received transcriptions instead
  of right after the websocket handshake, so an accept-then-close
  failure hits the max-attempts fatal error instead of looping forever
- reformat _handle_error_message to be black-clean
- tts: stop substring-matching 401/403 in exception text; real auth
  failures are already classified by the HTTP status-code branch, so
  transport-level exceptions are always plain errors now
- asr: latch the utterance start when its first transcript arrives and
  reuse it for word-less results, so interim start_ms stays monotonic
  instead of pointing at the end of the audio stream
The client now sets a default X-Source: ten-framework header, so the
header-count assertions in test_headers_configuration were off by one.
…ancel

When the vendor WebSocket dropped mid-session, _process_messages (running
inside _message_task) awaited _handle_reconnect, whose start_connection ->
stop_connection cancels _message_task itself, interrupting the reconnect
before a new message task was created.

Route every reconnect trigger through a _schedule_reconnect helper that runs
the attempt on an independent, tracked task, so the message loop can exit
cleanly while the reconnect proceeds. In-flight reconnect tasks are retained
against GC and cancelled on deinit. Add a regression test that drops the
socket mid-session and asserts the reconnect delivers a transcript.
After a failed reconnect self.ws can be None; the assert would raise
(or be stripped under -O) instead of returning False gracefully. Fold
the None check into the is_connected() guard, mirroring _send_finalize
and pipecat's state-is-OPEN send guard.
@harshitajain165
harshitajain165 force-pushed the feat/smallest-ai-integration branch from 8d0f1f4 to 38f6a55 Compare July 17, 2026 12:27
@harshitajain165

Copy link
Copy Markdown
Contributor Author

format check failed, please fix

Could you re-run the CI workflows now?

@plutoless
plutoless merged commit 8bc5638 into TEN-framework:main Jul 18, 2026
19 of 20 checks passed
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.

3 participants