lionagi 0.0.115__py3-none-any.whl → 0.0.204__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- lionagi/__init__.py +1 -2
- lionagi/_services/__init__.py +5 -0
- lionagi/_services/anthropic.py +79 -0
- lionagi/_services/base_service.py +414 -0
- lionagi/_services/oai.py +98 -0
- lionagi/_services/openrouter.py +44 -0
- lionagi/_services/services.py +91 -0
- lionagi/_services/transformers.py +46 -0
- lionagi/bridge/langchain.py +26 -16
- lionagi/bridge/llama_index.py +50 -20
- lionagi/configs/oai_configs.py +2 -14
- lionagi/configs/openrouter_configs.py +2 -2
- lionagi/core/__init__.py +7 -8
- lionagi/core/branch/branch.py +589 -0
- lionagi/core/branch/branch_manager.py +139 -0
- lionagi/core/branch/conversation.py +484 -0
- lionagi/core/core_util.py +59 -0
- lionagi/core/flow/flow.py +19 -0
- lionagi/core/flow/flow_util.py +62 -0
- lionagi/core/instruction_set/__init__.py +0 -5
- lionagi/core/instruction_set/instruction_set.py +343 -0
- lionagi/core/messages/messages.py +176 -0
- lionagi/core/sessions/__init__.py +0 -5
- lionagi/core/sessions/session.py +428 -0
- lionagi/loaders/chunker.py +51 -47
- lionagi/loaders/load_util.py +2 -2
- lionagi/loaders/reader.py +45 -39
- lionagi/models/imodel.py +53 -0
- lionagi/schema/async_queue.py +158 -0
- lionagi/schema/base_node.py +318 -147
- lionagi/schema/base_tool.py +31 -1
- lionagi/schema/data_logger.py +74 -38
- lionagi/schema/data_node.py +57 -6
- lionagi/structures/graph.py +132 -10
- lionagi/structures/relationship.py +58 -20
- lionagi/structures/structure.py +36 -25
- lionagi/tests/test_utils/test_api_util.py +219 -0
- lionagi/tests/test_utils/test_call_util.py +785 -0
- lionagi/tests/test_utils/test_encrypt_util.py +323 -0
- lionagi/tests/test_utils/test_io_util.py +238 -0
- lionagi/tests/test_utils/test_nested_util.py +338 -0
- lionagi/tests/test_utils/test_sys_util.py +358 -0
- lionagi/tools/tool_manager.py +186 -0
- lionagi/tools/tool_util.py +266 -3
- lionagi/utils/__init__.py +21 -61
- lionagi/utils/api_util.py +359 -71
- lionagi/utils/call_util.py +839 -264
- lionagi/utils/encrypt_util.py +283 -16
- lionagi/utils/io_util.py +178 -93
- lionagi/utils/nested_util.py +672 -0
- lionagi/utils/pd_util.py +57 -0
- lionagi/utils/sys_util.py +284 -156
- lionagi/utils/url_util.py +55 -0
- lionagi/version.py +1 -1
- {lionagi-0.0.115.dist-info → lionagi-0.0.204.dist-info}/METADATA +21 -17
- lionagi-0.0.204.dist-info/RECORD +106 -0
- lionagi/core/conversations/__init__.py +0 -5
- lionagi/core/conversations/conversation.py +0 -107
- lionagi/core/flows/__init__.py +0 -8
- lionagi/core/flows/flow.py +0 -8
- lionagi/core/flows/flow_util.py +0 -62
- lionagi/core/instruction_set/instruction_sets.py +0 -7
- lionagi/core/sessions/sessions.py +0 -185
- lionagi/endpoints/__init__.py +0 -5
- lionagi/endpoints/audio.py +0 -17
- lionagi/endpoints/chatcompletion.py +0 -54
- lionagi/messages/__init__.py +0 -11
- lionagi/messages/instruction.py +0 -15
- lionagi/messages/message.py +0 -110
- lionagi/messages/response.py +0 -33
- lionagi/messages/system.py +0 -12
- lionagi/objs/__init__.py +0 -11
- lionagi/objs/abc_objs.py +0 -39
- lionagi/objs/async_queue.py +0 -135
- lionagi/objs/messenger.py +0 -85
- lionagi/objs/tool_manager.py +0 -253
- lionagi/services/__init__.py +0 -11
- lionagi/services/base_api_service.py +0 -230
- lionagi/services/oai.py +0 -34
- lionagi/services/openrouter.py +0 -31
- lionagi/tests/test_api_util.py +0 -46
- lionagi/tests/test_call_util.py +0 -115
- lionagi/tests/test_convert_util.py +0 -202
- lionagi/tests/test_encrypt_util.py +0 -33
- lionagi/tests/test_flat_util.py +0 -426
- lionagi/tests/test_sys_util.py +0 -0
- lionagi/utils/convert_util.py +0 -229
- lionagi/utils/flat_util.py +0 -599
- lionagi-0.0.115.dist-info/RECORD +0 -110
- /lionagi/{services → _services}/anyscale.py +0 -0
- /lionagi/{services → _services}/azure.py +0 -0
- /lionagi/{services → _services}/bedrock.py +0 -0
- /lionagi/{services → _services}/everlyai.py +0 -0
- /lionagi/{services → _services}/gemini.py +0 -0
- /lionagi/{services → _services}/gpt4all.py +0 -0
- /lionagi/{services → _services}/huggingface.py +0 -0
- /lionagi/{services → _services}/litellm.py +0 -0
- /lionagi/{services → _services}/localai.py +0 -0
- /lionagi/{services → _services}/mistralai.py +0 -0
- /lionagi/{services → _services}/ollama.py +0 -0
- /lionagi/{services → _services}/openllm.py +0 -0
- /lionagi/{services → _services}/perplexity.py +0 -0
- /lionagi/{services → _services}/predibase.py +0 -0
- /lionagi/{services → _services}/rungpt.py +0 -0
- /lionagi/{services → _services}/vllm.py +0 -0
- /lionagi/{services → _services}/xinference.py +0 -0
- /lionagi/{endpoints/assistants.py → agents/__init__.py} +0 -0
- /lionagi/{tools → agents}/planner.py +0 -0
- /lionagi/{tools → agents}/prompter.py +0 -0
- /lionagi/{tools → agents}/scorer.py +0 -0
- /lionagi/{tools → agents}/summarizer.py +0 -0
- /lionagi/{tools → agents}/validator.py +0 -0
- /lionagi/{endpoints/embeddings.py → core/branch/__init__.py} +0 -0
- /lionagi/{services/anthropic.py → core/branch/cluster.py} +0 -0
- /lionagi/{endpoints/finetune.py → core/flow/__init__.py} +0 -0
- /lionagi/{endpoints/image.py → core/messages/__init__.py} +0 -0
- /lionagi/{endpoints/moderation.py → models/__init__.py} +0 -0
- /lionagi/{endpoints/vision.py → models/base_model.py} +0 -0
- /lionagi/{objs → schema}/status_tracker.py +0 -0
- /lionagi/tests/{test_io_util.py → test_utils/__init__.py} +0 -0
- {lionagi-0.0.115.dist-info → lionagi-0.0.204.dist-info}/LICENSE +0 -0
- {lionagi-0.0.115.dist-info → lionagi-0.0.204.dist-info}/WHEEL +0 -0
- {lionagi-0.0.115.dist-info → lionagi-0.0.204.dist-info}/top_level.txt +0 -0
lionagi/__init__.py
CHANGED
@@ -0,0 +1,79 @@
|
|
1
|
+
from os import getenv
|
2
|
+
from .base_service import BaseService, PayloadCreation
|
3
|
+
|
4
|
+
class AnthropicService(BaseService):
|
5
|
+
"""
|
6
|
+
A service to interact with Anthropic's API endpoints.
|
7
|
+
|
8
|
+
Attributes:
|
9
|
+
base_url (str): The base URL for the Anthropic API.
|
10
|
+
available_endpoints (list): A list of available API endpoints.
|
11
|
+
schema (dict): The schema configuration for the API.
|
12
|
+
key_scheme (str): The environment variable name for Anthropic API key.
|
13
|
+
token_encoding_name (str): The default token encoding scheme.
|
14
|
+
|
15
|
+
Examples:
|
16
|
+
>>> service = AnthropicService(api_key="your_api_key")
|
17
|
+
>>> asyncio.run(service.serve("Hello, world!", "chat/completions"))
|
18
|
+
(payload, completion)
|
19
|
+
"""
|
20
|
+
|
21
|
+
base_url = "https://api.anthropic.com/v1/"
|
22
|
+
available_endpoints = ['chat/completions']
|
23
|
+
schema = {} # TODO
|
24
|
+
key_scheme = "ANTHROPIC_API_KEY"
|
25
|
+
token_encoding_name = "cl100k_base"
|
26
|
+
|
27
|
+
def __init__(self, api_key = None, key_scheme = None,schema = None, token_encoding_name: str = "cl100k_base", **kwargs):
|
28
|
+
key_scheme = key_scheme or self.key_scheme
|
29
|
+
super().__init__(
|
30
|
+
api_key = api_key or getenv(key_scheme),
|
31
|
+
schema = schema or self.schema,
|
32
|
+
token_encoding_name=token_encoding_name,
|
33
|
+
**kwargs
|
34
|
+
)
|
35
|
+
self.active_endpoint = []
|
36
|
+
|
37
|
+
async def serve(self, input_, endpoint="chat/completions", method="post", **kwargs):
|
38
|
+
"""
|
39
|
+
Serves the input using the specified endpoint and method.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
input_: The input text to be processed.
|
43
|
+
endpoint: The API endpoint to use for processing.
|
44
|
+
method: The HTTP method to use for the request.
|
45
|
+
**kwargs: Additional keyword arguments to pass to the payload creation.
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
A tuple containing the payload and the completion response from the API.
|
49
|
+
"""
|
50
|
+
if endpoint not in self.active_endpoint:
|
51
|
+
await self. init_endpoint(endpoint)
|
52
|
+
if endpoint == "chat/completions":
|
53
|
+
return await self.serve_chat(input_, **kwargs)
|
54
|
+
else:
|
55
|
+
return ValueError(f'{endpoint} is currently not supported')
|
56
|
+
|
57
|
+
async def serve_chat(self, messages, **kwargs):
|
58
|
+
"""
|
59
|
+
Serves the chat completion request with the given messages.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
messages: The messages to be included in the chat completion.
|
63
|
+
**kwargs: Additional keyword arguments for payload creation.
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
A tuple containing the payload and the completion response from the API.
|
67
|
+
"""
|
68
|
+
if "chat/completions" not in self.active_endpoint:
|
69
|
+
await self. init_endpoint("chat/completions")
|
70
|
+
self.active_endpoint.append("chat/completions")
|
71
|
+
payload = PayloadCreation.chat_completion(
|
72
|
+
messages, self.endpoints["chat/completions"].config, self.schema["chat/completions"], **kwargs)
|
73
|
+
|
74
|
+
try:
|
75
|
+
completion = await self.call_api(payload, "chat/completions", "post")
|
76
|
+
return payload, completion
|
77
|
+
except Exception as e:
|
78
|
+
self.status_tracker.num_tasks_failed += 1
|
79
|
+
raise e
|
@@ -0,0 +1,414 @@
|
|
1
|
+
import asyncio
|
2
|
+
import logging
|
3
|
+
import aiohttp
|
4
|
+
from abc import ABC
|
5
|
+
from dataclasses import dataclass
|
6
|
+
from typing import Any, Dict, NoReturn, Optional, Type, List, Union
|
7
|
+
|
8
|
+
from ..utils import nget, APIUtil
|
9
|
+
|
10
|
+
|
11
|
+
@dataclass
|
12
|
+
class StatusTracker:
|
13
|
+
"""
|
14
|
+
Keeps track of various task statuses within a system.
|
15
|
+
|
16
|
+
Attributes:
|
17
|
+
num_tasks_started (int): The number of tasks that have been initiated.
|
18
|
+
num_tasks_in_progress (int): The number of tasks currently being processed.
|
19
|
+
num_tasks_succeeded (int): The number of tasks that have completed successfully.
|
20
|
+
num_tasks_failed (int): The number of tasks that have failed.
|
21
|
+
num_rate_limit_errors (int): The number of tasks that failed due to rate limiting.
|
22
|
+
num_api_errors (int): The number of tasks that failed due to API errors.
|
23
|
+
num_other_errors (int): The number of tasks that failed due to other errors.
|
24
|
+
|
25
|
+
Examples:
|
26
|
+
>>> tracker = StatusTracker()
|
27
|
+
>>> tracker.num_tasks_started += 1
|
28
|
+
>>> tracker.num_tasks_succeeded += 1
|
29
|
+
"""
|
30
|
+
num_tasks_started: int = 0
|
31
|
+
num_tasks_in_progress: int = 0
|
32
|
+
num_tasks_succeeded: int = 0
|
33
|
+
num_tasks_failed: int = 0
|
34
|
+
num_rate_limit_errors: int = 0
|
35
|
+
num_api_errors: int = 0
|
36
|
+
num_other_errors: int = 0
|
37
|
+
|
38
|
+
|
39
|
+
class BaseRateLimiter(ABC):
|
40
|
+
"""
|
41
|
+
Abstract base class for implementing rate limiters.
|
42
|
+
|
43
|
+
This class provides the basic structure for rate limiters, including
|
44
|
+
the replenishment of request and token capacities at regular intervals.
|
45
|
+
|
46
|
+
Attributes:
|
47
|
+
interval: The time interval in seconds for replenishing capacities.
|
48
|
+
max_requests: The maximum number of requests allowed per interval.
|
49
|
+
max_tokens: The maximum number of tokens allowed per interval.
|
50
|
+
available_request_capacity: The current available request capacity.
|
51
|
+
available_token_capacity: The current available token capacity.
|
52
|
+
rate_limit_replenisher_task: The asyncio task for replenishing capacities.
|
53
|
+
"""
|
54
|
+
|
55
|
+
def __init__(self, max_requests: int, max_tokens: int, interval: int = 60, token_encoding_name=None) -> None:
|
56
|
+
self.interval: int = interval
|
57
|
+
self.max_requests: int = max_requests
|
58
|
+
self.max_tokens: int = max_tokens
|
59
|
+
self.available_request_capacity: int = max_requests
|
60
|
+
self.available_token_capacity: int = max_tokens
|
61
|
+
self.rate_limit_replenisher_task: Optional[asyncio.Task[NoReturn]] = None
|
62
|
+
self._stop_replenishing: asyncio.Event = asyncio.Event()
|
63
|
+
self._lock: asyncio.Lock = asyncio.Lock()
|
64
|
+
self.token_encoding_name = token_encoding_name
|
65
|
+
|
66
|
+
async def start_replenishing(self) -> NoReturn:
|
67
|
+
"""Starts the replenishment of rate limit capacities at regular intervals."""
|
68
|
+
try:
|
69
|
+
while not self._stop_replenishing.is_set():
|
70
|
+
await asyncio.sleep(self.interval)
|
71
|
+
async with self._lock:
|
72
|
+
self.available_request_capacity = self.max_requests
|
73
|
+
self.available_token_capacity = self.max_tokens
|
74
|
+
except asyncio.CancelledError:
|
75
|
+
logging.info("Rate limit replenisher task cancelled.")
|
76
|
+
except Exception as e:
|
77
|
+
logging.error(f"An error occurred in the rate limit replenisher: {e}")
|
78
|
+
|
79
|
+
async def stop_replenishing(self) -> None:
|
80
|
+
"""Stops the replenishment task."""
|
81
|
+
if self.rate_limit_replenisher_task:
|
82
|
+
self.rate_limit_replenisher_task.cancel()
|
83
|
+
await self.rate_limit_replenisher_task
|
84
|
+
self._stop_replenishing.set()
|
85
|
+
|
86
|
+
async def request_permission(self, required_tokens) -> bool:
|
87
|
+
"""Requests permission to make an API call.
|
88
|
+
|
89
|
+
Returns True if the request can be made immediately, otherwise False.
|
90
|
+
"""
|
91
|
+
async with self._lock:
|
92
|
+
if self.available_request_capacity > 0 and self.available_token_capacity > 0:
|
93
|
+
self.available_request_capacity -= 1
|
94
|
+
self.available_token_capacity -= required_tokens # Assuming 1 token per request for simplicity
|
95
|
+
return True
|
96
|
+
return False
|
97
|
+
|
98
|
+
async def _call_api(
|
99
|
+
self,
|
100
|
+
http_session,
|
101
|
+
endpoint: str,
|
102
|
+
base_url: str,
|
103
|
+
api_key: str,
|
104
|
+
max_attempts: int = 3,
|
105
|
+
method: str = "post",
|
106
|
+
payload: Dict[str, any]=None
|
107
|
+
) -> Optional[Dict[str, any]]:
|
108
|
+
"""
|
109
|
+
Makes an API call to the specified endpoint using the provided HTTP session.
|
110
|
+
|
111
|
+
Args:
|
112
|
+
http_session: The aiohttp client session to use for the API call.
|
113
|
+
endpoint: The API endpoint to call.
|
114
|
+
base_url: The base URL of the API.
|
115
|
+
api_key: The API key for authentication.
|
116
|
+
max_attempts: The maximum number of attempts for the API call.
|
117
|
+
method: The HTTP method to use for the API call.
|
118
|
+
payload: The payload to send with the API call.
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
The JSON response from the API call if successful, otherwise None.
|
122
|
+
"""
|
123
|
+
endpoint = APIUtil.api_endpoint_from_url(base_url + endpoint)
|
124
|
+
while True:
|
125
|
+
if self.available_request_capacity < 1 or self.available_token_capacity < 10: # Minimum token count
|
126
|
+
await asyncio.sleep(1) # Wait for capacity
|
127
|
+
continue
|
128
|
+
required_tokens = APIUtil.calculate_num_token(payload, endpoint, self.token_encoding_name)
|
129
|
+
|
130
|
+
if await self.request_permission(required_tokens):
|
131
|
+
request_headers = {"Authorization": f"Bearer {api_key}"}
|
132
|
+
attempts_left = max_attempts
|
133
|
+
|
134
|
+
while attempts_left > 0:
|
135
|
+
try:
|
136
|
+
method = APIUtil.api_method(http_session, method)
|
137
|
+
async with method(
|
138
|
+
url=(base_url+endpoint), headers=request_headers, json=payload
|
139
|
+
) as response:
|
140
|
+
response_json = await response.json()
|
141
|
+
|
142
|
+
if "error" in response_json:
|
143
|
+
logging.warning(
|
144
|
+
f"API call failed with error: {response_json['error']}"
|
145
|
+
)
|
146
|
+
attempts_left -= 1
|
147
|
+
|
148
|
+
if "Rate limit" in response_json["error"].get("message", ""):
|
149
|
+
await asyncio.sleep(15)
|
150
|
+
else:
|
151
|
+
return response_json
|
152
|
+
except Exception as e:
|
153
|
+
logging.warning(f"API call failed with exception: {e}")
|
154
|
+
attempts_left -= 1
|
155
|
+
|
156
|
+
logging.error("API call failed after all attempts.")
|
157
|
+
break
|
158
|
+
else:
|
159
|
+
await asyncio.sleep(1)
|
160
|
+
|
161
|
+
@classmethod
|
162
|
+
async def create(cls, max_requests: int, max_tokens: int, interval: int = 60, token_encoding_name = None) -> 'BaseRateLimiter':
|
163
|
+
"""
|
164
|
+
Creates an instance of BaseRateLimiter and starts the replenisher task.
|
165
|
+
|
166
|
+
Args:
|
167
|
+
max_requests: The maximum number of requests allowed per interval.
|
168
|
+
max_tokens: The maximum number of tokens allowed per interval.
|
169
|
+
interval: The time interval in seconds for replenishing capacities.
|
170
|
+
token_encoding_name: The name of the token encoding to use.
|
171
|
+
|
172
|
+
Returns:
|
173
|
+
An instance of BaseRateLimiter with the replenisher task started.
|
174
|
+
"""
|
175
|
+
instance = cls(max_requests, max_tokens, interval, token_encoding_name)
|
176
|
+
instance.rate_limit_replenisher_task = asyncio.create_task(
|
177
|
+
instance.start_replenishing()
|
178
|
+
)
|
179
|
+
return instance
|
180
|
+
|
181
|
+
|
182
|
+
class SimpleRateLimiter(BaseRateLimiter):
|
183
|
+
"""
|
184
|
+
A simple implementation of a rate limiter.
|
185
|
+
|
186
|
+
Inherits from BaseRateLimiter and provides a basic rate limiting mechanism.
|
187
|
+
"""
|
188
|
+
|
189
|
+
def __init__(self, max_requests: int, max_tokens: int, interval: int = 60, token_encoding_name=None) -> None:
|
190
|
+
"""Initializes the SimpleRateLimiter with the specified parameters."""
|
191
|
+
super().__init__(max_requests, max_tokens, interval, token_encoding_name)
|
192
|
+
|
193
|
+
|
194
|
+
class EndPoint:
|
195
|
+
"""
|
196
|
+
Represents an API endpoint with rate limiting capabilities.
|
197
|
+
|
198
|
+
This class encapsulates the details of an API endpoint, including its rate limiter.
|
199
|
+
|
200
|
+
Attributes:
|
201
|
+
endpoint (str): The API endpoint path.
|
202
|
+
rate_limiter_class (Type[li.BaseRateLimiter]): The class used for rate limiting requests to the endpoint.
|
203
|
+
max_requests (int): The maximum number of requests allowed per interval.
|
204
|
+
max_tokens (int): The maximum number of tokens allowed per interval.
|
205
|
+
interval (int): The time interval in seconds for replenishing rate limit capacities.
|
206
|
+
config (Dict): Configuration parameters for the endpoint.
|
207
|
+
rate_limiter (Optional[li.BaseRateLimiter]): The rate limiter instance for this endpoint.
|
208
|
+
|
209
|
+
Examples:
|
210
|
+
# Example usage of EndPoint with SimpleRateLimiter
|
211
|
+
endpoint = EndPoint(
|
212
|
+
max_requests=100,
|
213
|
+
max_tokens=1000,
|
214
|
+
interval=60,
|
215
|
+
endpoint_='chat/completions',
|
216
|
+
rate_limiter_class=li.SimpleRateLimiter,
|
217
|
+
config={'param1': 'value1'}
|
218
|
+
)
|
219
|
+
asyncio.run(endpoint.init_rate_limiter())
|
220
|
+
"""
|
221
|
+
|
222
|
+
def __init__(
|
223
|
+
self,
|
224
|
+
max_requests: int = 1000,
|
225
|
+
max_tokens: int = 100000,
|
226
|
+
interval: int = 60,
|
227
|
+
endpoint_: Optional[str] = None,
|
228
|
+
rate_limiter_class: Type[BaseRateLimiter] = SimpleRateLimiter,
|
229
|
+
token_encoding_name=None,
|
230
|
+
config: Dict = None,
|
231
|
+
) -> None:
|
232
|
+
self.endpoint = endpoint_ or 'chat/completions'
|
233
|
+
self.rate_limiter_class = rate_limiter_class
|
234
|
+
self.max_requests = max_requests
|
235
|
+
self.max_tokens = max_tokens
|
236
|
+
self.interval = interval
|
237
|
+
self.token_encoding_name = token_encoding_name
|
238
|
+
self.config = config or {}
|
239
|
+
self.rate_limiter: Optional[BaseRateLimiter] = None
|
240
|
+
self._has_initialized = False
|
241
|
+
|
242
|
+
async def init_rate_limiter(self) -> None:
|
243
|
+
"""Initializes the rate limiter for the endpoint."""
|
244
|
+
self.rate_limiter = await self.rate_limiter_class.create(
|
245
|
+
self.max_requests, self.max_tokens, self.interval, self.token_encoding_name
|
246
|
+
)
|
247
|
+
self._has_initialized = True
|
248
|
+
|
249
|
+
|
250
|
+
class BaseService:
|
251
|
+
"""
|
252
|
+
Base class for services that interact with API endpoints.
|
253
|
+
|
254
|
+
This class provides a foundation for services that need to make API calls with rate limiting.
|
255
|
+
|
256
|
+
Attributes:
|
257
|
+
api_key (Optional[str]): The API key used for authentication.
|
258
|
+
schema (Dict[str, Any]): The schema defining the service's endpoints.
|
259
|
+
status_tracker (StatusTracker): The object tracking the status of API calls.
|
260
|
+
endpoints (Dict[str, EndPoint]): A dictionary of endpoint objects.
|
261
|
+
"""
|
262
|
+
|
263
|
+
base_url: str = ''
|
264
|
+
available_endpoints: list = []
|
265
|
+
|
266
|
+
def __init__(
|
267
|
+
self,
|
268
|
+
api_key: Optional[str] = None,
|
269
|
+
schema: Dict[str, Any] = None,
|
270
|
+
token_encoding_name: str = None,
|
271
|
+
max_tokens : int = 100_000,
|
272
|
+
max_requests : int = 1_000,
|
273
|
+
interval: int = 60
|
274
|
+
) -> None:
|
275
|
+
self.api_key = api_key
|
276
|
+
self.schema = schema or {}
|
277
|
+
self.status_tracker = StatusTracker()
|
278
|
+
self.endpoints: Dict[str, EndPoint] = {}
|
279
|
+
self.token_encoding_name = token_encoding_name
|
280
|
+
self.chat_config_rate_limit = {
|
281
|
+
'max_requests': max_requests,
|
282
|
+
'max_tokens': max_tokens,
|
283
|
+
'interval': interval
|
284
|
+
}
|
285
|
+
|
286
|
+
|
287
|
+
async def init_endpoint(self, endpoint_: Optional[Union[List[str], List[EndPoint], str, EndPoint]] = None) -> None:
|
288
|
+
"""
|
289
|
+
Initializes the specified endpoint or all endpoints if none is specified.
|
290
|
+
|
291
|
+
Args:
|
292
|
+
endpoint_: The endpoint(s) to initialize. Can be a string, an EndPoint, a list of strings, or a list of EndPoints.
|
293
|
+
"""
|
294
|
+
|
295
|
+
if endpoint_:
|
296
|
+
if not isinstance(endpoint_, list):
|
297
|
+
endpoint_ = [endpoint_]
|
298
|
+
for ep in endpoint_:
|
299
|
+
if ep not in self.available_endpoints:
|
300
|
+
raise ValueError (f"Endpoint {ep} not available for service {self.__class__.__name__}")
|
301
|
+
|
302
|
+
if ep not in self.endpoints:
|
303
|
+
endpoint_config = nget(self.schema, [ep, 'config'])
|
304
|
+
self.schema.get(ep, {})
|
305
|
+
if isinstance(ep, EndPoint):
|
306
|
+
self.endpoints[ep.endpoint] = ep
|
307
|
+
else:
|
308
|
+
if ep == "chat/completions":
|
309
|
+
self.endpoints[ep] = EndPoint(
|
310
|
+
max_requests=self.chat_config_rate_limit.get('max_requests', 1000),
|
311
|
+
max_tokens=self.chat_config_rate_limit.get('max_tokens', 100000),
|
312
|
+
interval=self.chat_config_rate_limit.get('interval', 60),
|
313
|
+
endpoint_=ep,
|
314
|
+
token_encoding_name=self.token_encoding_name,
|
315
|
+
config=endpoint_config,
|
316
|
+
)
|
317
|
+
else:
|
318
|
+
self.endpoints[ep] = EndPoint(
|
319
|
+
max_requests=endpoint_config.get('max_requests', 1000) if endpoint_config.get('max_requests', 1000) is not None else 1000,
|
320
|
+
max_tokens=endpoint_config.get('max_tokens', 100000) if endpoint_config.get('max_tokens', 100000) is not None else 100000,
|
321
|
+
interval=endpoint_config.get('interval', 60) if endpoint_config.get('interval', 60) is not None else 60,
|
322
|
+
endpoint_=ep,
|
323
|
+
token_encoding_name=self.token_encoding_name,
|
324
|
+
config=endpoint_config,
|
325
|
+
)
|
326
|
+
|
327
|
+
if not self.endpoints[ep]._has_initialized:
|
328
|
+
await self.endpoints[ep].init_rate_limiter()
|
329
|
+
|
330
|
+
else:
|
331
|
+
for ep in self.available_endpoints:
|
332
|
+
endpoint_config = nget(self.schema, [ep, 'config'])
|
333
|
+
self.schema.get(ep, {})
|
334
|
+
if ep not in self.endpoints:
|
335
|
+
self.endpoints[ep] = EndPoint(
|
336
|
+
max_requests=endpoint_config.get('max_requests', 1000),
|
337
|
+
max_tokens=endpoint_config.get('max_tokens', 100000),
|
338
|
+
interval=endpoint_config.get('interval', 60),
|
339
|
+
endpoint_=ep,
|
340
|
+
token_encoding_name=self.token_encoding_name,
|
341
|
+
config=endpoint_config,
|
342
|
+
)
|
343
|
+
if not self.endpoints[ep]._has_initialized:
|
344
|
+
await self.endpoints[ep].init_rate_limiter()
|
345
|
+
|
346
|
+
async def call_api(self, payload, endpoint, method):
|
347
|
+
"""
|
348
|
+
Calls the specified API endpoint with the given payload and method.
|
349
|
+
|
350
|
+
Args:
|
351
|
+
payload: The payload to send with the API call.
|
352
|
+
endpoint: The endpoint to call.
|
353
|
+
method: The HTTP method to use for the call.
|
354
|
+
|
355
|
+
Returns:
|
356
|
+
The response from the API call.
|
357
|
+
|
358
|
+
Raises:
|
359
|
+
ValueError: If the endpoint has not been initialized.
|
360
|
+
"""
|
361
|
+
if endpoint not in self.endpoints.keys():
|
362
|
+
raise ValueError(f'The endpoint {endpoint} has not initialized.')
|
363
|
+
async with aiohttp.ClientSession() as http_session:
|
364
|
+
completion = await self.endpoints[endpoint].rate_limiter._call_api(
|
365
|
+
http_session=http_session, endpoint=endpoint, base_url=self.base_url, api_key=self.api_key,
|
366
|
+
method=method, payload=payload)
|
367
|
+
return completion
|
368
|
+
|
369
|
+
|
370
|
+
class PayloadCreation:
|
371
|
+
|
372
|
+
@classmethod
|
373
|
+
def chat_completion(cls, messages, llmconfig, schema, **kwargs):
|
374
|
+
"""
|
375
|
+
Creates a payload for the chat completion operation.
|
376
|
+
|
377
|
+
Args:
|
378
|
+
messages: The messages to include in the chat completion.
|
379
|
+
llmconfig: Configuration for the language model.
|
380
|
+
schema: The schema describing required and optional fields.
|
381
|
+
**kwargs: Additional keyword arguments.
|
382
|
+
|
383
|
+
Returns:
|
384
|
+
The constructed payload.
|
385
|
+
"""
|
386
|
+
return APIUtil._create_payload(
|
387
|
+
input_=messages,
|
388
|
+
config=llmconfig,
|
389
|
+
required_=schema['required'],
|
390
|
+
optional_=schema['optional'],
|
391
|
+
input_key="messages",
|
392
|
+
**kwargs)
|
393
|
+
|
394
|
+
@classmethod
|
395
|
+
def fine_tuning(cls, training_file, llmconfig, schema, **kwargs):
|
396
|
+
"""
|
397
|
+
Creates a payload for the fine-tuning operation.
|
398
|
+
|
399
|
+
Args:
|
400
|
+
training_file: The file containing training data.
|
401
|
+
llmconfig: Configuration for the language model.
|
402
|
+
schema: The schema describing required and optional fields.
|
403
|
+
**kwargs: Additional keyword arguments.
|
404
|
+
|
405
|
+
Returns:
|
406
|
+
The constructed payload.
|
407
|
+
"""
|
408
|
+
return APIUtil._create_payload(
|
409
|
+
input_=training_file,
|
410
|
+
config=llmconfig,
|
411
|
+
required_=schema['required'],
|
412
|
+
optional_=schema['optional'],
|
413
|
+
input_key="training_file",
|
414
|
+
**kwargs)
|
lionagi/_services/oai.py
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
from os import getenv
|
2
|
+
from ..configs.oai_configs import oai_schema
|
3
|
+
from .base_service import BaseService, PayloadCreation
|
4
|
+
|
5
|
+
class OpenAIService(BaseService):
|
6
|
+
"""
|
7
|
+
A service to interact with OpenAI's API endpoints.
|
8
|
+
|
9
|
+
Attributes:
|
10
|
+
base_url (str): The base URL for the OpenAI API.
|
11
|
+
available_endpoints (list): A list of available API endpoints.
|
12
|
+
schema (dict): The schema configuration for the API.
|
13
|
+
key_scheme (str): The environment variable name for OpenAI API key.
|
14
|
+
token_encoding_name (str): The default token encoding scheme.
|
15
|
+
|
16
|
+
Examples:
|
17
|
+
>>> service = OpenAIService(api_key="your_api_key")
|
18
|
+
>>> asyncio.run(service.serve("Hello, world!", "chat/completions"))
|
19
|
+
(payload, completion)
|
20
|
+
|
21
|
+
>>> service = OpenAIService()
|
22
|
+
>>> asyncio.run(service.serve("Convert this text to speech.", "audio_speech"))
|
23
|
+
"""
|
24
|
+
|
25
|
+
base_url = "https://api.openai.com/v1/"
|
26
|
+
available_endpoints = ['chat/completions', 'finetune', 'audio_speech', 'audio_transcriptions', 'audio_translations']
|
27
|
+
schema = oai_schema
|
28
|
+
key_scheme = "OPENAI_API_KEY"
|
29
|
+
token_encoding_name = "cl100k_base"
|
30
|
+
|
31
|
+
def __init__(self, api_key = None, key_scheme = None,schema = None, token_encoding_name: str = "cl100k_base", **kwargs):
|
32
|
+
key_scheme = key_scheme or self.key_scheme
|
33
|
+
super().__init__(
|
34
|
+
api_key = api_key or getenv(key_scheme),
|
35
|
+
schema = schema or self.schema,
|
36
|
+
token_encoding_name=token_encoding_name,
|
37
|
+
**kwargs
|
38
|
+
)
|
39
|
+
self.active_endpoint = []
|
40
|
+
|
41
|
+
async def serve(self, input_, endpoint="chat/completions", method="post", **kwargs):
|
42
|
+
"""
|
43
|
+
Serves the input using the specified endpoint and method.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
input_: The input text to be processed.
|
47
|
+
endpoint: The API endpoint to use for processing.
|
48
|
+
method: The HTTP method to use for the request.
|
49
|
+
**kwargs: Additional keyword arguments to pass to the payload creation.
|
50
|
+
|
51
|
+
Returns:
|
52
|
+
A tuple containing the payload and the completion response from the API.
|
53
|
+
|
54
|
+
Raises:
|
55
|
+
ValueError: If the specified endpoint is not supported.
|
56
|
+
|
57
|
+
Examples:
|
58
|
+
>>> service = OpenAIService(api_key="your_api_key")
|
59
|
+
>>> asyncio.run(service.serve("Hello, world!", "chat/completions"))
|
60
|
+
(payload, completion)
|
61
|
+
|
62
|
+
>>> service = OpenAIService()
|
63
|
+
>>> asyncio.run(service.serve("Convert this text to speech.", "audio_speech"))
|
64
|
+
ValueError: 'audio_speech' is currently not supported
|
65
|
+
"""
|
66
|
+
if endpoint not in self.active_endpoint:
|
67
|
+
await self. init_endpoint(endpoint)
|
68
|
+
if endpoint == "chat/completions":
|
69
|
+
return await self.serve_chat(input_, **kwargs)
|
70
|
+
else:
|
71
|
+
return ValueError(f'{endpoint} is currently not supported')
|
72
|
+
|
73
|
+
async def serve_chat(self, messages, **kwargs):
|
74
|
+
"""
|
75
|
+
Serves the chat completion request with the given messages.
|
76
|
+
|
77
|
+
Args:
|
78
|
+
messages: The messages to be included in the chat completion.
|
79
|
+
**kwargs: Additional keyword arguments for payload creation.
|
80
|
+
|
81
|
+
Returns:
|
82
|
+
A tuple containing the payload and the completion response from the API.
|
83
|
+
|
84
|
+
Raises:
|
85
|
+
Exception: If the API call fails.
|
86
|
+
"""
|
87
|
+
if "chat/completions" not in self.active_endpoint:
|
88
|
+
await self. init_endpoint("chat/completions")
|
89
|
+
self.active_endpoint.append("chat/completions")
|
90
|
+
payload = PayloadCreation.chat_completion(
|
91
|
+
messages, self.endpoints["chat/completions"].config, self.schema["chat/completions"], **kwargs)
|
92
|
+
|
93
|
+
try:
|
94
|
+
completion = await self.call_api(payload, "chat/completions", "post")
|
95
|
+
return payload, completion
|
96
|
+
except Exception as e:
|
97
|
+
self.status_tracker.num_tasks_failed += 1
|
98
|
+
raise e
|
@@ -0,0 +1,44 @@
|
|
1
|
+
from os import getenv
|
2
|
+
from ..configs.openrouter_configs import openrouter_schema
|
3
|
+
from .base_service import BaseService, PayloadCreation
|
4
|
+
|
5
|
+
class OpenRouterService(BaseService):
|
6
|
+
base_url = "https://openrouter.ai/api/v1/"
|
7
|
+
available_endpoints = ['chat/completions']
|
8
|
+
schema = openrouter_schema
|
9
|
+
key_scheme = "OPENROUTER_API_KEY"
|
10
|
+
token_encoding_name = "cl100k_base"
|
11
|
+
|
12
|
+
|
13
|
+
def __init__(self, api_key = None, key_scheme = None,schema = None, token_encoding_name: str = "cl100k_base", **kwargs):
|
14
|
+
key_scheme = key_scheme or self.key_scheme
|
15
|
+
super().__init__(
|
16
|
+
api_key = api_key or getenv(key_scheme),
|
17
|
+
schema = schema or self.schema,
|
18
|
+
token_encoding_name=token_encoding_name, **kwargs
|
19
|
+
)
|
20
|
+
self.active_endpoint = []
|
21
|
+
|
22
|
+
async def serve(self, input_, endpoint="chat/completions", method="post", **kwargs):
|
23
|
+
if endpoint not in self.active_endpoint:
|
24
|
+
await self. init_endpoint(endpoint)
|
25
|
+
if endpoint == "chat/completions":
|
26
|
+
return await self.serve_chat(input_, **kwargs)
|
27
|
+
else:
|
28
|
+
return ValueError(f'{endpoint} is currently not supported')
|
29
|
+
|
30
|
+
async def serve_chat(self, messages, **kwargs):
|
31
|
+
endpoint = "chat/completions"
|
32
|
+
|
33
|
+
if endpoint not in self.active_endpoint:
|
34
|
+
await self. init_endpoint(endpoint)
|
35
|
+
self.active_endpoint.append(endpoint)
|
36
|
+
payload = PayloadCreation.chat_completion(
|
37
|
+
messages, self.endpoints[endpoint].config, self.schema[endpoint], **kwargs)
|
38
|
+
|
39
|
+
try:
|
40
|
+
completion = await self.call_api(payload, endpoint, "post")
|
41
|
+
return payload, completion
|
42
|
+
except Exception as e:
|
43
|
+
self.status_tracker.num_tasks_failed += 1
|
44
|
+
raise e
|