livekit-plugins-volcenginee 1.3.2__tar.gz → 1.3.4__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.
@@ -0,0 +1,6 @@
1
+ .vscode/
2
+ .venv/
3
+ .pytest_cache/
4
+ __pycache__/
5
+ .env
6
+ .env.*
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: livekit-plugins-volcenginee
3
- Version: 1.3.2
3
+ Version: 1.3.4
4
4
  Summary: LiveKit Agent Plugins for Volcengine
5
5
  Author-email: linsanzhu <2287225730@qq.com>
6
6
  Keywords: audio,livekit,realtime,video,webrtc
@@ -18,6 +18,11 @@ Requires-Dist: numpy
18
18
  Requires-Dist: openai>=1.80.0
19
19
  Requires-Dist: osc-data~=0.3.1
20
20
  Requires-Dist: pydantic
21
+ Provides-Extra: test
22
+ Requires-Dist: aiohttp>=3.9; extra == 'test'
23
+ Requires-Dist: pyaudio>=0.2.14; extra == 'test'
24
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'test'
25
+ Requires-Dist: pytest>=7.0; extra == 'test'
21
26
  Description-Content-Type: text/markdown
22
27
 
23
28
  # livekit-plugins-volcengine
@@ -394,18 +399,20 @@ if __name__ == "__main__":
394
399
 
395
400
  ```python
396
401
  volcengine.TTS(
397
- api_key: str, # API Key(新版控制台)
398
- resource_id: str | None = None, # 默认 seed-tts-2.0
402
+ api_key: str, # API Key(新版控制台)
403
+ resource_id: str | None = None, # 默认 seed-tts-2.0
399
404
  voice: str = "zh_female_xiaohe_uranus_bigtts",
400
- speed: float = 1.0, # 已废弃,保留仅用于兼容
401
- volume: float = 1.0, # 已废弃,保留仅用于兼容
402
- pitch: float = 1.0, # 已废弃,保留仅用于兼容
403
- speech_rate: int = 0, # 语速,[-50, 100],100=2.0x,-50=0.5x
404
- loudness_rate: int = 0, # 音量,[-50, 100],100=2.0x,-50=0.5x(mix 音色暂不支持)
405
+ speed: float = 1.0, # 语速倍率(OpenAI 规范),[0.2, 3.0],1.0=正常
406
+ volume: float = 1.0, # 音量倍率(OpenAI 规范),(0.1, 3.0],1.0=正常(mix 音色暂不支持)
407
+ pitch: float = 1.0, # 已废弃,保留仅用于兼容
405
408
  sample_rate: Literal[24000, 16000, 8000] = 16000,
406
409
  )
407
410
  ```
408
411
 
412
+ > `speed` / `volume` 遵循 OpenAI 接口规范(float 倍率),请求时按
413
+ > `speech_rate = round((speed - 1.0) * 200)`(clamp 到 `[-50, 100]`)转换为火山原生
414
+ > `speech_rate` / `loudness_rate` 整型字段。
415
+
409
416
  ### TTS 资源 ID 说明
410
417
 
411
418
  - `seed-tts-2.0`:豆包语音合成模型 2.0
@@ -372,18 +372,20 @@ if __name__ == "__main__":
372
372
 
373
373
  ```python
374
374
  volcengine.TTS(
375
- api_key: str, # API Key(新版控制台)
376
- resource_id: str | None = None, # 默认 seed-tts-2.0
375
+ api_key: str, # API Key(新版控制台)
376
+ resource_id: str | None = None, # 默认 seed-tts-2.0
377
377
  voice: str = "zh_female_xiaohe_uranus_bigtts",
378
- speed: float = 1.0, # 已废弃,保留仅用于兼容
379
- volume: float = 1.0, # 已废弃,保留仅用于兼容
380
- pitch: float = 1.0, # 已废弃,保留仅用于兼容
381
- speech_rate: int = 0, # 语速,[-50, 100],100=2.0x,-50=0.5x
382
- loudness_rate: int = 0, # 音量,[-50, 100],100=2.0x,-50=0.5x(mix 音色暂不支持)
378
+ speed: float = 1.0, # 语速倍率(OpenAI 规范),[0.2, 3.0],1.0=正常
379
+ volume: float = 1.0, # 音量倍率(OpenAI 规范),(0.1, 3.0],1.0=正常(mix 音色暂不支持)
380
+ pitch: float = 1.0, # 已废弃,保留仅用于兼容
383
381
  sample_rate: Literal[24000, 16000, 8000] = 16000,
384
382
  )
385
383
  ```
386
384
 
385
+ > `speed` / `volume` 遵循 OpenAI 接口规范(float 倍率),请求时按
386
+ > `speech_rate = round((speed - 1.0) * 200)`(clamp 到 `[-50, 100]`)转换为火山原生
387
+ > `speech_rate` / `loudness_rate` 整型字段。
388
+
387
389
  ### TTS 资源 ID 说明
388
390
 
389
391
  - `seed-tts-2.0`:豆包语音合成模型 2.0
@@ -32,7 +32,18 @@ class _LLMOptions:
32
32
  parallel_tool_calls: NotGivenOr[bool]
33
33
  tool_choice: NotGivenOr[ToolChoice]
34
34
  metadata: NotGivenOr[dict[str, str]]
35
- thinking: NotGivenOr[Literal["enabled", "disabled", "auto"] | dict[str, Any]]
35
+ reasoning_effort: NotGivenOr[Literal["minimal", "low", "medium", "high", "max"]]
36
+
37
+ def __post_init__(self) -> None:
38
+ if is_given(self.reasoning_effort):
39
+ if self.reasoning_effort not in (
40
+ "minimal", "low", "medium", "high", "max",
41
+ ):
42
+ raise ValueError(
43
+ f"reasoning_effort must be one of "
44
+ f"'minimal', 'low', 'medium', 'high', 'max'; "
45
+ f"got {self.reasoning_effort!r}"
46
+ )
36
47
 
37
48
 
38
49
  class LLM(llm.LLM):
@@ -48,7 +59,9 @@ class LLM(llm.LLM):
48
59
  parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
49
60
  tool_choice: NotGivenOr[ToolChoice] = NOT_GIVEN,
50
61
  metadata: NotGivenOr[dict[str, str]] = NOT_GIVEN,
51
- thinking: NotGivenOr[Literal["enabled", "disabled", "auto"] | dict[str, Any]] = NOT_GIVEN,
62
+ reasoning_effort: NotGivenOr[
63
+ Literal["minimal", "low", "medium", "high", "max"]
64
+ ] = NOT_GIVEN,
52
65
  timeout: httpx.Timeout | None = None,
53
66
  ) -> None:
54
67
  """
@@ -58,11 +71,14 @@ class LLM(llm.LLM):
58
71
  model: The model to use for the LLM, end
59
72
  api_key: The API key to use for the LLM.
60
73
  base_url: The base URL to use for the LLM.
61
- thinking: Controls deep thinking mode. Pass ``"disabled"`` to turn
62
- it off, ``"enabled"`` to force it on, ``"auto"`` to let the
63
- model decide, or a raw ``{"type": "..."}`` dict for extended
64
- parameters. See the Volcengine Chat Completions API reference
65
- for details. Omit to use the API default (``enabled``).
74
+ reasoning_effort: Constrains the effort the model spends on
75
+ reasoning before producing a final answer. One of
76
+ ``"minimal"``, ``"low"``, ``"medium"``, ``"high"``, ``"max"``.
77
+ Mapped to Volcengine Ark's ``reasoning_effort`` field; also
78
+ accepted by the OpenAI Python SDK as a standard Chat
79
+ Completions kwarg (same name, same semantics) so it is
80
+ forwarded at the top level of the request body. Omit to use
81
+ the API default (``medium``).
66
82
  """
67
83
  super().__init__()
68
84
  self._opts = _LLMOptions(
@@ -72,7 +88,7 @@ class LLM(llm.LLM):
72
88
  parallel_tool_calls=parallel_tool_calls,
73
89
  tool_choice=tool_choice,
74
90
  metadata=metadata,
75
- thinking=thinking,
91
+ reasoning_effort=reasoning_effort,
76
92
  )
77
93
  api_key = api_key if is_given(api_key) else os.getenv("VOLCENGINE_LLM_API_KEY")
78
94
  if api_key is None:
@@ -118,12 +134,13 @@ class LLM(llm.LLM):
118
134
  if is_given(self._opts.temperature):
119
135
  extra["temperature"] = self._opts.temperature
120
136
 
