livekit-plugins-volcenginee 1.3.4__tar.gz → 1.3.6__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.
Files changed (15) hide show
  1. {livekit_plugins_volcenginee-1.3.4 → livekit_plugins_volcenginee-1.3.6}/.gitignore +2 -0
  2. {livekit_plugins_volcenginee-1.3.4 → livekit_plugins_volcenginee-1.3.6}/PKG-INFO +1 -1
  3. {livekit_plugins_volcenginee-1.3.4 → livekit_plugins_volcenginee-1.3.6}/livekit/plugins/volcengine/llm.py +2 -2
  4. {livekit_plugins_volcenginee-1.3.4 → livekit_plugins_volcenginee-1.3.6}/livekit/plugins/volcengine/realtime.py +6 -3
  5. {livekit_plugins_volcenginee-1.3.4 → livekit_plugins_volcenginee-1.3.6}/livekit/plugins/volcengine/stt.py +9 -2
  6. {livekit_plugins_volcenginee-1.3.4 → livekit_plugins_volcenginee-1.3.6}/livekit/plugins/volcengine/tts.py +31 -4
  7. livekit_plugins_volcenginee-1.3.6/livekit/plugins/volcengine/utils.py +25 -0
  8. livekit_plugins_volcenginee-1.3.6/livekit/plugins/volcengine/version.py +1 -0
  9. livekit_plugins_volcenginee-1.3.4/livekit/plugins/volcengine/utils.py +0 -154
  10. livekit_plugins_volcenginee-1.3.4/livekit/plugins/volcengine/version.py +0 -1
  11. {livekit_plugins_volcenginee-1.3.4 → livekit_plugins_volcenginee-1.3.6}/README.md +0 -0
  12. {livekit_plugins_volcenginee-1.3.4 → livekit_plugins_volcenginee-1.3.6}/livekit/plugins/volcengine/__init__.py +0 -0
  13. {livekit_plugins_volcenginee-1.3.4 → livekit_plugins_volcenginee-1.3.6}/livekit/plugins/volcengine/log.py +0 -0
  14. {livekit_plugins_volcenginee-1.3.4 → livekit_plugins_volcenginee-1.3.6}/livekit/plugins/volcengine/py.typed +0 -0
  15. {livekit_plugins_volcenginee-1.3.4 → livekit_plugins_volcenginee-1.3.6}/pyproject.toml +0 -0
@@ -4,3 +4,5 @@
4
4
  __pycache__/
5
5
  .env
6
6
  .env.*
7
+ dist/
8
+ latency_agent.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: livekit-plugins-volcenginee
3
- Version: 1.3.4
3
+ Version: 1.3.6
4
4
  Summary: LiveKit Agent Plugins for Volcengine
5
5
  Author-email: linsanzhu <2287225730@qq.com>
6
6
  Keywords: audio,livekit,realtime,video,webrtc
