liteai-sdk 0.3.11__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.
- liteai_sdk-0.3.11/LICENSE +21 -0
- liteai_sdk-0.3.11/PKG-INFO +100 -0
- liteai_sdk-0.3.11/README.md +71 -0
- liteai_sdk-0.3.11/pyproject.toml +231 -0
- liteai_sdk-0.3.11/src/liteai_sdk/__init__.py +260 -0
- liteai_sdk-0.3.11/src/liteai_sdk/debug.py +4 -0
- liteai_sdk-0.3.11/src/liteai_sdk/param_parser.py +48 -0
- liteai_sdk-0.3.11/src/liteai_sdk/stream.py +82 -0
- liteai_sdk-0.3.11/src/liteai_sdk/tool/__init__.py +307 -0
- liteai_sdk-0.3.11/src/liteai_sdk/tool/execute.py +59 -0
- liteai_sdk-0.3.11/src/liteai_sdk/tool/utils.py +19 -0
- liteai_sdk-0.3.11/src/liteai_sdk/types/__init__.py +32 -0
- liteai_sdk-0.3.11/src/liteai_sdk/types/exceptions.py +27 -0
- liteai_sdk-0.3.11/src/liteai_sdk/types/message.py +151 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 BHznJNs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: liteai_sdk
|
|
3
|
+
Version: 0.3.11
|
|
4
|
+
Summary: A wrapper of LiteLLM
|
|
5
|
+
Author-email: BHznJNs <bhznjns@outlook.com>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: litellm>=1.80.0
|
|
17
|
+
Requires-Dist: pydantic>=2.0.0
|
|
18
|
+
Requires-Dist: python-dotenv>=1.2.1 ; extra == "dev"
|
|
19
|
+
Requires-Dist: pytest-cov ; extra == "test"
|
|
20
|
+
Requires-Dist: pytest-mock ; extra == "test"
|
|
21
|
+
Requires-Dist: pytest-runner ; extra == "test"
|
|
22
|
+
Requires-Dist: pytest ; extra == "test"
|
|
23
|
+
Requires-Dist: pytest-github-actions-annotate-failures ; extra == "test"
|
|
24
|
+
Project-URL: Source, https://github.com/BHznJNs/liteai
|
|
25
|
+
Project-URL: Tracker, https://github.com/BHznJNs/liteai/issues
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Provides-Extra: test
|
|
28
|
+
|
|
29
|
+
# LiteAI-SDK
|
|
30
|
+
|
|
31
|
+
LiteAI-SDK is a wrapper of LiteLLM which provides a more intuitive API and [AI SDK](https://github.com/vercel/ai) like DX.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
pip install liteai-sdk
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Develop with coding agent
|
|
40
|
+
|
|
41
|
+
You can access the complete usage guidance with [llms.txt](https://raw.githubusercontent.com/BHznJNs/liteai/refs/heads/main/llms.txt), just give it to your coding agent to tell it how to use LiteAI-SDK.
|
|
42
|
+
|
|
43
|
+
## Examples
|
|
44
|
+
|
|
45
|
+
Below is a simple example of just a API call:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import os
|
|
49
|
+
from dotenv import load_dotenv
|
|
50
|
+
from liteai_sdk import LLM, LlmProviders, LlmRequestParams, UserMessage
|
|
51
|
+
|
|
52
|
+
load_dotenv()
|
|
53
|
+
|
|
54
|
+
llm = LLM(provider=LlmProviders.OPENAI,
|
|
55
|
+
api_key=os.getenv("API_KEY", ""),
|
|
56
|
+
base_url=os.getenv("BASE_URL", ""))
|
|
57
|
+
|
|
58
|
+
response = llm.generate_text_sync( # sync API of generate_text
|
|
59
|
+
LlmRequestParams(
|
|
60
|
+
model="deepseek-v3.1",
|
|
61
|
+
messages=[UserMessage(content="Hello.")]))
|
|
62
|
+
print(response)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Below is an example that shows the automatically tool call:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import os
|
|
69
|
+
from dotenv import load_dotenv
|
|
70
|
+
from liteai_sdk import LLM, LlmProviders, LlmRequestParams, UserMessage
|
|
71
|
+
|
|
72
|
+
load_dotenv()
|
|
73
|
+
|
|
74
|
+
def example_tool():
|
|
75
|
+
"""
|
|
76
|
+
This is a test tool that is used to test the tool calling functionality.
|
|
77
|
+
"""
|
|
78
|
+
print("The example tool is called.")
|
|
79
|
+
return "Hello World"
|
|
80
|
+
|
|
81
|
+
llm = LLM(provider=LlmProviders.OPENAI,
|
|
82
|
+
api_key=os.getenv("API_KEY", ""),
|
|
83
|
+
base_url=os.getenv("BASE_URL", ""))
|
|
84
|
+
|
|
85
|
+
params = LlmRequestParams(
|
|
86
|
+
model="deepseek-v3.1",
|
|
87
|
+
tools=[example_tool],
|
|
88
|
+
execute_tools=True,
|
|
89
|
+
messages=[UserMessage(content="Please call the tool example_tool.")])
|
|
90
|
+
|
|
91
|
+
print("User: ", "Please call the tool example_tool.")
|
|
92
|
+
messages = llm.generate_text_sync(params)
|
|
93
|
+
for message in messages:
|
|
94
|
+
match message.role:
|
|
95
|
+
case "assistant":
|
|
96
|
+
print("Assistant: ", message.content)
|
|
97
|
+
case "tool":
|
|
98
|
+
print("Tool: ", message.result)
|
|
99
|
+
```
|
|
100
|
+
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# LiteAI-SDK
|
|
2
|
+
|
|
3
|
+
LiteAI-SDK is a wrapper of LiteLLM which provides a more intuitive API and [AI SDK](https://github.com/vercel/ai) like DX.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
pip install liteai-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Develop with coding agent
|
|
12
|
+
|
|
13
|
+
You can access the complete usage guidance with [llms.txt](https://raw.githubusercontent.com/BHznJNs/liteai/refs/heads/main/llms.txt), just give it to your coding agent to tell it how to use LiteAI-SDK.
|
|
14
|
+
|
|
15
|
+
## Examples
|
|
16
|
+
|
|
17
|
+
Below is a simple example of just a API call:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import os
|
|
21
|
+
from dotenv import load_dotenv
|
|
22
|
+
from liteai_sdk import LLM, LlmProviders, LlmRequestParams, UserMessage
|
|
23
|
+
|
|
24
|
+
load_dotenv()
|
|
25
|
+
|
|
26
|
+
llm = LLM(provider=LlmProviders.OPENAI,
|
|
27
|
+
api_key=os.getenv("API_KEY", ""),
|
|
28
|
+
base_url=os.getenv("BASE_URL", ""))
|
|
29
|
+
|
|
30
|
+
response = llm.generate_text_sync( # sync API of generate_text
|
|
31
|
+
LlmRequestParams(
|
|
32
|
+
model="deepseek-v3.1",
|
|
33
|
+
messages=[UserMessage(content="Hello.")]))
|
|
34
|
+
print(response)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Below is an example that shows the automatically tool call:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import os
|
|
41
|
+
from dotenv import load_dotenv
|
|
42
|
+
from liteai_sdk import LLM, LlmProviders, LlmRequestParams, UserMessage
|
|
43
|
+
|
|
44
|
+
load_dotenv()
|
|
45
|
+
|
|
46
|
+
def example_tool():
|
|
47
|
+
"""
|
|
48
|
+
This is a test tool that is used to test the tool calling functionality.
|
|
49
|
+
"""
|
|
50
|
+
print("The example tool is called.")
|
|
51
|
+
return "Hello World"
|
|
52
|
+
|
|
53
|
+
llm = LLM(provider=LlmProviders.OPENAI,
|
|
54
|
+
api_key=os.getenv("API_KEY", ""),
|
|
55
|
+
base_url=os.getenv("BASE_URL", ""))
|
|
56
|
+
|
|
57
|
+
params = LlmRequestParams(
|
|
58
|
+
model="deepseek-v3.1",
|
|
59
|
+
tools=[example_tool],
|
|
60
|
+
execute_tools=True,
|
|
61
|
+
messages=[UserMessage(content="Please call the tool example_tool.")])
|
|
62
|
+
|
|
63
|
+
print("User: ", "Please call the tool example_tool.")
|
|
64
|
+
messages = llm.generate_text_sync(params)
|
|
65
|
+
for message in messages:
|
|
66
|
+
match message.role:
|
|
67
|
+
case "assistant":
|
|
68
|
+
print("Assistant: ", message.content)
|
|
69
|
+
case "tool":
|
|
70
|
+
print("Tool: ", message.result)
|
|
71
|
+
```
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["flit_core >=2,<4"]
|
|
3
|
+
build-backend = "flit_core.buildapi"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "liteai_sdk"
|
|
7
|
+
authors = [{ name = "BHznJNs", email = "bhznjns@outlook.com" }]
|
|
8
|
+
description = "A wrapper of LiteLLM"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 3 - Alpha",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
15
|
+
"Programming Language :: Python :: 3.10",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
]
|
|
19
|
+
requires-python = ">=3.10"
|
|
20
|
+
version = "0.3.11"
|
|
21
|
+
|
|
22
|
+
dependencies = [
|
|
23
|
+
"litellm>=1.80.0",
|
|
24
|
+
"pydantic>=2.0.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
dev = [
|
|
29
|
+
"python-dotenv>=1.2.1"
|
|
30
|
+
]
|
|
31
|
+
test = [
|
|
32
|
+
"pytest-cov",
|
|
33
|
+
"pytest-mock",
|
|
34
|
+
"pytest-runner",
|
|
35
|
+
"pytest",
|
|
36
|
+
"pytest-github-actions-annotate-failures",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Source = "https://github.com/BHznJNs/liteai"
|
|
41
|
+
Tracker = "https://github.com/BHznJNs/liteai/issues"
|
|
42
|
+
|
|
43
|
+
[tool.flit.module]
|
|
44
|
+
name = "liteai_sdk"
|
|
45
|
+
|
|
46
|
+
[tool.bandit]
|
|
47
|
+
exclude_dirs = ["build", "dist", "tests", "scripts"]
|
|
48
|
+
number = 4
|
|
49
|
+
recursive = true
|
|
50
|
+
targets = "src"
|
|
51
|
+
|
|
52
|
+
[tool.black]
|
|
53
|
+
line-length = 120
|
|
54
|
+
fast = true
|
|
55
|
+
|
|
56
|
+
[tool.coverage.run]
|
|
57
|
+
branch = true
|
|
58
|
+
|
|
59
|
+
[tool.coverage.report]
|
|
60
|
+
fail_under = 100
|
|
61
|
+
|
|
62
|
+
[tool.flake8]
|
|
63
|
+
max-line-length = 120
|
|
64
|
+
select = "F,E,W,B,B901,B902,B903"
|
|
65
|
+
exclude = [
|
|
66
|
+
".eggs",
|
|
67
|
+
".git",
|
|
68
|
+
".tox",
|
|
69
|
+
"nssm",
|
|
70
|
+
"obj",
|
|
71
|
+
"out",
|
|
72
|
+
"packages",
|
|
73
|
+
"pywin32",
|
|
74
|
+
"tests",
|
|
75
|
+
"swagger_client",
|
|
76
|
+
]
|
|
77
|
+
ignore = ["E722", "B001", "W503", "E203"]
|
|
78
|
+
|
|
79
|
+
[tool.pyright]
|
|
80
|
+
include = ["src"]
|
|
81
|
+
exclude = ["**/node_modules", "**/__pycache__"]
|
|
82
|
+
venv = "env37"
|
|
83
|
+
|
|
84
|
+
reportMissingImports = true
|
|
85
|
+
reportMissingTypeStubs = false
|
|
86
|
+
|
|
87
|
+
pythonVersion = "3.10"
|
|
88
|
+
pythonPlatform = "Linux"
|
|
89
|
+
|
|
90
|
+
executionEnvironments = [{ root = "src" }]
|
|
91
|
+
|
|
92
|
+
[tool.pytest.ini_options]
|
|
93
|
+
addopts = "--cov-report xml:coverage.xml --cov src --cov-fail-under 0 --cov-append -m 'not integration'"
|
|
94
|
+
pythonpath = ["src"]
|
|
95
|
+
testpaths = "tests"
|
|
96
|
+
junit_family = "xunit2"
|
|
97
|
+
markers = [
|
|
98
|
+
"integration: marks as integration test",
|
|
99
|
+
"notebooks: marks as notebook test",
|
|
100
|
+
"gpu: marks as gpu test",
|
|
101
|
+
"spark: marks tests which need Spark",
|
|
102
|
+
"slow: marks tests as slow",
|
|
103
|
+
"unit: fast offline tests",
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
[tool.pylint]
|
|
107
|
+
extension-pkg-whitelist = [
|
|
108
|
+
"numpy",
|
|
109
|
+
"torch",
|
|
110
|
+
"cv2",
|
|
111
|
+
"pyodbc",
|
|
112
|
+
"pydantic",
|
|
113
|
+
"ciso8601",
|
|
114
|
+
"netcdf4",
|
|
115
|
+
"scipy",
|
|
116
|
+
]
|
|
117
|
+
ignore = "CVS"
|
|
118
|
+
ignore-patterns = "test.*?py,conftest.py"
|
|
119
|
+
init-hook = 'import sys; sys.setrecursionlimit(8 * sys.getrecursionlimit())'
|
|
120
|
+
jobs = 0
|
|
121
|
+
limit-inference-results = 100
|
|
122
|
+
persistent = "yes"
|
|
123
|
+
suggestion-mode = "yes"
|
|
124
|
+
unsafe-load-any-extension = "no"
|
|
125
|
+
|
|
126
|
+
[tool.pylint.'MESSAGES CONTROL']
|
|
127
|
+
enable = "c-extension-no-member"
|
|
128
|
+
|
|
129
|
+
[tool.pylint.'REPORTS']
|
|
130
|
+
evaluation = "10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)"
|
|
131
|
+
output-format = "text"
|
|
132
|
+
reports = "no"
|
|
133
|
+
score = "yes"
|
|
134
|
+
|
|
135
|
+
[tool.pylint.'REFACTORING']
|
|
136
|
+
max-nested-blocks = 5
|
|
137
|
+
never-returning-functions = "sys.exit"
|
|
138
|
+
|
|
139
|
+
[tool.pylint.'BASIC']
|
|
140
|
+
argument-naming-style = "snake_case"
|
|
141
|
+
attr-naming-style = "snake_case"
|
|
142
|
+
bad-names = ["foo", "bar"]
|
|
143
|
+
class-attribute-naming-style = "any"
|
|
144
|
+
class-naming-style = "PascalCase"
|
|
145
|
+
const-naming-style = "UPPER_CASE"
|
|
146
|
+
docstring-min-length = -1
|
|
147
|
+
function-naming-style = "snake_case"
|
|
148
|
+
good-names = ["i", "j", "k", "ex", "Run", "_"]
|
|
149
|
+
include-naming-hint = "yes"
|
|
150
|
+
inlinevar-naming-style = "any"
|
|
151
|
+
method-naming-style = "snake_case"
|
|
152
|
+
module-naming-style = "any"
|
|
153
|
+
no-docstring-rgx = "^_"
|
|
154
|
+
property-classes = "abc.abstractproperty"
|
|
155
|
+
variable-naming-style = "snake_case"
|
|
156
|
+
|
|
157
|
+
[tool.pylint.'FORMAT']
|
|
158
|
+
ignore-long-lines = "^\\s*(# )?.*['\"]?<?https?://\\S+>?"
|
|
159
|
+
indent-after-paren = 4
|
|
160
|
+
indent-string = ' '
|
|
161
|
+
max-line-length = 120
|
|
162
|
+
max-module-lines = 1000
|
|
163
|
+
single-line-class-stmt = "no"
|
|
164
|
+
single-line-if-stmt = "no"
|
|
165
|
+
|
|
166
|
+
[tool.pylint.'LOGGING']
|
|
167
|
+
logging-format-style = "old"
|
|
168
|
+
logging-modules = "logging"
|
|
169
|
+
|
|
170
|
+
[tool.pylint.'MISCELLANEOUS']
|
|
171
|
+
notes = ["FIXME", "XXX", "TODO"]
|
|
172
|
+
|
|
173
|
+
[tool.pylint.'SIMILARITIES']
|
|
174
|
+
ignore-comments = "yes"
|
|
175
|
+
ignore-docstrings = "yes"
|
|
176
|
+
ignore-imports = "yes"
|
|
177
|
+
min-similarity-lines = 7
|
|
178
|
+
|
|
179
|
+
[tool.pylint.'SPELLING']
|
|
180
|
+
max-spelling-suggestions = 4
|
|
181
|
+
spelling-store-unknown-words = "no"
|
|
182
|
+
|
|
183
|
+
[tool.pylint.'STRING']
|
|
184
|
+
check-str-concat-over-line-jumps = "no"
|
|
185
|
+
|
|
186
|
+
[tool.pylint.'TYPECHECK']
|
|
187
|
+
contextmanager-decorators = "contextlib.contextmanager"
|
|
188
|
+
generated-members = "numpy.*,np.*,pyspark.sql.functions,collect_list"
|
|
189
|
+
ignore-mixin-members = "yes"
|
|
190
|
+
ignore-none = "yes"
|
|
191
|
+
ignore-on-opaque-inference = "yes"
|
|
192
|
+
ignored-classes = "optparse.Values,thread._local,_thread._local,numpy,torch,swagger_client"
|
|
193
|
+
ignored-modules = "numpy,torch,swagger_client,netCDF4,scipy"
|
|
194
|
+
missing-member-hint = "yes"
|
|
195
|
+
missing-member-hint-distance = 1
|
|
196
|
+
missing-member-max-choices = 1
|
|
197
|
+
|
|
198
|
+
[tool.pylint.'VARIABLES']
|
|
199
|
+
additional-builtins = "dbutils"
|
|
200
|
+
allow-global-unused-variables = "yes"
|
|
201
|
+
callbacks = ["cb_", "_cb"]
|
|
202
|
+
dummy-variables-rgx = "_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_"
|
|
203
|
+
ignored-argument-names = "_.*|^ignored_|^unused_"
|
|
204
|
+
init-import = "no"
|
|
205
|
+
redefining-builtins-modules = "six.moves,past.builtins,future.builtins,builtins,io"
|
|
206
|
+
|
|
207
|
+
[tool.pylint.'CLASSES']
|
|
208
|
+
defining-attr-methods = ["__init__", "__new__", "setUp", "__post_init__"]
|
|
209
|
+
exclude-protected = ["_asdict", "_fields", "_replace", "_source", "_make"]
|
|
210
|
+
valid-classmethod-first-arg = "cls"
|
|
211
|
+
valid-metaclass-classmethod-first-arg = "cls"
|
|
212
|
+
|
|
213
|
+
[tool.pylint.'DESIGN']
|
|
214
|
+
max-args = 5
|
|
215
|
+
max-attributes = 7
|
|
216
|
+
max-bool-expr = 5
|
|
217
|
+
max-branches = 12
|
|
218
|
+
max-locals = 15
|
|
219
|
+
max-parents = 7
|
|
220
|
+
max-public-methods = 20
|
|
221
|
+
max-returns = 6
|
|
222
|
+
max-statements = 50
|
|
223
|
+
min-public-methods = 2
|
|
224
|
+
|
|
225
|
+
[tool.pylint.'IMPORTS']
|
|
226
|
+
allow-wildcard-with-all = "no"
|
|
227
|
+
analyse-fallback-blocks = "no"
|
|
228
|
+
deprecated-modules = "optparse,tkinter.tix"
|
|
229
|
+
|
|
230
|
+
[tool.pylint.'EXCEPTIONS']
|
|
231
|
+
overgeneral-exceptions = ["BaseException", "Exception"]
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import queue
|
|
3
|
+
from typing import cast
|
|
4
|
+
from collections.abc import AsyncGenerator, Generator
|
|
5
|
+
from litellm import ChatCompletionAssistantToolCall, CustomStreamWrapper, completion, acompletion
|
|
6
|
+
from litellm.exceptions import (
|
|
7
|
+
AuthenticationError,
|
|
8
|
+
PermissionDeniedError,
|
|
9
|
+
RateLimitError,
|
|
10
|
+
ContextWindowExceededError,
|
|
11
|
+
BadRequestError,
|
|
12
|
+
InvalidRequestError,
|
|
13
|
+
InternalServerError,
|
|
14
|
+
ServiceUnavailableError,
|
|
15
|
+
ContentPolicyViolationError,
|
|
16
|
+
APIError,
|
|
17
|
+
Timeout,
|
|
18
|
+
)
|
|
19
|
+
from litellm.utils import get_valid_models
|
|
20
|
+
from litellm.types.utils import LlmProviders,\
|
|
21
|
+
ModelResponse as LiteLlmModelResponse,\
|
|
22
|
+
ModelResponseStream as LiteLlmModelResponseStream,\
|
|
23
|
+
Choices as LiteLlmModelResponseChoices
|
|
24
|
+
from .debug import enable_debugging
|
|
25
|
+
from .param_parser import ParamParser
|
|
26
|
+
from .stream import AssistantMessageCollector
|
|
27
|
+
from .tool import ToolFn, ToolDef, RawToolDef, prepare_tools
|
|
28
|
+
from .tool.execute import execute_tool_sync, execute_tool, parse_arguments
|
|
29
|
+
from .tool.utils import filter_executable_tools, find_tool_by_name
|
|
30
|
+
from .types import LlmRequestParams, GenerateTextResponse, StreamTextResponseSync, StreamTextResponseAsync
|
|
31
|
+
from .types.exceptions import *
|
|
32
|
+
from .types.message import ChatMessage, UserMessage, SystemMessage, AssistantMessage, ToolMessage,\
|
|
33
|
+
MessageChunk, TextChunk, ReasoningChunk, AudioChunk, ImageChunk, ToolCallChunk,\
|
|
34
|
+
openai_chunk_normalizer
|
|
35
|
+
|
|
36
|
+
class LLM:
|
|
37
|
+
"""
|
|
38
|
+
The `stream_text` API will returns ToolMessage in the queue only if `params.execute_tools` is True.
|
|
39
|
+
|
|
40
|
+
Possible exceptions raises for `generate_text` and `stream_text`:
|
|
41
|
+
- AuthenticationError
|
|
42
|
+
- PermissionDeniedError
|
|
43
|
+
- RateLimitError
|
|
44
|
+
- ContextWindowExceededError
|
|
45
|
+
- BadRequestError
|
|
46
|
+
- InvalidRequestError
|
|
47
|
+
- InternalServerError
|
|
48
|
+
- ServiceUnavailableError
|
|
49
|
+
- ContentPolicyViolationError
|
|
50
|
+
|
|
51
|
+
- APIError
|
|
52
|
+
- Timeout
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self,
|
|
56
|
+
provider: LlmProviders,
|
|
57
|
+
base_url: str,
|
|
58
|
+
api_key: str):
|
|
59
|
+
self.provider = provider
|
|
60
|
+
self.base_url = base_url
|
|
61
|
+
self.api_key = api_key
|
|
62
|
+
self._param_parser = ParamParser(self.provider, self.base_url, self.api_key)
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def _should_resolve_tool_calls(
|
|
66
|
+
params: LlmRequestParams,
|
|
67
|
+
message: AssistantMessage,
|
|
68
|
+
) -> tuple[list[ToolFn | ToolDef | RawToolDef],
|
|
69
|
+
list[ChatCompletionAssistantToolCall]] | None:
|
|
70
|
+
message.tool_calls
|
|
71
|
+
condition = params.execute_tools and\
|
|
72
|
+
params.tools is not None and\
|
|
73
|
+
message.tool_calls is not None
|
|
74
|
+
if condition:
|
|
75
|
+
assert params.tools is not None
|
|
76
|
+
assert message.tool_calls is not None
|
|
77
|
+
return params.tools, message.tool_calls
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def _parse_tool_call(tool_call: ChatCompletionAssistantToolCall) -> tuple[str, str, str] | None:
|
|
82
|
+
id = tool_call.get("id")
|
|
83
|
+
function = tool_call.get("function")
|
|
84
|
+
function_name = function.get("name")
|
|
85
|
+
function_arguments = function.get("arguments")
|
|
86
|
+
if id is None or\
|
|
87
|
+
function is None or\
|
|
88
|
+
function_name is None or\
|
|
89
|
+
function_arguments is None: return None
|
|
90
|
+
return id, function_name, function_arguments
|
|
91
|
+
|
|
92
|
+
@staticmethod
|
|
93
|
+
async def _execute_tool_calls(
|
|
94
|
+
tools: list[ToolFn | ToolDef | RawToolDef],
|
|
95
|
+
tool_calls: list[ChatCompletionAssistantToolCall]
|
|
96
|
+
) -> list[ToolMessage]:
|
|
97
|
+
executable_tools = filter_executable_tools(tools)
|
|
98
|
+
results = []
|
|
99
|
+
for tool_call in tool_calls:
|
|
100
|
+
if (tool_call_data := LLM._parse_tool_call(tool_call)) is None: continue
|
|
101
|
+
id, function_name, function_arguments = tool_call_data
|
|
102
|
+
if (target_tool := find_tool_by_name(cast(list, executable_tools), function_name)) is None: continue
|
|
103
|
+
parsed_arguments = parse_arguments(function_arguments)
|
|
104
|
+
result, error = None, None
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
result = await execute_tool(target_tool, parsed_arguments)
|
|
108
|
+
except Exception as e:
|
|
109
|
+
error = f"{type(e).__name__}: {str(e)}"
|
|
110
|
+
results.append(ToolMessage(
|
|
111
|
+
id=id,
|
|
112
|
+
name=function_name,
|
|
113
|
+
arguments=parsed_arguments,
|
|
114
|
+
result=result,
|
|
115
|
+
error=error))
|
|
116
|
+
return results
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def _execute_tool_calls_sync(
|
|
120
|
+
tools: list[ToolFn | ToolDef | RawToolDef],
|
|
121
|
+
tool_calls: list[ChatCompletionAssistantToolCall]
|
|
122
|
+
) -> list[ToolMessage]:
|
|
123
|
+
executable_tools = filter_executable_tools(tools)
|
|
124
|
+
results = []
|
|
125
|
+
for tool_call in tool_calls:
|
|
126
|
+
if (tool_call_data := LLM._parse_tool_call(tool_call)) is None: continue
|
|
127
|
+
id, function_name, function_arguments = tool_call_data
|
|
128
|
+
if (target_tool := find_tool_by_name(cast(list, executable_tools), function_name)) is None: continue
|
|
129
|
+
parsed_arguments = parse_arguments(function_arguments)
|
|
130
|
+
|
|
131
|
+
result, error = None, None
|
|
132
|
+
try:
|
|
133
|
+
result = execute_tool_sync(target_tool, parsed_arguments)
|
|
134
|
+
except Exception as e:
|
|
135
|
+
error = f"{type(e).__name__}: {str(e)}"
|
|
136
|
+
results.append(ToolMessage(
|
|
137
|
+
id=id,
|
|
138
|
+
name=function_name,
|
|
139
|
+
arguments=parsed_arguments,
|
|
140
|
+
result=result,
|
|
141
|
+
error=error))
|
|
142
|
+
return results
|
|
143
|
+
|
|
144
|
+
def list_models(self) -> list[str]:
|
|
145
|
+
return get_valid_models(
|
|
146
|
+
custom_llm_provider=self.provider.value,
|
|
147
|
+
check_provider_endpoint=True,
|
|
148
|
+
api_base=self.base_url,
|
|
149
|
+
api_key=self.api_key)
|
|
150
|
+
|
|
151
|
+
def generate_text_sync(self, params: LlmRequestParams):
|
|
152
|
+
response = completion(**self._param_parser.parse_nonstream(params))
|
|
153
|
+
response = cast(LiteLlmModelResponse, response)
|
|
154
|
+
choices = cast(list[LiteLlmModelResponseChoices], response.choices)
|
|
155
|
+
message = choices[0].message
|
|
156
|
+
assistant_message = AssistantMessage.from_litellm_message(message)
|
|
157
|
+
result: GenerateTextResponse = [assistant_message]
|
|
158
|
+
if (tools_and_tool_calls := self._should_resolve_tool_calls(params, assistant_message)):
|
|
159
|
+
tools, tool_calls = tools_and_tool_calls
|
|
160
|
+
result += self._execute_tool_calls_sync(tools, tool_calls)
|
|
161
|
+
return result
|
|
162
|
+
|
|
163
|
+
async def generate_text(self, params: LlmRequestParams) -> GenerateTextResponse:
|
|
164
|
+
response = await acompletion(**self._param_parser.parse_nonstream(params))
|
|
165
|
+
response = cast(LiteLlmModelResponse, response)
|
|
166
|
+
choices = cast(list[LiteLlmModelResponseChoices], response.choices)
|
|
167
|
+
message = choices[0].message
|
|
168
|
+
assistant_message = AssistantMessage.from_litellm_message(message)
|
|
169
|
+
result: GenerateTextResponse = [assistant_message]
|
|
170
|
+
if (tools_and_tool_calls := self._should_resolve_tool_calls(params, assistant_message)):
|
|
171
|
+
tools, tool_calls = tools_and_tool_calls
|
|
172
|
+
result += await self._execute_tool_calls(tools, tool_calls)
|
|
173
|
+
return result
|
|
174
|
+
|
|
175
|
+
def stream_text_sync(self, params: LlmRequestParams) -> StreamTextResponseSync:
|
|
176
|
+
def stream(response: CustomStreamWrapper) -> Generator[MessageChunk]:
|
|
177
|
+
nonlocal message_collector
|
|
178
|
+
for chunk in response:
|
|
179
|
+
chunk = cast(LiteLlmModelResponseStream, chunk)
|
|
180
|
+
yield from openai_chunk_normalizer(chunk)
|
|
181
|
+
message_collector.collect(chunk)
|
|
182
|
+
|
|
183
|
+
message = message_collector.get_message()
|
|
184
|
+
full_message_queue.put(message)
|
|
185
|
+
if (tools_and_tool_calls := self._should_resolve_tool_calls(params, message)):
|
|
186
|
+
tools, tool_calls = tools_and_tool_calls
|
|
187
|
+
tool_messages = self._execute_tool_calls_sync(tools, tool_calls)
|
|
188
|
+
for tool_message in tool_messages:
|
|
189
|
+
full_message_queue.put(tool_message)
|
|
190
|
+
full_message_queue.put(None)
|
|
191
|
+
|
|
192
|
+
response = completion(**self._param_parser.parse_stream(params))
|
|
193
|
+
message_collector = AssistantMessageCollector()
|
|
194
|
+
returned_stream = stream(cast(CustomStreamWrapper, response))
|
|
195
|
+
full_message_queue = queue.Queue[AssistantMessage | ToolMessage | None]()
|
|
196
|
+
return returned_stream, full_message_queue
|
|
197
|
+
|
|
198
|
+
async def stream_text(self, params: LlmRequestParams) -> StreamTextResponseAsync:
|
|
199
|
+
async def stream(response: CustomStreamWrapper) -> AsyncGenerator[TextChunk | ReasoningChunk | AudioChunk | ImageChunk | ToolCallChunk]:
|
|
200
|
+
nonlocal message_collector
|
|
201
|
+
async for chunk in response:
|
|
202
|
+
chunk = cast(LiteLlmModelResponseStream, chunk)
|
|
203
|
+
for normalized_chunk in openai_chunk_normalizer(chunk):
|
|
204
|
+
yield normalized_chunk
|
|
205
|
+
message_collector.collect(chunk)
|
|
206
|
+
|
|
207
|
+
message = message_collector.get_message()
|
|
208
|
+
await full_message_queue.put(message)
|
|
209
|
+
if (tools_and_tool_calls := self._should_resolve_tool_calls(params, message)):
|
|
210
|
+
tools, tool_calls = tools_and_tool_calls
|
|
211
|
+
tool_messages = await self._execute_tool_calls(tools, tool_calls)
|
|
212
|
+
for tool_message in tool_messages:
|
|
213
|
+
await full_message_queue.put(tool_message)
|
|
214
|
+
await full_message_queue.put(None)
|
|
215
|
+
|
|
216
|
+
response = await acompletion(**self._param_parser.parse_stream(params))
|
|
217
|
+
message_collector = AssistantMessageCollector()
|
|
218
|
+
returned_stream = stream(cast(CustomStreamWrapper, response))
|
|
219
|
+
full_message_queue = asyncio.Queue[AssistantMessage | ToolMessage | None]()
|
|
220
|
+
return returned_stream, full_message_queue
|
|
221
|
+
|
|
222
|
+
__all__ = [
|
|
223
|
+
# Exceptions
|
|
224
|
+
"AuthenticationError",
|
|
225
|
+
"PermissionDeniedError",
|
|
226
|
+
"RateLimitError",
|
|
227
|
+
"ContextWindowExceededError",
|
|
228
|
+
"BadRequestError",
|
|
229
|
+
"InvalidRequestError",
|
|
230
|
+
"InternalServerError",
|
|
231
|
+
"ServiceUnavailableError",
|
|
232
|
+
"ContentPolicyViolationError",
|
|
233
|
+
"APIError",
|
|
234
|
+
"Timeout",
|
|
235
|
+
|
|
236
|
+
"enable_debugging",
|
|
237
|
+
|
|
238
|
+
"LLM",
|
|
239
|
+
"LlmRequestParams",
|
|
240
|
+
"ToolFn",
|
|
241
|
+
"ToolDef",
|
|
242
|
+
"RawToolDef",
|
|
243
|
+
|
|
244
|
+
"ChatMessage",
|
|
245
|
+
"UserMessage",
|
|
246
|
+
"SystemMessage",
|
|
247
|
+
"AssistantMessage",
|
|
248
|
+
"ToolMessage",
|
|
249
|
+
|
|
250
|
+
"MessageChunk",
|
|
251
|
+
"TextChunk",
|
|
252
|
+
"ReasoningChunk",
|
|
253
|
+
"AudioChunk",
|
|
254
|
+
"ImageChunk",
|
|
255
|
+
"ToolCallChunk",
|
|
256
|
+
|
|
257
|
+
"GenerateTextResponse",
|
|
258
|
+
"StreamTextResponseSync",
|
|
259
|
+
"StreamTextResponseAsync"
|
|
260
|
+
]
|