Skip to content

[Speechify] Add support for Speechify as a TTS provider#615

Open
DaemonLoki wants to merge 1 commit into
mainfrom
add-speechify-tts
Open

[Speechify] Add support for Speechify as a TTS provider#615
DaemonLoki wants to merge 1 commit into
mainfrom
add-speechify-tts

Conversation

@DaemonLoki

@DaemonLoki DaemonLoki commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

What's implemented?

  • Speechify as a TTS provider, usable with speechify.TTS()
  • We're supporting different models, up to their latest simba-3.2
  • We have support for the different voice (e.g. geffen_32 as the default, but others, like beatrice_32, etc)
  • Multilingual support works as well, using e.g. speechify.TTS(model="simba-multilingual", language="de-DE")

Demo

speechify-demo.mp4

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a Speechify TTS plugin with environment-based API-key configuration, configurable voice/model/language settings, and streaming 24 kHz mono S16 PCM output. Registers the plugin in workspace and optional dependency configuration, documents its API, adds unit and integration tests, and provides a voice-bot example plus a standalone WAV-producing smoke test.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 34555113-4b2e-4f30-8d0e-40eabb37b338

📥 Commits

Reviewing files that changed from the base of the PR and between 019edac and b277fbb.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • CHANGELOG.md
  • agents-core/pyproject.toml
  • plugins/speechify/README.md
  • plugins/speechify/example/.env.example
  • plugins/speechify/example/README.md
  • plugins/speechify/example/main.py
  • plugins/speechify/example/pyproject.toml
  • plugins/speechify/example/tts_smoke.py
  • plugins/speechify/py.typed
  • plugins/speechify/pyproject.toml
  • plugins/speechify/tests/__init__.py
  • plugins/speechify/tests/test_tts.py
  • plugins/speechify/vision_agents/plugins/speechify/__init__.py
  • plugins/speechify/vision_agents/plugins/speechify/tts.py
  • pyproject.toml

Comment on lines +54 to +60
audio_stream = self.client.audio.stream(
accept="audio/pcm",
input=text,
voice_id=self.voice_id,
model=self.model,
**({"language": self.language} if self.language else {}),
)

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.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
pip install speechify-api
python -c "
import inspect
from speechify import AsyncSpeechify
print('Is coroutine:', inspect.iscoroutinefunction(AsyncSpeechify().audio.stream))
"

Repository: GetStream/Vision-Agents

Length of output: 2237


🏁 Script executed:

#!/bin/bash
python3 - <<'PY'
import inspect
import speechify
from speechify import AsyncSpeechify

print("speechify module:", speechify.__file__)
print("AsyncSpeechify class:", AsyncSpeechify)

# Try to inspect the class attributes without instantiating a client.
audio_attr = getattr(AsyncSpeechify, "audio", None)
print("AsyncSpeechify.audio on class:", type(audio_attr), audio_attr)

# Locate source files for the package.
import speechify.client as client_mod
print("client module:", client_mod.__file__)

# Print relevant lines around AsyncSpeechify and any stream method definitions.
import pathlib, re
paths = [
    pathlib.Path(client_mod.__file__),
]
for path in paths:
    text = path.read_text()
    for needle in ["class AsyncSpeechify", "async def stream", "def stream"]:
        if needle in text:
            print(f"FOUND {needle} in {path}")
PY

Repository: GetStream/Vision-Agents

Length of output: 589


🏁 Script executed:

#!/bin/bash
python3 - <<'PY'
import pathlib
import inspect

import speechify.client as client_mod

path = pathlib.Path(client_mod.__file__)
text = path.read_text().splitlines()

# Show the AsyncSpeechify class and nearby stream-related methods.
needles = ("class AsyncSpeechify", "async def stream", "def stream")
for i, line in enumerate(text, start=1):
    if any(n in line for n in needles):
        start = max(1, i - 8)
        end = min(len(text), i + 20)
        print(f"\n--- {path}:{start}-{end} ---")
        for j in range(start, end + 1):
            print(f"{j:4d}: {text[j-1]}")
PY

Repository: GetStream/Vision-Agents

Length of output: 1461


🏁 Script executed:

#!/bin/bash
python3 - <<'PY'
from pathlib import Path
import speechify.client as client_mod

path = Path(client_mod.__file__)
print(path)
for i, line in enumerate(path.read_text().splitlines(), start=1):
    if "async def stream" in line or "def stream" in line or "class AsyncSpeechify" in line:
        print(f"{i}: {line}")
PY

Repository: GetStream/Vision-Agents

Length of output: 259


🏁 Script executed:

#!/bin/bash
python3 - <<'PY'
from pathlib import Path
import speechify
import speechify.client as client_mod

paths = [Path(speechify.__file__), Path(client_mod.__file__)]
for path in paths:
    print(f"\nFILE: {path}")
    text = path.read_text().splitlines()
    for i, line in enumerate(text, start=1):
        if "class AsyncSpeechify" in line or "async def stream" in line or "def stream" in line:
            start = max(1, i - 6)
            end = min(len(text), i + 18)
            for j in range(start, end + 1):
                print(f"{j:4d}: {text[j-1]}")
            print()
PY

Repository: GetStream/Vision-Agents

Length of output: 1213


🌐 Web query:

speechify-api Python SDK AsyncSpeechify audio stream await

💡 Result:

To perform asynchronous audio streaming using the Speechify Python SDK, you should use the AsyncSpeechify client class [1][2][3]. The SDK supports non-blocking operations, allowing you to iterate over audio chunks as they are generated by the API [4]. ### Async Streaming Implementation The SDK provides an asynchronous interface for streaming. You can use an async for loop to process the audio chunks returned by the client.audio.stream method [4]. Example usage: python import asyncio from speechify import AsyncSpeechify async def stream_audio(): # Initialize the async client client = AsyncSpeechify(token="YOUR_API_KEY") # Open the stream stream = await client.audio.stream( accept="audio/mpeg", input="Hello! This is a streaming test using the Speechify Python SDK.", voice_id="george" ) # Iterate over incoming audio chunks async for chunk in stream: # Process each binary chunk (e.g., write to a file or send to a buffer) print(f"Received chunk of size: {len(chunk)} bytes") # Run the async function asyncio.run(stream_audio()) ### Key Considerations * Async Client: Always instantiate AsyncSpeechify instead of the synchronous Speechify client to access non-blocking methods [1][2]. * Custom HTTP Client: If you need advanced control (e.g., custom timeouts or proxy settings), you can pass a httpx.AsyncClient() instance to the httpx_client parameter during the initialization of AsyncSpeechify [1][4]. * Resource Management: Ensure you are using asyncio or a compatible event loop runner to execute these calls [1][2]. * Cookbook Resources: For more complex patterns, including voice cloning or saving streams directly to disk, refer to the official Speechify API Cookbook on GitHub [5].

Citations:


Await self.client.audio.stream(...). audio_stream is a coroutine here, so PcmData.from_response(...) will fail.

license = "MIT"
dependencies = [
"vision-agents",
"speechify-api>=1.2.4,<2",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
"speechify-api>=1.2.4,<2",
"speechify-api==3.0.1",

recommend jumping to 3.0.1 but i did break the wire when going to v3 (sorry!)

raise ValueError("SPEECHIFY_API_KEY env var or api_key parameter required")

self.client = (
client if client is not None else AsyncSpeechify(api_key=self.api_key)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
client if client is not None else AsyncSpeechify(api_key=self.api_key)
client if client is not None else AsyncSpeechify(token=self.api_key)

i believe this is the breaking bit (again, sorry!)

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants