livekit-plugins-volcenginee 1.3.6__tar.gz → 1.3.9__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: livekit-plugins-volcenginee
3
- Version: 1.3.6
3
+ Version: 1.3.9
4
4
  Summary: LiveKit Agent Plugins for Volcengine
5
5
  Author-email: linsanzhu <2287225730@qq.com>
6
6
  Keywords: audio,livekit,realtime,video,webrtc
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import asyncio
3
4
  import os
5
+ import time
4
6
  from dataclasses import dataclass
5
7
  from typing import Any, Literal
6
8
 
@@ -110,6 +112,48 @@ class LLM(llm.LLM):
110
112
  ),
111
113
  )
112
114
 
115
+ self._prewarm_task: asyncio.Task[None] | None = None
116
+
117
+ def prewarm(self) -> None:
118
+ """Issue a tiny 1-token chat completion to warm the model and prime
119
+ the HTTP connection.
120
+
121
+ Doubao's first call typically pays 1–3 s of model warm-up cost; a
122
+ discardable 1-token call ahead of the first real turn hides that
123
+ cost from the per-turn TTFT — the single largest knob for first-turn
124
+ latency on this plugin. The cost is ~5–10 tokens per session start.
125
+
126
+ Mirrors the ``connection_pool.prewarm`` convention: schedule a
127
+ background task and return immediately.
128
+ """
129
+ if self._prewarm_task is not None and not self._prewarm_task.done():
130
+ return
131
+ try:
132
+ loop = asyncio.get_running_loop()
133
+ except RuntimeError:
134
+ logger.debug("llm prewarm skipped: no running event loop")
135
+ return
136
+
137
+ model = self._opts.model
138
+
139
+ async def _impl() -> None:
140
+ t0 = time.perf_counter()
141
+ try:
142
+ await self._client.chat.completions.create(
143
+ model=model,
144
+ messages=[{"role": "user", "content": "hi"}],
145
+ max_tokens=1,
146
+ stream=False,
147
+ )
148
+ logger.info(
149
+ "llm prewarm: warmup call completed in %.0f ms",
150
+ (time.perf_counter() - t0) * 1000,
151
+ )
152
+ except Exception as exc: # noqa: BLE001
153
+ logger.warning("llm prewarm failed: %s", exc)
154
+
155
+ self._prewarm_task = loop.create_task(_impl(), name="volcengine.llm.prewarm")
156
+
113
157
  def chat(
114
158
  self,
115
159
  *,
@@ -80,7 +80,7 @@ class STTOptions:
80
80
  source_type: Literal["duration", "concurrent"] = "duration"
81
81
  resource_id: str | None = None
82
82
 
83
- base_url: str = "wss://openspeech.bytedance.com/api/v3/sauc/bigmodel"
83
+ base_url: str = "wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_async"
84
84
  format: Literal["pcm", "wav", "ogg"] = "pcm"
85
85
  sample_rate: int = 16000
86
86
  bits: int = 16
@@ -118,6 +118,10 @@ class STTOptions:
118
118
  "enable_itn": self.enable_itn,
119
119
  "enable_punc": self.enable_punc,
120
120
  "enable_ddc": self.enable_ddc,
121
+ "enable_nonstream": True,
122
+ "enable_speaker_info": True,
123
+ "ssd_version": "200",
124
+ "enable_gender_detection": True,
121
125
  "show_utterance": self.show_utterance,
122
126
  "result_type": self.result_type,
123
127
  "vad_segment_duration": self.vad_segment_duration,
@@ -221,12 +225,52 @@ class STT(stt.STT):
221
225
 
222
226
  self._session = http_session
223
227
  self._streams = weakref.WeakSet[SpeechStream]()
228
+ self._prewarm_task: asyncio.Task[None] | None = None
224
229
 
225
230
  def _ensure_session(self) -> aiohttp.ClientSession:
226
231
  if not self._session:
227
232
  self._session = utils.http_context.http_session()
228
233
  return self._session
229
234
 
235
+ def prewarm(self) -> None:
236
+ """Open the STT WebSocket and send the handshake payload so the first
237
+ user turn doesn't pay the TLS + WS-upgrade + auth round-trip cost.
238
+
239
+ Mirrors the ``connection_pool.prewarm`` convention: schedule a
240
+ background task and return immediately. The framework calls this
241
+ synchronously before the session starts, so the actual WS work
242
+ happens concurrently while the user is still getting ready to
243
+ speak.
244
+ """
245
+ if self._prewarm_task is not None and not self._prewarm_task.done():
246
+ return
247
+ try:
248
+ loop = asyncio.get_running_loop()
249
+ except RuntimeError:
250
+ logger.debug("stt prewarm skipped: no running event loop")
251
+ return
252
+
253
+ async def _impl() -> None:
254
+ try:
255
+ session = self._ensure_session()
256
+ async with session.ws_connect(
257
+ self._opts.get_ws_url(),
258
+ headers=self._opts.get_ws_header(),
259
+ max_msg_size=1000000000,
260
+ ) as ws:
261
+ await ws.send_bytes(self._opts.get_ws_query_params())
262
+ # Wait briefly so we also eat the auth roundtrip; the
263
+ # server ack arrives within a few hundred ms.
264
+ try:
265
+ await asyncio.wait_for(ws.receive(), timeout=2.0)
266
+ except asyncio.TimeoutError:
267
+ pass
268
+ logger.info("stt prewarm: ws handshake completed")
269
+ except Exception as exc: # noqa: BLE001
270
+ logger.warning("stt prewarm failed: %s", exc)
271
+
272
+ self._prewarm_task = loop.create_task(_impl(), name="volcengine.stt.prewarm")
273
+
230
274
  async def _recognize_impl(
231
275
  self,
232
276
  buffer: AudioBuffer,
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import asyncio
3
4
  import base64
4
5
  import gzip
5
6
  import json
@@ -164,12 +165,53 @@ class TTS(tts.TTS):
164
165
  self._session = http_session
165
166
  self._min_sentence_length = min_sentence_length
166
167
  self._streams = weakref.WeakSet[SynthesizeStream]()
168
+ self._prewarm_task: asyncio.Task[None] | None = None
167
169
 
168
170
  def _ensure_session(self) -> aiohttp.ClientSession:
169
171
  if self._session is None:
170
172
  self._session = utils.http_context.http_session()
171
173
  return self._session
172
174
 
175
+ def prewarm(self) -> None:
176
+ """Make a tiny HTTP synthesize call to prime the TLS connection and
177
+ warm the TTS model.
178
+
179
+ Shaves the per-call TLS + model load cost off the first turn's TTS
180
+ TTFB. The warmup text is two characters; the audio is discarded.
181
+
182
+ Mirrors the ``connection_pool.prewarm`` convention: schedule a
183
+ background task and return immediately.
184
+ """
185
+ if self._prewarm_task is not None and not self._prewarm_task.done():
186
+ return
187
+ try:
188
+ loop = asyncio.get_running_loop()
189
+ except RuntimeError:
190
+ logger.debug("tts prewarm skipped: no running event loop")
191
+ return
192
+
193
+ async def _impl() -> None:
194
+ t0 = time.perf_counter()
195
+ try:
196
+ session = self._ensure_session()
197
+ payload = self._opts.get_http_request("你好")
198
+ headers = self._opts.get_http_header()
199
+ async with session.post(
200
+ self._opts.get_http_url(),
201
+ json=payload,
202
+ timeout=aiohttp.ClientTimeout(total=30, sock_connect=10),
203
+ headers=headers,
204
+ ) as resp:
205
+ await resp.read()
206
+ logger.info(
207
+ "tts prewarm: warmup synthesize completed in %.0f ms",
208
+ (time.perf_counter() - t0) * 1000,
209
+ )
210
+ except Exception as exc: # noqa: BLE001
211
+ logger.warning("tts prewarm failed: %s", exc)
212
+
213
+ self._prewarm_task = loop.create_task(_impl(), name="volcengine.tts.prewarm")
214
+
173
215
  def synthesize(
174
216
  self, text, *, conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS
175
217
  ):
@@ -0,0 +1 @@
1
+ __version__ = "1.3.9"
@@ -1 +0,0 @@
1
- __version__ = "1.3.6"