121
- if is_given(self._opts.thinking):
122
- extra["thinking"] = (
123
- self._opts.thinking
124
- if isinstance(self._opts.thinking, dict)
125
- else {"type": self._opts.thinking}
126
- )
137
+ if is_given(self._opts.reasoning_effort):
138
+ # ``reasoning_effort`` is part of the OpenAI Chat Completions
139
+ # signature (added for the o1 series; the OpenAI Python SDK
140
+ # accepts it at the top level). Volcengine Ark also accepts the
141
+ # same field name on its OpenAI-compatible endpoint, so we can
142
+ # forward it as a normal kwarg without ``extra_body``.
143
+ extra["reasoning_effort"] = self._opts.reasoning_effort
127
144
 
128
145
  parallel_tool_calls = (
129
146
  parallel_tool_calls
@@ -10,7 +10,7 @@ from collections.abc import ByteString
10
10
  from typing import Literal, Tuple
11
11
 
12
12
  import aiohttp
13
- from pydantic import BaseModel, Field
13
+ from pydantic import BaseModel, ConfigDict, Field
14
14
  from osc_data.text_stream import TextStreamSentencizer
15
15
 
16
16
  from livekit.agents import (
@@ -24,18 +24,43 @@ from livekit.agents.types import DEFAULT_API_CONNECT_OPTIONS
24
24
  from .log import logger
25
25
 
26
26
 
27
+ def _speed_to_speech_rate(speed: float) -> int:
28
+ """Convert OpenAI-style ``speed`` (1.0 = normal) to Volcengine ``speech_rate``.
29
+
30
+ Mapping: ``speech_rate = round((speed - 1.0) * 200)``, clamped to ``[-50, 100]``.
31
+
32
+ 1.0 -> 0, 1.5 -> 100, 0.5 -> -50. Values outside the OpenAI-allowed range
33
+ are clamped at the same boundaries used by the underlying API.
34
+ """
35
+ rate = round((speed - 1.0) * 200)
36
+ return max(-50, min(100, rate))
37
+
38
+
39
+ def _volume_to_loudness_rate(volume: float) -> int:
40
+ """Convert OpenAI-style ``volume`` (1.0 = normal) to Volcengine ``loudness_rate``.
41
+
42
+ Same mapping as ``_speed_to_speech_rate``.
43
+ """
44
+ rate = round((volume - 1.0) * 200)
45
+ return max(-50, min(100, rate))
46
+
47
+
27
48
  class _TTSOptions(BaseModel):
49
+ # Reject unknown fields so the legacy ``speech_rate`` / ``loudness_rate``
50
+ # kwargs that were removed from the public API surface immediately.
51
+ model_config = ConfigDict(extra="forbid")
52
+
28
53
  api_key: str
29
54
  resource_id: str | None = None
30
55
  voice: str = "zh_female_xiaohe_uranus_bigtts"
31
56
  base_url: str = "https://openspeech.bytedance.com"
32
57
  sample_rate: Literal[24000, 16000, 8000] = 24000
33
58
  encoding: Literal["mp3", "pcm"] = "pcm"
59
+ # OpenAI-style primary interface; converted to Volcengine speech_rate/loudness_rate
60
+ # at request time (see _speed_to_speech_rate / _volume_to_loudness_rate).
34
61
  speed: float = Field(1.0, ge=0.2, le=3.0)
35
62
  volume: float = Field(1.0, gt=0.1, le=3.0)
36
63
  pitch: float = Field(1.0, ge=0.1, le=3.0)
37
- speech_rate: int = Field(0, ge=-50, le=100)
38
- loudness_rate: int = Field(0, ge=-50, le=100)
39
64
 
40
65
  def get_http_url(self) -> str:
41
66
  return f"{self.base_url}/api/v3/tts/unidirectional"
@@ -55,8 +80,8 @@ class _TTSOptions(BaseModel):
55
80
  "audio_params": {
56
81
  "format": self.encoding,
57
82
  "sample_rate": self.sample_rate,
58
- "loudness_rate": self.loudness_rate,
59
- "speech_rate": self.speech_rate,
83
+ "loudness_rate": _volume_to_loudness_rate(self.volume),
84
+ "speech_rate": _speed_to_speech_rate(self.speed),
60
85
  },
61
86
  "request_id": reqid,
62
87
  },
@@ -85,8 +110,6 @@ class TTS(tts.TTS):
85
110
  speed: float = 1.0,
86
111
  volume: float = 1.0,
87
112
  pitch: float = 1.0,
88
- speech_rate: int = 0,
89
- loudness_rate: int = 0,
90
113
  sample_rate: Literal[24000, 16000, 8000] = 16000,
91
114
  http_session: aiohttp.ClientSession | None = None,
92
115
  ):
