livekit-plugins-lehui 0.0.1__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.
- livekit_plugins_lehui-0.0.1/.gitignore +222 -0
- livekit_plugins_lehui-0.0.1/PKG-INFO +33 -0
- livekit_plugins_lehui-0.0.1/README.md +24 -0
- livekit_plugins_lehui-0.0.1/livekit-plugins-aliyun/README.md +114 -0
- livekit_plugins_lehui-0.0.1/livekit-plugins-aliyun/livekit/plugins/aliyun/__init__.py +20 -0
- livekit_plugins_lehui-0.0.1/livekit-plugins-aliyun/livekit/plugins/aliyun/llm.py +209 -0
- livekit_plugins_lehui-0.0.1/livekit-plugins-aliyun/livekit/plugins/aliyun/log.py +3 -0
- livekit_plugins_lehui-0.0.1/livekit-plugins-aliyun/livekit/plugins/aliyun/py.typed +1 -0
- livekit_plugins_lehui-0.0.1/livekit-plugins-aliyun/livekit/plugins/aliyun/qwen_tts_realtime.py +599 -0
- livekit_plugins_lehui-0.0.1/livekit-plugins-aliyun/livekit/plugins/aliyun/stt.py +736 -0
- livekit_plugins_lehui-0.0.1/pyproject.toml +27 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
.DS_Store
|
|
2
|
+
|
|
3
|
+
# Byte-compiled / optimized / DLL files
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.py[codz]
|
|
6
|
+
*$py.class
|
|
7
|
+
|
|
8
|
+
# C extensions
|
|
9
|
+
*.so
|
|
10
|
+
|
|
11
|
+
# Distribution / packaging
|
|
12
|
+
.Python
|
|
13
|
+
build/
|
|
14
|
+
develop-eggs/
|
|
15
|
+
dist/
|
|
16
|
+
downloads/
|
|
17
|
+
eggs/
|
|
18
|
+
.eggs/
|
|
19
|
+
lib/
|
|
20
|
+
lib64/
|
|
21
|
+
parts/
|
|
22
|
+
sdist/
|
|
23
|
+
var/
|
|
24
|
+
wheels/
|
|
25
|
+
share/python-wheels/
|
|
26
|
+
*.egg-info/
|
|
27
|
+
.installed.cfg
|
|
28
|
+
*.egg
|
|
29
|
+
MANIFEST
|
|
30
|
+
|
|
31
|
+
# PyInstaller
|
|
32
|
+
# Usually these files are written by a python script from a template
|
|
33
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
34
|
+
*.manifest
|
|
35
|
+
*.spec
|
|
36
|
+
|
|
37
|
+
# Installer logs
|
|
38
|
+
pip-log.txt
|
|
39
|
+
pip-delete-this-directory.txt
|
|
40
|
+
|
|
41
|
+
# Unit test / coverage reports
|
|
42
|
+
htmlcov/
|
|
43
|
+
.tox/
|
|
44
|
+
.nox/
|
|
45
|
+
.coverage
|
|
46
|
+
.coverage.*
|
|
47
|
+
.cache
|
|
48
|
+
nosetests.xml
|
|
49
|
+
coverage.xml
|
|
50
|
+
*.cover
|
|
51
|
+
*.py.cover
|
|
52
|
+
*.lcov
|
|
53
|
+
.hypothesis/
|
|
54
|
+
.pytest_cache/
|
|
55
|
+
cover/
|
|
56
|
+
|
|
57
|
+
# Translations
|
|
58
|
+
*.mo
|
|
59
|
+
*.pot
|
|
60
|
+
|
|
61
|
+
# Django stuff:
|
|
62
|
+
*.log
|
|
63
|
+
local_settings.py
|
|
64
|
+
db.sqlite3
|
|
65
|
+
db.sqlite3-journal
|
|
66
|
+
|
|
67
|
+
# Flask stuff:
|
|
68
|
+
instance/
|
|
69
|
+
.webassets-cache
|
|
70
|
+
|
|
71
|
+
# Scrapy stuff:
|
|
72
|
+
.scrapy
|
|
73
|
+
|
|
74
|
+
# Sphinx documentation
|
|
75
|
+
docs/_build/
|
|
76
|
+
|
|
77
|
+
# PyBuilder
|
|
78
|
+
.pybuilder/
|
|
79
|
+
target/
|
|
80
|
+
|
|
81
|
+
# Jupyter Notebook
|
|
82
|
+
.ipynb_checkpoints
|
|
83
|
+
|
|
84
|
+
# IPython
|
|
85
|
+
profile_default/
|
|
86
|
+
ipython_config.py
|
|
87
|
+
|
|
88
|
+
# pyenv
|
|
89
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
90
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
91
|
+
# .python-version
|
|
92
|
+
|
|
93
|
+
# pipenv
|
|
94
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
95
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
96
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
97
|
+
# install all needed dependencies.
|
|
98
|
+
# Pipfile.lock
|
|
99
|
+
|
|
100
|
+
# UV
|
|
101
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
102
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
103
|
+
# commonly ignored for libraries.
|
|
104
|
+
uv.lock
|
|
105
|
+
|
|
106
|
+
# poetry
|
|
107
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
108
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
109
|
+
# commonly ignored for libraries.
|
|
110
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
111
|
+
# poetry.lock
|
|
112
|
+
# poetry.toml
|
|
113
|
+
|
|
114
|
+
# pdm
|
|
115
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
116
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
117
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
118
|
+
# pdm.lock
|
|
119
|
+
# pdm.toml
|
|
120
|
+
.pdm-python
|
|
121
|
+
.pdm-build/
|
|
122
|
+
|
|
123
|
+
# pixi
|
|
124
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
125
|
+
# pixi.lock
|
|
126
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
127
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
128
|
+
.pixi/*
|
|
129
|
+
!.pixi/config.toml
|
|
130
|
+
|
|
131
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
132
|
+
__pypackages__/
|
|
133
|
+
|
|
134
|
+
# Celery stuff
|
|
135
|
+
celerybeat-schedule*
|
|
136
|
+
celerybeat.pid
|
|
137
|
+
|
|
138
|
+
# Redis
|
|
139
|
+
*.rdb
|
|
140
|
+
*.aof
|
|
141
|
+
*.pid
|
|
142
|
+
|
|
143
|
+
# RabbitMQ
|
|
144
|
+
mnesia/
|
|
145
|
+
rabbitmq/
|
|
146
|
+
rabbitmq-data/
|
|
147
|
+
|
|
148
|
+
# ActiveMQ
|
|
149
|
+
activemq-data/
|
|
150
|
+
|
|
151
|
+
# SageMath parsed files
|
|
152
|
+
*.sage.py
|
|
153
|
+
|
|
154
|
+
# Environments
|
|
155
|
+
.env
|
|
156
|
+
.envrc
|
|
157
|
+
.venv
|
|
158
|
+
env/
|
|
159
|
+
venv/
|
|
160
|
+
ENV/
|
|
161
|
+
env.bak/
|
|
162
|
+
venv.bak/
|
|
163
|
+
|
|
164
|
+
# Spyder project settings
|
|
165
|
+
.spyderproject
|
|
166
|
+
.spyproject
|
|
167
|
+
|
|
168
|
+
# Rope project settings
|
|
169
|
+
.ropeproject
|
|
170
|
+
|
|
171
|
+
# mkdocs documentation
|
|
172
|
+
/site
|
|
173
|
+
|
|
174
|
+
# mypy
|
|
175
|
+
.mypy_cache/
|
|
176
|
+
.dmypy.json
|
|
177
|
+
dmypy.json
|
|
178
|
+
|
|
179
|
+
# Pyre type checker
|
|
180
|
+
.pyre/
|
|
181
|
+
|
|
182
|
+
# pytype static type analyzer
|
|
183
|
+
.pytype/
|
|
184
|
+
|
|
185
|
+
# Cython debug symbols
|
|
186
|
+
cython_debug/
|
|
187
|
+
|
|
188
|
+
# PyCharm
|
|
189
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
190
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
191
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
192
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
193
|
+
.idea/
|
|
194
|
+
|
|
195
|
+
# Abstra
|
|
196
|
+
# Abstra is an AI-powered process automation framework.
|
|
197
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
198
|
+
# Learn more at https://abstra.io/docs
|
|
199
|
+
.abstra/
|
|
200
|
+
|
|
201
|
+
# Visual Studio Code
|
|
202
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore that
|
|
203
|
+
# can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
204
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer, you
|
|
205
|
+
# could uncomment the following to ignore the entire vscode folder
|
|
206
|
+
.vscode/
|
|
207
|
+
# Temporary file for partial code execution
|
|
208
|
+
tempCodeRunnerFile.py
|
|
209
|
+
|
|
210
|
+
# Ruff stuff:
|
|
211
|
+
.ruff_cache/
|
|
212
|
+
|
|
213
|
+
# PyPI configuration file
|
|
214
|
+
.pypirc
|
|
215
|
+
|
|
216
|
+
# Marimo
|
|
217
|
+
marimo/_static/
|
|
218
|
+
marimo/_lsp/
|
|
219
|
+
__marimo__/
|
|
220
|
+
|
|
221
|
+
# Streamlit
|
|
222
|
+
.streamlit/secrets.toml
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: livekit-plugins-lehui
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Provider integrations for LiveKit Agents maintained by Lehui
|
|
5
|
+
Requires-Python: >=3.10.0
|
|
6
|
+
Requires-Dist: aiohttp>=3.10.0
|
|
7
|
+
Requires-Dist: livekit-agents[openai]>=1.6.2
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# Lehui plugins for LiveKit Agents
|
|
11
|
+
|
|
12
|
+
Provider integrations for LiveKit Agents maintained by Lehui.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install livekit-plugins-lehui
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Plugins
|
|
21
|
+
|
|
22
|
+
- [Aliyun](livekit-plugins-aliyun): realtime speech-to-text, text-to-speech, and
|
|
23
|
+
LLM support using DashScope.
|
|
24
|
+
|
|
25
|
+
## Publishing
|
|
26
|
+
|
|
27
|
+
Update `project.version` in `pyproject.toml`, then push `main` and a matching tag:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
git push origin main
|
|
31
|
+
git tag v<X.Y.Z>
|
|
32
|
+
git push origin v<X.Y.Z>
|
|
33
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Lehui plugins for LiveKit Agents
|
|
2
|
+
|
|
3
|
+
Provider integrations for LiveKit Agents maintained by Lehui.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install livekit-plugins-lehui
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Plugins
|
|
12
|
+
|
|
13
|
+
- [Aliyun](livekit-plugins-aliyun): realtime speech-to-text, text-to-speech, and
|
|
14
|
+
LLM support using DashScope.
|
|
15
|
+
|
|
16
|
+
## Publishing
|
|
17
|
+
|
|
18
|
+
Update `project.version` in `pyproject.toml`, then push `main` and a matching tag:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
git push origin main
|
|
22
|
+
git tag v<X.Y.Z>
|
|
23
|
+
git push origin v<X.Y.Z>
|
|
24
|
+
```
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Aliyun plugin for LiveKit Agents
|
|
2
|
+
|
|
3
|
+
Realtime speech-to-text, text-to-speech, and LLM support for LiveKit Agents using
|
|
4
|
+
Aliyun DashScope.
|
|
5
|
+
|
|
6
|
+
The plugin exposes:
|
|
7
|
+
|
|
8
|
+
- `livekit.plugins.aliyun.STT`
|
|
9
|
+
- `livekit.plugins.aliyun.QwenTTSRealtime`
|
|
10
|
+
- `livekit.plugins.aliyun.LLM`
|
|
11
|
+
|
|
12
|
+
## Pre-requisites
|
|
13
|
+
|
|
14
|
+
You'll need an API key from Aliyun. It can be set as an environment variable: `DASHSCOPE_API_KEY`
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from dotenv import load_dotenv
|
|
20
|
+
|
|
21
|
+
from livekit.agents import Agent, AgentSession
|
|
22
|
+
from livekit.plugins import aliyun
|
|
23
|
+
|
|
24
|
+
load_dotenv()
|
|
25
|
+
|
|
26
|
+
session = AgentSession(
|
|
27
|
+
llm=aliyun.LLM(),
|
|
28
|
+
stt=aliyun.STT(),
|
|
29
|
+
tts=aliyun.QwenTTSRealtime(),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
agent = Agent(instructions="Reply briefly and clearly.")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## LLM
|
|
36
|
+
|
|
37
|
+
### Common Options
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from livekit.plugins import aliyun
|
|
41
|
+
|
|
42
|
+
llm = aliyun.LLM(
|
|
43
|
+
model="qwen-plus",
|
|
44
|
+
enable_thinking=False,
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- `api_key`: DashScope API key. Overrides the environment variable.
|
|
49
|
+
- `model`: DashScope OpenAI-compatible chat model name. Defaults to `qwen-plus`.
|
|
50
|
+
- `base_url`: Override the OpenAI-compatible endpoint. Defaults to
|
|
51
|
+
`https://dashscope.aliyuncs.com/compatible-mode/v1`.
|
|
52
|
+
- `enable_thinking`: Whether to enable Qwen deep-thinking mode. Defaults to `False`.
|
|
53
|
+
- `thinking_budget`: Optional token budget for thinking mode. Requires
|
|
54
|
+
`enable_thinking=True`.
|
|
55
|
+
- `max_tokens`: Optional maximum output token count.
|
|
56
|
+
- `seed`: Optional deterministic sampling seed.
|
|
57
|
+
- `tool_choice`: Supports `auto`, `none`, and forcing a specific function tool. The
|
|
58
|
+
`required` option is not supported.
|
|
59
|
+
- `preserve_thinking`: Not supported in this version. Reasoning content is not persisted
|
|
60
|
+
or returned as ordinary text.
|
|
61
|
+
|
|
62
|
+
## STT
|
|
63
|
+
|
|
64
|
+
### Common Options
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from livekit.plugins import aliyun
|
|
68
|
+
|
|
69
|
+
stt = aliyun.STT(
|
|
70
|
+
model="qwen3-asr-flash-realtime",
|
|
71
|
+
language="zh",
|
|
72
|
+
sample_rate=16000,
|
|
73
|
+
interim_results=True,
|
|
74
|
+
)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- `api_key`: DashScope API key. Overrides the environment variable.
|
|
78
|
+
- `model`: Realtime ASR model name.
|
|
79
|
+
- `language`: Optional language code such as `zh`, `yue`, or `en`.
|
|
80
|
+
- `sample_rate`: Input sample rate. Typical value is `16000`.
|
|
81
|
+
- `interim_results`: Whether to emit interim transcripts.
|
|
82
|
+
- `turn_detection`: Server-side VAD settings. Set it to `None` to use manual mode.
|
|
83
|
+
|
|
84
|
+
### Manual Mode
|
|
85
|
+
|
|
86
|
+
To disable server-side turn detection and use manual mode:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from livekit.plugins import aliyun
|
|
90
|
+
|
|
91
|
+
stt = aliyun.STT(turn_detection=None)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Qwen TTS Realtime
|
|
95
|
+
|
|
96
|
+
### Common Options
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from livekit.plugins import aliyun
|
|
100
|
+
|
|
101
|
+
tts = aliyun.QwenTTSRealtime(
|
|
102
|
+
model="qwen3-tts-flash-realtime",
|
|
103
|
+
voice="Cherry",
|
|
104
|
+
language_type="Chinese",
|
|
105
|
+
)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- `api_key`: DashScope API key. Overrides the environment variable.
|
|
109
|
+
- `model`: Realtime TTS model name.
|
|
110
|
+
- `voice`: Realtime TTS voice name.
|
|
111
|
+
- `language_type`: One of `Auto`, `Chinese`, `English`, `German`, `Italian`,
|
|
112
|
+
`Portuguese`, `Spanish`, `Japanese`, `Korean`, `French`, or `Russian`.
|
|
113
|
+
- `base_url`: Override the websocket endpoint. For the international region, use
|
|
114
|
+
`wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime`.
|
|
@@ -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
|
+
|