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.
@@ -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,3 @@
1
+ import logging
2
+
3
+ logger = logging.getLogger("livekit.plugins.aliyun")
@@ -0,0 +1 @@
1
+