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.
Files changed (33) hide show
  1. lionagi/core/collections/model.py +32 -0
  2. lionagi/core/engine/branch_engine.py +4 -1
  3. lionagi/core/generic/graph.py +4 -2
  4. lionagi/core/generic/node.py +2 -2
  5. lionagi/core/session/directive_mixin.py +29 -5
  6. lionagi/core/unit/unit.py +18 -2
  7. lionagi/core/unit/unit_mixin.py +10 -12
  8. lionagi/core/validator/validator.py +17 -4
  9. lionagi/core/work/work_edge.py +8 -6
  10. lionagi/core/work/work_function.py +13 -6
  11. lionagi/core/work/work_function_node.py +19 -8
  12. lionagi/core/work/work_queue.py +4 -4
  13. lionagi/core/work/work_task.py +14 -24
  14. lionagi/core/work/worker.py +66 -39
  15. lionagi/core/work/worker_engine.py +38 -13
  16. lionagi/integrations/config/oai_configs.py +1 -1
  17. lionagi/integrations/provider/litellm.py +3 -4
  18. lionagi/libs/ln_parse.py +2 -2
  19. lionagi/tests/api/aws/conftest.py +17 -20
  20. lionagi/tests/api/aws/test_aws_s3.py +4 -5
  21. lionagi/tests/test_core/generic/test_structure.py +2 -2
  22. lionagi/tests/test_core/graph/test_graph.py +1 -1
  23. lionagi/tests/test_core/graph/test_tree.py +1 -1
  24. lionagi/tests/test_core/mail/test_mail.py +4 -5
  25. lionagi/tests/test_core/test_structure/test_base_structure.py +1 -1
  26. lionagi/tests/test_core/test_structure/test_graph.py +1 -1
  27. lionagi/tests/test_core/test_structure/test_tree.py +1 -1
  28. lionagi/version.py +1 -1
  29. {lionagi-0.2.4.dist-info → lionagi-0.2.6.dist-info}/METADATA +2 -2
  30. {lionagi-0.2.4.dist-info → lionagi-0.2.6.dist-info}/RECORD +33 -33
  31. {lionagi-0.2.4.dist-info → lionagi-0.2.6.dist-info}/WHEEL +1 -1
  32. {lionagi-0.2.4.dist-info → lionagi-0.2.6.dist-info}/LICENSE +0 -0
  33. {lionagi-0.2.4.dist-info → lionagi-0.2.6.dist-info}/top_level.txt +0 -0
@@ -93,7 +93,9 @@ class Worker(ABC):
93
93
 
94
94
  """
95
95
  if form_key not in self.forms.keys():
96
- raise ValueError(f"Unable to change default form. Key {form_key} does not exist.")
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(self.__class__, predicate=inspect.isfunction):
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(decorator_attr="_worklink_decorator_params", name_only=False)
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 "from_work" not in func_signature.parameters and "from_result" not in func_signature.parameters:
128
- raise ValueError(f"Either \"from_work\" or \"from_result\" must be a parameter in function {func_name}")
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(decorator_attr="_work_decorator_params", name_only=False)
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(f"Failed to locate form. \"{form_param_key}\" is not defined in the function.")
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__(assignment=work_func.assignment, task=work_func.guidance)
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(f"Cannot locate form in Worker's forms and default_form is not available.")
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
- wrapper._work_decorator_params = {"assignment": assignment,
275
- "function": func,
276
- "retry_kwargs": retry_kwargs,
277
- "guidance": guidance,
278
- "capacity": capacity,
279
- "refresh_time": refresh_time}
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(decorator_attr="_work_decorator_params")
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("Invalid link. 'from_' and 'to_' must be the name of work decorated functions.")
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 "from_work" not in func_signature.parameters and "from_result" not in func_signature.parameters:
321
- raise ValueError(f"Either \"from_work\" or \"from_result\" must be a parameter in function {func.__name__}")
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("Invalid type for from_work. Only work objects are accepted.")
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(f"Invalid work object in from_work. "
337
- f"async_task_name \"{from_work.async_task_name}\" does not match from_ \"{from_}\"")
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(next_params[1], dict):
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(self, *args, task_function: str, task_name=None, task_max_steps=10, task_post_processing=None, **kwargs):
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(name=task_name, max_steps=task_max_steps, post_processing=task_post_processing)
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
- Activates the work queues for all work functions.
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(decorator_attr="_work_decorator_params",
158
- name_only=False)
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(self.worker.work_functions[func_name], WorkFunctionNode):
165
- raise TypeError(f"WorkFunction {func_name} already exists but is not a WorkFunctionNode. "
166
- f"If you would like to use it in WorkerEngine, please convert it to a "
167
- f"WorkFunctionNode, or initiate a new worker, or pop it from work_function dict")
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(decorator_attr="_worklink_decorator_params",
174
- name_only=False)
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(head=head, tail=tail, convert_function=func, associated_worker=self.worker, edge_class=WorkEdge)
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
+ )
@@ -4,7 +4,7 @@ API_key_schema = ("OPENAI_API_KEY",)
4
4
 
5
5
  # ChatCompletion
6
6
  oai_chat_llmconfig = {
7
- "model": "gpt-4o",
7
+ "model": "gpt-4o-2024-08-06",
8
8
  "frequency_penalty": 0,
9
9
  "max_tokens": None,
10
10
  "n": 1,
@@ -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
- _config = _config_.ConfigSingleton()
14
- _config.config["MOCK"] = True # turn on mock mode
15
- yield _config
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
- yield _connect_.get_object("AWSS3")
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
- assert test_s3_conn.list_bucket_names() == []
7
-
4
+ # @mock_aws
5
+ # def test_awss3_list_bucket_pass(test_s3_conn):
6
+ # assert test_s3_conn.list_bucket_names() == []
@@ -1,4 +1,4 @@
1
- #TODO
1
+ # TODO
2
2
  # import unittest
3
3
  # from lionagi.core.generic.structure import *
4
4
  #
@@ -191,4 +191,4 @@
191
191
  # self.assertEqual(edge.label, "test_label")
192
192
  #
193
193
  # if __name__ == "__main__":
194
- # unittest.main()
194
+ # unittest.main()
@@ -1,4 +1,4 @@
1
- #TODO
1
+ # TODO
2
2
  # import unittest
3
3
  # from unittest.mock import MagicMock, patch
4
4
  # from lionagi.core.graph.graph import Graph
@@ -1,4 +1,4 @@
1
- #TODO
1
+ # TODO
2
2
  # import unittest
3
3
  # from lionagi.core.graph.tree import TreeNode, Tree
4
4
  #
@@ -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('%Y-%m-%d')
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(timestamp=datetime.today().strftime('%Y-%m-%d'), package=package)
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
@@ -1,4 +1,4 @@
1
- #TODO
1
+ # TODO
2
2
 
3
3
  # import unittest
4
4
  # from unittest.mock import MagicMock, patch
@@ -1,4 +1,4 @@
1
- #TODO
1
+ # TODO
2
2
  # import unittest
3
3
  # from unittest.mock import MagicMock, patch
4
4
  # from lionagi.core.generic import BaseNode, Edge
@@ -1,4 +1,4 @@
1
- #TODO
1
+ # TODO
2
2
  # import unittest
3
3
  # from lionagi.new.schema.todo.tree import Tree
4
4
  # from lionagi.core.generic import TreeNode
lionagi/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.2.4"
1
+ __version__ = "0.2.6"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lionagi
3
- Version: 0.2.4
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.3
240
+ pip install lionagi==0.2.5
241
241
  ```
242
242
 
243
243
  **Powerful Intelligent Workflow Automation**