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.
Files changed (123) hide show
  1. lionagi/__init__.py +1 -2
  2. lionagi/_services/__init__.py +5 -0
  3. lionagi/_services/anthropic.py +79 -0
  4. lionagi/_services/base_service.py +414 -0
  5. lionagi/_services/oai.py +98 -0
  6. lionagi/_services/openrouter.py +44 -0
  7. lionagi/_services/services.py +91 -0
  8. lionagi/_services/transformers.py +46 -0
  9. lionagi/bridge/langchain.py +26 -16
  10. lionagi/bridge/llama_index.py +50 -20
  11. lionagi/configs/oai_configs.py +2 -14
  12. lionagi/configs/openrouter_configs.py +2 -2
  13. lionagi/core/__init__.py +7 -8
  14. lionagi/core/branch/branch.py +589 -0
  15. lionagi/core/branch/branch_manager.py +139 -0
  16. lionagi/core/branch/conversation.py +484 -0
  17. lionagi/core/core_util.py +59 -0
  18. lionagi/core/flow/flow.py +19 -0
  19. lionagi/core/flow/flow_util.py +62 -0
  20. lionagi/core/instruction_set/__init__.py +0 -5
  21. lionagi/core/instruction_set/instruction_set.py +343 -0
  22. lionagi/core/messages/messages.py +176 -0
  23. lionagi/core/sessions/__init__.py +0 -5
  24. lionagi/core/sessions/session.py +428 -0
  25. lionagi/loaders/chunker.py +51 -47
  26. lionagi/loaders/load_util.py +2 -2
  27. lionagi/loaders/reader.py +45 -39
  28. lionagi/models/imodel.py +53 -0
  29. lionagi/schema/async_queue.py +158 -0
  30. lionagi/schema/base_node.py +318 -147
  31. lionagi/schema/base_tool.py +31 -1
  32. lionagi/schema/data_logger.py +74 -38
  33. lionagi/schema/data_node.py +57 -6
  34. lionagi/structures/graph.py +132 -10
  35. lionagi/structures/relationship.py +58 -20
  36. lionagi/structures/structure.py +36 -25
  37. lionagi/tests/test_utils/test_api_util.py +219 -0
  38. lionagi/tests/test_utils/test_call_util.py +785 -0
  39. lionagi/tests/test_utils/test_encrypt_util.py +323 -0
  40. lionagi/tests/test_utils/test_io_util.py +238 -0
  41. lionagi/tests/test_utils/test_nested_util.py +338 -0
  42. lionagi/tests/test_utils/test_sys_util.py +358 -0
  43. lionagi/tools/tool_manager.py +186 -0
  44. lionagi/tools/tool_util.py +266 -3
  45. lionagi/utils/__init__.py +21 -61
  46. lionagi/utils/api_util.py +359 -71
  47. lionagi/utils/call_util.py +839 -264
  48. lionagi/utils/encrypt_util.py +283 -16
  49. lionagi/utils/io_util.py +178 -93
  50. lionagi/utils/nested_util.py +672 -0
  51. lionagi/utils/pd_util.py +57 -0
  52. lionagi/utils/sys_util.py +284 -156
  53. lionagi/utils/url_util.py +55 -0
  54. lionagi/version.py +1 -1
  55. {lionagi-0.0.115.dist-info → lionagi-0.0.204.dist-info}/METADATA +21 -17
  56. lionagi-0.0.204.dist-info/RECORD +106 -0
  57. lionagi/core/conversations/__init__.py +0 -5
  58. lionagi/core/conversations/conversation.py +0 -107
  59. lionagi/core/flows/__init__.py +0 -8
  60. lionagi/core/flows/flow.py +0 -8
  61. lionagi/core/flows/flow_util.py +0 -62
  62. lionagi/core/instruction_set/instruction_sets.py +0 -7
  63. lionagi/core/sessions/sessions.py +0 -185
  64. lionagi/endpoints/__init__.py +0 -5
  65. lionagi/endpoints/audio.py +0 -17
  66. lionagi/endpoints/chatcompletion.py +0 -54
  67. lionagi/messages/__init__.py +0 -11
  68. lionagi/messages/instruction.py +0 -15
  69. lionagi/messages/message.py +0 -110
  70. lionagi/messages/response.py +0 -33
  71. lionagi/messages/system.py +0 -12
  72. lionagi/objs/__init__.py +0 -11
  73. lionagi/objs/abc_objs.py +0 -39
  74. lionagi/objs/async_queue.py +0 -135
  75. lionagi/objs/messenger.py +0 -85
  76. lionagi/objs/tool_manager.py +0 -253
  77. lionagi/services/__init__.py +0 -11
  78. lionagi/services/base_api_service.py +0 -230
  79. lionagi/services/oai.py +0 -34
  80. lionagi/services/openrouter.py +0 -31
  81. lionagi/tests/test_api_util.py +0 -46
  82. lionagi/tests/test_call_util.py +0 -115
  83. lionagi/tests/test_convert_util.py +0 -202
  84. lionagi/tests/test_encrypt_util.py +0 -33
  85. lionagi/tests/test_flat_util.py +0 -426
  86. lionagi/tests/test_sys_util.py +0 -0
  87. lionagi/utils/convert_util.py +0 -229
  88. lionagi/utils/flat_util.py +0 -599
  89. lionagi-0.0.115.dist-info/RECORD +0 -110
  90. /lionagi/{services → _services}/anyscale.py +0 -0
  91. /lionagi/{services → _services}/azure.py +0 -0
  92. /lionagi/{services → _services}/bedrock.py +0 -0
  93. /lionagi/{services → _services}/everlyai.py +0 -0
  94. /lionagi/{services → _services}/gemini.py +0 -0
  95. /lionagi/{services → _services}/gpt4all.py +0 -0
  96. /lionagi/{services → _services}/huggingface.py +0 -0
  97. /lionagi/{services → _services}/litellm.py +0 -0
  98. /lionagi/{services → _services}/localai.py +0 -0
  99. /lionagi/{services → _services}/mistralai.py +0 -0
  100. /lionagi/{services → _services}/ollama.py +0 -0
  101. /lionagi/{services → _services}/openllm.py +0 -0
  102. /lionagi/{services → _services}/perplexity.py +0 -0
  103. /lionagi/{services → _services}/predibase.py +0 -0
  104. /lionagi/{services → _services}/rungpt.py +0 -0
  105. /lionagi/{services → _services}/vllm.py +0 -0
  106. /lionagi/{services → _services}/xinference.py +0 -0
  107. /lionagi/{endpoints/assistants.py → agents/__init__.py} +0 -0
  108. /lionagi/{tools → agents}/planner.py +0 -0
  109. /lionagi/{tools → agents}/prompter.py +0 -0
  110. /lionagi/{tools → agents}/scorer.py +0 -0
  111. /lionagi/{tools → agents}/summarizer.py +0 -0
  112. /lionagi/{tools → agents}/validator.py +0 -0
  113. /lionagi/{endpoints/embeddings.py → core/branch/__init__.py} +0 -0
  114. /lionagi/{services/anthropic.py → core/branch/cluster.py} +0 -0
  115. /lionagi/{endpoints/finetune.py → core/flow/__init__.py} +0 -0
  116. /lionagi/{endpoints/image.py → core/messages/__init__.py} +0 -0
  117. /lionagi/{endpoints/moderation.py → models/__init__.py} +0 -0
  118. /lionagi/{endpoints/vision.py → models/base_model.py} +0 -0
  119. /lionagi/{objs → schema}/status_tracker.py +0 -0
  120. /lionagi/tests/{test_io_util.py → test_utils/__init__.py} +0 -0
  121. {lionagi-0.0.115.dist-info → lionagi-0.0.204.dist-info}/LICENSE +0 -0
  122. {lionagi-0.0.115.dist-info → lionagi-0.0.204.dist-info}/WHEEL +0 -0
  123. {lionagi-0.0.115.dist-info → lionagi-0.0.204.dist-info}/top_level.txt +0 -0
