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
56 changes: 56 additions & 0 deletions livekit-plugins/livekit-plugins-stepfun/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# StepFun plugin for LiveKit Agents

Support for [StepFun](https://platform.stepfun.ai/docs) speech recognition,
speech synthesis, and OpenAI-compatible chat models.

## Installation

```bash
pip install livekit-plugins-stepfun
# Include the OpenAI-compatible chat client:
pip install 'livekit-plugins-stepfun[llm]'
```

## Configuration

Set `STEPFUN_API_KEY`, or pass `api_key=` to the constructors. StepFun runs two
regional clusters with separate endpoints and separate credentials:

| Cluster | Endpoint | Value |
| --- | --- | --- |
| Global (default) | `api.stepfun.ai` | `"global"` |
| Mainland China | `api.stepfun.com` | `"china"` |

Select the cluster with `STEPFUN_CLUSTER` or `cluster=`. The two clusters use
separate endpoints and separate credentials, so a key from one will not work on
the other.

- `stepfun.STT()` streams recognition with server endpointing.
`recognize()` uses the batch route; `use_streaming=False` selects batch
recognition for sessions and requires local VAD through `stt.StreamAdapter`.
Streaming input is intended for paced conversational audio, not file uploads;
use `recognize()` for recorded audio. At end of input, server-VAD streams send
endpointing silence and then a best-effort `input_audio_buffer.commit` (the
gateway currently ignores it with server VAD), and keep receiving: while a
turn is pending this waits up to `conn_options.timeout` and fails on expiry,
otherwise it ends after a short quiet period. The API has no acknowledgement
that trailing silence was processed, so completion is best-effort: it succeeds
only after a final was delivered, and otherwise reports that completion cannot
be confirmed. `aclose()` cancels the stream without waiting or draining it.
A connection lost after any audio was consumed, or while a turn is still
pending, is reported as non-retryable: the SDK cannot replay that audio, and
guessing which part the server finalized would risk dropping or repeating
speech. Reliable recovery needs a server signal that identifies what a final
covers; until then, recorded audio belongs on the batch `recognize()` route.
- `stepfun.TTS()` supports streaming and one-shot synthesis. Discover voice IDs
using `await tts.list_voices()`. One-shot synthesis streams PCM over SSE, so
audio starts arriving before the whole request is synthesized; inputs longer
than 1,000 characters are split into sequential requests and emitted as one
audio segment. Each request has an idle-read timeout plus a total deadline
that scales with its text length.
- `stepfun.LLM()` uses the optional OpenAI plugin for chat and tool calling.

Pass `language="zh"` for Chinese; the speech services default to English.
Streaming recognition defaults to `stepaudio-2.5-asr-stream`, batch recognition
to `stepaudio-3-asr-max`, synthesis to `stepaudio-3-tts`, and chat to
`step-3.7-flash`. Custom model IDs are accepted.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""LiveKit Agents plugin for StepFun (阶跃星辰) ASR, TTS and chat models."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from livekit.agents import Plugin

from .log import logger
from .models import (
Cluster,
Language,
LLMModels,
ReasoningEffort,
STTBatchModels,
STTModels,
TTSModels,
TTSVoices,
)
from .stt import STT, RecognizeStream, SpeechStream
from .tts import TTS, ChunkedStream, SynthesizeStream
from .version import __version__

# Register the optional OpenAI plugin on the importing thread, before jobs start.
try:
from .llm import LLM

_LLM_IMPORT_ERROR: ImportError | None = None
except ImportError as exc:
_LLM_IMPORT_ERROR = exc

if TYPE_CHECKING:
from .llm import LLM

__all__ = [
"LLM",
"STT",
"TTS",
"ChunkedStream",
"Cluster",
"Language",
"LLMModels",
"ReasoningEffort",
"RecognizeStream",
"STTBatchModels",
"STTModels",
"SpeechStream",
"SynthesizeStream",
"TTSModels",
"TTSVoices",
"__version__",
]


def __getattr__(name: str) -> Any:
"""Explain a missing `llm` extra at access time.

Only reached when the eager import above failed, since a successful one
binds LLM as a module global and module __getattr__ is never consulted for
names that already exist.
"""
if name == "LLM":
assert _LLM_IMPORT_ERROR is not None
raise _LLM_IMPORT_ERROR
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


class StepFunPlugin(Plugin):
def __init__(self) -> None:
super().__init__(__name__, __version__, __package__, logger)


Plugin.register_plugin(StepFunPlugin())

# Hide symbols the doc generator would otherwise pick up
_module = dir()
NOT_IN_ALL = [m for m in _module if m not in __all__]

__pdoc__ = {}
for _n in NOT_IN_ALL:
__pdoc__[_n] = False
Loading
Loading