livekit-plugins-volcenginee 1.3.7__tar.gz → 1.3.10__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.7
3
+ Version: 1.3.10
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,57 @@ class LLM(llm.LLM):
110
112
  ),
111
113
  )
112
114
 
115
+ self._prewarm_task: asyncio.Task[None] | None = None
116
+
117
+ @property
118
+ def model(self) -> str:
119
+ """The underlying Doubao model name (e.g. ``"doubao-1-5-lite-32k-250115"``)."""
120
+ return self._opts.model
121
+
122
+ @property
123
+ def provider(self) -> str:
124
+ return "volcengine"
125
+
126
+ def prewarm(self) -> None:
127
+ """Issue a tiny 1-token chat completion to warm the model and prime
128
+ the HTTP connection.
129
+
130
+ Doubao's first call typically pays 1–3 s of model warm-up cost; a
131
+ discardable 1-token call ahead of the first real turn hides that
132
+ cost from the per-turn TTFT — the single largest knob for first-turn
133
+ latency on this plugin. The cost is ~5–10 tokens per session start.
134
+
135
+ Mirrors the ``connection_pool.prewarm`` convention: schedule a
136
+ background task and return immediately.
137
+ """
138
+ if self._prewarm_task is not None and not self._prewarm_task.done():
139
+ return
140
+ try:
141
+ loop = asyncio.get_running_loop()
142
+ except RuntimeError:
143
+ logger.debug("llm prewarm skipped: no running event loop")
144
+ return
145
+
146
+ model = self._opts.model
147
+
148
+ async def _impl() -> None:
149
+ t0 = time.perf_counter()
150
+ try:
151
+ await self._client.chat.completions.create(
152
+ model=model,
153
+ messages=[{"role": "user", "content": "hi"}],
154
+ max_tokens=1,
155
+ stream=False,
156
+ )
157
+ logger.info(
158
+ "llm prewarm: warmup call completed in %.0f ms",
159
+ (time.perf_counter() - t0) * 1000,
160
+ )
161
+ except Exception as exc: # noqa: BLE001
162
+ logger.warning("llm prewarm failed: %s", exc)
163
+
164
+ self._prewarm_task = loop.create_task(_impl(), name="volcengine.llm.prewarm")
165
+
113
166
  def chat(
114
167
  self,
115
168
  *,
@@ -225,12 +225,61 @@ class STT(stt.STT):
225
225
 
226
226
  self._session = http_session
227
227
  self._streams = weakref.WeakSet[SpeechStream]()
228
+ self._prewarm_task: asyncio.Task[None] | None = None
228
229
 
229
230
  def _ensure_session(self) -> aiohttp.ClientSession:
230
231
  if not self._session:
231
232
  self._session = utils.http_context.http_session()
232
233
  return self._session
233
234
 
235
+ @property
236
+ def model(self) -> str:
237
+ """The underlying ASR model name (e.g. ``"bigmodel"``)."""
238
+ return self._opts.model_name
239
+
240
+ @property
241
+ def provider(self) -> str:
242
+ return "volcengine"
243
+
244
+ def prewarm(self) -> None:
245
+ """Open the STT WebSocket and send the handshake payload so the first
246
+ user turn doesn't pay the TLS + WS-upgrade + auth round-trip cost.
247
+
248
+ Mirrors the ``connection_pool.prewarm`` convention: schedule a
249
+ background task and return immediately. The framework calls this
250
+ synchronously before the session starts, so the actual WS work
251
+ happens concurrently while the user is still getting ready to
252
+ speak.
253
+ """
254
+ if self._prewarm_task is not None and not self._prewarm_task.done():
255
+ return
256
+ try:
257
+ loop = asyncio.get_running_loop()
258
+ except RuntimeError:
259
+ logger.debug("stt prewarm skipped: no running event loop")
260
+ return
261
+
262
+ async def _impl() -> None:
263
+ try:
264
+ session = self._ensure_session()
265
+ async with session.ws_connect(
266
+ self._opts.get_ws_url(),
267
+ headers=self._opts.get_ws_header(),
268
+ max_msg_size=1000000000,
269
+ ) as ws:
270
+ await ws.send_bytes(self._opts.get_ws_query_params())
271
+ # Wait briefly so we also eat the auth roundtrip; the
272
+ # server ack arrives within a few hundred ms.
273
+ try:
274
+ await asyncio.wait_for(ws.receive(), timeout=2.0)
275
+ except asyncio.TimeoutError:
276
+ pass
277
+ logger.info("stt prewarm: ws handshake completed")
278
+ except Exception as exc: # noqa: BLE001
279
+ logger.warning("stt prewarm failed: %s", exc)
280
+
281
+ self._prewarm_task = loop.create_task(_impl(), name="volcengine.stt.prewarm")
282
+
234
283
  async def _recognize_impl(
235
284
  self,
236
285
  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,72 @@ 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
+ @property
176
+ def model(self) -> str:
177
+ """The TTS model identifier, composed as ``"{resource_id}/{voice}"``.
178
+
179
+ Both pieces matter: ``resource_id`` selects the model family
180
+ (``"seed-tts-2.0"``, ``"seed-tts-1.0"``, ``"seed-icl-2.0"``, …) and
181
+ ``voice`` selects the speaker profile. The effective ``resource_id``
182
+ falls back to ``VOLCENGINE_TTS_RESOURCE_ID`` and then ``"seed-tts-2.0"``,
183
+ matching ``_TTSOptions.get_http_header``.
184
+ """
185
+ resource_id = self._opts.resource_id or os.getenv(
186
+ "VOLCENGINE_TTS_RESOURCE_ID", "seed-tts-2.0"
187
+ )
188
+ return f"{resource_id}/{self._opts.voice}"
189
+
190
+ @property
191
+ def provider(self) -> str:
192
+ return "volcengine"
193
+
194
+ def prewarm(self) -> None:
195
+ """Make a tiny HTTP synthesize call to prime the TLS connection and
196
+ warm the TTS model.
197
+
198
+ Shaves the per-call TLS + model load cost off the first turn's TTS
199
+ TTFB. The warmup text is two characters; the audio is discarded.
200
+
201
+ Mirrors the ``connection_pool.prewarm`` convention: schedule a
202
+ background task and return immediately.
203
+ """
204
+ if self._prewarm_task is not None and not self._prewarm_task.done():
205
+ return
206
+ try:
207
+ loop = asyncio.get_running_loop()
208
+ except RuntimeError:
209
+ logger.debug("tts prewarm skipped: no running event loop")
210
+ return
211
+
212
+ async def _impl() -> None:
213
+ t0 = time.perf_counter()
214
+ try:
215
+ session = self._ensure_session()
216
+ payload = self._opts.get_http_request("你好")
217
+ headers = self._opts.get_http_header()
218
+ async with session.post(
219
+ self._opts.get_http_url(),
220
+ json=payload,
221
+ timeout=aiohttp.ClientTimeout(total=30, sock_connect=10),
222
+ headers=headers,
223
+ ) as resp:
224
+ await resp.read()
225
+ logger.info(
226
+ "tts prewarm: warmup synthesize completed in %.0f ms",
227
+ (time.perf_counter() - t0) * 1000,
228
+ )
229
+ except Exception as exc: # noqa: BLE001
230
+ logger.warning("tts prewarm failed: %s", exc)
231
+
232
+ self._prewarm_task = loop.create_task(_impl(), name="volcengine.tts.prewarm")
233
+
173
234
  def synthesize(
174
235
  self, text, *, conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS
175
236
  ):
@@ -0,0 +1 @@
1
+ __version__ = "1.3.10"
@@ -1 +0,0 @@
1
- __version__ = "1.3.7"