@@ -96,12 +119,10 @@ class TTS(tts.TTS):
96
119
  api_key (str): the API key of the tts, you can get it from the new console.
97
120
  resource_id (str | None, optional): VolcEngine TTS resource id. Use `seed-tts-2.0` for Doubao TTS 2.0 voices, `seed-tts-1.0` / `seed-tts-1.0-concurr` for Doubao TTS 1.0 voices, `seed-icl-2.0` for voice cloning 2.0, and `seed-icl-1.0` / `seed-icl-1.0-concurr` for voice cloning 1.0. Defaults to None.
98
121
  voice (str, optional): the voice id used by the tts request. Defaults to `zh_female_xiaohe_uranus_bigtts`.
99
- speed (float, optional): kept for backwards compatibility, no longer sent to the API. Defaults to 1.0.
100
- volume (float, optional): kept for backwards compatibility, no longer sent to the API. Defaults to 1.0.
101
- pitch (float, optional): kept for backwards compatibility, no longer sent to the API. Defaults to 1.0.
102
- speech_rate (int, optional): speech rate, range [-50, 100]. 100 = 2.0x, -50 = 0.5x. Defaults to 0 (normal).
103
- loudness_rate (int, optional): volume gain, range [-50, 100]. 100 = 2.0x, -50 = 0.5x. Not supported for mix voices. Defaults to 0 (normal).
104
- sample_rate (Literal[24000, 16000, 8000], optional): the sample rate of the tts. Defaults to 24000.
122
+ speed (float, optional): speech rate multiplier (OpenAI-style). Range [0.2, 3.0]; 1.0 = normal, 2.0 = 2x, 0.5 = 0.5x. Internally converted to the Volcengine ``speech_rate`` int. Defaults to 1.0.
123
+ volume (float, optional): volume multiplier (OpenAI-style). Range (0.1, 3.0]; 1.0 = normal, 2.0 = 2x, 0.5 = 0.5x. Internally converted to the Volcengine ``loudness_rate`` int. Not supported for mix voices. Defaults to 1.0.
124
+ pitch (float, optional): kept for backwards compatibility, not currently sent to the API. Defaults to 1.0.
125
+ sample_rate (Literal[24000, 16000, 8000], optional): the sample rate of the tts. Defaults to 16000.
105
126
  streaming (bool, optional): whether to use the streaming api. Defaults to True.
106
127
  http_session (aiohttp.ClientSession | None, optional): the http session to use. Defaults to None.
