Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ai_agents/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,8 @@ SARVAM_TTS_KEY=

# murf tts
MURF_TTS_KEY=

# Extension: speko_asr_python
# Extension: speko_tts_python
# Speko router API key (one key routes across STT/TTS providers)
SPEKO_API_KEY=
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@
{
"path": "../../../ten_packages/extension/murf_tts_python"
},
{
"path": "../../../ten_packages/extension/speko_asr_python"
},
{
"path": "../../../ten_packages/extension/speko_tts_python"
},
{
"path": "../../../ten_packages/extension/smallest_asr_python"
},
Expand Down
197 changes: 197 additions & 0 deletions ai_agents/agents/examples/voice-assistant/tenapp/property.json
Original file line number Diff line number Diff line change
Expand Up @@ -1779,6 +1779,203 @@
}
]
}
},
{
"name": "voice_assistant_speko",
"auto_start": false,
"graph": {
"nodes": [
{
"type": "extension",
"name": "agora_rtc",
"addon": "agora_rtc",
"extension_group": "default",
"property": {
"app_id": "${env:AGORA_APP_ID}",
"app_certificate": "${env:AGORA_APP_CERTIFICATE|}",
"channel": "ten_agent_test",
"stream_id": 1234,
"remote_stream_id": 123,
"subscribe_audio": true,
"publish_audio": true,
"publish_data": true,
"enable_agora_asr": false
}
},
{
"type": "extension",
"name": "stt",
"addon": "speko_asr_python",
"extension_group": "stt",
"property": {
"params": {
"api_key": "${env:SPEKO_API_KEY}",
"language": "en",
"sample_rate": 16000
}
}
},
{
"type": "extension",
"name": "llm",
"addon": "openai_llm2_python",
"extension_group": "chatgpt",
"property": {
"base_url": "https://api.openai.com/v1",
"api_key": "${env:OPENAI_API_KEY}",
"frequency_penalty": 0.9,
"model": "${env:OPENAI_MODEL}",
"max_tokens": 512,
"prompt": "",
"proxy_url": "${env:OPENAI_PROXY_URL|}",
"greeting": "TEN Agent connected. How can I help you today?",
"max_memory_length": 10
}
},
{
"type": "extension",
"name": "tts",
"addon": "speko_tts_python",
"extension_group": "tts",
"property": {
"params": {
"api_key": "${env:SPEKO_API_KEY}",
"model": "auto",
"language": "en"
}
}
},
{
"type": "extension",
"name": "main_control",
"addon": "main_python",
"extension_group": "control",
"property": {
"greeting": "TEN Agent connected. How can I help you today?"
}
},
{
"type": "extension",
"name": "message_collector",
"addon": "message_collector2",
"extension_group": "transcriber",
"property": {}
},
{
"type": "extension",
"name": "weatherapi_tool_python",
"addon": "weatherapi_tool_python",
"extension_group": "default",
"property": {
"api_key": "${env:WEATHERAPI_API_KEY|}"
}
},
{
"type": "extension",
"name": "streamid_adapter",
"addon": "streamid_adapter",
"property": {}
}
],
"connections": [
{
"extension": "main_control",
"cmd": [
{
"names": [
"on_user_joined",
"on_user_left"
],
"source": [
{
"extension": "agora_rtc"
}
]
},
{
"names": [
"tool_register"
],
"source": [
{
"extension": "weatherapi_tool_python"
}
]
}
],
"data": [
{
"name": "asr_result",
"source": [
{
"extension": "stt"
}
]
},
{
"name": "tts_audio_start",
"source": [
{
"extension": "tts"
}
]
},
{
"name": "tts_audio_end",
"source": [
{
"extension": "tts"
}
]
}
]
},
{
"extension": "agora_rtc",
"audio_frame": [
{
"name": "pcm_frame",
"dest": [
{
"extension": "streamid_adapter"
}
]
},
{
"name": "pcm_frame",
"source": [
{
"extension": "tts"
}
]
}
],
"data": [
{
"name": "data",
"source": [
{
"extension": "message_collector"
}
]
}
]
},
{
"extension": "streamid_adapter",
"audio_frame": [
{
"name": "pcm_frame",
"dest": [
{
"extension": "stt"
}
]
}
]
}
]
}
}
],
"log": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@
# Extensions that do not support Chinese and use Spanish as the second language
# (smallest: Chinese is region-gated on the vendor side; Spanish is
# universally enabled)
_EXTENSIONS_USE_SPANISH = {"oracle_asr_python", "smallest_asr_python"}
_EXTENSIONS_USE_SPANISH = {
"oracle_asr_python",
"smallest_asr_python",
"speko_asr_python",
}