@@ -1,253 +0,0 @@
1
- import json
2
- import asyncio
3
- from typing import Dict
4
- from lionagi.utils import lcall
5
- from lionagi.schema import BaseNode, Tool
6
-
7
-
8
- class ToolManager(BaseNode):
9
- registry: Dict = {}
10
-
11
- def name_existed(self, name: str):
12
- return True if name in self.registry.keys() else False
13
-
14
- def _register_tool(self, tool): #,update=False, new=False, prefix=None, postfix=None):
15
-
16
- # if self._name_existed(tool.name):
17
- # if update and new:
18
- # raise ValueError(f"Cannot both update and create new registry for existing function {tool.name} at the same time")
19
-
20
- # if len(name) > len(tool.func.__name__):
21
- # if new and not postfix:
22
- # try:
23
- # idx = str_to_num(name[-3:], int)
24
- # if idx > 0:
25
- # postfix = idx + 1
26
- # except:
27
- # pass
28
-
29
- # name = f"{prefix or ''}{name}{postfix}" if new else tool.func.__name__
30
-
31
- if not isinstance(tool, Tool):
32
- raise TypeError('Please register a Tool object.')
33
- name = tool.schema_['function']['name']
34
- self.registry.update({name: tool})
35
-
36
- async def invoke(self, func_call):
37
- name, kwargs = func_call
38
- if self.name_existed(name):
39
- tool = self.registry[name]
40
- func = tool.func
41
- parser = tool.parser
42
- try:
43
- if asyncio.iscoroutinefunction(func):
44
- return await parser(func(**kwargs)) if parser else func(**kwargs)
45
- else:
46
- return parser(func(**kwargs)) if parser else func(**kwargs)
47
- except Exception as e:
48
- raise ValueError(f"Error when invoking function {name} with arguments {kwargs} with error message {e}")
49
- else:
50
- raise ValueError(f"Function {name} is not registered.")
51
-
52
- @staticmethod
53
- def get_function_call(response):
54
- """
55
- Extract function name and arguments from a response JSON.
56
-
57
- Parameters:
58
- response (dict): The JSON response containing function information.
59
-
60
- Returns:
61
- Tuple[str, dict]: The function name and its arguments.
62
- """
63
- try:
64
- func = response['function'][5:]
65
- args = json.loads(response['arguments'])
66
- return (func, args)
67
- except:
68
- try:
69
- func = response['recipient_name'].split('.')[-1]
70
- args = response['parameters']
71
- return (func, args)
72
- except:
73
- raise ValueError('response is not a valid function call')
74
-
75
- def register_tools(self, tools): #, update=False, new=False, prefix=None, postfix=None ):
76
- lcall(tools, self._register_tool) #, update=update, new=new, prefix=prefix, postfix=postfix)
77
-
78
- def to_tool_schema_list(self):
79
- schema_list = []
80
- for tool in self.registry.values():
81
- schema_list.append(tool.schema_)
82
- return schema_list
83
-
84
-
85
- # import asyncio
86
- # import json
87
- # from typing import Dict, Any, Optional, List, Tuple
88
-
89
- # from lionagi.schema.base_tool import Tool
90
- # from lionagi.utils.type_util import to_list
91
- # from lionagi.utils.tool_util import func_to_tool
92
-
93
- # class ToolRegistry:
94
- # """
95
- # ToolManager manages the registration and invocation of tools.
96
-
97
- # This class provides functionalities to register tools, check for their existence,
98
- # and invoke them dynamically with specified arguments.
99
-
100
- # Attributes:
101
- # registry (Dict[str, BaseTool]): A dictionary to store registered tools by name.
102
-
103
- # Methods:
104
- # _name_exists: Checks if a tool name already exists in the registry.
105
-
106
- # _register_tool: Registers a tool in the registry.
107
-
108
- # invoke: Dynamically invokes a registered tool with given arguments.
109
-
110
- # register_tools: Registers multiple tools in the registry.
111
- # """
112
-
113
- # def __init__(self):
114
- # """
115
- # Initializes the ToolManager with an empty registry.
116
- # """
117
- # self.registry: Dict[str, Tool] = {}
118
-
119
- # def _name_exists(self, name: str) -> bool:
120
- # """
121
- # Checks if a tool name already exists in the registry.
122
-
123
- # Parameters:
124
- # name (str): The name of the tool to check.
125
-
126
- # Returns:
127
- # bool: True if the name exists in the registry, False otherwise.
128
- # """
129
- # return name in self.registry
130
-
131
- # def _register_tool(self, tool: Tool, name: Optional[str] = None, update: bool = False, new: bool = False, prefix: Optional[str] = None, postfix: Optional[int] = None):
132
- # """
133
- # Registers a tool in the registry.
134
-
135
- # Parameters:
136
- # tool (BaseTool): The tool to be registered.
137
-
138
- # name (Optional[str]): The name to register the tool with. Defaults to the tool's function name.
139
-
140
- # update (bool): If True, updates the existing tool. Defaults to False.
141
-
142
- # new (bool): If True, creates a new registry entry. Defaults to False.
143
-
144
- # prefix (Optional[str]): A prefix for the tool name.
145
-
146
- # postfix (Optional[int]): A postfix for the tool name.
147
-
148
- # Raises:
149
- # ValueError: If both update and new are True for an existing function.
150
- # """
151
- # name = name or tool.func.__name__
152
- # original_name = name
153
-
154
- # if self._name_exists(name):
155
- # if update and new:
156
- # raise ValueError("Cannot both update and create new registry for existing function.")
157
- # if new:
158
- # idx = 1
159
- # while self._name_exists(f"{prefix or ''}{name}{postfix or ''}{idx}"):
160
- # idx += 1
161
- # name = f"{prefix or ''}{name}{postfix or ''}{idx}"
162
- # else:
163
- # self.registry.pop(original_name, None)
164
-
165
- # self.registry[name] = tool
166
-
167
- # async def invoke(self, name_kwargs: Tuple) -> Any:
168
- # """
169
- # Dynamically invokes a registered tool with given arguments.
170
-
171
- # Parameters:
172
- # name (str): The name of the tool to invoke.
173
-
174
- # kwargs (Dict[str, Any]): A dictionary of keyword arguments to pass to the tool.
175
-
176
- # Returns:
177
- # Any: The result of the tool invocation.
178
-
179
- # Raises:
180
- # ValueError: If the tool is not registered or if an error occurs during invocation.
181
- # """
182
- # name, kwargs = name_kwargs
183
- # if not self._name_exists(name):
184
- # raise ValueError(f"Function {name} is not registered.")
185
-
186
- # tool = self.registry[name]
187
- # func = tool.func
188
- # parser = tool.parser
189
-
190
- # try:
191
- # result = await func(**kwargs) if asyncio.iscoroutinefunction(func) else func(**kwargs)
192
- # return await parser(result) if parser and asyncio.iscoroutinefunction(parser) else parser(result) if parser else result
193
- # except Exception as e:
194
- # raise ValueError(f"Error invoking function {name}: {str(e)}")
195
-
196
- # def register_tools(self, tools: List[Tool], update: bool = False, new: bool = False,
197
- # prefix: Optional[str] = None, postfix: Optional[int] = None):
198
- # """
199
- # Registers multiple tools in the registry.
200
-
201
- # Parameters:
202
- # tools (List[BaseTool]): A list of tools to register.
203
-
204
- # update (bool): If True, updates existing tools. Defaults to False.
205
-
206
- # new (bool): If True, creates new registry entries. Defaults to False.
207
-
208
- # prefix (Optional[str]): A prefix for the tool names.
209
-
210
- # postfix (Optional[int]): A postfix for the tool names.
211
- # """
212
- # for tool in tools:
213
- # self._register_tool(tool, update=update, new=new, prefix=prefix, postfix=postfix)
214
-
215
- # def _register_func(self, func_, parser=None, **kwargs):
216
- # # kwargs for _register_tool
217
-
218
- # tool = func_to_tool(func_=func_, parser=parser)
219
- # self._register_tool(tool=tool, **kwargs)
220
-
221
- # def register_funcs(self, funcs, parsers=None, **kwargs):
222
- # funcs, parsers = to_list(funcs), to_list(parsers)
223
- # if parsers is not None and len(parsers) != len(funcs):
224
- # raise ValueError("The number of funcs and tools should be the same")
225
- # parsers = parsers or [None for _ in range(len(funcs))]
226
-
227
- # for i, func in enumerate(funcs):
228
- # self._register_func(func_=func, parser=parsers[i], **kwargs)
229
-
230
- # @staticmethod
231
- # def get_function_call(response):
232
- # """
233
- # Extract function name and arguments from a response JSON.
234
-
235
- # Parameters:
236
- # response (dict): The JSON response containing function information.
237
-
238
- # Returns:
239
- # Tuple[str, dict]: The function name and its arguments.
240
- # """
241
- # try:
242
- # # out = json.loads(response)
243
- # func = response['function'][5:]
244
- # args = json.loads(response['arguments'])
245
- # return (func, args)
246
- # except:
247
- # try:
248
- # func = response['recipient_name'].split('.')[-1]
249
- # args = response['parameters']
250
- # return (func, args)
251
- # except:
252
- # raise ValueError('response is not a valid function call')
253
-
@@ -1,11 +0,0 @@
1
- from .base_api_service import BaseAPIService, BaseAPIRateLimiter
2
- from .oai import OpenAIService
3
- from .openrouter import OpenRouterService
4
-
5
-
6
- __all__ = [
7
- "BaseAPIService",
8
- "BaseAPIRateLimiter",
9
- "OpenAIService",
10
- "OpenRouterService",
11
- ]
@@ -1,230 +0,0 @@
1
- import re
2
- import asyncio
3
- import os
4
- import tiktoken
5
- import logging
6
- import aiohttp
7
- from typing import Generator, NoReturn, Dict, Any, Optional
8
- from ..objs.status_tracker import StatusTracker
9
- from ..objs.abc_objs import BaseService, RateLimiter
10
- from ..objs.async_queue import AsyncQueue
11
-
12
- class BaseAPIRateLimiter(RateLimiter):
13
-
14
- def __init__(
15
- self, max_requests_per_minute: int, max_tokens_per_minute: int
16
- ) -> None:
17
- super().__init__(max_requests_per_minute, max_tokens_per_minute)
18
-
19
- @classmethod
20
- async def create(
21
- cls, max_requests_per_minute: int, max_tokens_per_minute: int
22
- ) -> None:
23
- self = cls(max_requests_per_minute, max_tokens_per_minute)
24
- if not os.getenv("env_readthedocs"):
25
- self.rate_limit_replenisher_task = await asyncio.create_task(
26
- self.rate_limit_replenisher()
27
- )
28
- return self
29
-
30
- async def rate_limit_replenisher(self) -> NoReturn:
31
- """
32
- Asynchronously replenishes the rate limit capacities at regular intervals.
33
-
34
- This coroutine runs indefinitely, replenishing the available request and token capacities
35
- based on the maximum limits defined for the OpenAI API. This task should run in the background
36
- to consistently reset the capacities.
37
-
38
- Example:
39
- >>> rate_limiter = OpenAIRateLimiter(100, 200)
40
- >>> asyncio.create_task(rate_limiter.rate_limit_replenisher())
41
- # This will start the background task for rate limit replenishment.
42
- """
43
- while True:
44
- await asyncio.sleep(60) # Replenishes every 60 seconds
45
- self.available_request_capacity = self.max_requests_per_minute
46
- self.available_token_capacity = self.max_tokens_per_minute
47
-
48
- # credit to OpenAI for the following function
49
- def calculate_num_token(
50
- self,
51
- payload: Dict[str, Any] = None,
52
- api_endpoint: str = None,
53
- token_encoding_name: str = None,
54
- ) -> int:
55
- """
56
- Calculates the number of tokens required for a request based on the payload and API endpoint.
57
-
58
- The token calculation logic might vary based on different API endpoints and payload content.
59
- This method should be implemented in a subclass to provide the specific calculation logic
60
- for the OpenAI API.
61
-
62
- Parameters:
63
- payload (Dict[str, Any]): The payload of the request.
64
-
65
- api_endpoint (str): The specific API endpoint for the request.
66
-
67
- token_encoding_name (str): The name of the token encoding method.
68
-
69
- Returns:
70
- int: The estimated number of tokens required for the request.
71
-
72
- Example:
73
- >>> rate_limiter = OpenAIRateLimiter(100, 200)
74
- >>> payload = {'prompt': 'Translate the following text:', 'max_tokens': 50}
75
- >>> rate_limiter.calculate_num_token(payload, 'completions')
76
- # Expected token calculation for the given payload and endpoint.
77
- """
78
-
79
- encoding = tiktoken.get_encoding(token_encoding_name)
80
- if api_endpoint.endswith("completions"):
81
- max_tokens = payload.get("max_tokens", 15)
82
- n = payload.get("n", 1)
83
- completion_tokens = n * max_tokens
84
-
85
- # chat completions
86
- if api_endpoint.startswith("chat/"):
87
- num_tokens = 0
88
- for message in payload["messages"]:
89
- num_tokens += 4 # every message follows <im_start>{role/name}\n{content}<im_end>\n
90
- for key, value in message.items():
91
- num_tokens += len(encoding.encode(value))
92
- if key == "name": # if there's a name, the role is omitted
93
- num_tokens -= (
94
- 1 # role is always required and always 1 token
95
- )
96
- num_tokens += 2 # every reply is primed with <im_start>assistant
97
- return num_tokens + completion_tokens
98
- # normal completions
99
- else:
100
- prompt = payload["prompt"]
101
- if isinstance(prompt, str): # single prompt
102
- prompt_tokens = len(encoding.encode(prompt))
103
- num_tokens = prompt_tokens + completion_tokens
104
- return num_tokens
105
- elif isinstance(prompt, list): # multiple prompts
106
- prompt_tokens = sum([len(encoding.encode(p)) for p in prompt])
107
- num_tokens = prompt_tokens + completion_tokens * len(prompt)
108
- return num_tokens
109
- else:
110
- raise TypeError(
111
- 'Expecting either string or list of strings for "prompt" field in completion request'
112
- )
113
- elif api_endpoint == "embeddings":
114
- input = payload["input"]
115
- if isinstance(input, str): # single input
116
- num_tokens = len(encoding.encode(input))
117
- return num_tokens
118
- elif isinstance(input, list): # multiple inputs
119
- num_tokens = sum([len(encoding.encode(i)) for i in input])
120
- return num_tokens
121
- else:
122
- raise TypeError(
123
- 'Expecting either string or list of strings for "inputs" field in embedding request'
124
- )
125
- else:
126
- raise NotImplementedError(
127
- f'API endpoint "{api_endpoint}" not implemented in this script'
128
- )
129
-
130
-
131
- class BaseAPIService(BaseService):
132
-
133
- def __init__(self, api_key: str = None,
134
- status_tracker = None,
135
- queue = None, endpoint=None, schema=None,
136
- ratelimiter=None, max_requests_per_minute=None, max_tokens_per_minute=None) -> None:
137
- self.api_key = api_key
138
- self.status_tracker = status_tracker or StatusTracker()
139
- self.queue = queue or AsyncQueue()
140
- self.endpoint=endpoint
141
- self.schema = schema
142
- self.rate_limiter = ratelimiter(max_requests_per_minute, max_tokens_per_minute)
143
-
144
- @staticmethod
145
- def api_methods(http_session, method="post"):
146
- if method not in ["post", "delete", "head", "options", "patch"]:
147
- raise ValueError("Invalid request, method must be in ['post', 'delete', 'head', 'options', 'patch']")
148
- elif method == "post":
149
- return http_session.post
150
- elif method == "delete":
151
- return http_session.delete
152
- elif method == "head":
153
- return http_session.head
154
- elif method == "options":
155
- return http_session.options
156
- elif method == "patch":
157
- return http_session.patch
158
-
159
- @staticmethod
160
- def api_endpoint_from_url(request_url: str) -> str:
161
- match = re.search(r"^https://[^/]+/v\d+/(.+)$", request_url)
162
- if match:
163
- return match.group(1)
164
- else:
165
- return ""
166
-
167
- @staticmethod
168
- def task_id_generator_function() -> Generator[int, None, None]:
169
- task_id = 0
170
- while True:
171
- yield task_id
172
- task_id += 1
173
-
174
- async def _call_api(self, http_session, endpoint_, method="post", payload: Dict[str, any] =None) -> Optional[Dict[str, any]]:
175
- endpoint_ = self.api_endpoint_from_url("https://api.openai.com/v1/"+endpoint_)
176
-
177
- while True:
178
- if self.rate_limiter.available_request_capacity < 1 or self.rate_limiter.available_token_capacity < 10: # Minimum token count
179
- await asyncio.sleep(1) # Wait for capacity
180
- continue
181
-
182
- required_tokens = self.rate_limiter.calculate_num_token(payload, endpoint_, self.token_encoding_name)
183
-
184
- if self.rate_limiter.available_token_capacity >= required_tokens:
185
- self.rate_limiter.available_request_capacity -= 1
186
- self.rate_limiter.available_token_capacity -= required_tokens
187
-
188
- request_headers = {"Authorization": f"Bearer {self.api_key}"}
189
- attempts_left = self.max_attempts
190
-
191
- while attempts_left > 0:
192
- try:
193
- method = self.api_methods(http_session, method)
194
- async with method(
195
- url=(self.base_url+endpoint_), headers=request_headers, json=payload
196
- ) as response:
197
- response_json = await response.json()
198
-
199
- if "error" in response_json:
200
- logging.warning(
201
- f"API call failed with error: {response_json['error']}"
202
- )
203
- attempts_left -= 1
204
-
205
- if "Rate limit" in response_json["error"].get("message", ""):
206
- await asyncio.sleep(15)
207
- else:
208
- return response_json
209
- except Exception as e:
210
- logging.warning(f"API call failed with exception: {e}")
211
- attempts_left -= 1
212
-
213
- logging.error("API call failed after all attempts.")
214
- break
215
- else:
216
- await asyncio.sleep(1)
217
-
218
- async def _serve(self, payload, endpoint_="chat/completions", method="post"):
219
-
220
- async def call_api():
221
- async with aiohttp.ClientSession() as http_session:
222
- completion = await self._call_api(http_session=http_session, endpoint_=endpoint_, payload=payload, method=method)
223
- return completion
224
-
225
- try:
226
- return await call_api()
227
- except Exception as e:
228
- self.status_tracker.num_tasks_failed += 1
229
- raise e
230
-
lionagi/services/oai.py DELETED
@@ -1,34 +0,0 @@
1
- from os import getenv
2
- import dotenv
3
- from .base_api_service import BaseAPIService, BaseAPIRateLimiter
4
-
5
- dotenv.load_dotenv()
6
-
7
- class OpenAIService(BaseAPIService):
8
-
9
- base_url = "https://api.openai.com/v1/"
10
-
11
- def __init__(
12
- self,
13
- api_key: str = None,
14
- token_encoding_name: str = "cl100k_base",
15
- max_attempts: int = 3,
16
- max_requests_per_minute: int = 500,
17
- max_tokens_per_minute: int = 150_000,
18
- ratelimiter = BaseAPIRateLimiter ,
19
- status_tracker = None,
20
- queue = None,
21
- ):
22
- super().__init__(
23
- api_key = api_key or getenv("OPENAI_API_KEY"),
24
- status_tracker = status_tracker,
25
- queue = queue,
26
- ratelimiter=ratelimiter,
27
- max_requests_per_minute=max_requests_per_minute,
28
- max_tokens_per_minute=max_tokens_per_minute),
29
- self.token_encoding_name=token_encoding_name
30
- self.max_attempts = max_attempts
31
-
32
- async def serve(self, payload, endpoint_="chat/completions", method="post"):
33
- return await self._serve(payload=payload, endpoint_=endpoint_, method=method)
34
-
@@ -1,31 +0,0 @@
1
- from os import getenv
2
- from .base_api_service import BaseAPIService, BaseAPIRateLimiter
3
-
4
- class OpenRouterService(BaseAPIService):
5
- key_scheme = "OPENROUTER_API_KEY"
6
- base_url = "https://openrouter.ai/api/v1/"
7
-
8
- def __init__(
9
- self,
10
- api_key: str = None,
11
- token_encoding_name: str = "cl100k_base",
12
- max_attempts: int = 3,
13
- max_requests_per_minute: int = 500,
14
- max_tokens_per_minute: int = 150_000,
15
- ratelimiter = BaseAPIRateLimiter ,
16
- status_tracker = None,
17
- queue = None,
18
- ):
19
- super().__init__(
20
- api_key = api_key or getenv(self.key_scheme),
21
- status_tracker = status_tracker,
22
- queue = queue,
23
- ratelimiter=ratelimiter,
24
- max_requests_per_minute=max_requests_per_minute,
25
- max_tokens_per_minute=max_tokens_per_minute),
26
- self.token_encoding_name=token_encoding_name
27
- self.max_attempts = max_attempts
28
-
29
- async def serve(self, payload, endpoint_="chat/completions"):
30
- return await self._serve(payload=payload, endpoint_=endpoint_)
31
-
@@ -1,46 +0,0 @@
1
- import unittest
2
- from unittest.mock import MagicMock
3
-
4
- # Assuming the Python module with the above functions is named 'api_utils'
5
- from lionagi.utils.api_util import *
6
-
7
- class TestApiUtils(unittest.TestCase):
8
-
9
- def test_api_method_valid(self):
10
- session = MagicMock()
11
- methods = ['post', 'delete', 'head', 'options', 'patch']
12
- for method in methods:
13
- with self.subTest(method=method):
14
- func = api_method(session, method)
15
- self.assertTrue(callable(func))
16
-
17
- def test_api_method_invalid(self):
18
- session = MagicMock()
19
- with self.assertRaises(ValueError):
20
- api_method(session, 'get')
21
-
22
- def test_api_error(self):
23
- response_with_error = {'error': 'Something went wrong'}
24
- response_without_error = {'result': 'Success'}
25
-
26
- self.assertTrue(api_error(response_with_error))
27
- self.assertFalse(api_error(response_without_error))
28
-
29
- def test_api_rate_limit_error(self):
30
- response_with_rate_limit_error = {'error': {'message': 'Rate limit exceeded'}}
31
- response_without_rate_limit_error = {'error': {'message': 'Another error'}}
32
-
33
- self.assertTrue(api_rate_limit_error(response_with_rate_limit_error))
34
- self.assertFalse(api_rate_limit_error(response_without_rate_limit_error))
35
-
36
- def test_api_endpoint_from_url(self):
37
- url_with_endpoint = "https://api.example.com/v1/users"
38
- url_without_endpoint = "https://api.example.com/users"
39
- url_invalid = "Just a string"
40
-
41
- self.assertEqual(api_endpoint_from_url(url_with_endpoint), 'users')
42
- self.assertEqual(api_endpoint_from_url(url_without_endpoint), '')
43
- self.assertEqual(api_endpoint_from_url(url_invalid), '')
44
-
45
- if __name__ == '__main__':
46
- unittest.main()