pipecat-slng 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pipecat_slng-0.3.0/.env.example +3 -0
- pipecat_slng-0.3.0/.github/workflows/ci.yml +47 -0
- pipecat_slng-0.3.0/.gitignore +26 -0
- pipecat_slng-0.3.0/.python-version +1 -0
- pipecat_slng-0.3.0/CHANGELOG.md +49 -0
- pipecat_slng-0.3.0/LICENSE +25 -0
- pipecat_slng-0.3.0/PKG-INFO +165 -0
- pipecat_slng-0.3.0/README.md +147 -0
- pipecat_slng-0.3.0/examples/bot.py +173 -0
- pipecat_slng-0.3.0/pyproject.toml +65 -0
- pipecat_slng-0.3.0/src/pipecat_slng/__init__.py +18 -0
- pipecat_slng-0.3.0/src/pipecat_slng/py.typed +0 -0
- pipecat_slng-0.3.0/src/pipecat_slng/stt.py +493 -0
- pipecat_slng-0.3.0/src/pipecat_slng/tts.py +758 -0
- pipecat_slng-0.3.0/tests/conftest.py +84 -0
- pipecat_slng-0.3.0/tests/test_ci_workflow.py +40 -0
- pipecat_slng-0.3.0/tests/test_live_smoke.py +87 -0
- pipecat_slng-0.3.0/tests/test_stt.py +205 -0
- pipecat_slng-0.3.0/tests/test_tts.py +469 -0
- pipecat_slng-0.3.0/uv.lock +3036 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
16
|
+
env:
|
|
17
|
+
# Pin every uv invocation in this job to the matrix interpreter —
|
|
18
|
+
# an unpinned `uv run` rebuilds .venv with the default Python and
|
|
19
|
+
# drops the extras installed by `uv sync` (B1).
|
|
20
|
+
UV_PYTHON: ${{ matrix.python-version }}
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- name: Checkout
|
|
24
|
+
uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- name: Install uv
|
|
27
|
+
uses: astral-sh/setup-uv@v5
|
|
28
|
+
with:
|
|
29
|
+
enable-cache: true
|
|
30
|
+
|
|
31
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
32
|
+
run: uv python install ${{ matrix.python-version }}
|
|
33
|
+
|
|
34
|
+
- name: Install dependencies
|
|
35
|
+
run: uv sync --all-extras --locked
|
|
36
|
+
|
|
37
|
+
- name: Lint (ruff)
|
|
38
|
+
run: uv run --no-sync ruff check .
|
|
39
|
+
|
|
40
|
+
- name: Format check (ruff)
|
|
41
|
+
run: uv run --no-sync ruff format --check .
|
|
42
|
+
|
|
43
|
+
- name: Type check (ty)
|
|
44
|
+
run: uv run --no-sync ty check .
|
|
45
|
+
|
|
46
|
+
- name: Tests (offline only — live smoke skipped without SLNG_API_KEY)
|
|
47
|
+
run: uv run --no-sync pytest -k 'not live' -v
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
|
|
12
|
+
# Local design docs / plans
|
|
13
|
+
docs/
|
|
14
|
+
AGENTS.md
|
|
15
|
+
SPEC.md
|
|
16
|
+
|
|
17
|
+
# Secrets
|
|
18
|
+
.env
|
|
19
|
+
|
|
20
|
+
# Editor / IDE
|
|
21
|
+
.idea/
|
|
22
|
+
.claude/
|
|
23
|
+
|
|
24
|
+
# Tool caches
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.ruff_cache/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `pipecat-slng` are documented here. This project adheres
|
|
4
|
+
to [Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [0.3.0] - 2026-06-10
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- `SlngTTSService` now treats a server-initiated WebSocket close after `audio_end`/`flushed` as the expected per-utterance lifecycle (observed with `slng/rime/arcana` models) and reconnects quietly. Previously every bot turn triggered Pipecat reconnect warnings, and three short turns in a row could trip Pipecat's consecutive quick-failure cap and shut the TTS service down mid-call. Unexpected closes keep the full Pipecat retry/failure machinery.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Top-level constructor kwargs for runtime-tunable settings:
|
|
13
|
+
- `SlngSTTService`: `language`, `enable_vad`, `enable_partials`
|
|
14
|
+
- `SlngTTSService`: `language`, `speed`
|
|
15
|
+
- `SlngHttpTTSService`: `language`, `speed` (kept for parity; not sent over wire — HTTP body is `{text, voice}` only per the SLNG OpenAPI)
|
|
16
|
+
- STT confidence filter: drop transcripts with top-level `confidence < 0.5`, matching the Pipecat community-integration guide. No-op when the bridge does not surface confidence.
|
|
17
|
+
- `py.typed` marker (PEP 561) — downstream type checkers now see inline types.
|
|
18
|
+
- GitHub Actions CI workflow: ruff + ruff-format + ty + pytest matrix on Python 3.11/3.12/3.13.
|
|
19
|
+
- New unit tests covering region/world routing headers, WS-TTS interruption (`clear`/`flush`), STT finalize (`finalize` + `from_finalize`→`confirm_finalize`), and graceful disconnect (`{type: close}`). Suite now 23 unit + 3 live (gated).
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Error handling tightened to the community-integration guide ("raise AND push"):
|
|
23
|
+
- `_connect_websocket` (STT + WS-TTS) now raises after `push_error`, so connect failures surface through `PipelineRunner` instead of dribbling silent send-after-disconnect errors.
|
|
24
|
+
- In-stream send / non-200 / compressed-format paths in `run_stt` and `run_tts` (WS + HTTP) now call `push_error` alongside the existing `yield ErrorFrame`.
|
|
25
|
+
- `examples/bot.py` defaults to the streaming `SlngTTSService`; removed the three commented-out TTS variants (incl. the "Problematic provider" Cartesia stub).
|
|
26
|
+
- `README.md` reorganised "WebSocket first, HTTP fallback"; added explicit company attribution under the title; documented the HTTP body contract (`{text, voice}` only).
|
|
27
|
+
|
|
28
|
+
Tested with Pipecat v1.3.0.
|
|
29
|
+
|
|
30
|
+
## [0.2.0] - 2026-05-29
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
- `SlngHttpTTSService` — non-streaming HTTP/REST text-to-speech via the SLNG Unified TTS bridge (`POST /v1/bridges/unmute/tts/{model}`), built on `aiohttp`.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
- `SlngTTSService` now applies runtime settings updates: a `voice`/`speed`/`language` change reconnects to re-run the init handshake.
|
|
37
|
+
|
|
38
|
+
Tested with Pipecat v1.3.0.
|
|
39
|
+
|
|
40
|
+
## [0.1.0] - 2026-05-29
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
- `SlngSTTService` — real-time WebSocket speech-to-text via the SLNG Unmute STT bridge.
|
|
44
|
+
- `SlngTTSService` — real-time WebSocket text-to-speech via the SLNG Unmute TTS bridge.
|
|
45
|
+
- Region routing via `region_override` / `world_part_override`.
|
|
46
|
+
- Foundational cascade example (`examples/bot.py`).
|
|
47
|
+
- Unit tests (fake WebSocket) and gated live smoke tests.
|
|
48
|
+
|
|
49
|
+
Tested with Pipecat v1.3.0.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, SLNG and contributors.
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
17
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
19
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
20
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
21
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
22
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
23
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
24
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
25
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pipecat-slng
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: SLNG STT/TTS services for Pipecat (WebSocket + HTTP)
|
|
5
|
+
Project-URL: Homepage, https://slng.ai
|
|
6
|
+
Project-URL: Repository, https://github.com/slng-ai/pipecat-slng
|
|
7
|
+
Author: slng.ai
|
|
8
|
+
License-Expression: BSD-2-Clause
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: http,pipecat,slng,stt,tts,voice-ai,websocket
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Requires-Dist: aiohttp>=3.9
|
|
13
|
+
Requires-Dist: pipecat-ai>=1.3.0
|
|
14
|
+
Requires-Dist: websockets>=13.0
|
|
15
|
+
Provides-Extra: example
|
|
16
|
+
Requires-Dist: pipecat-ai[openai,runner,silero,webrtc]>=1.3.0; extra == 'example'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# pipecat-slng
|
|
20
|
+
|
|
21
|
+
[](https://github.com/slng-ai/pipecat-slng/actions/workflows/ci.yml)
|
|
22
|
+
|
|
23
|
+
_Built and maintained by the SLNG team (slng.ai)._
|
|
24
|
+
|
|
25
|
+
WebSocket STT and TTS services for [Pipecat](https://github.com/pipecat-ai/pipecat),
|
|
26
|
+
backed by [SLNG](https://slng.ai) — a unified voice AI gateway that routes to
|
|
27
|
+
multiple STT/TTS providers (Deepgram, ElevenLabs, Rime, Sarvam, and more)
|
|
28
|
+
through a single API key. Swap the `model` string to switch providers; no other
|
|
29
|
+
code changes needed.
|
|
30
|
+
|
|
31
|
+
> Tested with Pipecat v1.3.0.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
uv add pipecat-slng
|
|
37
|
+
# or
|
|
38
|
+
pip install pipecat-slng
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Environment variables
|
|
42
|
+
|
|
43
|
+
```env
|
|
44
|
+
SLNG_API_KEY=your_slng_api_key # get one at https://slng.ai
|
|
45
|
+
OPENAI_API_KEY=your_openai_api_key # only needed for the example bot (LLM)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Copy [`.env.example`](.env.example) to `.env` to get started.
|
|
49
|
+
|
|
50
|
+
## Usage (streaming WebSocket — recommended)
|
|
51
|
+
|
|
52
|
+
`SlngSTTService` and `SlngTTSService` run over WebSocket: low-latency, supports
|
|
53
|
+
mid-utterance interruption, and exposes the full SLNG config surface
|
|
54
|
+
(encoding, sample_rate, language, speed).
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import os
|
|
58
|
+
|
|
59
|
+
from pipecat_slng import SlngSTTService, SlngTTSService
|
|
60
|
+
|
|
61
|
+
stt = SlngSTTService(
|
|
62
|
+
api_key=os.getenv("SLNG_API_KEY"),
|
|
63
|
+
model="slng/deepgram/nova:3-en",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
tts = SlngTTSService(
|
|
67
|
+
api_key=os.getenv("SLNG_API_KEY"),
|
|
68
|
+
model="slng/deepgram/aura:2-en",
|
|
69
|
+
voice="aura-2-thalia-en",
|
|
70
|
+
)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Common runtime knobs are top-level kwargs (e.g. `language=`, `speed=`,
|
|
74
|
+
`enable_vad=`, `enable_partials=`). For richer overrides pass a
|
|
75
|
+
`SlngSTTSettings(...)` / `SlngTTSSettings(...)` to `settings=`.
|
|
76
|
+
|
|
77
|
+
Defaults when not specified: STT uses `language=Language.EN`,
|
|
78
|
+
`enable_vad=True`, `enable_partials=True`; TTS uses `language=Language.EN`
|
|
79
|
+
and the server's default `speed`.
|
|
80
|
+
|
|
81
|
+
Two behaviors worth knowing:
|
|
82
|
+
|
|
83
|
+
- **Confidence filter (STT).** When the provider surfaces a confidence
|
|
84
|
+
score, transcripts below 0.5 are dropped.
|
|
85
|
+
- **Runtime settings updates.** Changing `voice`, `speed`, or `language`
|
|
86
|
+
mid-session (via Pipecat settings updates) reconnects the WebSocket to
|
|
87
|
+
re-run the init handshake — expect a brief reconnect, not a silent no-op.
|
|
88
|
+
|
|
89
|
+
## HTTP TTS (non-streaming fallback)
|
|
90
|
+
|
|
91
|
+
For simple request/response synthesis where streaming is not required, use
|
|
92
|
+
`SlngHttpTTSService`. It issues one HTTP POST per utterance and returns the
|
|
93
|
+
full audio body in one frame.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
import os
|
|
97
|
+
|
|
98
|
+
from pipecat_slng import SlngHttpTTSService
|
|
99
|
+
|
|
100
|
+
tts = SlngHttpTTSService(
|
|
101
|
+
api_key=os.getenv("SLNG_API_KEY"),
|
|
102
|
+
model="slng/deepgram/aura:2-en",
|
|
103
|
+
voice="aura-2-thalia-en",
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**HTTP contract limits.** Per the SLNG Unified TTS HTTP OpenAPI, the request
|
|
108
|
+
body accepts **only `{text, voice}`** — there is no `config` object. Encoding,
|
|
109
|
+
sample_rate, language, and speed are therefore **not configurable over HTTP**;
|
|
110
|
+
the server returns its default audio format. The service auto-detects WAV
|
|
111
|
+
(decoded to raw PCM at the file's sample rate) and plain PCM (passed through
|
|
112
|
+
at the pipeline's sample rate). Compressed responses (MP3/Ogg) yield an
|
|
113
|
+
`ErrorFrame` — use the streaming `SlngTTSService` if you need codec control.
|
|
114
|
+
|
|
115
|
+
An `aiohttp.ClientSession` is created internally if you don't pass one; supply
|
|
116
|
+
`aiohttp_session=...` to reuse a shared session.
|
|
117
|
+
|
|
118
|
+
## Region routing
|
|
119
|
+
|
|
120
|
+
Both services support gateway region routing via `region_override` (pin to a
|
|
121
|
+
datacenter: `ap-southeast-2` | `eu-north-1` | `us-east-1`) and
|
|
122
|
+
`world_part_override` (broad zone: `ap` | `eu` | `na`). When both are set,
|
|
123
|
+
`region_override` wins. WebSocket services send these as the
|
|
124
|
+
`X-Region-Override` / `X-World-Part-Override` headers; the HTTP service uses
|
|
125
|
+
the `region` / `world-part` query parameters (per the bridge contract).
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
stt = SlngSTTService(
|
|
129
|
+
api_key=os.getenv("SLNG_API_KEY"),
|
|
130
|
+
model="slng/deepgram/nova:3-en",
|
|
131
|
+
region_override="eu-north-1",
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Example
|
|
136
|
+
|
|
137
|
+
A complete cascade bot (STT → LLM → TTS, WebSocket TTS by default) lives in
|
|
138
|
+
[`examples/bot.py`](examples/bot.py):
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
cp .env.example .env # fill in SLNG_API_KEY and OPENAI_API_KEY
|
|
142
|
+
uv run --extra example examples/bot.py
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Then open http://localhost:7860/client in your browser and start talking.
|
|
146
|
+
The bot uses the SmallWebRTC transport by default; pass `-t daily` to use
|
|
147
|
+
Daily instead (requires installing `pipecat-ai[daily]`).
|
|
148
|
+
|
|
149
|
+
## Development
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
uv sync --all-extras
|
|
153
|
+
uv run pytest # unit tests (live smoke tests skip without SLNG_API_KEY)
|
|
154
|
+
uv run ruff check .
|
|
155
|
+
uv run ty check .
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## About SLNG
|
|
159
|
+
|
|
160
|
+
SLNG (https://slng.ai) is a unified voice AI gateway. Learn more in the
|
|
161
|
+
[SLNG docs](https://docs.slng.ai/).
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
BSD-2-Clause — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# pipecat-slng
|
|
2
|
+
|
|
3
|
+
[](https://github.com/slng-ai/pipecat-slng/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
_Built and maintained by the SLNG team (slng.ai)._
|
|
6
|
+
|
|
7
|
+
WebSocket STT and TTS services for [Pipecat](https://github.com/pipecat-ai/pipecat),
|
|
8
|
+
backed by [SLNG](https://slng.ai) — a unified voice AI gateway that routes to
|
|
9
|
+
multiple STT/TTS providers (Deepgram, ElevenLabs, Rime, Sarvam, and more)
|
|
10
|
+
through a single API key. Swap the `model` string to switch providers; no other
|
|
11
|
+
code changes needed.
|
|
12
|
+
|
|
13
|
+
> Tested with Pipecat v1.3.0.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
uv add pipecat-slng
|
|
19
|
+
# or
|
|
20
|
+
pip install pipecat-slng
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Environment variables
|
|
24
|
+
|
|
25
|
+
```env
|
|
26
|
+
SLNG_API_KEY=your_slng_api_key # get one at https://slng.ai
|
|
27
|
+
OPENAI_API_KEY=your_openai_api_key # only needed for the example bot (LLM)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Copy [`.env.example`](.env.example) to `.env` to get started.
|
|
31
|
+
|
|
32
|
+
## Usage (streaming WebSocket — recommended)
|
|
33
|
+
|
|
34
|
+
`SlngSTTService` and `SlngTTSService` run over WebSocket: low-latency, supports
|
|
35
|
+
mid-utterance interruption, and exposes the full SLNG config surface
|
|
36
|
+
(encoding, sample_rate, language, speed).
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import os
|
|
40
|
+
|
|
41
|
+
from pipecat_slng import SlngSTTService, SlngTTSService
|
|
42
|
+
|
|
43
|
+
stt = SlngSTTService(
|
|
44
|
+
api_key=os.getenv("SLNG_API_KEY"),
|
|
45
|
+
model="slng/deepgram/nova:3-en",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
tts = SlngTTSService(
|
|
49
|
+
api_key=os.getenv("SLNG_API_KEY"),
|
|
50
|
+
model="slng/deepgram/aura:2-en",
|
|
51
|
+
voice="aura-2-thalia-en",
|
|
52
|
+
)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Common runtime knobs are top-level kwargs (e.g. `language=`, `speed=`,
|
|
56
|
+
`enable_vad=`, `enable_partials=`). For richer overrides pass a
|
|
57
|
+
`SlngSTTSettings(...)` / `SlngTTSSettings(...)` to `settings=`.
|
|
58
|
+
|
|
59
|
+
Defaults when not specified: STT uses `language=Language.EN`,
|
|
60
|
+
`enable_vad=True`, `enable_partials=True`; TTS uses `language=Language.EN`
|
|
61
|
+
and the server's default `speed`.
|
|
62
|
+
|
|
63
|
+
Two behaviors worth knowing:
|
|
64
|
+
|
|
65
|
+
- **Confidence filter (STT).** When the provider surfaces a confidence
|
|
66
|
+
score, transcripts below 0.5 are dropped.
|
|
67
|
+
- **Runtime settings updates.** Changing `voice`, `speed`, or `language`
|
|
68
|
+
mid-session (via Pipecat settings updates) reconnects the WebSocket to
|
|
69
|
+
re-run the init handshake — expect a brief reconnect, not a silent no-op.
|
|
70
|
+
|
|
71
|
+
## HTTP TTS (non-streaming fallback)
|
|
72
|
+
|
|
73
|
+
For simple request/response synthesis where streaming is not required, use
|
|
74
|
+
`SlngHttpTTSService`. It issues one HTTP POST per utterance and returns the
|
|
75
|
+
full audio body in one frame.
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
import os
|
|
79
|
+
|
|
80
|
+
from pipecat_slng import SlngHttpTTSService
|
|
81
|
+
|
|
82
|
+
tts = SlngHttpTTSService(
|
|
83
|
+
api_key=os.getenv("SLNG_API_KEY"),
|
|
84
|
+
model="slng/deepgram/aura:2-en",
|
|
85
|
+
voice="aura-2-thalia-en",
|
|
86
|
+
)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**HTTP contract limits.** Per the SLNG Unified TTS HTTP OpenAPI, the request
|
|
90
|
+
body accepts **only `{text, voice}`** — there is no `config` object. Encoding,
|
|
91
|
+
sample_rate, language, and speed are therefore **not configurable over HTTP**;
|
|
92
|
+
the server returns its default audio format. The service auto-detects WAV
|
|
93
|
+
(decoded to raw PCM at the file's sample rate) and plain PCM (passed through
|
|
94
|
+
at the pipeline's sample rate). Compressed responses (MP3/Ogg) yield an
|
|
95
|
+
`ErrorFrame` — use the streaming `SlngTTSService` if you need codec control.
|
|
96
|
+
|
|
97
|
+
An `aiohttp.ClientSession` is created internally if you don't pass one; supply
|
|
98
|
+
`aiohttp_session=...` to reuse a shared session.
|
|
99
|
+
|
|
100
|
+
## Region routing
|
|
101
|
+
|
|
102
|
+
Both services support gateway region routing via `region_override` (pin to a
|
|
103
|
+
datacenter: `ap-southeast-2` | `eu-north-1` | `us-east-1`) and
|
|
104
|
+
`world_part_override` (broad zone: `ap` | `eu` | `na`). When both are set,
|
|
105
|
+
`region_override` wins. WebSocket services send these as the
|
|
106
|
+
`X-Region-Override` / `X-World-Part-Override` headers; the HTTP service uses
|
|
107
|
+
the `region` / `world-part` query parameters (per the bridge contract).
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
stt = SlngSTTService(
|
|
111
|
+
api_key=os.getenv("SLNG_API_KEY"),
|
|
112
|
+
model="slng/deepgram/nova:3-en",
|
|
113
|
+
region_override="eu-north-1",
|
|
114
|
+
)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Example
|
|
118
|
+
|
|
119
|
+
A complete cascade bot (STT → LLM → TTS, WebSocket TTS by default) lives in
|
|
120
|
+
[`examples/bot.py`](examples/bot.py):
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
cp .env.example .env # fill in SLNG_API_KEY and OPENAI_API_KEY
|
|
124
|
+
uv run --extra example examples/bot.py
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Then open http://localhost:7860/client in your browser and start talking.
|
|
128
|
+
The bot uses the SmallWebRTC transport by default; pass `-t daily` to use
|
|
129
|
+
Daily instead (requires installing `pipecat-ai[daily]`).
|
|
130
|
+
|
|
131
|
+
## Development
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
uv sync --all-extras
|
|
135
|
+
uv run pytest # unit tests (live smoke tests skip without SLNG_API_KEY)
|
|
136
|
+
uv run ruff check .
|
|
137
|
+
uv run ty check .
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## About SLNG
|
|
141
|
+
|
|
142
|
+
SLNG (https://slng.ai) is a unified voice AI gateway. Learn more in the
|
|
143
|
+
[SLNG docs](https://docs.slng.ai/).
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
BSD-2-Clause — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2026, slng.ai
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: BSD-2-Clause
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
"""SLNG Voice Agent example.
|
|
8
|
+
|
|
9
|
+
Cascade pipeline: Speech-to-Text → LLM → Text-to-Speech, with SLNG as the
|
|
10
|
+
unified STT and TTS gateway. Defaults to the streaming WebSocket TTS service
|
|
11
|
+
(``SlngTTSService``) for low-latency, interruptible conversation.
|
|
12
|
+
|
|
13
|
+
Required services:
|
|
14
|
+
- SLNG (STT + TTS) — set SLNG_API_KEY
|
|
15
|
+
- OpenAI (LLM) — set OPENAI_API_KEY
|
|
16
|
+
|
|
17
|
+
Run with::
|
|
18
|
+
|
|
19
|
+
cp .env.example .env # set SLNG_API_KEY and OPENAI_API_KEY
|
|
20
|
+
uv run --extra example examples/bot.py
|
|
21
|
+
|
|
22
|
+
Then open http://localhost:7860/client in your browser and start talking.
|
|
23
|
+
Uses the SmallWebRTC transport by default; pass ``-t daily`` to use Daily
|
|
24
|
+
instead (requires ``pipecat-ai[daily]``).
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import os
|
|
28
|
+
|
|
29
|
+
from dotenv import load_dotenv
|
|
30
|
+
from loguru import logger
|
|
31
|
+
|
|
32
|
+
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
|
33
|
+
from pipecat.frames.frames import LLMRunFrame
|
|
34
|
+
from pipecat.pipeline.pipeline import Pipeline
|
|
35
|
+
from pipecat.pipeline.runner import PipelineRunner
|
|
36
|
+
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
|
37
|
+
from pipecat.processors.aggregators.llm_context import LLMContext
|
|
38
|
+
from pipecat.processors.aggregators.llm_response_universal import (
|
|
39
|
+
LLMContextAggregatorPair,
|
|
40
|
+
LLMUserAggregatorParams,
|
|
41
|
+
)
|
|
42
|
+
from pipecat.runner.types import (
|
|
43
|
+
DailyRunnerArguments,
|
|
44
|
+
RunnerArguments,
|
|
45
|
+
SmallWebRTCRunnerArguments,
|
|
46
|
+
)
|
|
47
|
+
from pipecat.services.openai.responses.llm import OpenAIResponsesLLMService
|
|
48
|
+
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
|
49
|
+
|
|
50
|
+
from pipecat_slng import SlngSTTService, SlngTTSService
|
|
51
|
+
|
|
52
|
+
load_dotenv(override=True)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def run_bot(transport: BaseTransport):
|
|
56
|
+
"""Main bot logic."""
|
|
57
|
+
logger.info("Starting bot")
|
|
58
|
+
|
|
59
|
+
slng_api_key = os.environ["SLNG_API_KEY"]
|
|
60
|
+
|
|
61
|
+
stt = SlngSTTService(
|
|
62
|
+
api_key=slng_api_key,
|
|
63
|
+
model="slng/deepgram/nova:3-en",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Streaming WebSocket TTS — low latency, supports mid-utterance interruption.
|
|
67
|
+
# Swap model= to switch provider. For non-streaming HTTP, see SlngHttpTTSService.
|
|
68
|
+
tts = SlngTTSService(
|
|
69
|
+
api_key=slng_api_key,
|
|
70
|
+
model="slng/deepgram/aura:2-en",
|
|
71
|
+
voice="aura-2-thalia-en",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
llm = OpenAIResponsesLLMService(
|
|
75
|
+
api_key=os.getenv("OPENAI_API_KEY"),
|
|
76
|
+
settings=OpenAIResponsesLLMService.Settings(
|
|
77
|
+
model=os.getenv("OPENAI_MODEL", "gpt-4.1"),
|
|
78
|
+
system_instruction=(
|
|
79
|
+
"You are a helpful assistant in a voice conversation. "
|
|
80
|
+
"Your responses will be spoken aloud, so avoid emojis, bullet points, "
|
|
81
|
+
"or other formatting that can't be spoken. "
|
|
82
|
+
"Respond to what the user said in a creative, helpful, and brief way."
|
|
83
|
+
),
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
context = LLMContext()
|
|
88
|
+
user_aggregator, assistant_aggregator = LLMContextAggregatorPair(
|
|
89
|
+
context,
|
|
90
|
+
user_params=LLMUserAggregatorParams(
|
|
91
|
+
vad_analyzer=SileroVADAnalyzer(),
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
pipeline = Pipeline(
|
|
96
|
+
[
|
|
97
|
+
transport.input(),
|
|
98
|
+
stt,
|
|
99
|
+
user_aggregator,
|
|
100
|
+
llm,
|
|
101
|
+
tts,
|
|
102
|
+
transport.output(),
|
|
103
|
+
assistant_aggregator,
|
|
104
|
+
]
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
task = PipelineTask(
|
|
108
|
+
pipeline,
|
|
109
|
+
params=PipelineParams(
|
|
110
|
+
enable_metrics=True,
|
|
111
|
+
enable_usage_metrics=True,
|
|
112
|
+
),
|
|
113
|
+
observers=[],
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
@task.rtvi.event_handler("on_client_ready")
|
|
117
|
+
async def on_client_ready(rtvi):
|
|
118
|
+
context.add_message({"role": "user", "content": "Please introduce yourself."})
|
|
119
|
+
await task.queue_frames([LLMRunFrame()])
|
|
120
|
+
|
|
121
|
+
@transport.event_handler("on_client_connected")
|
|
122
|
+
async def on_client_connected(transport, client):
|
|
123
|
+
logger.info("Client connected")
|
|
124
|
+
|
|
125
|
+
@transport.event_handler("on_client_disconnected")
|
|
126
|
+
async def on_client_disconnected(transport, client):
|
|
127
|
+
logger.info("Client disconnected")
|
|
128
|
+
await task.cancel()
|
|
129
|
+
|
|
130
|
+
runner = PipelineRunner(handle_sigint=False)
|
|
131
|
+
await runner.run(task)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
async def bot(runner_args: RunnerArguments):
|
|
135
|
+
"""Main bot entry point."""
|
|
136
|
+
transport = None
|
|
137
|
+
|
|
138
|
+
match runner_args:
|
|
139
|
+
case DailyRunnerArguments():
|
|
140
|
+
# Imported lazily so the bot runs with only the transport extra
|
|
141
|
+
# you have installed (Daily needs `pipecat-ai[daily]`).
|
|
142
|
+
from pipecat.transports.daily.transport import DailyParams, DailyTransport
|
|
143
|
+
|
|
144
|
+
transport = DailyTransport(
|
|
145
|
+
runner_args.room_url,
|
|
146
|
+
runner_args.token,
|
|
147
|
+
"PipecatSLNG Bot",
|
|
148
|
+
params=DailyParams(
|
|
149
|
+
audio_in_enabled=True,
|
|
150
|
+
audio_out_enabled=True,
|
|
151
|
+
),
|
|
152
|
+
)
|
|
153
|
+
case SmallWebRTCRunnerArguments():
|
|
154
|
+
from pipecat.transports.smallwebrtc.transport import SmallWebRTCTransport
|
|
155
|
+
|
|
156
|
+
transport = SmallWebRTCTransport(
|
|
157
|
+
webrtc_connection=runner_args.webrtc_connection,
|
|
158
|
+
params=TransportParams(
|
|
159
|
+
audio_in_enabled=True,
|
|
160
|
+
audio_out_enabled=True,
|
|
161
|
+
),
|
|
162
|
+
)
|
|
163
|
+
case _:
|
|
164
|
+
logger.error(f"Unsupported runner arguments type: {type(runner_args)}")
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
await run_bot(transport)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
if __name__ == "__main__":
|
|
171
|
+
from pipecat.runner.run import main
|
|
172
|
+
|
|
173
|
+
main()
|