lionagi 0.2.11__py3-none-any.whl → 0.3.1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- lionagi/core/action/function_calling.py +13 -6
- lionagi/core/action/tool.py +10 -9
- lionagi/core/action/tool_manager.py +18 -9
- lionagi/core/agent/README.md +1 -1
- lionagi/core/agent/base_agent.py +5 -2
- lionagi/core/agent/eval/README.md +1 -1
- lionagi/core/collections/README.md +1 -1
- lionagi/core/collections/_logger.py +16 -6
- lionagi/core/collections/abc/README.md +1 -1
- lionagi/core/collections/abc/component.py +35 -11
- lionagi/core/collections/abc/concepts.py +5 -3
- lionagi/core/collections/abc/exceptions.py +3 -1
- lionagi/core/collections/flow.py +16 -5
- lionagi/core/collections/model.py +34 -8
- lionagi/core/collections/pile.py +65 -28
- lionagi/core/collections/progression.py +1 -2
- lionagi/core/collections/util.py +11 -2
- lionagi/core/director/README.md +1 -1
- lionagi/core/engine/branch_engine.py +35 -10
- lionagi/core/engine/instruction_map_engine.py +14 -5
- lionagi/core/engine/sandbox_.py +3 -1
- lionagi/core/engine/script_engine.py +6 -2
- lionagi/core/executor/base_executor.py +10 -3
- lionagi/core/executor/graph_executor.py +12 -4
- lionagi/core/executor/neo4j_executor.py +18 -6
- lionagi/core/generic/edge.py +7 -2
- lionagi/core/generic/graph.py +23 -7
- lionagi/core/generic/node.py +14 -5
- lionagi/core/generic/tree_node.py +5 -1
- lionagi/core/mail/mail_manager.py +3 -1
- lionagi/core/mail/package.py +3 -1
- lionagi/core/message/action_request.py +9 -2
- lionagi/core/message/action_response.py +9 -3
- lionagi/core/message/instruction.py +8 -2
- lionagi/core/message/util.py +15 -5
- lionagi/core/report/base.py +12 -7
- lionagi/core/report/form.py +7 -4
- lionagi/core/report/report.py +10 -3
- lionagi/core/report/util.py +3 -1
- lionagi/core/rule/action.py +4 -1
- lionagi/core/rule/base.py +17 -6
- lionagi/core/rule/rulebook.py +8 -4
- lionagi/core/rule/string.py +3 -1
- lionagi/core/session/branch.py +15 -4
- lionagi/core/session/directive_mixin.py +11 -3
- lionagi/core/session/session.py +6 -2
- lionagi/core/unit/parallel_unit.py +9 -3
- lionagi/core/unit/template/action.py +1 -1
- lionagi/core/unit/template/predict.py +3 -1
- lionagi/core/unit/template/select.py +5 -3
- lionagi/core/unit/unit.py +38 -4
- lionagi/core/unit/unit_form.py +13 -15
- lionagi/core/unit/unit_mixin.py +45 -27
- lionagi/core/unit/util.py +7 -3
- lionagi/core/validator/validator.py +28 -15
- lionagi/core/work/work_edge.py +7 -3
- lionagi/core/work/work_task.py +11 -5
- lionagi/core/work/worker.py +20 -5
- lionagi/core/work/worker_engine.py +6 -2
- lionagi/core/work/worklog.py +3 -1
- lionagi/experimental/compressor/llm_compressor.py +20 -5
- lionagi/experimental/directive/README.md +1 -1
- lionagi/experimental/directive/parser/base_parser.py +41 -14
- lionagi/experimental/directive/parser/base_syntax.txt +23 -23
- lionagi/experimental/directive/template/base_template.py +14 -6
- lionagi/experimental/directive/tokenizer.py +3 -1
- lionagi/experimental/evaluator/README.md +1 -1
- lionagi/experimental/evaluator/ast_evaluator.py +6 -2
- lionagi/experimental/evaluator/base_evaluator.py +27 -16
- lionagi/integrations/bridge/autogen_/autogen_.py +7 -3
- lionagi/integrations/bridge/langchain_/documents.py +13 -10
- lionagi/integrations/bridge/llamaindex_/llama_pack.py +36 -12
- lionagi/integrations/bridge/llamaindex_/node_parser.py +8 -3
- lionagi/integrations/bridge/llamaindex_/reader.py +3 -1
- lionagi/integrations/bridge/llamaindex_/textnode.py +9 -3
- lionagi/integrations/bridge/pydantic_/pydantic_bridge.py +7 -1
- lionagi/integrations/bridge/transformers_/install_.py +3 -1
- lionagi/integrations/chunker/chunk.py +5 -2
- lionagi/integrations/loader/load.py +7 -3
- lionagi/integrations/loader/load_util.py +35 -16
- lionagi/integrations/provider/oai.py +13 -4
- lionagi/integrations/provider/openrouter.py +13 -4
- lionagi/integrations/provider/services.py +3 -1
- lionagi/integrations/provider/transformers.py +5 -3
- lionagi/integrations/storage/neo4j.py +23 -7
- lionagi/integrations/storage/storage_util.py +23 -7
- lionagi/integrations/storage/structure_excel.py +7 -2
- lionagi/integrations/storage/to_csv.py +8 -2
- lionagi/integrations/storage/to_excel.py +11 -3
- lionagi/libs/ln_api.py +41 -19
- lionagi/libs/ln_context.py +4 -4
- lionagi/libs/ln_convert.py +35 -14
- lionagi/libs/ln_dataframe.py +9 -3
- lionagi/libs/ln_func_call.py +53 -18
- lionagi/libs/ln_image.py +9 -5
- lionagi/libs/ln_knowledge_graph.py +21 -7
- lionagi/libs/ln_nested.py +57 -16
- lionagi/libs/ln_parse.py +45 -15
- lionagi/libs/ln_queue.py +8 -3
- lionagi/libs/ln_tokenize.py +19 -6
- lionagi/libs/ln_validate.py +14 -3
- lionagi/libs/sys_util.py +44 -12
- lionagi/lions/coder/coder.py +24 -8
- lionagi/lions/coder/util.py +6 -2
- lionagi/lions/researcher/data_source/google_.py +12 -4
- lionagi/lions/researcher/data_source/wiki_.py +3 -1
- lionagi/version.py +1 -1
- {lionagi-0.2.11.dist-info → lionagi-0.3.1.dist-info}/METADATA +6 -7
- lionagi-0.3.1.dist-info/RECORD +226 -0
- lionagi/tests/__init__.py +0 -0
- lionagi/tests/api/__init__.py +0 -0
- lionagi/tests/api/aws/__init__.py +0 -0
- lionagi/tests/api/aws/conftest.py +0 -25
- lionagi/tests/api/aws/test_aws_s3.py +0 -6
- lionagi/tests/integrations/__init__.py +0 -0
- lionagi/tests/libs/__init__.py +0 -0
- lionagi/tests/libs/test_api.py +0 -48
- lionagi/tests/libs/test_convert.py +0 -89
- lionagi/tests/libs/test_field_validators.py +0 -354
- lionagi/tests/libs/test_func_call.py +0 -701
- lionagi/tests/libs/test_nested.py +0 -382
- lionagi/tests/libs/test_parse.py +0 -171
- lionagi/tests/libs/test_queue.py +0 -68
- lionagi/tests/libs/test_sys_util.py +0 -222
- lionagi/tests/test_core/__init__.py +0 -0
- lionagi/tests/test_core/collections/__init__.py +0 -0
- lionagi/tests/test_core/collections/test_component.py +0 -208
- lionagi/tests/test_core/collections/test_exchange.py +0 -139
- lionagi/tests/test_core/collections/test_flow.py +0 -146
- lionagi/tests/test_core/collections/test_pile.py +0 -172
- lionagi/tests/test_core/collections/test_progression.py +0 -130
- lionagi/tests/test_core/generic/__init__.py +0 -0
- lionagi/tests/test_core/generic/test_edge.py +0 -69
- lionagi/tests/test_core/generic/test_graph.py +0 -97
- lionagi/tests/test_core/generic/test_node.py +0 -107
- lionagi/tests/test_core/generic/test_structure.py +0 -194
- lionagi/tests/test_core/generic/test_tree_node.py +0 -74
- lionagi/tests/test_core/graph/__init__.py +0 -0
- lionagi/tests/test_core/graph/test_graph.py +0 -71
- lionagi/tests/test_core/graph/test_tree.py +0 -76
- lionagi/tests/test_core/mail/__init__.py +0 -0
- lionagi/tests/test_core/mail/test_mail.py +0 -98
- lionagi/tests/test_core/test_branch.py +0 -116
- lionagi/tests/test_core/test_form.py +0 -47
- lionagi/tests/test_core/test_report.py +0 -106
- lionagi/tests/test_core/test_structure/__init__.py +0 -0
- lionagi/tests/test_core/test_structure/test_base_structure.py +0 -198
- lionagi/tests/test_core/test_structure/test_graph.py +0 -55
- lionagi/tests/test_core/test_structure/test_tree.py +0 -49
- lionagi/tests/test_core/test_validator.py +0 -112
- lionagi-0.2.11.dist-info/RECORD +0 -267
- {lionagi-0.2.11.dist-info → lionagi-0.3.1.dist-info}/LICENSE +0 -0
- {lionagi-0.2.11.dist-info → lionagi-0.3.1.dist-info}/WHEEL +0 -0
lionagi/libs/ln_nested.py
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
from collections import defaultdict
|
2
|
+
from collections.abc import Callable, Generator
|
2
3
|
from itertools import chain
|
3
|
-
from typing import Any
|
4
|
+
from typing import Any
|
4
5
|
|
5
6
|
import lionagi.libs.ln_convert as convert
|
6
7
|
from lionagi.libs.sys_util import SysUtil
|
7
8
|
|
8
9
|
|
9
|
-
def nset(
|
10
|
+
def nset(
|
11
|
+
nested_structure: dict | list, indices: list[int | str], value: Any
|
12
|
+
) -> None:
|
10
13
|
"""
|
11
14
|
sets a value within a nested structure at the specified path defined by indices.
|
12
15
|
|
@@ -34,7 +37,9 @@ def nset(nested_structure: dict | list, indices: list[int | str], value: Any) ->
|
|
34
37
|
>>> assert data == [0, [1, 99], 3]
|
35
38
|
"""
|
36
39
|
if not indices:
|
37
|
-
raise ValueError(
|
40
|
+
raise ValueError(
|
41
|
+
"Indices list is empty, cannot determine target container"
|
42
|
+
)
|
38
43
|
|
39
44
|
_indices = convert.to_list(indices)
|
40
45
|
target_container = _get_target_container(nested_structure, _indices[:-1])
|
@@ -86,7 +91,9 @@ def nget(
|
|
86
91
|
"""
|
87
92
|
|
88
93
|
try:
|
89
|
-
target_container = _get_target_container(
|
94
|
+
target_container = _get_target_container(
|
95
|
+
nested_structure, indices[:-1]
|
96
|
+
)
|
90
97
|
last_index = indices[-1]
|
91
98
|
|
92
99
|
if (
|
@@ -96,17 +103,24 @@ def nget(
|
|
96
103
|
):
|
97
104
|
|
98
105
|
return target_container[last_index]
|
99
|
-
elif
|
106
|
+
elif (
|
107
|
+
isinstance(target_container, dict)
|
108
|
+
and last_index in target_container
|
109
|
+
):
|
100
110
|
return target_container[last_index]
|
101
111
|
elif default is not ...:
|
102
112
|
return default
|
103
113
|
else:
|
104
|
-
raise LookupError(
|
114
|
+
raise LookupError(
|
115
|
+
"Target not found and no default value provided."
|
116
|
+
)
|
105
117
|
except (IndexError, KeyError, TypeError):
|
106
118
|
if default is not ...:
|
107
119
|
return default
|
108
120
|
else:
|
109
|
-
raise LookupError(
|
121
|
+
raise LookupError(
|
122
|
+
"Target not found and no default value provided."
|
123
|
+
)
|
110
124
|
|
111
125
|
|
112
126
|
# nested merge
|
@@ -212,7 +226,9 @@ def flatten(
|
|
212
226
|
"""
|
213
227
|
if inplace:
|
214
228
|
if not isinstance(nested_structure, dict):
|
215
|
-
raise ValueError(
|
229
|
+
raise ValueError(
|
230
|
+
"Object must be a dictionary when 'inplace' is True."
|
231
|
+
)
|
216
232
|
_dynamic_flatten_in_place(
|
217
233
|
nested_structure,
|
218
234
|
parent_key=parent_key,
|
@@ -278,7 +294,13 @@ def unflatten(
|
|
278
294
|
if not unflattened and all(isinstance(part, int) for part in parts):
|
279
295
|
unflattened = []
|
280
296
|
|
281
|
-
ninsert(
|
297
|
+
ninsert(
|
298
|
+
unflattened,
|
299
|
+
indices=parts,
|
300
|
+
value=value,
|
301
|
+
sep=sep,
|
302
|
+
max_depth=max_depth,
|
303
|
+
)
|
282
304
|
|
283
305
|
if isinstance(unflattened, dict) and all(
|
284
306
|
isinstance(k, int) for k in unflattened.keys()
|
@@ -321,7 +343,9 @@ def nfilter(
|
|
321
343
|
elif isinstance(nested_structure, list):
|
322
344
|
return _filter_list(nested_structure, condition)
|
323
345
|
else:
|
324
|
-
raise TypeError(
|
346
|
+
raise TypeError(
|
347
|
+
"The nested_structure must be either a dict or a list."
|
348
|
+
)
|
325
349
|
|
326
350
|
|
327
351
|
def ninsert(
|
@@ -373,7 +397,9 @@ def ninsert(
|
|
373
397
|
nested_structure[part], (dict, list)
|
374
398
|
):
|
375
399
|
next_part = indices[i + 1]
|
376
|
-
nested_structure[part] =
|
400
|
+
nested_structure[part] = (
|
401
|
+
[] if isinstance(next_part, int) else {}
|
402
|
+
)
|
377
403
|
elif part not in nested_structure:
|
378
404
|
next_part = indices[i + 1]
|
379
405
|
nested_structure[part] = [] if isinstance(next_part, int) else {}
|
@@ -434,11 +460,20 @@ def get_flattened_keys(
|
|
434
460
|
if not inplace:
|
435
461
|
return convert.to_list(
|
436
462
|
flatten(
|
437
|
-
nested_structure,
|
463
|
+
nested_structure,
|
464
|
+
sep=sep,
|
465
|
+
max_depth=max_depth,
|
466
|
+
dict_only=dict_only,
|
438
467
|
).keys()
|
439
468
|
)
|
440
469
|
obj_copy = SysUtil.create_copy(nested_structure, num=1)
|
441
|
-
flatten(
|
470
|
+
flatten(
|
471
|
+
obj_copy,
|
472
|
+
sep=sep,
|
473
|
+
max_depth=max_depth,
|
474
|
+
inplace=True,
|
475
|
+
dict_only=dict_only,
|
476
|
+
)
|
442
477
|
return convert.to_list(obj_copy.keys())
|
443
478
|
|
444
479
|
|
@@ -484,7 +519,9 @@ def _dynamic_flatten_in_place(
|
|
484
519
|
for k, v in items:
|
485
520
|
new_key = f"{parent_key}{sep}{k}" if parent_key else k
|
486
521
|
|
487
|
-
if isinstance(v, dict) and (
|
522
|
+
if isinstance(v, dict) and (
|
523
|
+
max_depth is None or current_depth < max_depth
|
524
|
+
):
|
488
525
|
_dynamic_flatten_in_place(
|
489
526
|
v,
|
490
527
|
parent_key=new_key,
|
@@ -744,7 +781,9 @@ def _filter_dict(
|
|
744
781
|
return {k: v for k, v in dictionary.items() if condition((k, v))}
|
745
782
|
|
746
783
|
|
747
|
-
def _filter_list(
|
784
|
+
def _filter_list(
|
785
|
+
lst: list[Any], condition: Callable[[Any], bool]
|
786
|
+
) -> list[Any]:
|
748
787
|
"""
|
749
788
|
Filters elements in a list based on a specified condition.
|
750
789
|
|
@@ -777,5 +816,7 @@ def _get_target_container(
|
|
777
816
|
else:
|
778
817
|
raise KeyError("Key not found in dictionary")
|
779
818
|
else:
|
780
|
-
raise TypeError(
|
819
|
+
raise TypeError(
|
820
|
+
"Current element is neither a list nor a dictionary"
|
821
|
+
)
|
781
822
|
return current_element
|
lionagi/libs/ln_parse.py
CHANGED
@@ -63,7 +63,9 @@ class ParseUtil:
|
|
63
63
|
open_brackets.append(brackets[char])
|
64
64
|
elif char in brackets.values():
|
65
65
|
if not open_brackets or open_brackets[-1] != char:
|
66
|
-
raise ValueError(
|
66
|
+
raise ValueError(
|
67
|
+
"Mismatched or extra closing bracket found."
|
68
|
+
)
|
67
69
|
open_brackets.pop()
|
68
70
|
|
69
71
|
return str_to_parse + "".join(reversed(open_brackets))
|
@@ -94,7 +96,9 @@ class ParseUtil:
|
|
94
96
|
def replacement(match):
|
95
97
|
char = match.group(0)
|
96
98
|
_char_map = char_map or md_json_char_map
|
97
|
-
return _char_map.get(
|
99
|
+
return _char_map.get(
|
100
|
+
char, char
|
101
|
+
) # Default to the char itself if not in map
|
98
102
|
|
99
103
|
# Match any of the special characters to be escaped.
|
100
104
|
return re.sub(r'[\n\r\t"]', replacement, value)
|
@@ -149,7 +153,9 @@ class ParseUtil:
|
|
149
153
|
)
|
150
154
|
if not match:
|
151
155
|
str_to_parse = str_to_parse.strip()
|
152
|
-
if str_to_parse.startswith("```json\n") and str_to_parse.endswith(
|
156
|
+
if str_to_parse.startswith("```json\n") and str_to_parse.endswith(
|
157
|
+
"\n```"
|
158
|
+
):
|
153
159
|
str_to_parse = str_to_parse[8:-4].strip()
|
154
160
|
|
155
161
|
parser = parser or ParseUtil.fuzzy_parse_json
|
@@ -209,11 +215,15 @@ class ParseUtil:
|
|
209
215
|
{'key': 'value'}
|
210
216
|
"""
|
211
217
|
json_obj = ParseUtil.extract_json_block(
|
212
|
-
str_to_parse,
|
218
|
+
str_to_parse,
|
219
|
+
language="json",
|
220
|
+
parser=parser or ParseUtil.fuzzy_parse_json,
|
213
221
|
)
|
214
222
|
|
215
223
|
if expected_keys:
|
216
|
-
if missing_keys := [
|
224
|
+
if missing_keys := [
|
225
|
+
key for key in expected_keys if key not in json_obj
|
226
|
+
]:
|
217
227
|
raise ValueError(
|
218
228
|
f"Missing expected keys in JSON object: {', '.join(missing_keys)}"
|
219
229
|
)
|
@@ -276,7 +286,9 @@ class ParseUtil:
|
|
276
286
|
elif lines[i].startswith(" "):
|
277
287
|
param_desc = lines[i].split(":", 1)
|
278
288
|
if len(param_desc) == 1:
|
279
|
-
params_description[
|
289
|
+
params_description[
|
290
|
+
current_param
|
291
|
+
] += f" {param_desc[0].strip()}"
|
280
292
|
continue
|
281
293
|
param, desc = param_desc
|
282
294
|
param = param.split("(")[0].strip()
|
@@ -443,9 +455,13 @@ class ParseUtil:
|
|
443
455
|
func_name = func.__name__
|
444
456
|
|
445
457
|
if not func_description:
|
446
|
-
func_description, _ = ParseUtil._extract_docstring_details(
|
458
|
+
func_description, _ = ParseUtil._extract_docstring_details(
|
459
|
+
func, style
|
460
|
+
)
|
447
461
|
if not params_description:
|
448
|
-
_, params_description = ParseUtil._extract_docstring_details(
|
462
|
+
_, params_description = ParseUtil._extract_docstring_details(
|
463
|
+
func, style
|
464
|
+
)
|
449
465
|
|
450
466
|
# Extracting parameters with typing hints
|
451
467
|
sig = inspect.signature(func)
|
@@ -459,7 +475,9 @@ class ParseUtil:
|
|
459
475
|
# Default type to string and update if type hint is available
|
460
476
|
param_type = "string"
|
461
477
|
if param.annotation is not inspect.Parameter.empty:
|
462
|
-
param_type = ParseUtil._python_to_json_type(
|
478
|
+
param_type = ParseUtil._python_to_json_type(
|
479
|
+
param.annotation.__name__
|
480
|
+
)
|
463
481
|
|
464
482
|
# Extract parameter description from docstring, if available
|
465
483
|
param_description = params_description.get(
|
@@ -546,7 +564,9 @@ class StringMatch:
|
|
546
564
|
|
547
565
|
transpositions //= 2
|
548
566
|
return (
|
549
|
-
matches / s_len
|
567
|
+
matches / s_len
|
568
|
+
+ matches / t_len
|
569
|
+
+ (matches - transpositions) / matches
|
550
570
|
) / 3.0
|
551
571
|
|
552
572
|
@staticmethod
|
@@ -639,7 +659,9 @@ class StringMatch:
|
|
639
659
|
used_keys.add(k)
|
640
660
|
else:
|
641
661
|
# Calculate Jaro-Winkler similarity scores for each potential match
|
642
|
-
scores = np.array(
|
662
|
+
scores = np.array(
|
663
|
+
[score_func(k, field) for field in fields_set]
|
664
|
+
)
|
643
665
|
# Find the index of the highest score
|
644
666
|
max_score_index = np.argmax(scores)
|
645
667
|
# Select the best match based on the highest score
|
@@ -697,19 +719,25 @@ class StringMatch:
|
|
697
719
|
|
698
720
|
except Exception:
|
699
721
|
# if still failed we try to extract the json block using re and parse it again
|
700
|
-
match = re.search(
|
722
|
+
match = re.search(
|
723
|
+
r"```json\n({.*?})\n```", out_, re.DOTALL
|
724
|
+
)
|
701
725
|
if match:
|
702
726
|
out_ = match.group(1)
|
703
727
|
try:
|
704
728
|
out_ = ParseUtil.fuzzy_parse_json(out_)
|
705
|
-
return StringMatch.correct_dict_keys(
|
729
|
+
return StringMatch.correct_dict_keys(
|
730
|
+
keys, out_
|
731
|
+
)
|
706
732
|
|
707
733
|
except:
|
708
734
|
try:
|
709
735
|
out_ = ParseUtil.fuzzy_parse_json(
|
710
736
|
out_.replace("'", '"')
|
711
737
|
)
|
712
|
-
return StringMatch.correct_dict_keys(
|
738
|
+
return StringMatch.correct_dict_keys(
|
739
|
+
keys, out_
|
740
|
+
)
|
713
741
|
except:
|
714
742
|
pass
|
715
743
|
|
@@ -717,4 +745,6 @@ class StringMatch:
|
|
717
745
|
try:
|
718
746
|
return StringMatch.correct_dict_keys(keys, out_)
|
719
747
|
except Exception as e:
|
720
|
-
raise ValueError(
|
748
|
+
raise ValueError(
|
749
|
+
f"Failed to force_validate_dict for input: {x}"
|
750
|
+
) from e
|
lionagi/libs/ln_queue.py
CHANGED
@@ -3,7 +3,8 @@ A class that manages asynchronous task processing with controlled concurrency.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import asyncio
|
6
|
-
from
|
6
|
+
from collections.abc import Callable
|
7
|
+
from typing import Any
|
7
8
|
|
8
9
|
from lionagi.libs import func_call
|
9
10
|
|
@@ -69,7 +70,9 @@ class AsyncQueue:
|
|
69
70
|
"""
|
70
71
|
return self._stop_event.is_set()
|
71
72
|
|
72
|
-
async def process_requests(
|
73
|
+
async def process_requests(
|
74
|
+
self, func: Callable, retry_kwargs: dict = {}
|
75
|
+
) -> None:
|
73
76
|
"""
|
74
77
|
Processes tasks from the queue using the provided function with retries.
|
75
78
|
|
@@ -85,7 +88,9 @@ class AsyncQueue:
|
|
85
88
|
tasks = set()
|
86
89
|
while not self.stopped():
|
87
90
|
if len(tasks) >= self.max_concurrent_tasks:
|
88
|
-
_, done = await asyncio.wait(
|
91
|
+
_, done = await asyncio.wait(
|
92
|
+
tasks, return_when=asyncio.FIRST_COMPLETED
|
93
|
+
)
|
89
94
|
tasks.difference_update(done)
|
90
95
|
|
91
96
|
async with self.semaphore:
|
lionagi/libs/ln_tokenize.py
CHANGED
@@ -20,11 +20,16 @@ class TokenizeUtil:
|
|
20
20
|
|
21
21
|
if encoding_model:
|
22
22
|
try:
|
23
|
-
encoding_name = tiktoken.encoding_name_for_model(
|
23
|
+
encoding_name = tiktoken.encoding_name_for_model(
|
24
|
+
encoding_model
|
25
|
+
)
|
24
26
|
except:
|
25
27
|
encoding_name = encoding_name or "cl100k_base"
|
26
28
|
|
27
|
-
if
|
29
|
+
if (
|
30
|
+
not encoding_name
|
31
|
+
or encoding_name in tiktoken.list_encoding_names()
|
32
|
+
):
|
28
33
|
encoding_name = encoding_name or "cl100k_base"
|
29
34
|
encoding = tiktoken.get_encoding(encoding_name)
|
30
35
|
|
@@ -88,9 +93,13 @@ class TokenizeUtil:
|
|
88
93
|
chunks.append(text[start_idx:end_idx])
|
89
94
|
|
90
95
|
if len(text) - chunk_size * (n_chunks - 1) > threshold:
|
91
|
-
chunks.append(
|
96
|
+
chunks.append(
|
97
|
+
text[chunk_size * (n_chunks - 1) - overlap_size :]
|
98
|
+
)
|
92
99
|
else:
|
93
|
-
chunks[-1] += text[
|
100
|
+
chunks[-1] += text[
|
101
|
+
chunk_size * (n_chunks - 1) + overlap_size :
|
102
|
+
]
|
94
103
|
|
95
104
|
return chunks
|
96
105
|
|
@@ -157,10 +166,14 @@ class TokenizeUtil:
|
|
157
166
|
chunks.append(tokens[start_idx:end_idx])
|
158
167
|
|
159
168
|
if len(tokens) - chunk_size * (n_chunks - 1) > threshold:
|
160
|
-
chunks.append(
|
169
|
+
chunks.append(
|
170
|
+
tokens[chunk_size * (n_chunks - 1) - overlap_size :]
|
171
|
+
)
|
161
172
|
else:
|
162
173
|
chunks[-1] += tokens[-residue:]
|
163
174
|
|
164
175
|
return (
|
165
|
-
[" ".join(chunk) for chunk in chunks]
|
176
|
+
[" ".join(chunk) for chunk in chunks]
|
177
|
+
if not return_tokens
|
178
|
+
else chunks
|
166
179
|
)
|
lionagi/libs/ln_validate.py
CHANGED
@@ -6,7 +6,14 @@ including numeric, boolean, string, and enum. It also provides a dictionary `val
|
|
6
6
|
maps data types to their corresponding validation functions.
|
7
7
|
"""
|
8
8
|
|
9
|
-
from .ln_convert import
|
9
|
+
from .ln_convert import (
|
10
|
+
is_same_dtype,
|
11
|
+
strip_lower,
|
12
|
+
to_dict,
|
13
|
+
to_list,
|
14
|
+
to_num,
|
15
|
+
to_str,
|
16
|
+
)
|
10
17
|
from .ln_parse import ParseUtil, StringMatch
|
11
18
|
|
12
19
|
|
@@ -19,7 +26,9 @@ def check_dict_field(x, keys: list[str] | dict, fix_=True, **kwargs):
|
|
19
26
|
return StringMatch.force_validate_dict(x, keys=keys, **kwargs)
|
20
27
|
except Exception as e:
|
21
28
|
raise ValueError("Invalid dict field type.") from e
|
22
|
-
raise ValueError(
|
29
|
+
raise ValueError(
|
30
|
+
f"Default value for DICT must be a dict, got {type(x).__name__}"
|
31
|
+
)
|
23
32
|
|
24
33
|
|
25
34
|
def check_action_field(x, fix_=True, **kwargs):
|
@@ -275,7 +284,9 @@ def _fix_enum_field(x, choices, **kwargs):
|
|
275
284
|
x = to_str(x)
|
276
285
|
return StringMatch.choose_most_similar(x, choices, **kwargs)
|
277
286
|
except Exception as e:
|
278
|
-
raise ValueError(
|
287
|
+
raise ValueError(
|
288
|
+
f"Failed to convert {x} into one of the choices"
|
289
|
+
) from e
|
279
290
|
|
280
291
|
|
281
292
|
validation_funcs = {
|
lionagi/libs/sys_util.py
CHANGED
@@ -48,7 +48,9 @@ class SysUtil:
|
|
48
48
|
return datetime.now(**config_)
|
49
49
|
|
50
50
|
@staticmethod
|
51
|
-
def change_dict_key(
|
51
|
+
def change_dict_key(
|
52
|
+
dict_: dict[Any, Any], old_key: str, new_key: str
|
53
|
+
) -> None:
|
52
54
|
"""Safely changes a key in a dictionary if the old key exists.
|
53
55
|
|
54
56
|
Args:
|
@@ -122,7 +124,9 @@ class SysUtil:
|
|
122
124
|
return sha256(current_time + random_bytes).hexdigest()[:n]
|
123
125
|
|
124
126
|
@staticmethod
|
125
|
-
def get_bins(
|
127
|
+
def get_bins(
|
128
|
+
input_: list[str], upper: int | None = 2000
|
129
|
+
) -> list[list[int]]:
|
126
130
|
"""Organizes indices of strings into bins based on a cumulative upper limit.
|
127
131
|
|
128
132
|
Args:
|
@@ -157,7 +161,11 @@ class SysUtil:
|
|
157
161
|
str: A string identifying the CPU architecture ('apple_silicon' or 'other_cpu').
|
158
162
|
"""
|
159
163
|
arch: str = platform.machine().lower()
|
160
|
-
return
|
164
|
+
return (
|
165
|
+
"apple_silicon"
|
166
|
+
if "arm" in arch or "aarch64" in arch
|
167
|
+
else "other_cpu"
|
168
|
+
)
|
161
169
|
|
162
170
|
@staticmethod
|
163
171
|
def install_import(
|
@@ -195,7 +203,9 @@ class SysUtil:
|
|
195
203
|
print(
|
196
204
|
f"Module {full_import_path} or attribute {import_name} not found. Installing {pip_name}..."
|
197
205
|
)
|
198
|
-
subprocess.check_call(
|
206
|
+
subprocess.check_call(
|
207
|
+
[sys.executable, "-m", "pip", "install", pip_name]
|
208
|
+
)
|
199
209
|
|
200
210
|
# Retry the import after installation
|
201
211
|
if import_name:
|
@@ -254,18 +264,25 @@ class SysUtil:
|
|
254
264
|
package_name, module_name, import_name, pip_name
|
255
265
|
)
|
256
266
|
else:
|
257
|
-
logging.info(
|
267
|
+
logging.info(
|
268
|
+
f"Package {package_name} not found. {error_message}"
|
269
|
+
)
|
258
270
|
raise ImportError(
|
259
271
|
f"Package {package_name} not found. {error_message}"
|
260
272
|
)
|
261
273
|
except ImportError as e: # More specific exception handling
|
262
274
|
logging.error(f"Failed to import {package_name}. Error: {e}")
|
263
|
-
raise ValueError(
|
275
|
+
raise ValueError(
|
276
|
+
f"Failed to import {package_name}. Error: {e}"
|
277
|
+
) from e
|
264
278
|
|
265
279
|
@staticmethod
|
266
280
|
def list_installed_packages() -> list:
|
267
281
|
"""list all installed packages using importlib.metadata."""
|
268
|
-
return [
|
282
|
+
return [
|
283
|
+
dist.metadata["Name"]
|
284
|
+
for dist in importlib.metadata.distributions()
|
285
|
+
]
|
269
286
|
|
270
287
|
@staticmethod
|
271
288
|
def uninstall_package(package_name: str) -> None:
|
@@ -283,7 +300,14 @@ class SysUtil:
|
|
283
300
|
"""Update a specified package."""
|
284
301
|
try:
|
285
302
|
subprocess.check_call(
|
286
|
-
[
|
303
|
+
[
|
304
|
+
sys.executable,
|
305
|
+
"-m",
|
306
|
+
"pip",
|
307
|
+
"install",
|
308
|
+
"--upgrade",
|
309
|
+
package_name,
|
310
|
+
]
|
287
311
|
)
|
288
312
|
print(f"Successfully updated {package_name}.")
|
289
313
|
except subprocess.CalledProcessError as e:
|
@@ -291,7 +315,9 @@ class SysUtil:
|
|
291
315
|
|
292
316
|
@staticmethod
|
293
317
|
def clear_dir(
|
294
|
-
dir_path: Path | str,
|
318
|
+
dir_path: Path | str,
|
319
|
+
recursive: bool = False,
|
320
|
+
exclude: list[str] = None,
|
295
321
|
) -> None:
|
296
322
|
"""
|
297
323
|
Clears all files (and, if recursive, directories) in the specified directory,
|
@@ -376,7 +402,9 @@ class SysUtil:
|
|
376
402
|
"Invalid filename. Ensure it doesn't contain illegal characters and has a valid extension."
|
377
403
|
)
|
378
404
|
|
379
|
-
name, ext =
|
405
|
+
name, ext = (
|
406
|
+
filename.rsplit(".", 1) if "." in filename else (filename, "")
|
407
|
+
)
|
380
408
|
ext = f".{ext}" if ext else ""
|
381
409
|
|
382
410
|
timestamp_str = ""
|
@@ -384,7 +412,9 @@ class SysUtil:
|
|
384
412
|
timestamp_format = custom_timestamp_format or "%Y%m%d%H%M%S"
|
385
413
|
timestamp_str = datetime.now().strftime(timestamp_format)
|
386
414
|
filename = (
|
387
|
-
f"{timestamp_str}_{name}"
|
415
|
+
f"{timestamp_str}_{name}"
|
416
|
+
if time_prefix
|
417
|
+
else f"{name}_{timestamp_str}"
|
388
418
|
)
|
389
419
|
else:
|
390
420
|
filename = name
|
@@ -462,7 +492,9 @@ class SysUtil:
|
|
462
492
|
if path.is_file():
|
463
493
|
return path.stat().st_size
|
464
494
|
elif path.is_dir():
|
465
|
-
return sum(
|
495
|
+
return sum(
|
496
|
+
f.stat().st_size for f in path.glob("**/*") if f.is_file()
|
497
|
+
)
|
466
498
|
else:
|
467
499
|
raise FileNotFoundError(f"{path} does not exist.")
|
468
500
|
|
lionagi/lions/coder/coder.py
CHANGED
@@ -6,7 +6,11 @@ from lionagi.core import Session
|
|
6
6
|
from lionagi.libs import ParseUtil
|
7
7
|
|
8
8
|
from .base_prompts import CODER_PROMPTS
|
9
|
-
from .util import
|
9
|
+
from .util import (
|
10
|
+
install_missing_dependencies,
|
11
|
+
save_code_file,
|
12
|
+
set_up_interpreter,
|
13
|
+
)
|
10
14
|
|
11
15
|
|
12
16
|
class Coder:
|
@@ -55,38 +59,50 @@ class Coder:
|
|
55
59
|
|
56
60
|
async def _plan_code(self, context):
|
57
61
|
print("Planning code...")
|
58
|
-
plans = await self.session.chat(
|
62
|
+
plans = await self.session.chat(
|
63
|
+
self.prompts["plan_code"], context=context
|
64
|
+
)
|
59
65
|
print("Code planning completed.")
|
60
66
|
return plans
|
61
67
|
|
62
68
|
async def _write_code(self, context=None):
|
63
69
|
print("Writing code...")
|
64
|
-
code = await self.session.chat(
|
70
|
+
code = await self.session.chat(
|
71
|
+
self.prompts["write_code"], context=context
|
72
|
+
)
|
65
73
|
print("Code writing completed.")
|
66
74
|
return ParseUtil.extract_code_blocks(code)
|
67
75
|
|
68
76
|
async def _review_code(self, context=None):
|
69
77
|
print("Reviewing code...")
|
70
|
-
code = await self.session.chat(
|
78
|
+
code = await self.session.chat(
|
79
|
+
self.prompts["review_code"], context=context
|
80
|
+
)
|
71
81
|
print("Code review completed.")
|
72
82
|
return code
|
73
83
|
|
74
84
|
async def _modify_code(self, context=None):
|
75
85
|
print("Modifying code...")
|
76
|
-
code = await self.session.chat(
|
86
|
+
code = await self.session.chat(
|
87
|
+
self.prompts["modify_code"], context=context
|
88
|
+
)
|
77
89
|
print("Code modification completed.")
|
78
90
|
return code
|
79
91
|
|
80
92
|
async def _debug_code(self, context=None):
|
81
93
|
print("Debugging code...")
|
82
|
-
code = await self.session.chat(
|
94
|
+
code = await self.session.chat(
|
95
|
+
self.prompts["debug_code"], context=context
|
96
|
+
)
|
83
97
|
print("Code debugging completed.")
|
84
98
|
return code
|
85
99
|
|
86
100
|
def _handle_execution_error(self, execution, required_libraries=None):
|
87
101
|
print("Handling execution error...")
|
88
102
|
if execution.error and execution.error.name == "ModuleNotFoundError":
|
89
|
-
print(
|
103
|
+
print(
|
104
|
+
"ModuleNotFoundError detected. Installing missing dependencies..."
|
105
|
+
)
|
90
106
|
install_missing_dependencies(required_libraries)
|
91
107
|
print("Dependencies installed. Retrying execution.")
|
92
108
|
return "try again"
|
@@ -118,7 +134,7 @@ class Coder:
|
|
118
134
|
def _load_code_file(file_path):
|
119
135
|
print("Loading code...")
|
120
136
|
try:
|
121
|
-
with open(file_path
|
137
|
+
with open(file_path) as file:
|
122
138
|
print("Code loaded.")
|
123
139
|
return file.read()
|
124
140
|
except FileNotFoundError:
|
lionagi/lions/coder/util.py
CHANGED
@@ -28,7 +28,9 @@ def set_up_interpreter(interpreter_provider="e2b", key_scheme=E2B_key_scheme):
|
|
28
28
|
def install_missing_dependencies(required_libraries):
|
29
29
|
print("Checking for missing dependencies...")
|
30
30
|
missing_libraries = [
|
31
|
-
library
|
31
|
+
library
|
32
|
+
for library in required_libraries
|
33
|
+
if not is_library_installed(library)
|
32
34
|
]
|
33
35
|
|
34
36
|
if missing_libraries:
|
@@ -58,7 +60,9 @@ def install_library(library):
|
|
58
60
|
import subprocess
|
59
61
|
import sys
|
60
62
|
|
61
|
-
subprocess.check_call(
|
63
|
+
subprocess.check_call(
|
64
|
+
[sys.executable, "-m", "pip", "install", library]
|
65
|
+
)
|
62
66
|
print(f"Successfully installed {library}.")
|
63
67
|
except subprocess.CalledProcessError as e:
|
64
68
|
print(f"Error occurred while installing {library}: {str(e)}")
|