lionagi 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. lionagi/core/agent/base_agent.py +2 -3
  2. lionagi/core/branch/base.py +1 -1
  3. lionagi/core/branch/branch.py +2 -1
  4. lionagi/core/branch/flow_mixin.py +1 -1
  5. lionagi/core/branch/util.py +1 -1
  6. lionagi/core/execute/base_executor.py +1 -4
  7. lionagi/core/execute/branch_executor.py +66 -3
  8. lionagi/core/execute/instruction_map_executor.py +48 -0
  9. lionagi/core/execute/neo4j_executor.py +381 -0
  10. lionagi/core/execute/structure_executor.py +120 -4
  11. lionagi/core/flow/monoflow/ReAct.py +21 -19
  12. lionagi/core/flow/monoflow/chat_mixin.py +1 -1
  13. lionagi/core/flow/monoflow/followup.py +14 -13
  14. lionagi/core/flow/polyflow/__init__.py +1 -1
  15. lionagi/core/generic/component.py +197 -122
  16. lionagi/core/generic/condition.py +3 -1
  17. lionagi/core/generic/edge.py +77 -25
  18. lionagi/core/graph/graph.py +1 -1
  19. lionagi/core/mail/mail_manager.py +3 -2
  20. lionagi/core/session/session.py +1 -1
  21. lionagi/core/tool/tool_manager.py +10 -9
  22. lionagi/experimental/__init__.py +0 -0
  23. lionagi/experimental/directive/__init__.py +0 -0
  24. lionagi/experimental/directive/evaluator/__init__.py +0 -0
  25. lionagi/experimental/directive/evaluator/ast_evaluator.py +115 -0
  26. lionagi/experimental/directive/evaluator/base_evaluator.py +202 -0
  27. lionagi/experimental/directive/evaluator/sandbox_.py +14 -0
  28. lionagi/experimental/directive/evaluator/script_engine.py +83 -0
  29. lionagi/experimental/directive/parser/__init__.py +0 -0
  30. lionagi/experimental/directive/parser/base_parser.py +215 -0
  31. lionagi/experimental/directive/schema.py +36 -0
  32. lionagi/experimental/directive/template_/__init__.py +0 -0
  33. lionagi/experimental/directive/template_/base_template.py +63 -0
  34. lionagi/experimental/report/__init__.py +0 -0
  35. lionagi/experimental/report/form.py +64 -0
  36. lionagi/experimental/report/report.py +138 -0
  37. lionagi/experimental/report/util.py +47 -0
  38. lionagi/experimental/tool/__init__.py +0 -0
  39. lionagi/experimental/tool/function_calling.py +43 -0
  40. lionagi/experimental/tool/manual.py +66 -0
  41. lionagi/experimental/tool/schema.py +59 -0
  42. lionagi/experimental/tool/tool_manager.py +138 -0
  43. lionagi/experimental/tool/util.py +16 -0
  44. lionagi/experimental/validator/__init__.py +0 -0
  45. lionagi/experimental/validator/rule.py +139 -0
  46. lionagi/experimental/validator/validator.py +56 -0
  47. lionagi/experimental/work/__init__.py +10 -0
  48. lionagi/experimental/work/async_queue.py +54 -0
  49. lionagi/experimental/work/schema.py +73 -0
  50. lionagi/experimental/work/work_function.py +67 -0
  51. lionagi/experimental/work/worker.py +56 -0
  52. lionagi/experimental/work2/__init__.py +0 -0
  53. lionagi/experimental/work2/form.py +371 -0
  54. lionagi/experimental/work2/report.py +289 -0
  55. lionagi/experimental/work2/schema.py +30 -0
  56. lionagi/experimental/work2/tests.py +72 -0
  57. lionagi/experimental/work2/util.py +0 -0
  58. lionagi/experimental/work2/work.py +0 -0
  59. lionagi/experimental/work2/work_function.py +89 -0
  60. lionagi/experimental/work2/worker.py +12 -0
  61. lionagi/integrations/bridge/autogen_/__init__.py +0 -0
  62. lionagi/integrations/bridge/autogen_/autogen_.py +124 -0
  63. lionagi/integrations/bridge/llamaindex_/get_index.py +294 -0
  64. lionagi/integrations/bridge/llamaindex_/llama_pack.py +227 -0
  65. lionagi/integrations/bridge/transformers_/__init__.py +0 -0
  66. lionagi/integrations/bridge/transformers_/install_.py +36 -0
  67. lionagi/integrations/config/oai_configs.py +1 -1
  68. lionagi/integrations/config/ollama_configs.py +1 -1
  69. lionagi/integrations/config/openrouter_configs.py +1 -1
  70. lionagi/integrations/storage/__init__.py +3 -0
  71. lionagi/integrations/storage/neo4j.py +673 -0
  72. lionagi/integrations/storage/storage_util.py +289 -0
  73. lionagi/integrations/storage/structure_excel.py +268 -0
  74. lionagi/integrations/storage/to_csv.py +63 -0
  75. lionagi/integrations/storage/to_excel.py +76 -0
  76. lionagi/libs/__init__.py +4 -0
  77. lionagi/libs/ln_knowledge_graph.py +405 -0
  78. lionagi/libs/ln_queue.py +101 -0
  79. lionagi/libs/ln_tokenizer.py +57 -0
  80. lionagi/libs/sys_util.py +1 -1
  81. lionagi/lions/__init__.py +0 -0
  82. lionagi/lions/coder/__init__.py +0 -0
  83. lionagi/lions/coder/add_feature.py +20 -0
  84. lionagi/lions/coder/base_prompts.py +22 -0
  85. lionagi/lions/coder/coder.py +121 -0
  86. lionagi/lions/coder/util.py +91 -0
  87. lionagi/lions/researcher/__init__.py +0 -0
  88. lionagi/lions/researcher/data_source/__init__.py +0 -0
  89. lionagi/lions/researcher/data_source/finhub_.py +191 -0
  90. lionagi/lions/researcher/data_source/google_.py +199 -0
  91. lionagi/lions/researcher/data_source/wiki_.py +96 -0
  92. lionagi/lions/researcher/data_source/yfinance_.py +21 -0
  93. lionagi/tests/libs/test_queue.py +67 -0
  94. lionagi/tests/test_core/generic/__init__.py +0 -0
  95. lionagi/tests/test_core/generic/test_component.py +89 -0
  96. lionagi/tests/test_core/test_branch.py +0 -1
  97. lionagi/version.py +1 -1
  98. {lionagi-0.1.0.dist-info → lionagi-0.1.2.dist-info}/METADATA +1 -1
  99. lionagi-0.1.2.dist-info/RECORD +206 -0
  100. lionagi-0.1.0.dist-info/RECORD +0 -136
  101. {lionagi-0.1.0.dist-info → lionagi-0.1.2.dist-info}/LICENSE +0 -0
  102. {lionagi-0.1.0.dist-info → lionagi-0.1.2.dist-info}/WHEEL +0 -0
  103. {lionagi-0.1.0.dist-info → lionagi-0.1.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,215 @@
1
+ from typing import List, Optional
2
+
3
+ from lionagi.libs.ln_tokenizer import BaseToken
4
+ from ..schema import IfNode, TryNode, ForNode
5
+
6
+
7
+ class BaseDirectiveParser:
8
+ """A base parser with lookahead, error recovery, and backtracking support.
9
+
10
+ Attributes:
11
+ tokens (List[BaseToken]): A list of tokens to be parsed.
12
+ current_token_index (int): The index of the current token in the tokens list.
13
+ current_token (Optional[BaseToken]): The current token being processed.
14
+
15
+ Examples:
16
+ >>> tokenizer = BaseTokenizer("IF x > 10 THEN DO something ENDIF")
17
+ >>> tokens = tokenizer.get_tokens()
18
+ >>> parser = BaseParser(tokens)
19
+ >>> print(parser.current_token)
20
+ BaseToken(KEYWORD, IF)
21
+ """
22
+
23
+ def __init__(self, tokens: List[BaseToken]):
24
+ self.tokens = tokens
25
+ self.current_token_index = -1
26
+ self.current_token: Optional[BaseToken] = None
27
+ self.next_token()
28
+
29
+ def next_token(self) -> None:
30
+ """Advances to the next token in the list."""
31
+ self.current_token_index += 1
32
+ if self.current_token_index < len(self.tokens):
33
+ self.current_token = self.tokens[self.current_token_index]
34
+ else:
35
+ self.current_token = None
36
+
37
+ def peek_next_token(self, offset: int = 1) -> BaseToken | None:
38
+ """Peeks at the next token without consuming it.
39
+
40
+ Args:
41
+ offset (int): The number of tokens to look ahead.
42
+
43
+ Returns:
44
+ Optional[BaseToken]: The token at the specified lookahead offset, or None if end of list.
45
+ """
46
+ peek_index = self.current_token_index + offset
47
+ if peek_index < len(self.tokens):
48
+ return self.tokens[peek_index]
49
+ else:
50
+ return None
51
+
52
+ def skip_until(self, token_types: List[str]) -> None:
53
+ """Skips tokens until a token of the specified type is found.
54
+
55
+ Args:
56
+ token_types (List[str]): A list of token types to stop skipping.
57
+ """
58
+ while self.current_token and self.current_token.type not in token_types:
59
+ self.next_token()
60
+
61
+ def mark(self) -> int:
62
+ """Marks the current position in the token list for potential backtracking.
63
+
64
+ Returns:
65
+ int: The current token index.
66
+ """
67
+ return self.current_token_index
68
+
69
+ def reset_to_mark(self, mark: int) -> None:
70
+ """Resets the parser to a previously marked position.
71
+
72
+ Args:
73
+ mark (int): The token index to reset to.
74
+ """
75
+ self.current_token_index = mark - 1
76
+ self.next_token()
77
+
78
+ def skip_semicolon(self):
79
+ if self.current_token and self.current_token.value == ";":
80
+ self.next_token()
81
+
82
+ def parse_expression(self):
83
+ expr = ""
84
+ while self.current_token and self.current_token.value != ";":
85
+ expr += self.current_token.value + " "
86
+ self.next_token()
87
+ # Expecting a semicolon at the end of the condition
88
+ if self.current_token.value != ";":
89
+ raise SyntaxError("Expected ';' at the end of the condition")
90
+ self.next_token() # Move past the semicolon to the next part of the statement
91
+ return expr.strip()
92
+
93
+ def parse_if_block(self):
94
+ block = []
95
+ # Parse the block until 'ELSE', 'ENDIF', ensuring not to include semicolons as part of the block
96
+ while self.current_token and self.current_token.value not in ("ENDIF", "ELSE"):
97
+ if self.current_token.value == "DO":
98
+ self.next_token() # Move past 'DO' to get to the action
99
+ block.append(self.current_token.value) # Add the action to the block
100
+ self.next_token() # Move to the next token, which could be a semicolon or the next action
101
+ if self.current_token.value == ";":
102
+ self.next_token() # Move past the semicolon
103
+ return block
104
+
105
+ def parse_if_statement(self):
106
+ if self.current_token.type != "KEYWORD" or self.current_token.value != "IF":
107
+ raise SyntaxError("Expected IF statement")
108
+ self.next_token() # Skip 'IF'
109
+
110
+ condition = self.parse_expression() # Now properly ends after the semicolon
111
+
112
+ true_block = []
113
+ if self.current_token.value == "DO":
114
+ true_block = self.parse_if_block() # Parse true block after 'DO'
115
+
116
+ false_block = None
117
+ if self.current_token and self.current_token.value == "ELSE":
118
+ self.next_token() # Skip 'ELSE', expect 'DO' next for the false block
119
+ self.skip_semicolon()
120
+ if self.current_token.value != "DO":
121
+ raise SyntaxError("Expected 'DO' after 'ELSE'")
122
+ self.next_token() # Skip 'DO'
123
+ false_block = self.parse_if_block() # Parse false block
124
+
125
+ return IfNode(condition, true_block, false_block)
126
+
127
+ def parse_for_statement(self):
128
+ if self.current_token.type != "KEYWORD" or self.current_token.value != "FOR":
129
+ raise SyntaxError("Expected FOR statement")
130
+ self.next_token() # Skip 'FOR'
131
+
132
+ # Parse the iterator variable
133
+ if self.current_token.type != "IDENTIFIER":
134
+ raise SyntaxError("Expected iterator variable after FOR")
135
+ iterator = self.current_token.value
136
+ self.next_token() # Move past the iterator variable
137
+
138
+ # Expect and skip 'IN' keyword
139
+ if self.current_token.type != "KEYWORD" or self.current_token.value != "IN":
140
+ raise SyntaxError("Expected 'IN' after iterator variable")
141
+ self.next_token() # Move past 'IN'
142
+
143
+ # Parse the collection
144
+ if self.current_token.type not in ["IDENTIFIER", "LITERAL"]:
145
+ raise SyntaxError("Expected collection after 'IN'")
146
+ collection = self.current_token.value
147
+ self.next_token() # Move past the collection
148
+
149
+ # Now, parse the block of statements to execute
150
+ true_block = self.parse_for_block()
151
+
152
+ # Construct and return a ForNode
153
+ return ForNode(iterator, collection, true_block)
154
+
155
+ def parse_for_block(self):
156
+ block = []
157
+ # Skip initial 'DO' if present
158
+ if self.current_token and self.current_token.value == "DO":
159
+ self.next_token()
160
+
161
+ while self.current_token and self.current_token.value not in ("ENDFOR",):
162
+ if self.current_token.value == ";":
163
+ # If a semicolon is encountered, skip it and move to the next token
164
+ self.next_token()
165
+ continue
166
+ # Add the current token to the block unless it's a 'DO' or ';'
167
+ if self.current_token.value != "DO":
168
+ block.append(self.current_token.value)
169
+ self.next_token()
170
+
171
+ # The loop exits when 'ENDFOR' is encountered; move past it for subsequent parsing
172
+ self.next_token() # Skip 'ENDFOR'
173
+ return block
174
+
175
+ def parse_try_statement(self):
176
+ if self.current_token.type != "KEYWORD" or self.current_token.value != "TRY":
177
+ raise SyntaxError("Expected TRY statement")
178
+ self.next_token() # Skip 'TRY'
179
+
180
+ try_block = self.parse_try_block("EXCEPT") # Parse the try block until 'EXCEPT'
181
+
182
+ # Now expecting 'EXCEPT' keyword
183
+ if not (self.current_token and self.current_token.value == "EXCEPT"):
184
+ raise SyntaxError("Expected 'EXCEPT' after try block")
185
+ self.next_token() # Move past 'EXCEPT'
186
+
187
+ except_block = self.parse_try_block(
188
+ "ENDTRY"
189
+ ) # Parse the except block until 'ENDTRY'
190
+
191
+ # Ensure we are correctly positioned after 'ENDTRY'
192
+ if self.current_token and self.current_token.value != "ENDTRY":
193
+ raise SyntaxError("Expected 'ENDTRY' at the end of except block")
194
+ self.next_token() # Move past 'ENDTRY' for subsequent parsing
195
+
196
+ return TryNode(try_block, except_block)
197
+
198
+ def parse_try_block(self, stop_keyword):
199
+ block = []
200
+ while self.current_token and self.current_token.value != stop_keyword:
201
+ if self.current_token.value == "DO":
202
+ self.next_token() # Move past 'DO' to get to the action
203
+ elif self.current_token.value == ";":
204
+ self.next_token() # Move past the semicolon
205
+ continue # Skip adding ';' to the block
206
+ else:
207
+ block.append(self.current_token.value) # Add the action to the block
208
+ self.next_token()
209
+
210
+ return block
211
+
212
+
213
+ # "IF condition1 && condition2; DO action2; ELSE; DO action3; ENDIF;"
214
+ # "FOR input_ IN collections; DO action(input_); ENDFOR;"
215
+ # "TRY; DO action(); EXCEPT; DO action(input_); ENDTRY;"
@@ -0,0 +1,36 @@
1
+ class Node:
2
+ """Base class for all nodes in the abstract syntax tree (AST)."""
3
+
4
+ pass
5
+
6
+
7
+ class IfNode(Node):
8
+ """Represents an 'IF' statement in the AST."""
9
+
10
+ def __init__(self, condition, true_block, false_block=None):
11
+ self.condition = condition
12
+ self.true_block = true_block
13
+ self.false_block = false_block
14
+
15
+
16
+ class ForNode(Node):
17
+ """Represents a 'FOR' loop in the AST."""
18
+
19
+ def __init__(self, iterator, collection, block):
20
+ self.iterator = iterator
21
+ self.collection = collection
22
+ self.block = block
23
+
24
+
25
+ class TryNode(Node):
26
+ """Represents a 'TRY-EXCEPT' block in the AST."""
27
+
28
+ def __init__(self, try_block, except_block):
29
+ self.try_block = try_block
30
+ self.except_block = except_block
31
+
32
+
33
+ class ActionNode(Node):
34
+
35
+ def __init__(self, action) -> None:
36
+ self.action = action
File without changes
@@ -0,0 +1,63 @@
1
+ from typing import Any, Dict
2
+ import re
3
+
4
+ from ..evaluator.base_evaluator import BaseEvaluator
5
+
6
+
7
+ class BaseDirectiveTemplate:
8
+ """Enhanced base template class for processing templates with conditionals and loops."""
9
+
10
+ def __init__(self, template_str: str):
11
+ self.template_str = template_str
12
+ self.evaluator = BaseEvaluator()
13
+
14
+ def _render_conditionals(self, context: Dict[str, Any]) -> str:
15
+ """Processes conditional statements with improved logic and support for 'else'."""
16
+ pattern = re.compile(r"\{if (.*?)\}(.*?)\{else\}(.*?)\{endif\}", re.DOTALL)
17
+
18
+ def evaluate_condition(match):
19
+ condition, if_text, else_text = match.groups()
20
+ if self.evaluator.evaluate(condition, context):
21
+ return if_text
22
+ else:
23
+ return else_text
24
+
25
+ return pattern.sub(evaluate_condition, self.template_str)
26
+
27
+ def _render_loops(self, template: str, context: Dict[str, Any]) -> str:
28
+ """Processes loop statements within the template."""
29
+ loop_pattern = re.compile(r"\{for (\w+) in (\w+)\}(.*?)\{endfor\}", re.DOTALL)
30
+
31
+ def render_loop(match):
32
+ iterator_var, collection_name, loop_body = match.groups()
33
+ collection = context.get(collection_name, [])
34
+ if not isinstance(collection, (list, range)):
35
+ raise ValueError(
36
+ f"Expected list or range for '{collection_name}', got {type(collection).__name__}."
37
+ )
38
+
39
+ loop_result = ""
40
+ for item in collection:
41
+ loop_context = context.copy()
42
+ loop_context[iterator_var] = item
43
+ loop_result += self.fill(loop_body, loop_context)
44
+
45
+ return loop_result
46
+
47
+ return loop_pattern.sub(render_loop, template)
48
+
49
+ def fill(self, template_str: str = "", context: Dict[str, Any] = {}) -> str:
50
+ """Fills the template with values from context after processing conditionals and loops."""
51
+ if not template_str: # Use the instance's template if not provided
52
+ template_str = self.template_str
53
+
54
+ # First, process conditionals with 'else'
55
+ template_with_conditionals = self._render_conditionals(template_str)
56
+ # Then, process loops
57
+ template_with_loops = self._render_loops(template_with_conditionals, context)
58
+ # Finally, substitute the placeholders with context values
59
+ try:
60
+ return template_with_loops.format(**context)
61
+ except KeyError as e:
62
+ print(f"Missing key in context: {e}")
63
+ return template_with_loops
File without changes
@@ -0,0 +1,64 @@
1
+ from pydantic import Field
2
+
3
+ # from lionagi import logging as _logging
4
+ from lionagi.core.generic import BaseComponent
5
+ from lionagi.experimental.report.util import get_input_output_fields, system_fields
6
+
7
+
8
+ class Form(BaseComponent):
9
+
10
+ assignment: str = Field(..., examples=["input1, input2 -> output"])
11
+
12
+ input_fields: list[str] = Field(default_factory=list)
13
+ output_fields: list[str] = Field(default_factory=list)
14
+
15
+ def __init__(self, **kwargs):
16
+ """
17
+ at initialization, all relevant fields if not already provided, are set to None,
18
+ not every field is required to be filled, nor required to be declared at initialization
19
+ """
20
+ super().__init__(**kwargs)
21
+ self.input_fields, self.output_fields = get_input_output_fields(self.assignment)
22
+ for i in self.input_fields + self.output_fields:
23
+ if i not in self.model_fields:
24
+ self._add_field(i, value=None)
25
+
26
+ @property
27
+ def workable(self):
28
+ if self.filled:
29
+ return False
30
+
31
+ for i in self.input_fields:
32
+ if not getattr(self, i, None):
33
+ return False
34
+
35
+ return True
36
+
37
+ @property
38
+ def work_fields(self):
39
+ dict_ = self.to_dict()
40
+ return {
41
+ k: v
42
+ for k, v in dict_.items()
43
+ if k not in system_fields and k in self.input_fields + self.output_fields
44
+ }
45
+
46
+ @property
47
+ def filled(self):
48
+ return all([value is not None for _, value in self.work_fields.items()])
49
+
50
+ def fill(self, form: "Form" = None, **kwargs):
51
+ """
52
+ only work fields for this form can be filled
53
+ a field can only be filled once
54
+ """
55
+ if self.filled:
56
+ raise ValueError("Form is already filled")
57
+
58
+ fields = form.work_fields if form else {}
59
+ kwargs = {**fields, **kwargs}
60
+
61
+ for k, v in kwargs.items():
62
+ if k not in self.work_fields:
63
+ raise ValueError(f"Field {k} is not a valid work field")
64
+ setattr(self, k, v)
@@ -0,0 +1,138 @@
1
+ from typing import Any, Type
2
+ from pydantic import Field
3
+
4
+ # from lionagi import logging as _logging
5
+ from lionagi.core.generic import BaseComponent
6
+ from lionagi.experimental.report.form import Form
7
+ from lionagi.experimental.report.util import get_input_output_fields
8
+
9
+
10
+ class Report(BaseComponent):
11
+
12
+ assignment: str = Field(..., examples=["input1, input2 -> output"])
13
+
14
+ forms: dict[str, Form] = Field(
15
+ default_factory=dict,
16
+ description="A dictionary of forms related to the report, in {assignment: Form} format.",
17
+ )
18
+
19
+ form_assignments: list = Field(
20
+ [],
21
+ description="assignment for the report",
22
+ examples=[["a, b -> c", "a -> e", "b -> f", "c -> g", "e, f, g -> h"]],
23
+ )
24
+
25
+ form_template: Type[Form] = Field(
26
+ Form, description="The template for the forms in the report."
27
+ )
28
+
29
+ input_fields: list[str] = Field(default_factory=list)
30
+ output_fields: list[str] = Field(default_factory=list)
31
+
32
+ def __init__(self, **kwargs):
33
+ """
34
+ at initialization, all relevant fields if not already provided, are set to None
35
+ """
36
+ super().__init__(**kwargs)
37
+ self.input_fields, self.output_fields = get_input_output_fields(self.assignment)
38
+
39
+ # if assignments is not provided, set it to assignment
40
+ if self.form_assignments == []:
41
+ self.form_assignments.append(self.assignment)
42
+
43
+ # create forms
44
+ new_forms = {i: self.form_template(assignment=i) for i in self.form_assignments}
45
+
46
+ # add new forms into the report (will ignore new forms already in the
47
+ # report with same assignment)
48
+ for k, v in new_forms.items():
49
+ if k not in self.forms:
50
+ self.forms[k] = v
51
+
52
+ # if the fields are not declared in the report, add them to report
53
+ # with value set to None
54
+ for k, v in self.forms.items():
55
+ for f in list(v.work_fields.keys()):
56
+ if f not in self.model_fields:
57
+ field = v.model_fields[f]
58
+ self._add_field(f, value=None, field=field)
59
+
60
+ # if there are fields in the report that are not in the forms, add them to
61
+ # the forms with values
62
+ for k, v in self.model_fields.items():
63
+ if getattr(self, k, None) is not None:
64
+ for f in self.forms.values():
65
+ if k in f.work_fields:
66
+ f.fill(**{k: getattr(self, k)})
67
+
68
+ @property
69
+ def work_fields(self) -> dict[str, Any]:
70
+ """
71
+ all work fields across all forms, including intermediate output fields,
72
+ this information is extracted from the forms
73
+ """
74
+
75
+ all_fields = {}
76
+ for form in self.forms.values():
77
+ for k, v in form.work_fields.items():
78
+ if k not in all_fields:
79
+ all_fields[k] = v
80
+ return all_fields
81
+
82
+ def fill(self, **kwargs):
83
+ """
84
+ fill the information to both the report and forms
85
+ """
86
+ kwargs = {**self.work_fields, **kwargs}
87
+ for k, v in kwargs.items():
88
+ if k in self.work_fields and getattr(self, k, None) is None:
89
+ setattr(self, k, v)
90
+
91
+ for form in self.forms.values():
92
+ if not form.filled:
93
+ _kwargs = {k: v for k, v in kwargs.items() if k in form.work_fields}
94
+ form.fill(**_kwargs)
95
+
96
+ @property
97
+ def filled(self):
98
+ return all([value is not None for _, value in self.work_fields.items()])
99
+
100
+ @property
101
+ def workable(self) -> bool:
102
+
103
+ if self.filled:
104
+ # _logging.info("The report is already filled, no need to work on it.")
105
+ return False
106
+
107
+ for i in self.input_fields:
108
+ if not getattr(self, i, None):
109
+ # _logging.error(f"Field '{i}' is required to work on the report.")
110
+ return False
111
+
112
+ # this is the required fields from report's own assignment
113
+ fields = self.input_fields
114
+ fields.extend(self.output_fields)
115
+
116
+ # if the report's own assignment is not in the forms, return False
117
+ for f in fields:
118
+ if f not in self.work_fields:
119
+ # _logging.error(f"Field {f} is a required deliverable, not found in work field.")
120
+ return False
121
+
122
+ # get all the output fields from all the forms
123
+ outs = []
124
+ for form in self.forms.values():
125
+ outs.extend(form.output_fields)
126
+
127
+ # all output fields should be unique, not a single output field should be
128
+ # calculated by more than one form
129
+ if len(outs) != len(set(outs)):
130
+ # _logging.error("There are duplicate output fields in the forms.")
131
+ return False
132
+
133
+ return True
134
+
135
+ @property
136
+ def next_forms(self) -> list[Form] | None:
137
+ a = [i for i in self.forms.values() if i.workable]
138
+ return a if len(a) > 0 else None
@@ -0,0 +1,47 @@
1
+ from lionagi.libs import convert
2
+
3
+ system_fields = [
4
+ "id_",
5
+ "node_id",
6
+ "meta",
7
+ "metadata",
8
+ "timestamp",
9
+ "content",
10
+ "assignment",
11
+ "assignments",
12
+ "task",
13
+ "template_name",
14
+ "version",
15
+ "description",
16
+ "in_validation_kwargs",
17
+ "out_validation_kwargs",
18
+ "fix_input",
19
+ "fix_output",
20
+ "input_fields",
21
+ "output_fields",
22
+ "choices",
23
+ "prompt_fields",
24
+ "prompt_fields_annotation",
25
+ "instruction_context",
26
+ "instruction",
27
+ "instruction_output_fields",
28
+ "inputs",
29
+ "outputs",
30
+ "process",
31
+ "_validate_field",
32
+ "_process_input",
33
+ "_process_response",
34
+ "_validate_field_choices",
35
+ "_validate_input_choices",
36
+ "_validate_output_choices",
37
+ ]
38
+
39
+
40
+ def get_input_output_fields(str_: str) -> list[list[str]]:
41
+
42
+ inputs, outputs = str_.split("->")
43
+
44
+ input_fields = [convert.strip_lower(i) for i in inputs.split(",")]
45
+ output_fields = [convert.strip_lower(o) for o in outputs.split(",")]
46
+
47
+ return input_fields, output_fields
File without changes
@@ -0,0 +1,43 @@
1
+ from typing import Any, Callable
2
+ from pydantic import BaseModel, Field, field_serializer
3
+ from functools import singledispatchmethod
4
+ from lionagi.libs import convert
5
+
6
+
7
+ class FunctionCalling(BaseModel):
8
+ func: Any = Field(..., alias="function")
9
+ kwargs: Any = Field({}, alias="arguments")
10
+
11
+ @field_serializer("func")
12
+ def serialize_func(self, func: Callable):
13
+ return func.__name__
14
+
15
+ @property
16
+ def func_name(self):
17
+ return self.func.__name__
18
+
19
+ @classmethod
20
+ @singledispatchmethod
21
+ def create(cls, func_call: Any):
22
+ raise TypeError(f"Unsupported type {type(func_call)}")
23
+
24
+ @create.register
25
+ def _(cls, func_call: tuple):
26
+ if len(func_call) == 2:
27
+ return cls(func=func_call[0], kwargs=func_call[1])
28
+ else:
29
+ raise ValueError(f"Invalid tuple length {len(func_call)}")
30
+
31
+ @create.register
32
+ def _(cls, func_call: dict):
33
+ return cls(**func_call)
34
+
35
+ @create.register
36
+ def _(cls, func_call: str):
37
+ try:
38
+ return cls(**convert.to_dict(func_call))
39
+ except Exception as e:
40
+ raise ValueError(f"Invalid string {func_call}") from e
41
+
42
+ def __str__(self):
43
+ return f"{self.func_name}({self.kwargs})"
@@ -0,0 +1,66 @@
1
+ import re
2
+ from typing import Dict, Union, Callable, Any
3
+
4
+
5
+ class BaseManual:
6
+ def __init__(self, template_str: str):
7
+ self.template_str = template_str
8
+
9
+ def _evaluate_condition(self, match, context):
10
+ condition, text = match.groups()
11
+ # Future implementations might parse and evaluate the condition more thoroughly
12
+ return text if condition in context and context[condition] else ""
13
+
14
+ def _render_conditionals(self, context: Dict[str, Union[str, int, float]]) -> str:
15
+ conditional_pattern = re.compile(r"\{if (.*?)\}(.*?)\{endif\}", re.DOTALL)
16
+ return conditional_pattern.sub(
17
+ lambda match: self._evaluate_condition(match, context), self.template_str
18
+ )
19
+
20
+ def _replace_callable(self, match, context):
21
+ key = match.group(1)
22
+ if key in context:
23
+ value = context[key]
24
+ return str(value() if callable(value) else value)
25
+ return match.group(0) # Unmatched placeholders remain unchanged.
26
+
27
+ def _render_placeholders(
28
+ self,
29
+ rendered_template: str,
30
+ context: Dict[str, Union[str, int, float, Callable]],
31
+ ) -> str:
32
+ return re.sub(
33
+ r"\{(\w+)\}",
34
+ lambda match: self._replace_callable(match, context),
35
+ rendered_template,
36
+ )
37
+
38
+ def generate(self, context: Dict[str, Union[str, int, float, Callable]]) -> str:
39
+ """
40
+ Generates output by first processing conditionals, then rendering placeholders,
41
+ including executing callable objects for dynamic data generation.
42
+ """
43
+ template_with_conditionals = self._render_conditionals(context)
44
+ final_output = self._render_placeholders(template_with_conditionals, context)
45
+ return final_output
46
+
47
+
48
+ # from experiments.executor.executor import SafeEvaluator
49
+
50
+ # class DecisionTreeManual:
51
+ # def __init__(self, root):
52
+ # self.root = root
53
+ # self.evaluator = SafeEvaluator()
54
+
55
+ # def evaluate(self, context):
56
+ # return self._traverse_tree(self.root, context)
57
+
58
+ # def _traverse_tree(self, node, context):
59
+ # if isinstance(node, CompositeActionNode) or isinstance(node, ActionNode):
60
+ # return node.execute(context)
61
+ # elif isinstance(node, DecisionNode):
62
+ # condition_result = self.evaluator.evaluate(node.condition, context)
63
+ # next_node = node.true_branch if condition_result else node.false_branch
64
+ # return self._traverse_tree(next_node, context)
65
+ # else:
66
+ # raise ValueError("Invalid node type.")