RESULT_WAIT_TIMEOUT_SECS = 30
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ EXTENSION_NAME=${EXT_NAME:-elevenlabs_tts_python}

export PYTHONPATH=.:ten_packages/system/ten_runtime_python/lib:ten_packages/system/ten_runtime_python/interface:ten_packages/system/ten_ai_base/interface:ten_packages/extension/${EXTENSION_NAME}:$PYTHONPATH
# some tts extension does not support sample rate comparison, so we need to set the enable_sample_rate parameter
if [[ "${EXTENSION_NAME}" == "humeai_tts_python" || "${EXTENSION_NAME}" == "openai_tts_python" || "${EXTENSION_NAME}" == "openai_tts2_python" || "${EXTENSION_NAME}" == "mistral_tts_python" || "${EXTENSION_NAME}" == "ezai_tw_tts_python" || "${EXTENSION_NAME}" == "typecast_tts_python" ]]; then
if [[ "${EXTENSION_NAME}" == "humeai_tts_python" || "${EXTENSION_NAME}" == "openai_tts_python" || "${EXTENSION_NAME}" == "openai_tts2_python" || "${EXTENSION_NAME}" == "mistral_tts_python" || "${EXTENSION_NAME}" == "ezai_tw_tts_python" || "${EXTENSION_NAME}" == "typecast_tts_python" || "${EXTENSION_NAME}" == "speko_tts_python" ]]; then
export ENABLE_SAMPLE_RATE="False"
else
export ENABLE_SAMPLE_RATE="True"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Speko ASR Extension

Streaming transcription through the [Speko](https://speko.ai) model
router. Instead of pinning one STT vendor, the router benchmarks
providers per language and dials the best one for each session, with
automatic failover between providers before the first byte.

## Features

- One API key for every supported STT provider
- Benchmark-led routing by language, word error rate, cost, and latency
- Interim (partial) and final transcripts over WebSocket
- Routing controls: objective, provider allow/deny lists, price ceiling

## Configuration

| Field | Default | Description |
|---|---|---|
| `params.api_key` | `${env:SPEKO_API_KEY}` | Speko router API key (`sk_live_...`) |
| `params.language` | `en` | BCP-47 tag or bare ISO 639-1 code |
| `params.sample_rate` | `16000` | Input PCM sample rate (8000–48000) |
| `params.interim_results` | `true` | Emit partial transcripts |
| `params.objective` | key policy | `latency` \| `quality` \| `cost` \| `balanced` |
| `params.allow` | — | CSV of `provider` or `provider:model` ids |
| `params.deny` | — | CSV of `provider` or `provider:model` ids |
| `params.max_price` | — | USD per minute ceiling |

Languages currently enabled by the router: `en`, `ar`, `de`, `es`,
`fr`, `hi`, `nb`, `ta`, `te`. `GET https://api.speko.ai/v1/models`
lists the live model catalog and language set.

## Wire

`wss://api.speko.ai/v1/transcribe/stream` — one JSON config frame,
then bare little-endian 16-bit mono PCM binary frames (no WAV header).
Transcript frames come back as JSON; an `end` frame flushes and closes
the session.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import addon
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#
# This file is part of TEN Framework, an open source project.
# Licensed under the Apache License, Version 2.0.
# See the LICENSE file for more information.
#
from ten_runtime import (
Addon,
register_addon_as_extension,
TenEnv,
)


@register_addon_as_extension("speko_asr_python")
class SpekoASRExtensionAddon(Addon):
def on_create_instance(self, ten: TenEnv, addon_name: str, context) -> None:
from .extension import SpekoASRExtension

ten.log_info("on_create_instance")
ten.on_create_instance_done(SpekoASRExtension(addon_name), context)
Loading
Loading