rusticai-litellm 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.
- rusticai_litellm-0.0.1/PKG-INFO +38 -0
- rusticai_litellm-0.0.1/README.md +17 -0
- rusticai_litellm-0.0.1/pyproject.toml +66 -0
- rusticai_litellm-0.0.1/src/rustic_ai/litellm/__init__.py +4 -0
- rusticai_litellm-0.0.1/src/rustic_ai/litellm/agent.py +127 -0
- rusticai_litellm-0.0.1/src/rustic_ai/litellm/conf.py +81 -0
- rusticai_litellm-0.0.1/src/rustic_ai/litellm/dependency.py +78 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: rusticai-litellm
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Rustic AI module leveraging Litellm for LLMs
|
|
5
|
+
Home-page: https://www.rustic.ai/
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Author: Dragonscale Industries Inc.
|
|
8
|
+
Author-email: dev@dragonscale.ai
|
|
9
|
+
Requires-Python: >=3.12,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Requires-Dist: dotenv (>=0.9.9,<0.10.0)
|
|
15
|
+
Requires-Dist: litellm (>=1.65.4.post1,<2.0.0)
|
|
16
|
+
Requires-Dist: openai (>=1.71.0,<2.0.0)
|
|
17
|
+
Requires-Dist: rusticai-core (>=0.0.4,<0.1.0)
|
|
18
|
+
Project-URL: Repository, https://github.com/rustic-ai/python-framework
|
|
19
|
+
Project-URL: Rustic AI Core, https://pypi.org/project/rusticai-core/
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Rustic AI LiteLLM
|
|
23
|
+
|
|
24
|
+
This module offers seamless integration with various LLMs through [LiteLLM](https://docs.litellm.ai/docs/), simplifying the use of language models within [Rustic AI](https://www.rustic.ai/) guilds.
|
|
25
|
+
|
|
26
|
+
## Installing
|
|
27
|
+
|
|
28
|
+
```shell
|
|
29
|
+
pip install rusticai-litellm
|
|
30
|
+
```
|
|
31
|
+
**Note:** It depends on [rusticai-core](https://pypi.org/project/rusticai-core/)
|
|
32
|
+
|
|
33
|
+
## Building from Source
|
|
34
|
+
|
|
35
|
+
```shell
|
|
36
|
+
poetry install --with dev
|
|
37
|
+
poetry build
|
|
38
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Rustic AI LiteLLM
|
|
2
|
+
|
|
3
|
+
This module offers seamless integration with various LLMs through [LiteLLM](https://docs.litellm.ai/docs/), simplifying the use of language models within [Rustic AI](https://www.rustic.ai/) guilds.
|
|
4
|
+
|
|
5
|
+
## Installing
|
|
6
|
+
|
|
7
|
+
```shell
|
|
8
|
+
pip install rusticai-litellm
|
|
9
|
+
```
|
|
10
|
+
**Note:** It depends on [rusticai-core](https://pypi.org/project/rusticai-core/)
|
|
11
|
+
|
|
12
|
+
## Building from Source
|
|
13
|
+
|
|
14
|
+
```shell
|
|
15
|
+
poetry install --with dev
|
|
16
|
+
poetry build
|
|
17
|
+
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["poetry-core"]
|
|
3
|
+
build-backend = "poetry.core.masonry.api"
|
|
4
|
+
|
|
5
|
+
[tool.poetry]
|
|
6
|
+
name = "rusticai-litellm"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "Rustic AI module leveraging Litellm for LLMs"
|
|
9
|
+
authors = ["Dragonscale Industries Inc. <dev@dragonscale.ai>"]
|
|
10
|
+
license = "Apache-2.0"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
homepage = "https://www.rustic.ai/"
|
|
13
|
+
repository = "https://github.com/rustic-ai/python-framework"
|
|
14
|
+
packages = [{ include = "rustic_ai", from = "src" }]
|
|
15
|
+
|
|
16
|
+
[tool.poetry.urls]
|
|
17
|
+
"Rustic AI Core" = "https://pypi.org/project/rusticai-core/"
|
|
18
|
+
|
|
19
|
+
[tool.poetry.dependencies]
|
|
20
|
+
python = "^3.12"
|
|
21
|
+
rusticai-core = { version = "0.0.4"}
|
|
22
|
+
litellm = "^1.65.4.post1"
|
|
23
|
+
dotenv = "^0.9.9"
|
|
24
|
+
openai = "^1.71.0"
|
|
25
|
+
|
|
26
|
+
[tool.poetry-monorepo.deps]
|
|
27
|
+
|
|
28
|
+
[tool.poetry.group.dev.dependencies]
|
|
29
|
+
pytest = "^8.3.4"
|
|
30
|
+
black = { version = "^24.2.0", extras = ["jupyter"] }
|
|
31
|
+
flake8 = "^7.1.2"
|
|
32
|
+
tox = "^4.24.1"
|
|
33
|
+
isort = "^6.0.0"
|
|
34
|
+
mypy = "^1.15.0"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
[tool.black]
|
|
38
|
+
line-length = 120
|
|
39
|
+
target-version = ['py312']
|
|
40
|
+
include = '\.pyi?$'
|
|
41
|
+
exclude = '''
|
|
42
|
+
/(
|
|
43
|
+
\.git
|
|
44
|
+
| \.mypy_cache
|
|
45
|
+
| \.tox
|
|
46
|
+
| \.venv
|
|
47
|
+
| _build
|
|
48
|
+
| buck-out
|
|
49
|
+
| build
|
|
50
|
+
| dist
|
|
51
|
+
)/
|
|
52
|
+
'''
|
|
53
|
+
|
|
54
|
+
[tool.mypy]
|
|
55
|
+
python_version = "3.12"
|
|
56
|
+
ignore_missing_imports = true
|
|
57
|
+
check_untyped_defs = true
|
|
58
|
+
plugins = "pydantic.mypy"
|
|
59
|
+
explicit_package_bases = true
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
[tool.isort]
|
|
63
|
+
profile = "black"
|
|
64
|
+
known_third_party = ["pydantic"]
|
|
65
|
+
known_first_party = ["rustic_ai.core", "rustic_ai.litellm"]
|
|
66
|
+
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from collections import deque
|
|
3
|
+
from typing import Deque, List, Union
|
|
4
|
+
|
|
5
|
+
import openai
|
|
6
|
+
from dotenv import load_dotenv
|
|
7
|
+
|
|
8
|
+
import litellm
|
|
9
|
+
from litellm import validate_environment
|
|
10
|
+
from rustic_ai.core.guild.agent import (
|
|
11
|
+
Agent,
|
|
12
|
+
AgentMode,
|
|
13
|
+
AgentType,
|
|
14
|
+
ProcessContext,
|
|
15
|
+
processor,
|
|
16
|
+
)
|
|
17
|
+
from rustic_ai.core.guild.agent_ext.depends.llm.models import (
|
|
18
|
+
AssistantMessage,
|
|
19
|
+
ChatCompletionError,
|
|
20
|
+
ChatCompletionRequest,
|
|
21
|
+
ChatCompletionResponse,
|
|
22
|
+
ChatCompletionTool,
|
|
23
|
+
FunctionMessage,
|
|
24
|
+
ResponseCodes,
|
|
25
|
+
SystemMessage,
|
|
26
|
+
ToolMessage,
|
|
27
|
+
UserMessage,
|
|
28
|
+
)
|
|
29
|
+
from rustic_ai.core.guild.dsl import AgentSpec
|
|
30
|
+
|
|
31
|
+
from .conf import LiteLLMConf
|
|
32
|
+
|
|
33
|
+
load_dotenv()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class LiteLLMAgent(Agent[LiteLLMConf]):
|
|
37
|
+
"""
|
|
38
|
+
LiteLLM agent for invoking various LLM models using LiteLLM.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
litellm.drop_params = True
|
|
42
|
+
|
|
43
|
+
def __init__(self, agent_spec: AgentSpec[LiteLLMConf]):
|
|
44
|
+
validation_result = validate_environment(agent_spec.props.model)
|
|
45
|
+
if not validation_result.get("keys_in_environment"):
|
|
46
|
+
missing_keys = validation_result.get("missing_keys", [])
|
|
47
|
+
raise RuntimeError(f"Required environment variable(s) {'{}'.join(missing_keys)} not set")
|
|
48
|
+
|
|
49
|
+
super().__init__(agent_spec, agent_type=AgentType.BOT, agent_mode=AgentMode.LOCAL)
|
|
50
|
+
self.pre_messages = agent_spec.props.messages
|
|
51
|
+
self.pre_tools = agent_spec.props.tools
|
|
52
|
+
self.model = agent_spec.props.model
|
|
53
|
+
self.client_props = agent_spec.props.model_dump(
|
|
54
|
+
mode="json", exclude_unset=True, exclude_none=True, exclude={"message_memory"}
|
|
55
|
+
)
|
|
56
|
+
self.message_memory_size = agent_spec.props.message_memory or 0
|
|
57
|
+
self.message_queue: Deque[SystemMessage | UserMessage | AssistantMessage | ToolMessage | FunctionMessage] = (
|
|
58
|
+
deque(maxlen=self.message_memory_size)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
@processor(ChatCompletionRequest)
|
|
62
|
+
def llm_completion(self, ctx: ProcessContext[ChatCompletionRequest]):
|
|
63
|
+
|
|
64
|
+
prompt = ctx.payload
|
|
65
|
+
|
|
66
|
+
messages = self.pre_messages if self.pre_messages else []
|
|
67
|
+
|
|
68
|
+
all_messages = messages + list(self.message_queue) + prompt.messages
|
|
69
|
+
|
|
70
|
+
messages_dict = [m.model_dump(exclude_none=True) for m in all_messages]
|
|
71
|
+
|
|
72
|
+
tools: List[ChatCompletionTool] = self.pre_tools if self.pre_tools else []
|
|
73
|
+
if prompt.tools:
|
|
74
|
+
tools.extend(prompt.tools)
|
|
75
|
+
|
|
76
|
+
full_prompt = {
|
|
77
|
+
**self.client_props,
|
|
78
|
+
**prompt.model_dump(exclude_unset=True, exclude_none=True),
|
|
79
|
+
"messages": messages_dict,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if tools:
|
|
83
|
+
full_prompt["tools"] = tools
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
completion = litellm.completion(**full_prompt)
|
|
87
|
+
|
|
88
|
+
response = ChatCompletionResponse.from_litellm_completion(completion)
|
|
89
|
+
|
|
90
|
+
self.message_queue.extend(prompt.messages) # Append the prompt messages (from user) to the message queue
|
|
91
|
+
if response.choices and response.choices[0].message:
|
|
92
|
+
self.message_queue.append(
|
|
93
|
+
response.choices[0].message
|
|
94
|
+
) # Append the response message (from LLM) to the message queue
|
|
95
|
+
|
|
96
|
+
if response:
|
|
97
|
+
ctx.send(response)
|
|
98
|
+
|
|
99
|
+
except openai.APIStatusError as e: # pragma: no cover
|
|
100
|
+
logging.error(f"Error in LLM completion: {e}")
|
|
101
|
+
# Publish the error message
|
|
102
|
+
self.process_api_status_error(
|
|
103
|
+
ctx=ctx,
|
|
104
|
+
status_code=ResponseCodes(e.status_code),
|
|
105
|
+
error=e,
|
|
106
|
+
messages=messages,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def process_api_status_error(
|
|
110
|
+
self,
|
|
111
|
+
ctx: ProcessContext[ChatCompletionRequest],
|
|
112
|
+
status_code: ResponseCodes,
|
|
113
|
+
error: openai.APIStatusError,
|
|
114
|
+
messages: List[Union[SystemMessage, UserMessage, AssistantMessage, ToolMessage, FunctionMessage]],
|
|
115
|
+
): # pragma: no cover
|
|
116
|
+
"""
|
|
117
|
+
Process API status error and return a ChatCompletionError object.
|
|
118
|
+
"""
|
|
119
|
+
ctx.send_error(
|
|
120
|
+
ChatCompletionError(
|
|
121
|
+
status_code=status_code,
|
|
122
|
+
message=error.message,
|
|
123
|
+
response=error.response.text if error.response else None,
|
|
124
|
+
model=self.model,
|
|
125
|
+
request_messages=messages,
|
|
126
|
+
)
|
|
127
|
+
) # pragma: no cover
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from typing import Annotated, List, Optional, Union
|
|
2
|
+
|
|
3
|
+
from pydantic import ConfigDict, Field
|
|
4
|
+
|
|
5
|
+
from rustic_ai.core.guild.agent_ext.depends.llm.models import (
|
|
6
|
+
AssistantMessage,
|
|
7
|
+
ChatCompletionTool,
|
|
8
|
+
FunctionMessage,
|
|
9
|
+
Models,
|
|
10
|
+
SystemMessage,
|
|
11
|
+
ToolMessage,
|
|
12
|
+
UserMessage,
|
|
13
|
+
)
|
|
14
|
+
from rustic_ai.core.guild.dsl import BaseAgentProps
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LiteLLMConf(BaseAgentProps):
|
|
18
|
+
"""
|
|
19
|
+
Properties to apply to all LiteLLM calls made by the agent.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
model_config = ConfigDict(extra="ignore")
|
|
23
|
+
|
|
24
|
+
model: Annotated[Union[str, Models], Field(examples=["gpt-4-turbo"])]
|
|
25
|
+
"""
|
|
26
|
+
ID of the model to use. See the [model endpoint compatibility](/docs/models/model-endpoint-compatibility) table
|
|
27
|
+
for details on which models work with the Chat API.
|
|
28
|
+
"""
|
|
29
|
+
stream: Optional[bool] = False
|
|
30
|
+
"""
|
|
31
|
+
If set, partial message deltas will be sent, like in ChatGPT. Tokens will be sent as data-only
|
|
32
|
+
[server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format)
|
|
33
|
+
as they become available, with the stream terminated by a `data: [DONE]` message.
|
|
34
|
+
[Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions).
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
# set api_base, api_version, api_key
|
|
38
|
+
base_url: Optional[str] = None
|
|
39
|
+
api_version: Optional[str] = None
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
If provided, return a mock completion response for testing or debugging purposes (default is None).
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
custom_llm_provider: Optional[str] = None
|
|
46
|
+
"""
|
|
47
|
+
Used for Non-OpenAI LLMs, Example usage for bedrock, set model="amazon.titan-tg1-large" and custom_llm_provider="bedrock"
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
timeout: Optional[float] = None
|
|
51
|
+
"""
|
|
52
|
+
The maximum time in seconds to wait for the completion.
|
|
53
|
+
If the completion is not returned within this time, the request will time out.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
messages: Optional[
|
|
57
|
+
List[
|
|
58
|
+
Union[
|
|
59
|
+
SystemMessage,
|
|
60
|
+
UserMessage,
|
|
61
|
+
AssistantMessage,
|
|
62
|
+
ToolMessage,
|
|
63
|
+
FunctionMessage,
|
|
64
|
+
]
|
|
65
|
+
]
|
|
66
|
+
] = None
|
|
67
|
+
"""
|
|
68
|
+
List of messages to send to the model before the prompt.
|
|
69
|
+
These messages will be prependended to the list of messages in the prompt.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
tools: Optional[List[ChatCompletionTool]] = None
|
|
73
|
+
"""
|
|
74
|
+
List of tools to enable for completion.
|
|
75
|
+
These tools will be appended to the list of tools in the prompt.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
message_memory: Optional[int] = None
|
|
79
|
+
"""
|
|
80
|
+
Number of messages to remember in the conversation history.
|
|
81
|
+
"""
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
import litellm
|
|
4
|
+
from rustic_ai.core.guild.agent_ext.depends.dependency_resolver import (
|
|
5
|
+
DependencyResolver,
|
|
6
|
+
)
|
|
7
|
+
from rustic_ai.core.guild.agent_ext.depends.llm import LLM
|
|
8
|
+
from rustic_ai.core.guild.agent_ext.depends.llm.models import (
|
|
9
|
+
ChatCompletionRequest,
|
|
10
|
+
ChatCompletionResponse,
|
|
11
|
+
ChatCompletionTool,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from .conf import LiteLLMConf
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LiteLLM(LLM):
|
|
18
|
+
def __init__(self, props: LiteLLMConf):
|
|
19
|
+
self.preset_messages = props.messages
|
|
20
|
+
self.preset_tools = props.tools
|
|
21
|
+
self._model = props.model
|
|
22
|
+
self.client_props = props.model_dump(mode="json", exclude_unset=True, exclude_none=True)
|
|
23
|
+
|
|
24
|
+
def _prep_prompt(self, prompt: ChatCompletionRequest) -> dict:
|
|
25
|
+
messages = self.preset_messages if self.preset_messages else []
|
|
26
|
+
|
|
27
|
+
all_messages = messages + prompt.messages
|
|
28
|
+
|
|
29
|
+
messages_dict = [m.model_dump(exclude_none=True) for m in all_messages]
|
|
30
|
+
|
|
31
|
+
tools: List[ChatCompletionTool] = self.preset_tools if self.preset_tools else []
|
|
32
|
+
if prompt.tools:
|
|
33
|
+
tools.extend(prompt.tools)
|
|
34
|
+
|
|
35
|
+
full_prompt = {
|
|
36
|
+
**self.client_props,
|
|
37
|
+
**prompt.model_dump(exclude_unset=True, exclude_none=True),
|
|
38
|
+
"messages": messages_dict,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if tools:
|
|
42
|
+
full_prompt["tools"] = tools
|
|
43
|
+
|
|
44
|
+
return full_prompt
|
|
45
|
+
|
|
46
|
+
def completion(self, prompt: ChatCompletionRequest):
|
|
47
|
+
full_prompt = self._prep_prompt(prompt)
|
|
48
|
+
|
|
49
|
+
completion = litellm.completion(**full_prompt)
|
|
50
|
+
response = ChatCompletionResponse.from_litellm_completion(completion)
|
|
51
|
+
return response
|
|
52
|
+
|
|
53
|
+
async def async_completion(self, prompt: ChatCompletionRequest):
|
|
54
|
+
full_prompt = self._prep_prompt(prompt)
|
|
55
|
+
|
|
56
|
+
completion = await litellm.acompletion(**full_prompt)
|
|
57
|
+
response = ChatCompletionResponse.from_litellm_completion(completion)
|
|
58
|
+
return response
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def model(self) -> str:
|
|
62
|
+
return self._model
|
|
63
|
+
|
|
64
|
+
def get_config(self) -> dict:
|
|
65
|
+
return self.client_props
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class LiteLLMResolver(DependencyResolver[LLM]):
|
|
69
|
+
memoize_resolution: bool = False
|
|
70
|
+
|
|
71
|
+
def __init__(self, model: str, conf: dict = {}):
|
|
72
|
+
super().__init__()
|
|
73
|
+
conf["model"] = model
|
|
74
|
+
self.props = LiteLLMConf.model_validate(conf)
|
|
75
|
+
self.LiteLLM = LiteLLM(self.props)
|
|
76
|
+
|
|
77
|
+
def resolve(self, guild_id: str, agent_id: str) -> LLM:
|
|
78
|
+
return self.LiteLLM # We can always return the same instance of LiteLLM
|