107
128
  """
@@ -118,8 +139,6 @@ class TTS(tts.TTS):
118
139
  speed=speed,
119
140
  volume=volume,
120
141
  pitch=pitch,
121
- speech_rate=speech_rate,
122
- loudness_rate=loudness_rate,
123
142
  )
124
143
  self._session = http_session
125
144
  self._streams = weakref.WeakSet[SynthesizeStream]()
@@ -0,0 +1 @@
1
+ __version__ = "1.3.4"
@@ -27,6 +27,13 @@ classifiers = [
27
27
  "Programming Language :: Python :: 3 :: Only",
28
28
  ]
29
29
 
30
+ [project.optional-dependencies]
31
+ test = [
32
+ "pytest>=7.0",
33
+ "pytest-asyncio>=0.21",
34
+ "aiohttp>=3.9",
35
+ "pyaudio>=0.2.14",
36
+ ]
30
37
 
31
38
 
32
39
  [build-system]
@@ -41,3 +48,16 @@ packages = ["livekit"]
41
48
 
42
49
  [tool.hatch.build.targets.sdist]
43
50
  include = ["/livekit"]
51
+
52
+ [tool.pytest.ini_options]
53
+ asyncio_mode = "auto"
54
+ testpaths = ["tests"]
55
+ log_cli = true
56
+ log_cli_level = "INFO"
57
+ log_cli_format = "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
58
+ log_cli_date_format = "%H:%M:%S"
59
+ markers = [
60
+ "integration: marks tests that require real Volcengine API credentials (skipped automatically when keys are not set in the environment)",
61
+ "llm: marks LLM integration tests (require VOLCENGINE_LLM_API_KEY)",
62
+ "tts: marks TTS integration tests (require VOLCENGINE_TTS_API_KEY)",
63
+ ]
@@ -1,183 +0,0 @@
1
- # Byte-compiled / optimized / DLL files
2
- __pycache__/
3
- *.py[cod]
4
- *$py.class
5
-
6
- # C extensions
7
- *.so
8
-
9
- # Distribution / packaging
10
- .Python
11
- build/
12
- develop-eggs/
13
- dist/
14
- downloads/
15
- eggs/
16
- .eggs/
17
- lib/
18
- lib64/
19
- parts/
20
- sdist/
21
- var/
22
- wheels/
23
- share/python-wheels/
24
- *.egg-info/
25
- .installed.cfg
26
- *.egg
27
- MANIFEST
28
-
29
- # PyInstaller
30
- # Usually these files are written by a python script from a template
31
- # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
- *.manifest
33
- *.spec
34
-
35
- # Installer logs
36
- pip-log.txt
37
- pip-delete-this-directory.txt
38
-
39
- # Unit test / coverage reports
40
- htmlcov/
41
- .tox/
42
- .nox/
43
- .coverage
44
- .coverage.*
45
- .cache
46
- nosetests.xml
47
- coverage.xml
48
- *.cover
49
- *.py,cover
50
- .hypothesis/
51
- .pytest_cache/
52
- cover/
53
-
54
- # Translations
55
- *.mo
56
- *.pot
57
-
58
- # Django stuff:
59
- *.log
60
- local_settings.py
61
- db.sqlite3
62
- db.sqlite3-journal
63
-
64
- # Flask stuff:
65
- instance/
66
- .webassets-cache
67
-
68
- # Scrapy stuff:
69
- .scrapy
70
-
71
- # Sphinx documentation
72
- docs/_build/
73
-
74
- # PyBuilder
75
- .pybuilder/
76
- target/
77
-
78
- # Jupyter Notebook
79
- .ipynb_checkpoints
80
-
81
- # IPython
82
- profile_default/
83
- ipython_config.py
84
-
85
- # pyenv
86
- # For a library or package, you might want to ignore these files since the code is
87
- # intended to run in multiple environments; otherwise, check them in:
88
- # .python-version
89
-
90
- # pipenv
91
- # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
- # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
- # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
- # install all needed dependencies.
95
- #Pipfile.lock
96
-
97
- # UV
98
- # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
- # This is especially recommended for binary packages to ensure reproducibility, and is more
100
- # commonly ignored for libraries.
101
- #uv.lock
102
-
103
- # poetry
104
- # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
- # This is especially recommended for binary packages to ensure reproducibility, and is more
106
- # commonly ignored for libraries.
107
- # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
- #poetry.lock
109
-
110
- # pdm
111
- # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
- #pdm.lock
113
- # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
- # in version control.
115
- # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
- .pdm.toml
117
- .pdm-python
118
- .pdm-build/
119
-
120
- # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
- __pypackages__/
122
-
123
- # Celery stuff
124
- celerybeat-schedule
125
- celerybeat.pid
126
-
127
- # SageMath parsed files
128
- *.sage.py
129
-
130
- # Environments
131
- .env
132
- .venv
133
- env/
134
- venv/
135
- ENV/
136
- env.bak/
137
- venv.bak/
138
-
139
- # Spyder project settings
140
- .spyderproject
141
- .spyproject
142
-
143
- # Rope project settings
144
- .ropeproject
145
-
146
- # mkdocs documentation
147
- /site
148
-
149
- # mypy
150
- .mypy_cache/
151
- .dmypy.json
152
- dmypy.json
153
-
154
- # Pyre type checker
155
- .pyre/
156
-
157
- # pytype static type analyzer
158
- .pytype/
159
-
160
- # Cython debug symbols
161
- cython_debug/
162
-
163
- # PyCharm
164
- # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
- # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
- # and can be added to the global gitignore or merged into this file. For a more nuclear
167
- # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
- #.idea/
169
-
170
- # Ruff stuff:
171
- .ruff_cache/
172
-
173
- # PyPI configuration file
174
- .pypirc
175
-
176
- # tests
177
- test.py
178
-
179
- .python-version
180
-
181
- *.ipynb
182
- config.yaml
183
- uv.lock
@@ -1 +0,0 @@
1
- __version__ = "1.3.2"