livekit-plugins-lehui 0.0.1__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/aliyun/__init__.py +20 -0
- livekit/plugins/aliyun/llm.py +209 -0
- livekit/plugins/aliyun/log.py +3 -0
- livekit/plugins/aliyun/py.typed +1 -0
- livekit/plugins/aliyun/qwen_tts_realtime.py +599 -0
- livekit/plugins/aliyun/stt.py +736 -0
- livekit_plugins_lehui-0.0.1.dist-info/METADATA +33 -0
- livekit_plugins_lehui-0.0.1.dist-info/RECORD +9 -0
- livekit_plugins_lehui-0.0.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from importlib.metadata import version as distribution_version
|
|
2
|
+
|
|
3
|
+
from livekit.agents import Plugin
|
|
4
|
+
|
|
5
|
+
from .llm import LLM
|
|
6
|
+
from .log import logger
|
|
7
|
+
from .qwen_tts_realtime import QwenTTSRealtime
|
|
8
|
+
from .stt import STT
|
|
9
|
+
|
|
10
|
+
__version__ = distribution_version("livekit-plugins-lehui")
|
|
11
|
+
|
|
12
|
+
__all__ = ["STT", "QwenTTSRealtime", "LLM", "__version__"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AliyunPlugin(Plugin):
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
super().__init__(__name__, __version__, __package__, logger)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
Plugin.register_plugin(AliyunPlugin())
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
import openai
|
|
8
|
+
from openai.types.chat import completion_create_params
|
|
9
|
+
|
|
10
|
+
from livekit.agents import llm
|
|
11
|
+
from livekit.agents.llm import ToolChoice, utils as llm_utils
|
|
12
|
+
from livekit.agents.types import (
|
|
13
|
+
DEFAULT_API_CONNECT_OPTIONS,
|
|
14
|
+
NOT_GIVEN,
|
|
15
|
+
APIConnectOptions,
|
|
16
|
+
NotGivenOr,
|
|
17
|
+
)
|
|
18
|
+
from livekit.agents.utils import is_given
|
|
19
|
+
from livekit.plugins.openai.llm import LLM as OpenAILLM, LLMStream as OpenAILLMStream
|
|
20
|
+
|
|
21
|
+
DEFAULT_MODEL = "qwen-plus"
|
|
22
|
+
DEFAULT_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class LLM(OpenAILLM):
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
*,
|
|
29
|
+
model: str = DEFAULT_MODEL,
|
|
30
|
+
api_key: NotGivenOr[str] = NOT_GIVEN,
|
|
31
|
+
base_url: NotGivenOr[str] = DEFAULT_BASE_URL,
|
|
32
|
+
client: openai.AsyncClient | None = None,
|
|
33
|
+
user: NotGivenOr[str] = NOT_GIVEN,
|
|
34
|
+
temperature: NotGivenOr[float] = NOT_GIVEN,
|
|
35
|
+
top_p: NotGivenOr[float] = NOT_GIVEN,
|
|
36
|
+
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
|
|
37
|
+
tool_choice: NotGivenOr[ToolChoice] = NOT_GIVEN,
|
|
38
|
+
max_tokens: NotGivenOr[int] = NOT_GIVEN,
|
|
39
|
+
seed: NotGivenOr[int] = NOT_GIVEN,
|
|
40
|
+
enable_thinking: bool = False,
|
|
41
|
+
thinking_budget: NotGivenOr[int] = NOT_GIVEN,
|
|
42
|
+
extra_body: NotGivenOr[dict[str, Any]] = NOT_GIVEN,
|
|
43
|
+
extra_headers: NotGivenOr[dict[str, str]] = NOT_GIVEN,
|
|
44
|
+
extra_query: NotGivenOr[dict[str, str]] = NOT_GIVEN,
|
|
45
|
+
timeout: httpx.Timeout | None = None,
|
|
46
|
+
max_retries: NotGivenOr[int] = NOT_GIVEN,
|
|
47
|
+
) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Create a new instance of Aliyun DashScope LLM.
|
|
50
|
+
|
|
51
|
+
``api_key`` must be set to your DashScope API key, either using the argument or by
|
|
52
|
+
setting the ``DASHSCOPE_API_KEY`` environment variable.
|
|
53
|
+
"""
|
|
54
|
+
_validate_tool_choice(tool_choice, enable_thinking=enable_thinking)
|
|
55
|
+
merged_body = _merge_extra_body(
|
|
56
|
+
extra_body,
|
|
57
|
+
enable_thinking=enable_thinking,
|
|
58
|
+
thinking_budget=thinking_budget,
|
|
59
|
+
max_tokens=max_tokens,
|
|
60
|
+
seed=seed,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
self._enable_thinking = enable_thinking
|
|
64
|
+
self._aliyun_extra_body = merged_body
|
|
65
|
+
|
|
66
|
+
super().__init__(
|
|
67
|
+
model=model,
|
|
68
|
+
api_key=_get_api_key(api_key),
|
|
69
|
+
base_url=base_url,
|
|
70
|
+
client=client,
|
|
71
|
+
user=user,
|
|
72
|
+
temperature=temperature,
|
|
73
|
+
top_p=top_p,
|
|
74
|
+
parallel_tool_calls=parallel_tool_calls,
|
|
75
|
+
tool_choice=tool_choice,
|
|
76
|
+
extra_body=NOT_GIVEN,
|
|
77
|
+
extra_headers=extra_headers,
|
|
78
|
+
extra_query=extra_query,
|
|
79
|
+
timeout=timeout,
|
|
80
|
+
max_retries=max_retries,
|
|
81
|
+
_strict_tool_schema=False,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def model(self) -> str:
|
|
86
|
+
return self._opts.model
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def provider(self) -> str:
|
|
90
|
+
return "Aliyun"
|
|
91
|
+
|
|
92
|
+
def chat(
|
|
93
|
+
self,
|
|
94
|
+
*,
|
|
95
|
+
chat_ctx: llm.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
|
+
response_format: NotGivenOr[
|
|
101
|
+
completion_create_params.ResponseFormat | type[llm_utils.ResponseFormatT]
|
|
102
|
+
] = NOT_GIVEN,
|
|
103
|
+
extra_kwargs: NotGivenOr[dict[str, Any]] = NOT_GIVEN,
|
|
104
|
+
) -> OpenAILLMStream:
|
|
105
|
+
resolved_tool_choice = tool_choice if is_given(tool_choice) else self._opts.tool_choice
|
|
106
|
+
_validate_tool_choice(resolved_tool_choice, enable_thinking=self._enable_thinking)
|
|
107
|
+
merged_extra_kwargs = _merge_call_extra_kwargs(
|
|
108
|
+
extra_kwargs,
|
|
109
|
+
aliyun_extra_body=self._aliyun_extra_body,
|
|
110
|
+
enable_thinking=self._enable_thinking,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return super().chat(
|
|
114
|
+
chat_ctx=chat_ctx,
|
|
115
|
+
tools=tools,
|
|
116
|
+
conn_options=conn_options,
|
|
117
|
+
parallel_tool_calls=parallel_tool_calls,
|
|
118
|
+
tool_choice=tool_choice,
|
|
119
|
+
response_format=response_format,
|
|
120
|
+
extra_kwargs=merged_extra_kwargs,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _get_api_key(key: NotGivenOr[str]) -> str:
|
|
125
|
+
dashscope_api_key = key if is_given(key) else os.environ.get("DASHSCOPE_API_KEY")
|
|
126
|
+
if not dashscope_api_key:
|
|
127
|
+
raise ValueError(
|
|
128
|
+
"DASHSCOPE_API_KEY is required, either as argument or set "
|
|
129
|
+
"DASHSCOPE_API_KEY environment variable"
|
|
130
|
+
)
|
|
131
|
+
return dashscope_api_key
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _merge_extra_body(
|
|
135
|
+
extra_body: NotGivenOr[dict[str, Any]],
|
|
136
|
+
*,
|
|
137
|
+
enable_thinking: bool,
|
|
138
|
+
thinking_budget: NotGivenOr[int],
|
|
139
|
+
max_tokens: NotGivenOr[int],
|
|
140
|
+
seed: NotGivenOr[int],
|
|
141
|
+
) -> dict[str, Any]:
|
|
142
|
+
merged_body = dict(extra_body) if is_given(extra_body) else {}
|
|
143
|
+
_validate_no_preserve_thinking(merged_body)
|
|
144
|
+
|
|
145
|
+
if is_given(thinking_budget):
|
|
146
|
+
_validate_thinking_budget(thinking_budget, enable_thinking=enable_thinking)
|
|
147
|
+
merged_body["thinking_budget"] = thinking_budget
|
|
148
|
+
if is_given(max_tokens):
|
|
149
|
+
merged_body["max_tokens"] = max_tokens
|
|
150
|
+
if is_given(seed):
|
|
151
|
+
merged_body["seed"] = seed
|
|
152
|
+
|
|
153
|
+
merged_body["enable_thinking"] = enable_thinking
|
|
154
|
+
return merged_body
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _validate_tool_choice(
|
|
158
|
+
tool_choice: NotGivenOr[ToolChoice],
|
|
159
|
+
*,
|
|
160
|
+
enable_thinking: bool,
|
|
161
|
+
) -> None:
|
|
162
|
+
if not is_given(tool_choice):
|
|
163
|
+
return
|
|
164
|
+
if tool_choice == "required":
|
|
165
|
+
raise ValueError("Aliyun LLM does not support tool_choice='required'")
|
|
166
|
+
if enable_thinking and isinstance(tool_choice, dict):
|
|
167
|
+
raise ValueError(
|
|
168
|
+
"Aliyun LLM does not support forcing a specific tool when enable_thinking=True"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _merge_call_extra_kwargs(
|
|
173
|
+
extra_kwargs: NotGivenOr[dict[str, Any]],
|
|
174
|
+
*,
|
|
175
|
+
aliyun_extra_body: dict[str, Any],
|
|
176
|
+
enable_thinking: bool,
|
|
177
|
+
) -> dict[str, Any]:
|
|
178
|
+
merged_kwargs = dict(extra_kwargs) if is_given(extra_kwargs) else {}
|
|
179
|
+
per_call_body = _get_per_call_extra_body(merged_kwargs)
|
|
180
|
+
_validate_no_preserve_thinking(per_call_body)
|
|
181
|
+
|
|
182
|
+
if "thinking_budget" in per_call_body:
|
|
183
|
+
_validate_thinking_budget(per_call_body["thinking_budget"], enable_thinking=enable_thinking)
|
|
184
|
+
|
|
185
|
+
final_body = {**aliyun_extra_body, **per_call_body}
|
|
186
|
+
final_body["enable_thinking"] = enable_thinking
|
|
187
|
+
merged_kwargs["extra_body"] = final_body
|
|
188
|
+
return merged_kwargs
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _get_per_call_extra_body(extra_kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
192
|
+
extra_body = extra_kwargs.get("extra_body", NOT_GIVEN)
|
|
193
|
+
if not is_given(extra_body):
|
|
194
|
+
return {}
|
|
195
|
+
if not isinstance(extra_body, dict):
|
|
196
|
+
raise TypeError("extra_kwargs['extra_body'] must be a dict")
|
|
197
|
+
return dict(extra_body)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _validate_no_preserve_thinking(extra_body: dict[str, Any]) -> None:
|
|
201
|
+
if "preserve_thinking" in extra_body:
|
|
202
|
+
raise ValueError("Aliyun LLM does not support preserve_thinking")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _validate_thinking_budget(thinking_budget: int, *, enable_thinking: bool) -> None:
|
|
206
|
+
if not enable_thinking:
|
|
207
|
+
raise ValueError("thinking_budget requires enable_thinking=True")
|
|
208
|
+
if thinking_budget < 0:
|
|
209
|
+
raise ValueError("thinking_budget must be greater than or equal to 0")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|