pipecat-oogam 0.1.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_oogam-0.1.0/.gitignore +7 -0
- pipecat_oogam-0.1.0/CHANGELOG.md +19 -0
- pipecat_oogam-0.1.0/LICENSE +21 -0
- pipecat_oogam-0.1.0/PKG-INFO +168 -0
- pipecat_oogam-0.1.0/README.md +142 -0
- pipecat_oogam-0.1.0/examples/voice_agent.py +119 -0
- pipecat_oogam-0.1.0/pyproject.toml +58 -0
- pipecat_oogam-0.1.0/src/pipecat_oogam/__init__.py +25 -0
- pipecat_oogam-0.1.0/src/pipecat_oogam/_version.py +1 -0
- pipecat_oogam-0.1.0/src/pipecat_oogam/languages.py +109 -0
- pipecat_oogam-0.1.0/src/pipecat_oogam/llm.py +45 -0
- pipecat_oogam-0.1.0/src/pipecat_oogam/stt.py +437 -0
- pipecat_oogam-0.1.0/src/pipecat_oogam/tts.py +169 -0
- pipecat_oogam-0.1.0/tests/test_languages.py +62 -0
- pipecat_oogam-0.1.0/tests/test_llm.py +17 -0
- pipecat_oogam-0.1.0/tests/test_stt.py +196 -0
- pipecat_oogam-0.1.0/tests/test_tts.py +113 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-11-12
|
|
4
|
+
|
|
5
|
+
First release. Tested with Pipecat v1.16.0.
|
|
6
|
+
|
|
7
|
+
- `OogamSTTService` — streaming speech-to-text over
|
|
8
|
+
`wss://platform.oogam.ai/v1/realtime?intent=transcription`. Sends `finalize`
|
|
9
|
+
when Pipecat's VAD reports the user stopped speaking, and Oogam confirms it,
|
|
10
|
+
so transcripts are marked finalized for Pipecat's TTFB metrics. Optional
|
|
11
|
+
`auto_switch` follows a caller who changes language (`on_language_changed`
|
|
12
|
+
event). Language changes through `STTUpdateSettingsFrame` apply live, without
|
|
13
|
+
reconnecting.
|
|
14
|
+
- `OogamTTSService` — streaming text-to-speech from `POST /v1/audio/speech`
|
|
15
|
+
(`stream: true`, `response_format: "pcm"`, 24 kHz).
|
|
16
|
+
- `OogamLLMService` — Sutra (`sutra-conversation`) through Pipecat's OpenAI
|
|
17
|
+
service. Function calling is not supported by the endpoint yet.
|
|
18
|
+
- Language helpers mapping Pipecat `Language` values to Oogam's realtime and
|
|
19
|
+
speech codes.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dwet Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pipecat-oogam
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Oogam speech services for Pipecat — streaming STT with client-driven turn finalize, streaming TTS, and Sutra — for 27 Indian languages and English.
|
|
5
|
+
Project-URL: Homepage, https://oogam.ai/integrations/pipecat
|
|
6
|
+
Project-URL: Documentation, https://oogam.ai/docs/streaming-stt
|
|
7
|
+
Author-email: Dwet Labs <support@dwetlabs.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: indian-languages,naad,oogam,pipecat,stt,sutra,tts,voice-agent
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: aiohttp>=3.9
|
|
20
|
+
Requires-Dist: pipecat-ai<2,>=1.16
|
|
21
|
+
Requires-Dist: websockets>=13
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# pipecat-oogam
|
|
28
|
+
|
|
29
|
+
Oogam speech services for [Pipecat](https://github.com/pipecat-ai/pipecat):
|
|
30
|
+
streaming speech-to-text, streaming text-to-speech and the Sutra conversation
|
|
31
|
+
model, for 27 Indian languages and English.
|
|
32
|
+
|
|
33
|
+
| Service | Oogam endpoint | What it does |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `OogamSTTService` | `wss://platform.oogam.ai/v1/realtime?intent=transcription` | Streaming STT. Final transcripts; finalizes the turn the moment Pipecat's VAD says the user stopped. |
|
|
36
|
+
| `OogamTTSService` | `POST /v1/audio/speech` (`stream: true`, `response_format: "pcm"`) | Streaming TTS — 24 kHz PCM played as it is generated. |
|
|
37
|
+
| `OogamLLMService` | `POST /v1/chat/completions` | Sutra (`sutra-conversation`) through Pipecat's OpenAI service. |
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install pipecat-oogam
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Requires Python 3.11+, Pipecat 1.16 or later (1.x) and an Oogam API key
|
|
44
|
+
with the NAAD product enabled. **Tested with Pipecat v1.16.0.**
|
|
45
|
+
|
|
46
|
+
Maintained by [DWET Labs](https://oogam.ai), the team behind Oogam. Integration
|
|
47
|
+
guide: [oogam.ai/integrations/pipecat](https://oogam.ai/integrations/pipecat).
|
|
48
|
+
|
|
49
|
+
## Speech-to-text
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from pipecat.transcriptions.language import Language
|
|
53
|
+
from pipecat_oogam import OogamSTTService
|
|
54
|
+
|
|
55
|
+
stt = OogamSTTService(
|
|
56
|
+
api_key=os.environ["OOGAM_API_KEY"],
|
|
57
|
+
settings=OogamSTTService.Settings(language=Language.HI),
|
|
58
|
+
)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Turn-taking stays with your pipeline.** Oogam's own end-of-turn detection
|
|
62
|
+
waits for 700 ms of silence. With this service you do not wait for it: when
|
|
63
|
+
Pipecat's VAD reports that the user stopped speaking, the service sends Oogam
|
|
64
|
+
`finalize` and Oogam closes the turn straight away. Smart Turn — Pipecat's
|
|
65
|
+
default — or any other turn strategy then decides whether the user's turn is
|
|
66
|
+
really over, exactly as with any other streaming STT. Set
|
|
67
|
+
`finalize_on_vad_stop=False` only if you want Oogam to decide instead.
|
|
68
|
+
|
|
69
|
+
Each transcript is final. Measured on the live engine (12 Nov 2026): with
|
|
70
|
+
`finalize` the transcript arrives about 0.33 s after the caller stops, against
|
|
71
|
+
about 0.8 s when Oogam waits out its own silence window. If a caller pauses for
|
|
72
|
+
0.8 s or more mid-sentence, Oogam closes a turn there and the rest arrives as a
|
|
73
|
+
second transcript; Pipecat's user aggregator joins them, so Smart Turn still
|
|
74
|
+
decides when the agent replies. (If Oogam ever re-transcribes a turn with
|
|
75
|
+
`replace`, the service pushes only the new words.)
|
|
76
|
+
|
|
77
|
+
**The language must be given** — Oogam does not auto-detect. To follow a caller
|
|
78
|
+
who switches language mid-call, turn on auto-switch:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
stt = OogamSTTService(
|
|
82
|
+
api_key=os.environ["OOGAM_API_KEY"],
|
|
83
|
+
settings=OogamSTTService.Settings(
|
|
84
|
+
language=Language.HI,
|
|
85
|
+
auto_switch=True,
|
|
86
|
+
candidates=[Language.HI, Language.EN_IN, Language.GU],
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@stt.event_handler("on_language_changed")
|
|
91
|
+
async def on_language_changed(service, from_code, to_code, reason):
|
|
92
|
+
# e.g. switch the TTS language to match
|
|
93
|
+
await task.queue_frame(TTSUpdateSettingsFrame(delta=OogamTTSService.Settings(language=to_code)))
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Auto-switch acts on Oogam's language hint, which comes after the turn it was
|
|
97
|
+
detected in — so the first sentence after a switch is transcribed in the old
|
|
98
|
+
language, and the switch applies from the next one. Changing `language` with
|
|
99
|
+
an `STTUpdateSettingsFrame` re-pins the live session without reconnecting.
|
|
100
|
+
|
|
101
|
+
| Setting | Default | |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `language` | `Language.HI` | A Pipecat `Language` or an Oogam short code (`hi`, `en-IN`, `bho`). |
|
|
104
|
+
| `vad_end_silence_ms` | Oogam's (700) | 700–5000. Reserved: sent to Oogam, but the current engine does not act on it. |
|
|
105
|
+
| `auto_switch` | `False` | Follow a caller who changes language. |
|
|
106
|
+
| `candidates` | any | Languages a call may switch to. |
|
|
107
|
+
| `min_confidence` | 0.6 | Hint confidence needed to switch. |
|
|
108
|
+
|
|
109
|
+
Audio is sent at 16 kHz mono PCM16; Pipecat resamples whatever your
|
|
110
|
+
transport delivers.
|
|
111
|
+
|
|
112
|
+
## Text-to-speech
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
async with aiohttp.ClientSession() as session:
|
|
116
|
+
tts = OogamTTSService(
|
|
117
|
+
api_key=os.environ["OOGAM_API_KEY"],
|
|
118
|
+
aiohttp_session=session,
|
|
119
|
+
settings=OogamTTSService.Settings(voice="aditi", language=Language.HI),
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`voice` is a preset id (`aditi`, `arjun`, …) or a cloned voice id from
|
|
124
|
+
`GET /v1/voices`. Set `language` deliberately: Oogam falls back to Hindi for a
|
|
125
|
+
code it does not know.
|
|
126
|
+
|
|
127
|
+
## Sutra
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
llm = OogamLLMService(api_key=os.environ["OOGAM_API_KEY"])
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**No function calling yet.** `/v1/chat/completions` answers a request that
|
|
134
|
+
carries `tools` with `400 tools_not_supported`, so do not register tools on
|
|
135
|
+
this service. For tool use, keep Oogam for STT and TTS and pair them with an
|
|
136
|
+
LLM service that supports tools.
|
|
137
|
+
|
|
138
|
+
## A complete agent
|
|
139
|
+
|
|
140
|
+
`examples/voice_agent.py` (in the source distribution, and on
|
|
141
|
+
[oogam.ai/integrations/pipecat](https://oogam.ai/integrations/pipecat#quickstart))
|
|
142
|
+
runs Oogam STT → Sutra → Oogam TTS with Silero VAD and Smart Turn, in the
|
|
143
|
+
browser through Pipecat's development runner:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
pip install "pipecat-ai[runner,webrtc,silero,local-smart-turn-v3]" pipecat-oogam
|
|
147
|
+
OOGAM_API_KEY=sk-setu-… python examples/voice_agent.py
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Billing
|
|
151
|
+
|
|
152
|
+
Streaming STT is billed at the speech-to-text rate per second the socket is
|
|
153
|
+
open, TTS per character as on the REST API, and Sutra per token.
|
|
154
|
+
|
|
155
|
+
## Developing
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
pip install -e ".[dev]"
|
|
159
|
+
pytest
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Tests that need a running Oogam gateway are skipped unless `OOGAM_DEV_STACK`
|
|
163
|
+
points at one.
|
|
164
|
+
|
|
165
|
+
## Support
|
|
166
|
+
|
|
167
|
+
[support@dwetlabs.com](mailto:support@dwetlabs.com) ·
|
|
168
|
+
[Streaming STT reference](https://oogam.ai/docs/streaming-stt)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# pipecat-oogam
|
|
2
|
+
|
|
3
|
+
Oogam speech services for [Pipecat](https://github.com/pipecat-ai/pipecat):
|
|
4
|
+
streaming speech-to-text, streaming text-to-speech and the Sutra conversation
|
|
5
|
+
model, for 27 Indian languages and English.
|
|
6
|
+
|
|
7
|
+
| Service | Oogam endpoint | What it does |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `OogamSTTService` | `wss://platform.oogam.ai/v1/realtime?intent=transcription` | Streaming STT. Final transcripts; finalizes the turn the moment Pipecat's VAD says the user stopped. |
|
|
10
|
+
| `OogamTTSService` | `POST /v1/audio/speech` (`stream: true`, `response_format: "pcm"`) | Streaming TTS — 24 kHz PCM played as it is generated. |
|
|
11
|
+
| `OogamLLMService` | `POST /v1/chat/completions` | Sutra (`sutra-conversation`) through Pipecat's OpenAI service. |
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install pipecat-oogam
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Requires Python 3.11+, Pipecat 1.16 or later (1.x) and an Oogam API key
|
|
18
|
+
with the NAAD product enabled. **Tested with Pipecat v1.16.0.**
|
|
19
|
+
|
|
20
|
+
Maintained by [DWET Labs](https://oogam.ai), the team behind Oogam. Integration
|
|
21
|
+
guide: [oogam.ai/integrations/pipecat](https://oogam.ai/integrations/pipecat).
|
|
22
|
+
|
|
23
|
+
## Speech-to-text
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from pipecat.transcriptions.language import Language
|
|
27
|
+
from pipecat_oogam import OogamSTTService
|
|
28
|
+
|
|
29
|
+
stt = OogamSTTService(
|
|
30
|
+
api_key=os.environ["OOGAM_API_KEY"],
|
|
31
|
+
settings=OogamSTTService.Settings(language=Language.HI),
|
|
32
|
+
)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Turn-taking stays with your pipeline.** Oogam's own end-of-turn detection
|
|
36
|
+
waits for 700 ms of silence. With this service you do not wait for it: when
|
|
37
|
+
Pipecat's VAD reports that the user stopped speaking, the service sends Oogam
|
|
38
|
+
`finalize` and Oogam closes the turn straight away. Smart Turn — Pipecat's
|
|
39
|
+
default — or any other turn strategy then decides whether the user's turn is
|
|
40
|
+
really over, exactly as with any other streaming STT. Set
|
|
41
|
+
`finalize_on_vad_stop=False` only if you want Oogam to decide instead.
|
|
42
|
+
|
|
43
|
+
Each transcript is final. Measured on the live engine (12 Nov 2026): with
|
|
44
|
+
`finalize` the transcript arrives about 0.33 s after the caller stops, against
|
|
45
|
+
about 0.8 s when Oogam waits out its own silence window. If a caller pauses for
|
|
46
|
+
0.8 s or more mid-sentence, Oogam closes a turn there and the rest arrives as a
|
|
47
|
+
second transcript; Pipecat's user aggregator joins them, so Smart Turn still
|
|
48
|
+
decides when the agent replies. (If Oogam ever re-transcribes a turn with
|
|
49
|
+
`replace`, the service pushes only the new words.)
|
|
50
|
+
|
|
51
|
+
**The language must be given** — Oogam does not auto-detect. To follow a caller
|
|
52
|
+
who switches language mid-call, turn on auto-switch:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
stt = OogamSTTService(
|
|
56
|
+
api_key=os.environ["OOGAM_API_KEY"],
|
|
57
|
+
settings=OogamSTTService.Settings(
|
|
58
|
+
language=Language.HI,
|
|
59
|
+
auto_switch=True,
|
|
60
|
+
candidates=[Language.HI, Language.EN_IN, Language.GU],
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@stt.event_handler("on_language_changed")
|
|
65
|
+
async def on_language_changed(service, from_code, to_code, reason):
|
|
66
|
+
# e.g. switch the TTS language to match
|
|
67
|
+
await task.queue_frame(TTSUpdateSettingsFrame(delta=OogamTTSService.Settings(language=to_code)))
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Auto-switch acts on Oogam's language hint, which comes after the turn it was
|
|
71
|
+
detected in — so the first sentence after a switch is transcribed in the old
|
|
72
|
+
language, and the switch applies from the next one. Changing `language` with
|
|
73
|
+
an `STTUpdateSettingsFrame` re-pins the live session without reconnecting.
|
|
74
|
+
|
|
75
|
+
| Setting | Default | |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `language` | `Language.HI` | A Pipecat `Language` or an Oogam short code (`hi`, `en-IN`, `bho`). |
|
|
78
|
+
| `vad_end_silence_ms` | Oogam's (700) | 700–5000. Reserved: sent to Oogam, but the current engine does not act on it. |
|
|
79
|
+
| `auto_switch` | `False` | Follow a caller who changes language. |
|
|
80
|
+
| `candidates` | any | Languages a call may switch to. |
|
|
81
|
+
| `min_confidence` | 0.6 | Hint confidence needed to switch. |
|
|
82
|
+
|
|
83
|
+
Audio is sent at 16 kHz mono PCM16; Pipecat resamples whatever your
|
|
84
|
+
transport delivers.
|
|
85
|
+
|
|
86
|
+
## Text-to-speech
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
async with aiohttp.ClientSession() as session:
|
|
90
|
+
tts = OogamTTSService(
|
|
91
|
+
api_key=os.environ["OOGAM_API_KEY"],
|
|
92
|
+
aiohttp_session=session,
|
|
93
|
+
settings=OogamTTSService.Settings(voice="aditi", language=Language.HI),
|
|
94
|
+
)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`voice` is a preset id (`aditi`, `arjun`, …) or a cloned voice id from
|
|
98
|
+
`GET /v1/voices`. Set `language` deliberately: Oogam falls back to Hindi for a
|
|
99
|
+
code it does not know.
|
|
100
|
+
|
|
101
|
+
## Sutra
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
llm = OogamLLMService(api_key=os.environ["OOGAM_API_KEY"])
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**No function calling yet.** `/v1/chat/completions` answers a request that
|
|
108
|
+
carries `tools` with `400 tools_not_supported`, so do not register tools on
|
|
109
|
+
this service. For tool use, keep Oogam for STT and TTS and pair them with an
|
|
110
|
+
LLM service that supports tools.
|
|
111
|
+
|
|
112
|
+
## A complete agent
|
|
113
|
+
|
|
114
|
+
`examples/voice_agent.py` (in the source distribution, and on
|
|
115
|
+
[oogam.ai/integrations/pipecat](https://oogam.ai/integrations/pipecat#quickstart))
|
|
116
|
+
runs Oogam STT → Sutra → Oogam TTS with Silero VAD and Smart Turn, in the
|
|
117
|
+
browser through Pipecat's development runner:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pip install "pipecat-ai[runner,webrtc,silero,local-smart-turn-v3]" pipecat-oogam
|
|
121
|
+
OOGAM_API_KEY=sk-setu-… python examples/voice_agent.py
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Billing
|
|
125
|
+
|
|
126
|
+
Streaming STT is billed at the speech-to-text rate per second the socket is
|
|
127
|
+
open, TTS per character as on the REST API, and Sutra per token.
|
|
128
|
+
|
|
129
|
+
## Developing
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pip install -e ".[dev]"
|
|
133
|
+
pytest
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Tests that need a running Oogam gateway are skipped unless `OOGAM_DEV_STACK`
|
|
137
|
+
points at one.
|
|
138
|
+
|
|
139
|
+
## Support
|
|
140
|
+
|
|
141
|
+
[support@dwetlabs.com](mailto:support@dwetlabs.com) ·
|
|
142
|
+
[Streaming STT reference](https://oogam.ai/docs/streaming-stt)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""A Hindi voice agent on Oogam: Oogam STT → Sutra → Oogam TTS.
|
|
2
|
+
|
|
3
|
+
Silero VAD and Smart Turn (Pipecat's default turn detector) decide when the
|
|
4
|
+
caller has finished; Oogam STT finalizes each turn the moment the VAD hears
|
|
5
|
+
the caller stop, so there is no extra wait for Oogam's own end-of-turn
|
|
6
|
+
silence. If the caller switches language, the session follows and the agent
|
|
7
|
+
answers in the new language.
|
|
8
|
+
|
|
9
|
+
pip install "pipecat-ai[runner,webrtc,silero,local-smart-turn-v3]" pipecat-oogam
|
|
10
|
+
OOGAM_API_KEY=sk-setu-… python examples/voice_agent.py
|
|
11
|
+
|
|
12
|
+
Then open the URL it prints and talk.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
import aiohttp
|
|
18
|
+
from loguru import logger
|
|
19
|
+
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
|
20
|
+
from pipecat.frames.frames import LLMRunFrame, TTSUpdateSettingsFrame
|
|
21
|
+
from pipecat.pipeline.pipeline import Pipeline
|
|
22
|
+
from pipecat.pipeline.runner import PipelineRunner
|
|
23
|
+
from pipecat.pipeline.worker import PipelineParams, PipelineWorker
|
|
24
|
+
from pipecat.processors.aggregators.llm_context import LLMContext
|
|
25
|
+
from pipecat.processors.aggregators.llm_response_universal import (
|
|
26
|
+
LLMContextAggregatorPair,
|
|
27
|
+
LLMUserAggregatorParams,
|
|
28
|
+
)
|
|
29
|
+
from pipecat.runner.types import RunnerArguments
|
|
30
|
+
from pipecat.runner.utils import create_transport
|
|
31
|
+
from pipecat.transcriptions.language import Language
|
|
32
|
+
from pipecat.transports.base_transport import TransportParams
|
|
33
|
+
|
|
34
|
+
from pipecat_oogam import OogamLLMService, OogamSTTService, OogamTTSService, to_tts_code
|
|
35
|
+
|
|
36
|
+
SYSTEM_PROMPT = (
|
|
37
|
+
"You are a helpful voice assistant for an Indian bank. Answer in the language "
|
|
38
|
+
"the caller is speaking, in one or two short spoken sentences. No lists, no "
|
|
39
|
+
"markdown — everything you write is read aloud."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def run_bot(transport, runner_args: RunnerArguments):
|
|
44
|
+
api_key = os.environ["OOGAM_API_KEY"]
|
|
45
|
+
|
|
46
|
+
async with aiohttp.ClientSession() as session:
|
|
47
|
+
stt = OogamSTTService(
|
|
48
|
+
api_key=api_key,
|
|
49
|
+
settings=OogamSTTService.Settings(
|
|
50
|
+
language=Language.HI,
|
|
51
|
+
# Follow a caller who switches between these mid-call.
|
|
52
|
+
auto_switch=True,
|
|
53
|
+
candidates=[Language.HI, Language.EN_IN, Language.GU],
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
llm = OogamLLMService(api_key=api_key)
|
|
57
|
+
tts = OogamTTSService(
|
|
58
|
+
api_key=api_key,
|
|
59
|
+
aiohttp_session=session,
|
|
60
|
+
settings=OogamTTSService.Settings(voice="aditi", language=Language.HI),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
context = LLMContext([{"role": "system", "content": SYSTEM_PROMPT}])
|
|
64
|
+
aggregators = LLMContextAggregatorPair(
|
|
65
|
+
context,
|
|
66
|
+
# Smart Turn v3 is the default stop strategy; the VAD feeds it.
|
|
67
|
+
user_params=LLMUserAggregatorParams(vad_analyzer=SileroVADAnalyzer()),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
pipeline = Pipeline(
|
|
71
|
+
[
|
|
72
|
+
transport.input(),
|
|
73
|
+
stt,
|
|
74
|
+
aggregators.user(),
|
|
75
|
+
llm,
|
|
76
|
+
tts,
|
|
77
|
+
transport.output(),
|
|
78
|
+
aggregators.assistant(),
|
|
79
|
+
]
|
|
80
|
+
)
|
|
81
|
+
worker = PipelineWorker(
|
|
82
|
+
pipeline,
|
|
83
|
+
params=PipelineParams(enable_metrics=True, enable_usage_metrics=True),
|
|
84
|
+
idle_timeout_secs=runner_args.pipeline_idle_timeout_secs,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
@stt.event_handler("on_language_changed")
|
|
88
|
+
async def on_language_changed(service, from_code, to_code, reason):
|
|
89
|
+
# Speak the language the caller switched to.
|
|
90
|
+
logger.info(f"caller switched {from_code} -> {to_code}")
|
|
91
|
+
await worker.queue_frame(
|
|
92
|
+
TTSUpdateSettingsFrame(delta=OogamTTSService.Settings(language=to_tts_code(to_code)))
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
@transport.event_handler("on_client_connected")
|
|
96
|
+
async def on_client_connected(transport, client):
|
|
97
|
+
context.add_message({"role": "user", "content": "Greet the caller briefly in Hindi."})
|
|
98
|
+
await worker.queue_frames([LLMRunFrame()])
|
|
99
|
+
|
|
100
|
+
@transport.event_handler("on_client_disconnected")
|
|
101
|
+
async def on_client_disconnected(transport, client):
|
|
102
|
+
await worker.cancel()
|
|
103
|
+
|
|
104
|
+
runner = PipelineRunner(handle_sigint=runner_args.handle_sigint)
|
|
105
|
+
await runner.run(worker)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
async def bot(runner_args: RunnerArguments):
|
|
109
|
+
transport = await create_transport(
|
|
110
|
+
runner_args,
|
|
111
|
+
{"webrtc": lambda: TransportParams(audio_in_enabled=True, audio_out_enabled=True)},
|
|
112
|
+
)
|
|
113
|
+
await run_bot(transport, runner_args)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
from pipecat.runner.run import main
|
|
118
|
+
|
|
119
|
+
main()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pipecat-oogam"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Oogam speech services for Pipecat — streaming STT with client-driven turn finalize, streaming TTS, and Sutra — for 27 Indian languages and English."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Dwet Labs", email = "support@dwetlabs.com" }]
|
|
13
|
+
keywords = ["pipecat", "oogam", "naad", "sutra", "stt", "tts", "voice-agent", "indian-languages"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Topic :: Multimedia :: Sound/Audio :: Speech",
|
|
22
|
+
]
|
|
23
|
+
# Built and tested against Pipecat 1.16. The 2.0 line has announced removals,
|
|
24
|
+
# so it is excluded until it is tested.
|
|
25
|
+
dependencies = ["pipecat-ai>=1.16,<2", "aiohttp>=3.9", "websockets>=13"]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
dev = ["pytest>=8", "pytest-asyncio>=0.23"]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://oogam.ai/integrations/pipecat"
|
|
32
|
+
Documentation = "https://oogam.ai/docs/streaming-stt"
|
|
33
|
+
|
|
34
|
+
[tool.hatch.version]
|
|
35
|
+
path = "src/pipecat_oogam/_version.py"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel]
|
|
38
|
+
packages = ["src/pipecat_oogam"]
|
|
39
|
+
|
|
40
|
+
# Public source distribution: the package, its unit tests and the example
|
|
41
|
+
# agent only. The internal Voice Lab and the live end-to-end check stay out.
|
|
42
|
+
[tool.hatch.build.targets.sdist]
|
|
43
|
+
only-include = [
|
|
44
|
+
"src",
|
|
45
|
+
"tests/test_languages.py",
|
|
46
|
+
"tests/test_llm.py",
|
|
47
|
+
"tests/test_stt.py",
|
|
48
|
+
"tests/test_tts.py",
|
|
49
|
+
"examples/voice_agent.py",
|
|
50
|
+
"README.md",
|
|
51
|
+
"CHANGELOG.md",
|
|
52
|
+
"LICENSE",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[tool.pytest.ini_options]
|
|
56
|
+
asyncio_mode = "auto"
|
|
57
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
58
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Oogam speech services for Pipecat: streaming STT, streaming TTS and Sutra."""
|
|
2
|
+
|
|
3
|
+
from pipecat_oogam._version import __version__
|
|
4
|
+
from pipecat_oogam.languages import (
|
|
5
|
+
SUPPORTED_REALTIME_CODES,
|
|
6
|
+
from_realtime_code,
|
|
7
|
+
to_realtime_code,
|
|
8
|
+
to_tts_code,
|
|
9
|
+
)
|
|
10
|
+
from pipecat_oogam.llm import OogamLLMService
|
|
11
|
+
from pipecat_oogam.stt import OogamSTTService, OogamSTTSettings
|
|
12
|
+
from pipecat_oogam.tts import OogamTTSService, OogamTTSSettings
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"__version__",
|
|
16
|
+
"OogamLLMService",
|
|
17
|
+
"OogamSTTService",
|
|
18
|
+
"OogamSTTSettings",
|
|
19
|
+
"OogamTTSService",
|
|
20
|
+
"OogamTTSSettings",
|
|
21
|
+
"SUPPORTED_REALTIME_CODES",
|
|
22
|
+
"from_realtime_code",
|
|
23
|
+
"to_realtime_code",
|
|
24
|
+
"to_tts_code",
|
|
25
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Language codes for Oogam.
|
|
2
|
+
|
|
3
|
+
Oogam uses two code systems, and the same language has a code in each:
|
|
4
|
+
|
|
5
|
+
* the realtime socket (streaming STT) takes short codes — ``hi``, ``gu``,
|
|
6
|
+
``en-IN`` — the same ones as Live talk;
|
|
7
|
+
* the REST speech endpoints (TTS) take 3-letter codes — ``HIN``, ``GUJ``,
|
|
8
|
+
``ENG``.
|
|
9
|
+
|
|
10
|
+
Indian English is ``en-IN`` on the socket and ``ENG`` for speech. Plain
|
|
11
|
+
``en`` on the socket is American English.
|
|
12
|
+
|
|
13
|
+
The table mirrors ``setu-platform/src/constants/languages.ts``. Languages that
|
|
14
|
+
Pipecat's ``Language`` enum does not have (Bhojpuri, Chhattisgarhi, Marwadi,
|
|
15
|
+
Haryanvi, Magahi, Gujarati Kathiyawadi) can be passed as their short code
|
|
16
|
+
string.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from pipecat.transcriptions.language import Language
|
|
22
|
+
|
|
23
|
+
# realtime short code -> TTS 3-letter code
|
|
24
|
+
_LANGUAGES: dict[str, str] = {
|
|
25
|
+
"hi": "HIN",
|
|
26
|
+
"en-IN": "ENG",
|
|
27
|
+
"en": "ENG",
|
|
28
|
+
"gu": "GUJ",
|
|
29
|
+
"pa": "PUN",
|
|
30
|
+
"ne": "NEP",
|
|
31
|
+
"mr": "MAR",
|
|
32
|
+
"ta": "TAM",
|
|
33
|
+
"te": "TEL",
|
|
34
|
+
"kn": "KAN",
|
|
35
|
+
"ml": "MAL",
|
|
36
|
+
"bn": "BEN",
|
|
37
|
+
"ur": "URD",
|
|
38
|
+
"or": "ODI",
|
|
39
|
+
"sd": "SND",
|
|
40
|
+
"kok": "KON",
|
|
41
|
+
"as": "ASS",
|
|
42
|
+
"bho": "BHO",
|
|
43
|
+
"cha": "CHA",
|
|
44
|
+
"mai": "MAI",
|
|
45
|
+
"doi": "DOG",
|
|
46
|
+
"mwr": "MWR",
|
|
47
|
+
"bgc": "HAR",
|
|
48
|
+
"sa": "SAN",
|
|
49
|
+
"ks": "KAS",
|
|
50
|
+
"mag": "MAG",
|
|
51
|
+
"mni": "MNI",
|
|
52
|
+
"sat": "SAT",
|
|
53
|
+
"brx": "BRX",
|
|
54
|
+
"guk": "GUK",
|
|
55
|
+
}
|
|
56
|
+
_BY_LOWER = {code.lower(): code for code in _LANGUAGES}
|
|
57
|
+
_FROM_TTS = {}
|
|
58
|
+
for _short, _tts in _LANGUAGES.items():
|
|
59
|
+
# ENG maps back to Indian English, the variant Oogam is built for.
|
|
60
|
+
_FROM_TTS.setdefault(_tts, "en-IN" if _tts == "ENG" else _short)
|
|
61
|
+
|
|
62
|
+
SUPPORTED_REALTIME_CODES = tuple(_LANGUAGES)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _raw(language: Language | str) -> str:
|
|
66
|
+
value = language.value if isinstance(language, Language) else str(language)
|
|
67
|
+
return value.strip()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def to_realtime_code(language: Language | str | None) -> str | None:
|
|
71
|
+
"""The realtime-socket code for a language, or None if Oogam does not serve it.
|
|
72
|
+
|
|
73
|
+
``Language.HI`` and ``Language.HI_IN`` → ``hi``; ``Language.EN_IN`` →
|
|
74
|
+
``en-IN``; any other English → ``en``; ``"GUJ"`` → ``gu``.
|
|
75
|
+
"""
|
|
76
|
+
if language is None:
|
|
77
|
+
return None
|
|
78
|
+
raw = _raw(language)
|
|
79
|
+
if not raw:
|
|
80
|
+
return None
|
|
81
|
+
if raw.upper() in _FROM_TTS and len(raw) == 3 and raw.isupper():
|
|
82
|
+
return _FROM_TTS[raw.upper()]
|
|
83
|
+
low = raw.lower().replace("_", "-")
|
|
84
|
+
if low in _BY_LOWER:
|
|
85
|
+
return _BY_LOWER[low]
|
|
86
|
+
base = low.split("-")[0]
|
|
87
|
+
if base == "en":
|
|
88
|
+
return "en-IN" if low == "en-in" else "en"
|
|
89
|
+
return _BY_LOWER.get(base)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def to_tts_code(language: Language | str | None) -> str | None:
|
|
93
|
+
"""The 3-letter speech code for a language, or None if Oogam does not serve it."""
|
|
94
|
+
short = to_realtime_code(language)
|
|
95
|
+
return _LANGUAGES.get(short) if short else None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def from_realtime_code(code: str | None) -> Language | None:
|
|
99
|
+
"""A Pipecat ``Language`` for a realtime code, when the enum has one."""
|
|
100
|
+
if not code:
|
|
101
|
+
return None
|
|
102
|
+
try:
|
|
103
|
+
return Language(code)
|
|
104
|
+
except ValueError:
|
|
105
|
+
pass
|
|
106
|
+
try:
|
|
107
|
+
return Language(code.split("-")[0])
|
|
108
|
+
except ValueError:
|
|
109
|
+
return None
|