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.
@@ -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
+ ]
@@ -0,0 +1,4 @@
1
+ import litellm
2
+
3
+ def enable_debugging():
4
+ litellm._turn_on_debug()