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.
@@ -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,4 @@
1
+ from .agent import LiteLLMAgent
2
+ from .conf import LiteLLMConf
3
+
4
+ __all__ = ["LiteLLMAgent", "LiteLLMConf"]
@@ -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