lionagi 0.2.4__py3-none-any.whl → 0.2.6__py3-none-any.whl
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.
- lionagi/core/collections/model.py +32 -0
- lionagi/core/engine/branch_engine.py +4 -1
- lionagi/core/generic/graph.py +4 -2
- lionagi/core/generic/node.py +2 -2
- lionagi/core/session/directive_mixin.py +29 -5
- lionagi/core/unit/unit.py +18 -2
- lionagi/core/unit/unit_mixin.py +10 -12
- lionagi/core/validator/validator.py +17 -4
- lionagi/core/work/work_edge.py +8 -6
- lionagi/core/work/work_function.py +13 -6
- lionagi/core/work/work_function_node.py +19 -8
- lionagi/core/work/work_queue.py +4 -4
- lionagi/core/work/work_task.py +14 -24
- lionagi/core/work/worker.py +66 -39
- lionagi/core/work/worker_engine.py +38 -13
- lionagi/integrations/config/oai_configs.py +1 -1
- lionagi/integrations/provider/litellm.py +3 -4
- lionagi/libs/ln_parse.py +2 -2
- lionagi/tests/api/aws/conftest.py +17 -20
- lionagi/tests/api/aws/test_aws_s3.py +4 -5
- lionagi/tests/test_core/generic/test_structure.py +2 -2
- lionagi/tests/test_core/graph/test_graph.py +1 -1
- lionagi/tests/test_core/graph/test_tree.py +1 -1
- lionagi/tests/test_core/mail/test_mail.py +4 -5
- lionagi/tests/test_core/test_structure/test_base_structure.py +1 -1
- lionagi/tests/test_core/test_structure/test_graph.py +1 -1
- lionagi/tests/test_core/test_structure/test_tree.py +1 -1
- lionagi/version.py +1 -1
- {lionagi-0.2.4.dist-info → lionagi-0.2.6.dist-info}/METADATA +2 -2
- {lionagi-0.2.4.dist-info → lionagi-0.2.6.dist-info}/RECORD +33 -33
- {lionagi-0.2.4.dist-info → lionagi-0.2.6.dist-info}/WHEEL +1 -1
- {lionagi-0.2.4.dist-info → lionagi-0.2.6.dist-info}/LICENSE +0 -0
- {lionagi-0.2.4.dist-info → lionagi-0.2.6.dist-info}/top_level.txt +0 -0
lionagi/core/work/worker.py
CHANGED
@@ -93,7 +93,9 @@ class Worker(ABC):
|
|
93
93
|
|
94
94
|
"""
|
95
95
|
if form_key not in self.forms.keys():
|
96
|
-
raise ValueError(
|
96
|
+
raise ValueError(
|
97
|
+
f"Unable to change default form. Key {form_key} does not exist."
|
98
|
+
)
|
97
99
|
self.default_form = self.forms[form_key]
|
98
100
|
|
99
101
|
def _get_decorated_functions(self, decorator_attr, name_only=True):
|
@@ -108,7 +110,9 @@ class Worker(ABC):
|
|
108
110
|
list: List of decorated function names or tuples containing function details.
|
109
111
|
"""
|
110
112
|
decorated_functions = []
|
111
|
-
for name, func in inspect.getmembers(
|
113
|
+
for name, func in inspect.getmembers(
|
114
|
+
self.__class__, predicate=inspect.isfunction
|
115
|
+
):
|
112
116
|
if hasattr(func, decorator_attr):
|
113
117
|
if name_only:
|
114
118
|
decorated_functions.append(name)
|
@@ -121,11 +125,18 @@ class Worker(ABC):
|
|
121
125
|
"""
|
122
126
|
Validates worklink functions to ensure they have the required parameters.
|
123
127
|
"""
|
124
|
-
worklink_decorated_function = self._get_decorated_functions(
|
128
|
+
worklink_decorated_function = self._get_decorated_functions(
|
129
|
+
decorator_attr="_worklink_decorator_params", name_only=False
|
130
|
+
)
|
125
131
|
for func_name, func, _ in worklink_decorated_function:
|
126
132
|
func_signature = inspect.signature(func)
|
127
|
-
if
|
128
|
-
|
133
|
+
if (
|
134
|
+
"from_work" not in func_signature.parameters
|
135
|
+
and "from_result" not in func_signature.parameters
|
136
|
+
):
|
137
|
+
raise ValueError(
|
138
|
+
f'Either "from_work" or "from_result" must be a parameter in function {func_name}'
|
139
|
+
)
|
129
140
|
|
130
141
|
def construct_all_work_functions(self):
|
131
142
|
"""
|
@@ -133,7 +144,9 @@ class Worker(ABC):
|
|
133
144
|
"""
|
134
145
|
if getattr(self, "work_functions", None) is None:
|
135
146
|
self.work_functions = {}
|
136
|
-
work_decorated_function = self._get_decorated_functions(
|
147
|
+
work_decorated_function = self._get_decorated_functions(
|
148
|
+
decorator_attr="_work_decorator_params", name_only=False
|
149
|
+
)
|
137
150
|
for func_name, func, dec_params in work_decorated_function:
|
138
151
|
if func_name not in self.work_functions:
|
139
152
|
self.work_functions[func_name] = WorkFunction(**dec_params)
|
@@ -174,7 +187,7 @@ class Worker(ABC):
|
|
174
187
|
retry_kwargs=retry_kwargs or {},
|
175
188
|
guidance=guidance or function.__doc__,
|
176
189
|
capacity=capacity,
|
177
|
-
refresh_time=refresh_time
|
190
|
+
refresh_time=refresh_time,
|
178
191
|
)
|
179
192
|
|
180
193
|
work_func: WorkFunction = self.work_functions[function.__name__]
|
@@ -183,7 +196,9 @@ class Worker(ABC):
|
|
183
196
|
if form_param_key:
|
184
197
|
func_signature = inspect.signature(function)
|
185
198
|
if form_param_key not in func_signature.parameters:
|
186
|
-
raise KeyError(
|
199
|
+
raise KeyError(
|
200
|
+
f'Failed to locate form. "{form_param_key}" is not defined in the function.'
|
201
|
+
)
|
187
202
|
if "self" in func_signature.parameters:
|
188
203
|
bound_args = func_signature.bind(None, *args, **kwargs)
|
189
204
|
else:
|
@@ -199,14 +214,18 @@ class Worker(ABC):
|
|
199
214
|
form = self.forms.get(form_key) or self.default_form
|
200
215
|
|
201
216
|
if form:
|
202
|
-
subform = form.__class__(
|
217
|
+
subform = form.__class__(
|
218
|
+
assignment=work_func.assignment, task=work_func.guidance
|
219
|
+
)
|
203
220
|
for k in subform.input_fields:
|
204
221
|
v = getattr(form, k, None)
|
205
222
|
setattr(subform, k, v)
|
206
223
|
subform.origin = form
|
207
224
|
kwargs = {"form": subform} | kwargs
|
208
225
|
else:
|
209
|
-
raise ValueError(
|
226
|
+
raise ValueError(
|
227
|
+
f"Cannot locate form in Worker's forms and default_form is not available."
|
228
|
+
)
|
210
229
|
|
211
230
|
task = work_func.perform(self, *args, **kwargs)
|
212
231
|
work = Work(async_task=task, async_task_name=work_func.name)
|
@@ -221,7 +240,7 @@ def work(
|
|
221
240
|
guidance=None,
|
222
241
|
retry_kwargs=None,
|
223
242
|
timeout=10,
|
224
|
-
refresh_time=1
|
243
|
+
refresh_time=1,
|
225
244
|
):
|
226
245
|
"""
|
227
246
|
Decorator to mark a method as a work function.
|
@@ -271,23 +290,22 @@ def work(
|
|
271
290
|
refresh_time=refresh_time,
|
272
291
|
**kwargs,
|
273
292
|
)
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
293
|
+
|
294
|
+
wrapper._work_decorator_params = {
|
295
|
+
"assignment": assignment,
|
296
|
+
"function": func,
|
297
|
+
"retry_kwargs": retry_kwargs,
|
298
|
+
"guidance": guidance,
|
299
|
+
"capacity": capacity,
|
300
|
+
"refresh_time": refresh_time,
|
301
|
+
}
|
280
302
|
|
281
303
|
return wrapper
|
282
304
|
|
283
305
|
return decorator
|
284
306
|
|
285
307
|
|
286
|
-
def worklink(
|
287
|
-
from_: str,
|
288
|
-
to_: str,
|
289
|
-
auto_schedule: bool = True
|
290
|
-
):
|
308
|
+
def worklink(from_: str, to_: str, auto_schedule: bool = True):
|
291
309
|
"""
|
292
310
|
Decorator to create a link between two work functions.
|
293
311
|
|
@@ -299,26 +317,31 @@ def worklink(
|
|
299
317
|
Returns:
|
300
318
|
Callable: The decorated function.
|
301
319
|
"""
|
320
|
+
|
302
321
|
def decorator(func):
|
303
322
|
@wraps(func)
|
304
323
|
async def wrapper(
|
305
|
-
self: Worker,
|
306
|
-
*args,
|
307
|
-
func=func,
|
308
|
-
from_=from_,
|
309
|
-
to_=to_,
|
310
|
-
**kwargs
|
324
|
+
self: Worker, *args, func=func, from_=from_, to_=to_, **kwargs
|
311
325
|
):
|
312
326
|
if not inspect.iscoroutinefunction(func):
|
313
327
|
raise TypeError(f"{func.__name__} must be an asynchronous function")
|
314
328
|
|
315
|
-
work_funcs = self._get_decorated_functions(
|
329
|
+
work_funcs = self._get_decorated_functions(
|
330
|
+
decorator_attr="_work_decorator_params"
|
331
|
+
)
|
316
332
|
if from_ not in work_funcs or to_ not in work_funcs:
|
317
|
-
raise ValueError(
|
333
|
+
raise ValueError(
|
334
|
+
"Invalid link. 'from_' and 'to_' must be the name of work decorated functions."
|
335
|
+
)
|
318
336
|
|
319
337
|
func_signature = inspect.signature(func)
|
320
|
-
if
|
321
|
-
|
338
|
+
if (
|
339
|
+
"from_work" not in func_signature.parameters
|
340
|
+
and "from_result" not in func_signature.parameters
|
341
|
+
):
|
342
|
+
raise ValueError(
|
343
|
+
f'Either "from_work" or "from_result" must be a parameter in function {func.__name__}'
|
344
|
+
)
|
322
345
|
|
323
346
|
if "self" in func_signature.parameters:
|
324
347
|
bound_args = func_signature.bind(None, *args, **kwargs)
|
@@ -331,10 +354,14 @@ def worklink(
|
|
331
354
|
|
332
355
|
if from_work := arguments.get("from_work"):
|
333
356
|
if not isinstance(from_work, Work):
|
334
|
-
raise ValueError(
|
357
|
+
raise ValueError(
|
358
|
+
"Invalid type for from_work. Only work objects are accepted."
|
359
|
+
)
|
335
360
|
if from_work.async_task_name != from_:
|
336
|
-
raise ValueError(
|
337
|
-
|
361
|
+
raise ValueError(
|
362
|
+
f"Invalid work object in from_work. "
|
363
|
+
f'async_task_name "{from_work.async_task_name}" does not match from_ "{from_}"'
|
364
|
+
)
|
338
365
|
|
339
366
|
next_params = await func(self, *args, **kwargs)
|
340
367
|
to_work_func = getattr(self, to_)
|
@@ -347,7 +374,9 @@ def worklink(
|
|
347
374
|
if wrapper.auto_schedule:
|
348
375
|
return await to_work_func(**next_params)
|
349
376
|
elif isinstance(next_params, tuple) and len(next_params) == 2:
|
350
|
-
if isinstance(next_params[0], list) and isinstance(
|
377
|
+
if isinstance(next_params[0], list) and isinstance(
|
378
|
+
next_params[1], dict
|
379
|
+
):
|
351
380
|
if wrapper.auto_schedule:
|
352
381
|
return await to_work_func(*next_params[0], **next_params[1])
|
353
382
|
else:
|
@@ -358,9 +387,7 @@ def worklink(
|
|
358
387
|
return next_params
|
359
388
|
|
360
389
|
wrapper.auto_schedule = auto_schedule
|
361
|
-
wrapper._worklink_decorator_params = {"func": func,
|
362
|
-
"from_": from_,
|
363
|
-
"to_": to_}
|
390
|
+
wrapper._worklink_decorator_params = {"func": func, "from_": from_, "to_": to_}
|
364
391
|
|
365
392
|
return wrapper
|
366
393
|
|
@@ -41,7 +41,15 @@ class WorkerEngine:
|
|
41
41
|
self.refresh_time = refresh_time
|
42
42
|
self._stop_event = asyncio.Event()
|
43
43
|
|
44
|
-
async def add_task(
|
44
|
+
async def add_task(
|
45
|
+
self,
|
46
|
+
*args,
|
47
|
+
task_function: str,
|
48
|
+
task_name=None,
|
49
|
+
task_max_steps=10,
|
50
|
+
task_post_processing=None,
|
51
|
+
**kwargs,
|
52
|
+
):
|
45
53
|
"""
|
46
54
|
Adds a new task to the task queue.
|
47
55
|
|
@@ -56,7 +64,11 @@ class WorkerEngine:
|
|
56
64
|
Returns:
|
57
65
|
WorkTask: The newly created task.
|
58
66
|
"""
|
59
|
-
task = WorkTask(
|
67
|
+
task = WorkTask(
|
68
|
+
name=task_name,
|
69
|
+
max_steps=task_max_steps,
|
70
|
+
post_processing=task_post_processing,
|
71
|
+
)
|
60
72
|
self.tasks.append(task)
|
61
73
|
function = getattr(self.worker, task_function)
|
62
74
|
work = await function(*args, **kwargs)
|
@@ -66,7 +78,7 @@ class WorkerEngine:
|
|
66
78
|
|
67
79
|
async def activate_work_queues(self):
|
68
80
|
"""
|
69
|
-
|
81
|
+
Activates the work queues for all work functions.
|
70
82
|
"""
|
71
83
|
for work_function in self.worker.work_functions.values():
|
72
84
|
if not work_function.worklog.queue.execution_mode:
|
@@ -141,11 +153,12 @@ class WorkerEngine:
|
|
141
153
|
Executes tasks continuously until stopped.
|
142
154
|
"""
|
143
155
|
self._stop_event.clear()
|
144
|
-
|
156
|
+
|
145
157
|
async def execute_lasting_inner():
|
146
158
|
while not self.stopped:
|
147
159
|
await self.execute(stop_queue=False)
|
148
160
|
await asyncio.sleep(self.refresh_time)
|
161
|
+
|
149
162
|
asyncio.create_task(execute_lasting_inner())
|
150
163
|
|
151
164
|
def _construct_work_functions(self):
|
@@ -154,26 +167,38 @@ class WorkerEngine:
|
|
154
167
|
"""
|
155
168
|
if getattr(self.worker, "work_functions", None) is None:
|
156
169
|
self.worker.work_functions = {}
|
157
|
-
work_decorated_function = self.worker._get_decorated_functions(
|
158
|
-
|
170
|
+
work_decorated_function = self.worker._get_decorated_functions(
|
171
|
+
decorator_attr="_work_decorator_params", name_only=False
|
172
|
+
)
|
159
173
|
for func_name, func, dec_params in work_decorated_function:
|
160
174
|
if func_name not in self.worker.work_functions:
|
161
175
|
self.worker.work_functions[func_name] = WorkFunctionNode(**dec_params)
|
162
176
|
self.worker_graph.add_node(self.worker.work_functions[func_name])
|
163
177
|
else:
|
164
|
-
if not isinstance(
|
165
|
-
|
166
|
-
|
167
|
-
|
178
|
+
if not isinstance(
|
179
|
+
self.worker.work_functions[func_name], WorkFunctionNode
|
180
|
+
):
|
181
|
+
raise TypeError(
|
182
|
+
f"WorkFunction {func_name} already exists but is not a WorkFunctionNode. "
|
183
|
+
f"If you would like to use it in WorkerEngine, please convert it to a "
|
184
|
+
f"WorkFunctionNode, or initiate a new worker, or pop it from work_function dict"
|
185
|
+
)
|
168
186
|
|
169
187
|
def _construct_workedges(self):
|
170
188
|
"""
|
171
189
|
Constructs work edges for the worker graph.
|
172
190
|
"""
|
173
|
-
worklink_decorated_function = self.worker._get_decorated_functions(
|
174
|
-
|
191
|
+
worklink_decorated_function = self.worker._get_decorated_functions(
|
192
|
+
decorator_attr="_worklink_decorator_params", name_only=False
|
193
|
+
)
|
175
194
|
|
176
195
|
for func_name, func, dec_params in worklink_decorated_function:
|
177
196
|
head = self.worker.work_functions[dec_params["from_"]]
|
178
197
|
tail = self.worker.work_functions[dec_params["to_"]]
|
179
|
-
self.worker_graph.add_edge(
|
198
|
+
self.worker_graph.add_edge(
|
199
|
+
head=head,
|
200
|
+
tail=tail,
|
201
|
+
convert_function=func,
|
202
|
+
associated_worker=self.worker,
|
203
|
+
edge_class=WorkEdge,
|
204
|
+
)
|
@@ -27,8 +27,9 @@ class LiteLLMService(BaseService):
|
|
27
27
|
SysUtil.check_import("litellm")
|
28
28
|
|
29
29
|
import litellm
|
30
|
+
|
30
31
|
litellm.drop_params = True
|
31
|
-
|
32
|
+
|
32
33
|
self.acompletion = litellm.acompletion
|
33
34
|
self.model = model
|
34
35
|
self.kwargs = kwargs
|
@@ -45,9 +46,7 @@ class LiteLLMService(BaseService):
|
|
45
46
|
kwargs["model"] = self.model or kwargs.get("model")
|
46
47
|
|
47
48
|
try:
|
48
|
-
completion = await self.acompletion(
|
49
|
-
messages=messages, **kwargs
|
50
|
-
)
|
49
|
+
completion = await self.acompletion(messages=messages, **kwargs)
|
51
50
|
return payload, completion.model_dump()
|
52
51
|
except Exception as e:
|
53
52
|
self.status_tracker.num_tasks_failed += 1
|
lionagi/libs/ln_parse.py
CHANGED
@@ -694,11 +694,11 @@ class StringMatch:
|
|
694
694
|
|
695
695
|
if isinstance(out_, str):
|
696
696
|
# first try to parse it straight as a fuzzy json
|
697
|
-
|
697
|
+
|
698
698
|
try:
|
699
699
|
out_ = ParseUtil.fuzzy_parse_json(out_)
|
700
700
|
return StringMatch.correct_dict_keys(keys, out_)
|
701
|
-
|
701
|
+
|
702
702
|
except:
|
703
703
|
try:
|
704
704
|
out_ = ParseUtil.md_to_json(out_)
|
@@ -1,28 +1,25 @@
|
|
1
|
-
import pytest
|
2
|
-
from api.apicore import _connect as _connect_
|
3
|
-
from api.apicore import _config as _config_
|
1
|
+
# import pytest
|
2
|
+
# from api.apicore import _connect as _connect_
|
3
|
+
# from api.apicore import _config as _config_
|
4
4
|
|
5
5
|
|
6
|
-
"""
|
7
|
-
Setup the testing construct to ease future testing cases
|
8
|
-
"""
|
6
|
+
# """
|
7
|
+
# Setup the testing construct to ease future testing cases
|
8
|
+
# """
|
9
9
|
|
10
10
|
|
11
|
-
@pytest.fixture()
|
12
|
-
def config():
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
# @pytest.fixture()
|
12
|
+
# def config():
|
13
|
+
# _config = _config_.ConfigSingleton()
|
14
|
+
# _config.config["MOCK"] = True # turn on mock mode
|
15
|
+
# yield _config
|
16
16
|
|
17
17
|
|
18
|
-
@pytest.fixture
|
19
|
-
def test_s3_conn(config):
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
@pytest.fixture
|
24
|
-
def test_ec2_conn(config):
|
25
|
-
yield _connect_.get_object("AWSEC2")
|
26
|
-
|
18
|
+
# @pytest.fixture
|
19
|
+
# def test_s3_conn(config):
|
20
|
+
# yield _connect_.get_object("AWSS3")
|
27
21
|
|
28
22
|
|
23
|
+
# @pytest.fixture
|
24
|
+
# def test_ec2_conn(config):
|
25
|
+
# yield _connect_.get_object("AWSEC2")
|
@@ -1,7 +1,6 @@
|
|
1
|
-
from moto import mock_aws
|
1
|
+
# from moto import mock_aws
|
2
2
|
|
3
3
|
|
4
|
-
@mock_aws
|
5
|
-
def test_awss3_list_bucket_pass(test_s3_conn):
|
6
|
-
|
7
|
-
|
4
|
+
# @mock_aws
|
5
|
+
# def test_awss3_list_bucket_pass(test_s3_conn):
|
6
|
+
# assert test_s3_conn.list_bucket_names() == []
|
@@ -11,7 +11,7 @@ More to be added
|
|
11
11
|
|
12
12
|
def trim_timestamp_to_day(timestamp_str):
|
13
13
|
dt = datetime.fromisoformat(timestamp_str)
|
14
|
-
return dt.strftime(
|
14
|
+
return dt.strftime("%Y-%m-%d")
|
15
15
|
|
16
16
|
|
17
17
|
class MockPackage(Package):
|
@@ -22,7 +22,9 @@ class MockPackage(Package):
|
|
22
22
|
def mail():
|
23
23
|
"""Fixture to create a Mail instance with a MockPackage."""
|
24
24
|
package = MockPackage()
|
25
|
-
mail_instance = Mail(
|
25
|
+
mail_instance = Mail(
|
26
|
+
timestamp=datetime.today().strftime("%Y-%m-%d"), package=package
|
27
|
+
)
|
26
28
|
return mail_instance
|
27
29
|
|
28
30
|
|
@@ -31,9 +33,6 @@ def test_mail_category(mail):
|
|
31
33
|
assert mail.category is None
|
32
34
|
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
36
|
# from lionagi.core.generic.mail import *
|
38
37
|
#
|
39
38
|
# import unittest
|
lionagi/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.2.
|
1
|
+
__version__ = "0.2.6"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: lionagi
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.6
|
4
4
|
Summary: Towards automated general intelligence.
|
5
5
|
Author: HaiyangLi
|
6
6
|
Author-email: Haiyang Li <ocean@lionagi.ai>
|
@@ -237,7 +237,7 @@ Requires-Dist: boto3 >=1.34.131
|
|
237
237
|
### an AGentic Intelligence Operating System
|
238
238
|
|
239
239
|
```
|
240
|
-
pip install lionagi==0.2.
|
240
|
+
pip install lionagi==0.2.5
|
241
241
|
```
|
242
242
|
|
243
243
|
**Powerful Intelligent Workflow Automation**
|