@@ -21,7 +21,7 @@ from livekit.agents.types import (
21
21
  from livekit.agents.utils import is_given
22
22
 
23
23
  from .log import logger
24
- from .utils import to_chat_ctx, to_fnc_ctx
24
+ from .utils import to_fnc_ctx
25
25
 
26
26
 
27
27
  @dataclass
@@ -206,7 +206,7 @@ class LLMStream(llm.LLMStream):
206
206
  stream: openai.AsyncStream[
207
207
  ChatCompletionChunk
208
208
  ] = await self._client.chat.completions.create(
209
- messages=to_chat_ctx(self._chat_ctx, id(self._llm)),
209
+ messages=self._chat_ctx.to_provider_format(format="openai")[0],
210
210
  tools=to_fnc_ctx(self._tools) if self._tools else openai.NOT_GIVEN,
211
211
  model=self._model,
212
212
  stream_options={"include_usage": True},
@@ -259,7 +259,7 @@ class RealtimeModel(llm.RealtimeModel):
259
259
  speaking_style: str = "你的说话风格简洁明了,语速适中,语调自然。",
260
260
  speaker: str = "zh_female_vv_jupiter_bigtts",
261
261
  opening: str | None = None,
262
- api_key: str | None = None,
262
+ api_key: NotGivenOr[str] = NOT_GIVEN,
263
263
  system_role: str | None = None,
264
264
  character_manifest: str = None,
265
265
  model: Literal["O", "SC"] = "O",
@@ -295,9 +295,12 @@ class RealtimeModel(llm.RealtimeModel):
295
295
  logger.info(
296
296
  f"Volc Websearch No Result Message: {volc_websearch_no_result_message}"
297
297
  )
298
- api_key = api_key or os.environ.get("VOLCENGINE_REALTIME_API_KEY")
298
+ api_key = api_key if utils.is_given(api_key) else os.environ.get("VOLCENGINE_REALTIME_API_KEY")
299
299
  if api_key is None:
300
- raise ValueError("VOLCENGINE_REALTIME_API_KEY is required")
300
+ raise ValueError(
301
+ "api_key is required (pass it explicitly or set the "
302
+ "VOLCENGINE_REALTIME_API_KEY environment variable)"
303
+ )
301
304
  self._opts = _RealtimeOptions(
302
305
  api_key=api_key,
303
306
  bot_name=bot_name,
@@ -22,7 +22,7 @@ from livekit.agents.types import (
22
22
  NOT_GIVEN,
23
23
  NotGivenOr,
24
24
  )
25
- from livekit.agents.utils import AudioBuffer
25
+ from livekit.agents.utils import AudioBuffer, is_given
26
26
 
27
27
  from .log import logger
28
28
 
@@ -180,7 +180,7 @@ class STT(stt.STT):
180
180
  def __init__(
181
181
  self,
182
182
  *,
183
- api_key: str | None = None,
183
+ api_key: NotGivenOr[str] = NOT_GIVEN,
184
184
  base_url: str = "wss://openspeech.bytedance.com/api/v3/sauc/bigmodel",
185
185
  resource_id: str | None = None,
186
186
  model_name: str = "bigmodel",
@@ -199,6 +199,13 @@ class STT(stt.STT):
199
199
  )
200
200
  )
201
201
 
202
+ api_key = api_key if is_given(api_key) else os.getenv("VOLCENGINE_STT_API_KEY")
203
+ if api_key is None:
204
+ raise ValueError(
205
+ "api_key is required (pass it explicitly or set the "
206
+ "VOLCENGINE_STT_API_KEY environment variable)"
207
+ )
208
+
202
209
  self._opts = STTOptions(
203
210
  base_url=base_url,
204
211
  api_key=api_key,
@@ -19,7 +19,12 @@ from livekit.agents import (
19
19
  tts,
20
20
  utils,
21
21
  )
22
- from livekit.agents.types import DEFAULT_API_CONNECT_OPTIONS
22
+ from livekit.agents.types import (
23
+ DEFAULT_API_CONNECT_OPTIONS,
24
+ NOT_GIVEN,
25
+ NotGivenOr,
26
+ )
27
+ from livekit.agents.utils import is_given
23
28
 
24
29
  from .log import logger
25
30
 
@@ -104,28 +109,44 @@ class _TTSOptions(BaseModel):
104
109
  class TTS(tts.TTS):
105
110
  def __init__(
106
111
  self,
107
- api_key: str,
112
+ *,
113
+ api_key: NotGivenOr[str] = NOT_GIVEN,
108
114
  resource_id: str | None = None,
109
115
  voice: str = "zh_female_xiaohe_uranus_bigtts",
110
116
  speed: float = 1.0,
111
117
  volume: float = 1.0,
112
118
  pitch: float = 1.0,
119
+ min_sentence_length: int = 10,
113
120
  sample_rate: Literal[24000, 16000, 8000] = 16000,
114
121
  http_session: aiohttp.ClientSession | None = None,
115
122
  ):
116
123
  """VolcEngine TTS
117
124
 
118
125
  Args:
119
- api_key (str): the API key of the tts, you can get it from the new console.
126
+ api_key (NotGivenOr[str], optional): the API key of the tts, you can get it from the new console.
127
+ Falls back to the ``VOLCENGINE_TTS_API_KEY`` environment variable when not provided.
120
128
  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.
121
129
  voice (str, optional): the voice id used by the tts request. Defaults to `zh_female_xiaohe_uranus_bigtts`.
122
130
  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
131
  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
132
  pitch (float, optional): kept for backwards compatibility, not currently sent to the API. Defaults to 1.0.
133
+ min_sentence_length (int, optional): minimum number of characters
134
+ (Chinese chars / tokens) the internal ``TextStreamSentencizer``
135
+ must accumulate before yielding a sentence to TTS. Lower
136
+ values trade off TTS efficiency (more / shorter requests)
137
+ against latency — useful for short-reply agents where the
138
+ model often returns <10 chars. Defaults to 10 (the upstream
139
+ default; matches the legacy behavior).
125
140
  sample_rate (Literal[24000, 16000, 8000], optional): the sample rate of the tts. Defaults to 16000.
126
141
  streaming (bool, optional): whether to use the streaming api. Defaults to True.
127
142
  http_session (aiohttp.ClientSession | None, optional): the http session to use. Defaults to None.
128
143
  """
144
+ api_key = api_key if is_given(api_key) else os.getenv("VOLCENGINE_TTS_API_KEY")
145
+ if api_key is None:
146
+ raise ValueError(
147
+ "api_key is required (pass it explicitly or set the "
148
+ "VOLCENGINE_TTS_API_KEY environment variable)"
149
+ )
129
150
  super().__init__(
130
151
  capabilities=tts.TTSCapabilities(streaming=True),
131
152
  sample_rate=sample_rate,
@@ -141,6 +162,7 @@ class TTS(tts.TTS):
141
162
  pitch=pitch,
142
163
  )
143
164
  self._session = http_session
165
+ self._min_sentence_length = min_sentence_length
144
166
  self._streams = weakref.WeakSet[SynthesizeStream]()
145
167
 
146
168
  def _ensure_session(self) -> aiohttp.ClientSession:
@@ -159,6 +181,7 @@ class TTS(tts.TTS):
159
181
  conn_options=conn_options,
160
182
  opts=self._opts,
161
183
  session=self._ensure_session(),
184
+ min_sentence_length=self._min_sentence_length,
162
185
  )
163
186
  self._streams.add(stream)
164
187
  return stream
@@ -178,15 +201,19 @@ class SynthesizeStream(tts.SynthesizeStream):
178
201
  session: aiohttp.ClientSession,
179
202
  tts: TTS,
180
203
  conn_options=None,
204
+ min_sentence_length: int = 10,
181
205
  ):
182
206
  super().__init__(tts=tts, conn_options=conn_options)
183
207
  self._opts: _TTSOptions = opts
184
208
  self._session = session
209
+ self._min_sentence_length = min_sentence_length
185
210
 
186
211
  async def _run(self, emitter: tts.AudioEmitter):
187
212
  request_id = utils.shortuuid()
188
213
 
189
- sentence_splitter = TextStreamSentencizer()
214
+ sentence_splitter = TextStreamSentencizer(
215
+ min_sentence_length=self._min_sentence_length
216
+ )
190
217
  emitter.initialize(
191
218
  request_id=request_id,
192
219
  sample_rate=self._opts.sample_rate,
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from collections.abc import Sequence
5
+ from typing import Awaitable, Callable, Union, cast
6
+
7
+ from livekit.agents import llm
8
+ from openai.types.chat import ChatCompletionToolParam
9
+
10
+ AsyncAzureADTokenProvider = Callable[[], Union[str, Awaitable[str]]]
11
+
12
+
13
+ def get_base_url(base_url: str | None) -> str:
14
+ if not base_url:
15
+ base_url = os.getenv(
16
+ "VOLCENGINE_LLM_BASE_URL", "https://ark.cn-beijing.volces.com/api/v3"
17
+ )
18
+ return base_url
19
+
20
+
21
+ def to_fnc_ctx(fnc_ctx: Sequence[llm.Tool]) -> list[ChatCompletionToolParam]:
22
+ return cast(
23
+ list[ChatCompletionToolParam],
24
+ llm.ToolContext(fnc_ctx).parse_function_tools("openai"),
25
+ )
@@ -0,0 +1 @@
1
+ __version__ = "1.3.6"
@@ -1,154 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import base64
4
- import os
5
- from collections import OrderedDict
6
- from collections.abc import Awaitable, Sequence
7
- from typing import Any, Callable, Union, cast
8
-
9
- from openai.types.chat import (
10
- ChatCompletionContentPartParam,
11
- ChatCompletionMessageParam,
12
- ChatCompletionToolParam,
13
- )
14
-
15
- from livekit.agents import llm
16
-
17
- AsyncAzureADTokenProvider = Callable[[], Union[str, Awaitable[str]]]
18
-
19
-
20
- def get_base_url(base_url: str | None) -> str:
21
- if not base_url:
22
- base_url = os.getenv(
23
- "VOLCENGINE_LLM_BASE_URL", "https://ark.cn-beijing.volces.com/api/v3"
24
- )
25
- return base_url
26
-
27
-
28
- def to_fnc_ctx(fnc_ctx: Sequence[llm.Tool]) -> list[ChatCompletionToolParam]:
29
- return cast(
30
- list[ChatCompletionToolParam],
31
- llm.ToolContext(fnc_ctx).parse_function_tools("openai"),
32
- )
33
-
34
-
35
- def to_chat_ctx(
36
- chat_ctx: llm.ChatContext, cache_key: Any
37
- ) -> list[ChatCompletionMessageParam]:
38
- # group the message and function_calls
39
- item_groups: dict[str, list[llm.ChatItem]] = OrderedDict()
40
- for item in chat_ctx.items:
41
- if (
42
- item.type == "message" and item.role == "assistant"
43
- ) or item.type == "function_call":
44
- group_id = item.id.split("/")[0]
45
- if group_id not in item_groups:
46
- item_groups[group_id] = []
47
- item_groups[group_id].append(item)
48
- else:
49
- item_groups[item.id] = [item]
50
-
51
- return [_group_to_chat_item(items, cache_key) for items in item_groups.values()]
52
-
53
-
54
- def _group_to_chat_item(
55
- items: list[llm.ChatItem], cache_key: Any
56
- ) -> ChatCompletionMessageParam:
57
- if len(items) == 1:
58
- return _to_chat_item(items[0], cache_key)
59
- else:
60
- msg = {"role": "assistant", "tool_calls": []}
61
- for item in items:
62
- if item.type == "message":
63
- assert item.role == "assistant", (
64
- "only assistant messages can be grouped"
65
- )
66
- assert "content" not in msg, (
67
- "only one assistant message is allowed in a group"
68
- )
69
-
70
- msg.update(_to_chat_item(item, cache_key))
71
- elif item.type == "function_call":
72
- msg["tool_calls"].append(
73
- {
74
- "id": item.call_id,
75
- "type": "function",
76
- "function": {"name": item.name, "arguments": item.arguments},
77
- }
78
- )
79
- return msg
80
-
81
-
82
- def _to_chat_item(msg: llm.ChatItem, cache_key: Any) -> ChatCompletionMessageParam:
83
- if msg.type == "message":
84
- list_content: list[ChatCompletionContentPartParam] = []
85
- text_content = ""
86
- for content in msg.content:
87
- if isinstance(content, str):
88
- if text_content:
89
- text_content += "\n"
90
- text_content += content
91
- elif isinstance(content, llm.ImageContent):
92
- list_content.append(_to_image_content(content, cache_key))
93
-
94
- if not list_content:
95
- # certain providers require text-only content in a string vs a list.
96
- # for max-compatibility, we will combine all text content into a single string.
97
- return {
98
- "role": msg.role, # type: ignore
99
- "content": text_content,
100
- }
101
-
102
- if text_content:
103
- list_content.append({"type": "text", "text": text_content})
104
-
105
- return {
106
- "role": msg.role, # type: ignore
107
- "content": list_content,
108
- }
109
-
110
- elif msg.type == "function_call":
111
- return {
112
- "role": "assistant",
113
- "tool_calls": [
114
- {
115
- "id": msg.call_id,
116
- "type": "function",
117
- "function": {
118
- "name": msg.name,
119
- "arguments": msg.arguments,
120
- },
121
- }
122
- ],
123
- }
124
-
125
- elif msg.type == "function_call_output":
126
- return {
127
- "role": "tool",
128
- "tool_call_id": msg.call_id,
129
- "content": msg.output,
130
- }
131
-
132
-
133
- def _to_image_content(
134
- image: llm.ImageContent, cache_key: Any
135
- ) -> ChatCompletionContentPartParam:
136
- img = llm.utils.serialize_image(image)
137
- if img.external_url:
138
- return {
139
- "type": "image_url",
140
- "image_url": {
141
- "url": img.external_url,
142
- "detail": img.inference_detail,
143
- },
144
- }
145
- if cache_key not in image._cache:
146
- image._cache[cache_key] = img.data_bytes
147
- b64_data = base64.b64encode(image._cache[cache_key]).decode("utf-8")
148
- return {
149
- "type": "image_url",
150
- "image_url": {
151
- "url": f"data:{img.mime_type};base64,{b64_data}",
152
- "detail": img.inference_detail,
153
- },
154
- }
@@ -1 +0,0 @@
1
- __version__ = "1.3.4"