livekit-plugins-volcenginee 1.3.0__py3-none-any.whl
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.
- livekit/plugins/volcengine/__init__.py +28 -0
- livekit/plugins/volcengine/llm.py +283 -0
- livekit/plugins/volcengine/log.py +3 -0
- livekit/plugins/volcengine/py.typed +0 -0
- livekit/plugins/volcengine/realtime.py +906 -0
- livekit/plugins/volcengine/stt.py +495 -0
- livekit/plugins/volcengine/tts.py +298 -0
- livekit/plugins/volcengine/utils.py +154 -0
- livekit/plugins/volcengine/version.py +1 -0
- livekit_plugins_volcenginee-1.3.0.dist-info/METADATA +615 -0
- livekit_plugins_volcenginee-1.3.0.dist-info/RECORD +12 -0
- livekit_plugins_volcenginee-1.3.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from .llm import LLM
|
|
2
|
+
from .stt import STT
|
|
3
|
+
from .tts import TTS
|
|
4
|
+
from .realtime import RealtimeModel
|
|
5
|
+
from .version import __version__
|
|
6
|
+
|
|
7
|
+
__all__ = ["TTS", "LLM", "STT", "RealtimeModel", "__version__"]
|
|
8
|
+
|
|
9
|
+
from livekit.agents import Plugin
|
|
10
|
+
|
|
11
|
+
from .log import logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class VolcenginePlugin(Plugin):
|
|
15
|
+
def __init__(self):
|
|
16
|
+
super().__init__(__name__, __version__, __package__, logger)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
Plugin.register_plugin(VolcenginePlugin())
|
|
20
|
+
|
|
21
|
+
# Cleanup docs of unexported modules
|
|
22
|
+
_module = dir()
|
|
23
|
+
NOT_IN_ALL = [m for m in _module if m not in __all__]
|
|
24
|
+
|
|
25
|
+
__pdoc__ = {}
|
|
26
|
+
|
|
27
|
+
for n in NOT_IN_ALL:
|
|
28
|
+
__pdoc__[n] = False
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
import openai
|
|
9
|
+
from openai.types.chat import ChatCompletionChunk, ChatCompletionToolChoiceOptionParam
|
|
10
|
+
from openai.types.chat.chat_completion_chunk import Choice
|
|
11
|
+
|
|
12
|
+
from livekit.agents import APIConnectionError, APIStatusError, APITimeoutError, llm
|
|
13
|
+
from livekit.agents.llm import ToolChoice
|
|
14
|
+
from livekit.agents.llm.chat_context import ChatContext
|
|
15
|
+
from livekit.agents.types import (
|
|
16
|
+
DEFAULT_API_CONNECT_OPTIONS,
|
|
17
|
+
NOT_GIVEN,
|
|
18
|
+
APIConnectOptions,
|
|
19
|
+
NotGivenOr,
|
|
20
|
+
)
|
|
21
|
+
from livekit.agents.utils import is_given
|
|
22
|
+
|
|
23
|
+
from .log import logger
|
|
24
|
+
from .utils import to_chat_ctx, to_fnc_ctx
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class _LLMOptions:
|
|
29
|
+
model: str
|
|
30
|
+
user: NotGivenOr[str]
|
|
31
|
+
temperature: NotGivenOr[float]
|
|
32
|
+
parallel_tool_calls: NotGivenOr[bool]
|
|
33
|
+
tool_choice: NotGivenOr[ToolChoice]
|
|
34
|
+
store: NotGivenOr[bool]
|
|
35
|
+
metadata: NotGivenOr[dict[str, str]]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class LLM(llm.LLM):
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
*,
|
|
42
|
+
model: str = "doubao-1-5-lite-32k-250115",
|
|
43
|
+
api_key: NotGivenOr[str] = NOT_GIVEN,
|
|
44
|
+
base_url: NotGivenOr[str] = "https://ark.cn-beijing.volces.com/api/v3/",
|
|
45
|
+
client: openai.AsyncClient | None = None,
|
|
46
|
+
user: NotGivenOr[str] = NOT_GIVEN,
|
|
47
|
+
temperature: NotGivenOr[float] = NOT_GIVEN,
|
|
48
|
+
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
|
|
49
|
+
tool_choice: NotGivenOr[ToolChoice] = NOT_GIVEN,
|
|
50
|
+
store: NotGivenOr[bool] = NOT_GIVEN,
|
|
51
|
+
metadata: NotGivenOr[dict[str, str]] = NOT_GIVEN,
|
|
52
|
+
timeout: httpx.Timeout | None = None,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Create a new instance of Volcengine LLM.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
model: The model to use for the LLM, end
|
|
59
|
+
api_key: The API key to use for the LLM.
|
|
60
|
+
base_url: The base URL to use for the LLM.
|
|
61
|
+
"""
|
|
62
|
+
super().__init__()
|
|
63
|
+
self._opts = _LLMOptions(
|
|
64
|
+
model=model,
|
|
65
|
+
user=user,
|
|
66
|
+
temperature=temperature,
|
|
67
|
+
parallel_tool_calls=parallel_tool_calls,
|
|
68
|
+
tool_choice=tool_choice,
|
|
69
|
+
store=store,
|
|
70
|
+
metadata=metadata,
|
|
71
|
+
)
|
|
72
|
+
api_key = api_key if is_given(api_key) else os.getenv("VOLCENGINE_LLM_API_KEY")
|
|
73
|
+
if api_key is None:
|
|
74
|
+
raise ValueError("api_key is required")
|
|
75
|
+
self._client = client or openai.AsyncClient(
|
|
76
|
+
api_key=api_key if is_given(api_key) else None,
|
|
77
|
+
base_url=base_url if is_given(base_url) else None,
|
|
78
|
+
max_retries=0,
|
|
79
|
+
http_client=httpx.AsyncClient(
|
|
80
|
+
timeout=timeout
|
|
81
|
+
if timeout
|
|
82
|
+
else httpx.Timeout(connect=15.0, read=5.0, write=5.0, pool=5.0),
|
|
83
|
+
follow_redirects=True,
|
|
84
|
+
limits=httpx.Limits(
|
|
85
|
+
max_connections=50,
|
|
86
|
+
max_keepalive_connections=50,
|
|
87
|
+
keepalive_expiry=120,
|
|
88
|
+
),
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def chat(
|
|
93
|
+
self,
|
|
94
|
+
*,
|
|
95
|
+
chat_ctx: ChatContext,
|
|
96
|
+
tools: list[llm.Tool] | None = None,
|
|
97
|
+
conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS,
|
|
98
|
+
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
|
|
99
|
+
tool_choice: NotGivenOr[ToolChoice] = NOT_GIVEN,
|
|
100
|
+
extra_kwargs: NotGivenOr[dict[str, Any]] = NOT_GIVEN,
|
|
101
|
+
) -> LLMStream:
|
|
102
|
+
extra = {}
|
|
103
|
+
|
|
104
|
+
if is_given(extra_kwargs):
|
|
105
|
+
extra.update(extra_kwargs)
|
|
106
|
+
|
|
107
|
+
if is_given(self._opts.metadata):
|
|
108
|
+
extra["metadata"] = self._opts.metadata
|
|
109
|
+
|
|
110
|
+
if is_given(self._opts.user):
|
|
111
|
+
extra["user"] = self._opts.user
|
|
112
|
+
|
|
113
|
+
parallel_tool_calls = (
|
|
114
|
+
parallel_tool_calls
|
|
115
|
+
if is_given(parallel_tool_calls)
|
|
116
|
+
else self._opts.parallel_tool_calls
|
|
117
|
+
)
|
|
118
|
+
if is_given(parallel_tool_calls):
|
|
119
|
+
extra["parallel_tool_calls"] = parallel_tool_calls
|
|
120
|
+
|
|
121
|
+
tool_choice = tool_choice if is_given(tool_choice) else self._opts.tool_choice # type: ignore
|
|
122
|
+
if is_given(tool_choice):
|
|
123
|
+
oai_tool_choice: ChatCompletionToolChoiceOptionParam
|
|
124
|
+
if isinstance(tool_choice, dict):
|
|
125
|
+
oai_tool_choice = {
|
|
126
|
+
"type": "function",
|
|
127
|
+
"function": {"name": tool_choice["function"]["name"]},
|
|
128
|
+
}
|
|
129
|
+
extra["tool_choice"] = oai_tool_choice
|
|
130
|
+
elif tool_choice in ("auto", "required", "none"):
|
|
131
|
+
oai_tool_choice = tool_choice
|
|
132
|
+
extra["tool_choice"] = oai_tool_choice
|
|
133
|
+
logger.info("llm start", extra={"model": self._opts.model})
|
|
134
|
+
return LLMStream(
|
|
135
|
+
self,
|
|
136
|
+
model=self._opts.model,
|
|
137
|
+
client=self._client,
|
|
138
|
+
chat_ctx=chat_ctx,
|
|
139
|
+
tools=tools or [],
|
|
140
|
+
conn_options=conn_options,
|
|
141
|
+
extra_kwargs=extra,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class LLMStream(llm.LLMStream):
|
|
146
|
+
def __init__(
|
|
147
|
+
self,
|
|
148
|
+
llm: LLM,
|
|
149
|
+
*,
|
|
150
|
+
model: str,
|
|
151
|
+
client: openai.AsyncClient,
|
|
152
|
+
chat_ctx: llm.ChatContext,
|
|
153
|
+
tools: list[llm.Tool],
|
|
154
|
+
conn_options: APIConnectOptions,
|
|
155
|
+
extra_kwargs: dict[str, Any],
|
|
156
|
+
) -> None:
|
|
157
|
+
super().__init__(llm, chat_ctx=chat_ctx, tools=tools, conn_options=conn_options)
|
|
158
|
+
self._model = model
|
|
159
|
+
self._client = client
|
|
160
|
+
self._llm = llm
|
|
161
|
+
self._extra_kwargs = extra_kwargs
|
|
162
|
+
|
|
163
|
+
async def _run(self) -> None:
|
|
164
|
+
# current function call that we're waiting for full completion (args are streamed)
|
|
165
|
+
# (defined inside the _run method to make sure the state is reset for each run/attempt)
|
|
166
|
+
self._oai_stream: openai.AsyncStream[ChatCompletionChunk] | None = None
|
|
167
|
+
self._tool_call_id: str | None = None
|
|
168
|
+
self._fnc_name: str | None = None
|
|
169
|
+
self._fnc_raw_arguments: str | None = None
|
|
170
|
+
self._tool_index: int | None = None
|
|
171
|
+
retryable = True
|
|
172
|
+
first_response = True
|
|
173
|
+
try:
|
|
174
|
+
stream: openai.AsyncStream[
|
|
175
|
+
ChatCompletionChunk
|
|
176
|
+
] = await self._client.chat.completions.create(
|
|
177
|
+
messages=to_chat_ctx(self._chat_ctx, id(self._llm)),
|
|
178
|
+
tools=to_fnc_ctx(self._tools) if self._tools else openai.NOT_GIVEN,
|
|
179
|
+
model=self._model,
|
|
180
|
+
stream_options={"include_usage": True},
|
|
181
|
+
stream=True,
|
|
182
|
+
**self._extra_kwargs,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
async with stream:
|
|
186
|
+
async for chunk in stream:
|
|
187
|
+
for choice in chunk.choices:
|
|
188
|
+
chat_chunk = self._parse_choice(chunk.id, choice)
|
|
189
|
+
if chat_chunk is not None:
|
|
190
|
+
retryable = False
|
|
191
|
+
self._event_ch.send_nowait(chat_chunk)
|
|
192
|
+
if first_response:
|
|
193
|
+
logger.info("llm first response")
|
|
194
|
+
first_response = False
|
|
195
|
+
|
|
196
|
+
if chunk.usage is not None:
|
|
197
|
+
retryable = False
|
|
198
|
+
usage_chunk = llm.ChatChunk(
|
|
199
|
+
id=chunk.id,
|
|
200
|
+
usage=llm.CompletionUsage(
|
|
201
|
+
completion_tokens=chunk.usage.completion_tokens,
|
|
202
|
+
prompt_tokens=chunk.usage.prompt_tokens,
|
|
203
|
+
total_tokens=chunk.usage.total_tokens,
|
|
204
|
+
),
|
|
205
|
+
)
|
|
206
|
+
self._event_ch.send_nowait(usage_chunk)
|
|
207
|
+
logger.info("llm end")
|
|
208
|
+
|
|
209
|
+
except openai.APITimeoutError:
|
|
210
|
+
raise APITimeoutError(retryable=retryable) # noqa: B904
|
|
211
|
+
except openai.APIStatusError as e:
|
|
212
|
+
raise APIStatusError( # noqa: B904
|
|
213
|
+
e.message,
|
|
214
|
+
status_code=e.status_code,
|
|
215
|
+
request_id=e.request_id,
|
|
216
|
+
body=e.body,
|
|
217
|
+
retryable=retryable,
|
|
218
|
+
)
|
|
219
|
+
except Exception as e:
|
|
220
|
+
raise APIConnectionError(retryable=retryable) from e
|
|
221
|
+
|
|
222
|
+
def _parse_choice(self, id: str, choice: Choice) -> llm.ChatChunk | None:
|
|
223
|
+
delta = choice.delta
|
|
224
|
+
|
|
225
|
+
if delta is None:
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
if delta.tool_calls:
|
|
229
|
+
for tool in delta.tool_calls:
|
|
230
|
+
if not tool.function:
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
call_chunk = None
|
|
234
|
+
if self._tool_call_id and tool.id and tool.index != self._tool_index:
|
|
235
|
+
call_chunk = llm.ChatChunk(
|
|
236
|
+
id=id,
|
|
237
|
+
delta=llm.ChoiceDelta(
|
|
238
|
+
role="assistant",
|
|
239
|
+
content=delta.content,
|
|
240
|
+
tool_calls=[
|
|
241
|
+
llm.FunctionToolCall(
|
|
242
|
+
arguments=self._fnc_raw_arguments or "",
|
|
243
|
+
name=self._fnc_name or "",
|
|
244
|
+
call_id=self._tool_call_id or "",
|
|
245
|
+
)
|
|
246
|
+
],
|
|
247
|
+
),
|
|
248
|
+
)
|
|
249
|
+
self._tool_call_id = self._fnc_name = self._fnc_raw_arguments = None
|
|
250
|
+
|
|
251
|
+
if tool.function.name:
|
|
252
|
+
self._tool_index = tool.index
|
|
253
|
+
self._tool_call_id = tool.id
|
|
254
|
+
self._fnc_name = tool.function.name
|
|
255
|
+
self._fnc_raw_arguments = tool.function.arguments or ""
|
|
256
|
+
elif tool.function.arguments:
|
|
257
|
+
self._fnc_raw_arguments += tool.function.arguments # type: ignore
|
|
258
|
+
|
|
259
|
+
if call_chunk is not None:
|
|
260
|
+
return call_chunk
|
|
261
|
+
|
|
262
|
+
if choice.finish_reason in ("tool_calls", "stop") and self._tool_call_id:
|
|
263
|
+
call_chunk = llm.ChatChunk(
|
|
264
|
+
id=id,
|
|
265
|
+
delta=llm.ChoiceDelta(
|
|
266
|
+
role="assistant",
|
|
267
|
+
content=delta.content,
|
|
268
|
+
tool_calls=[
|
|
269
|
+
llm.FunctionToolCall(
|
|
270
|
+
arguments=self._fnc_raw_arguments or "",
|
|
271
|
+
name=self._fnc_name or "",
|
|
272
|
+
call_id=self._tool_call_id or "",
|
|
273
|
+
)
|
|
274
|
+
],
|
|
275
|
+
),
|
|
276
|
+
)
|
|
277
|
+
self._tool_call_id = self._fnc_name = self._fnc_raw_arguments = None
|
|
278
|
+
return call_chunk
|
|
279
|
+
|
|
280
|
+
return llm.ChatChunk(
|
|
281
|
+
id=id,
|
|
282
|
+
delta=llm.ChoiceDelta(content=delta.content, role="assistant"),
|
|
283
|
+
)
|
|
File without changes
|