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
@@ -1,15 +1,15 @@
1
1
  """
2
- Module for representing conditions and edges between nodes in a graph structure.
2
+ Module for representing conditions and edges between nodes in a graph.
3
3
 
4
- This module provides the base for creating and managing edges that connect nodes
5
- within a graph. It includes support for conditional edges, allowing the dynamic
6
- evaluation of connections based on custom logic.
4
+ This module provides the base for creating and managing edges that connect
5
+ nodes within a graph. It includes support for conditional edges, allowing
6
+ the dynamic evaluation of connections based on custom logic.
7
7
  """
8
8
 
9
9
  from typing import Any
10
10
  from pydantic import Field, field_validator
11
- from lionagi.core.generic.component import BaseComponent, BaseNode
12
- from lionagi.core.generic.condition import Condition
11
+ from .component import BaseComponent
12
+ from .condition import Condition
13
13
 
14
14
 
15
15
  class Edge(BaseComponent):
@@ -19,12 +19,14 @@ class Edge(BaseComponent):
19
19
  Attributes:
20
20
  head (str): The identifier of the head node of the edge.
21
21
  tail (str): The identifier of the tail node of the edge.
22
- condition (Optional[Condition]): An optional condition that must be met
22
+ condition (Condition | None): Optional condition that must be met
23
23
  for the edge to be considered active.
24
- label (Optional[str]): An optional label for the edge.
24
+ label (str | None): An optional label for the edge.
25
+ bundle (bool): A flag indicating if the edge is bundled.
25
26
 
26
27
  Methods:
27
- check_condition: Evaluates if the condition associated with the edge is met.
28
+ check_condition: Evaluates if the condition is met.
29
+ string_condition: Retrieves the condition class source code.
28
30
  """
29
31
 
30
32
  head: str = Field(
@@ -37,7 +39,8 @@ class Edge(BaseComponent):
37
39
  )
38
40
  condition: Condition | None = Field(
39
41
  default=None,
40
- description="An optional condition that must be met for the edge to be considered active.",
42
+ description="Optional condition that must be met for the edge "
43
+ "to be considered active.",
41
44
  )
42
45
  label: str | None = Field(
43
46
  default=None,
@@ -51,20 +54,19 @@ class Edge(BaseComponent):
51
54
  @field_validator("head", "tail", mode="before")
52
55
  def _validate_head_tail(cls, value):
53
56
  """
54
- Validates the head and tail fields to ensure they are valid node identifiers.
57
+ Validates head and tail fields to ensure valid node identifiers.
55
58
 
56
59
  Args:
57
- value: The value of the field being validated.
58
- values: A dictionary of all other values on the model.
59
- field: The model field being validated.
60
+ value (Any): The value of the field being validated.
60
61
 
61
62
  Returns:
62
- The validated value, ensuring it is a valid identifier.
63
+ str: The validated value, ensuring it is a valid identifier.
63
64
 
64
65
  Raises:
65
66
  ValueError: If the validation fails.
66
67
  """
67
- if isinstance(value, BaseNode):
68
+
69
+ if isinstance(value, BaseComponent):
68
70
  return value.id_
69
71
  return value
70
72
 
@@ -73,7 +75,7 @@ class Edge(BaseComponent):
73
75
  Evaluates if the condition associated with the edge is met.
74
76
 
75
77
  Args:
76
- obj (dict[str, Any]): The context object used for condition evaluation.
78
+ obj (dict[str, Any]): Context for condition evaluation.
77
79
 
78
80
  Returns:
79
81
  bool: True if the condition is met, False otherwise.
@@ -85,11 +87,66 @@ class Edge(BaseComponent):
85
87
  raise ValueError("The condition for the edge is not set.")
86
88
  return self.condition(obj)
87
89
 
88
- def __str__(self) -> str:
90
+ def string_condition(self):
89
91
  """
90
- Returns a simple string representation of the Relationship.
92
+ Retrieves the condition class source code as a string.
93
+
94
+ This method is useful for serialization and debugging, allowing
95
+ the condition logic to be inspected or stored in a human-readable
96
+ format. It employs advanced introspection techniques to locate and
97
+ extract the exact class definition, handling edge cases like
98
+ dynamically defined classes or classes defined interactively.
99
+
100
+ Returns:
101
+ str | None: The condition class source code if available.
102
+ If the condition is None or the source code cannot be
103
+ located, this method returns None.
104
+
105
+ Raises:
106
+ TypeError: If the condition class source code cannot be found
107
+ due to the class being defined in a non-standard manner or
108
+ in the interactive interpreter (__main__ context).
91
109
  """
110
+ if self.condition is None:
111
+ return
112
+
113
+ import inspect, sys
114
+
115
+ def new_getfile(object, _old_getfile=inspect.getfile):
116
+ if not inspect.isclass(object):
117
+ return _old_getfile(object)
118
+
119
+ # Lookup by parent module (as in current inspect)
120
+ if hasattr(object, "__module__"):
121
+ object_ = sys.modules.get(object.__module__)
122
+ if hasattr(object_, "__file__"):
123
+ return object_.__file__
124
+
125
+ # If parent module is __main__, lookup by methods (NEW)
126
+ for name, member in inspect.getmembers(object):
127
+ if (
128
+ inspect.isfunction(member)
129
+ and object.__qualname__ + "." + member.__name__
130
+ == member.__qualname__
131
+ ):
132
+ return inspect.getfile(member)
133
+ else:
134
+ raise TypeError("Source for {!r} not found".format(object))
135
+
136
+ inspect.getfile = new_getfile
137
+
138
+ import inspect
139
+ from IPython.core.magics.code import extract_symbols
140
+
141
+ obj = self.condition.__class__
142
+ cell_code = "".join(inspect.linecache.getlines(new_getfile(obj)))
143
+ class_code = extract_symbols(cell_code, obj.__name__)[0][0]
144
+ return class_code
92
145
 
146
+ def __str__(self) -> str:
147
+ """
148
+ Returns a simple string representation of the Edge.
149
+ """
93
150
  return (
94
151
  f"Edge (id_={self.id_}, from={self.head}, to={self.tail}, "
95
152
  f"label={self.label})"
@@ -97,12 +154,7 @@ class Edge(BaseComponent):
97
154
 
98
155
  def __repr__(self) -> str:
99
156
  """
100
- Returns a detailed string representation of the Relationship.
101
-
102
- Examples:
103
- >>> edge = Relationship(source_node_id="node1", target_node_id="node2")
104
- >>> repr(edge)
105
- 'Relationship(id_=None, from=node1, to=node2, content=None, metadata=None, label=None)'
157
+ Returns a detailed string representation of the Edge.
106
158
  """
107
159
  return (
108
160
  f"Edge(id_={self.id_}, from={self.head}, to={self.tail}, "
@@ -92,7 +92,7 @@ class Graph(BaseStructure):
92
92
  for node_id, node in self.internal_nodes.items():
93
93
  node_info = node.to_dict()
94
94
  node_info.pop("id_")
95
- node_info.update({"class_name": node.class_name()})
95
+ node_info.update({"class_name": node.class_name})
96
96
  g.add_node(node_id, **node_info)
97
97
 
98
98
  for _edge in list(self.internal_edges.values()):
@@ -17,10 +17,11 @@ class MailManager:
17
17
  and sender.
18
18
  """
19
19
 
20
- def __init__(self, sources):
20
+ def __init__(self, sources=None):
21
21
  self.sources = {}
22
22
  self.mails = {}
23
- self.add_sources(sources)
23
+ if sources:
24
+ self.add_sources(sources)
24
25
  self.execute_stop = False
25
26
 
26
27
  def add_sources(self, sources):
@@ -982,4 +982,4 @@ class Session:
982
982
  if system:
983
983
  self.default_branch.add_message(system=system, sender=sender)
984
984
 
985
- self.llmconfig = self.default_branch.llmconfig
985
+ self.llmconfig = self.default_branch.llmconfig
@@ -159,15 +159,16 @@ class ToolManager:
159
159
  else:
160
160
  raise ValueError(f"Function {tool} is not registered.")
161
161
 
162
- if isinstance(tools, bool):
163
- tool_kwarg = {"tools": self.to_tool_schema_list()}
164
- kwargs = tool_kwarg | kwargs
165
-
166
- else:
167
- if not isinstance(tools, list):
168
- tools = [tools]
169
- tool_kwarg = {"tools": func_call.lcall(tools, tool_check)}
170
- kwargs = tool_kwarg | kwargs
162
+ if tools:
163
+ if isinstance(tools, bool):
164
+ tool_kwarg = {"tools": self.to_tool_schema_list()}
165
+ kwargs = tool_kwarg | kwargs
166
+
167
+ else:
168
+ if not isinstance(tools, list):
169
+ tools = [tools]
170
+ tool_kwarg = {"tools": func_call.lcall(tools, tool_check)}
171
+ kwargs = tool_kwarg | kwargs
171
172
 
172
173
  return kwargs
173
174
 
File without changes
File without changes
File without changes
@@ -0,0 +1,115 @@
1
+ import ast
2
+ import operator
3
+
4
+
5
+ class ASTEvaluator:
6
+ """
7
+ Safely evaluates expressions using AST parsing to prevent unsafe operations.
8
+ """
9
+
10
+ def __init__(self):
11
+ self.allowed_operators = {
12
+ ast.Eq: operator.eq,
13
+ ast.NotEq: operator.ne,
14
+ ast.Lt: operator.lt,
15
+ ast.LtE: operator.le,
16
+ ast.Gt: operator.gt,
17
+ ast.GtE: operator.ge,
18
+ # Additional operators can be added here as needed
19
+ }
20
+
21
+ def evaluate(self, expression, context):
22
+ """
23
+ Evaluate a condition expression within a given context using AST parsing.
24
+ """
25
+ try:
26
+ tree = ast.parse(expression, mode="eval")
27
+ return self._evaluate_node(tree.body, context)
28
+ except Exception as e:
29
+ raise ValueError(f"Failed to evaluate expression: {expression}. Error: {e}")
30
+
31
+ def _evaluate_node(self, node, context):
32
+ if isinstance(node, ast.Compare):
33
+ left = self._evaluate_node(node.left, context)
34
+ for operation, comparator in zip(node.ops, node.comparators):
35
+ op_func = self.allowed_operators.get(type(operation))
36
+ if not op_func:
37
+ raise ValueError(
38
+ f"Operation {type(operation).__name__} is not allowed."
39
+ )
40
+ right = self._evaluate_node(comparator, context)
41
+ if not op_func(left, right):
42
+ return False
43
+ return True
44
+ elif isinstance(node, ast.Name):
45
+ return context.get(node.id)
46
+ elif isinstance(node, ast.Constant):
47
+ return node.n
48
+ else:
49
+ raise ValueError(
50
+ "Unsupported AST node type encountered in condition evaluation."
51
+ )
52
+
53
+
54
+ class ASTEvaluationEngine:
55
+ """
56
+ Executes scripts safely using the SafeEvaluator for expression evaluation.
57
+ """
58
+
59
+ def __init__(self):
60
+ self.variables = {}
61
+ self.safe_evaluator = ASTEvaluator()
62
+ self.functions = {
63
+ "processData": self.process_data,
64
+ }
65
+
66
+ def process_data(self, data):
67
+ # Example placeholder function for data processing
68
+ return data * 2
69
+
70
+ def _evaluate_expression(self, expression):
71
+ """
72
+ Evaluates expressions within scripts using SafeEvaluator.
73
+ """
74
+ # Here, 'self.variables' serves as the context for the evaluation
75
+ return self.safe_evaluator.evaluate(expression, self.variables)
76
+
77
+ def _assign_variable(self, var_name, value):
78
+ """
79
+ Assigns a value to a variable within the script's context.
80
+ """
81
+ self.variables[var_name] = value
82
+
83
+ def _execute_function(self, func_name, arg):
84
+ """
85
+ Executes a predefined function with the given argument.
86
+ """
87
+ if func_name in self.functions:
88
+ function = self.functions[func_name]
89
+ return function(arg)
90
+ else:
91
+ raise ValueError(f"Function '{func_name}' is not defined.")
92
+
93
+ def execute(self, script):
94
+ """
95
+ Parses and executes a script, handling variable assignments and function calls.
96
+ """
97
+ tree = ast.parse(script, mode="exec")
98
+ for stmt in tree.body:
99
+ if isinstance(stmt, ast.Assign):
100
+ var_name = stmt.targets[
101
+ 0
102
+ ].id # Assumes single target assignment for simplicity
103
+ # Convert the AST node back to a string for evaluation
104
+ value_expr = ast.unparse(stmt.value)
105
+ value = self._evaluate_expression(value_expr)
106
+ self._assign_variable(var_name, value)
107
+ elif isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Call):
108
+ func_name = stmt.value.func.id
109
+ arg_expr = ast.unparse(stmt.value.args[0])
110
+ arg = self._evaluate_expression(arg_expr)
111
+ self._execute_function(func_name, arg)
112
+ else:
113
+ raise ValueError(
114
+ "Unsupported statement type encountered in script execution."
115
+ )
@@ -0,0 +1,202 @@
1
+ import ast
2
+ import operator
3
+ from typing import Any, Dict, Tuple, Callable
4
+
5
+ from lionagi.libs.ln_convert import to_dict
6
+
7
+
8
+ class BaseEvaluator:
9
+ """
10
+ A class to evaluate mathematical and boolean expressions from strings using Python's AST.
11
+
12
+ Attributes:
13
+ allowed_operators (Dict[type, Any]): A dictionary mapping AST node types to their corresponding Python operator functions.
14
+ cache (Dict[Tuple[str, Tuple], Any]): A dictionary used to cache the results of evaluated expressions and sub-expressions.
15
+ """
16
+
17
+ def __init__(self) -> None:
18
+ """Initializes the evaluator with supported operators and an empty cache."""
19
+ self.allowed_operators: Dict[type, Any] = {
20
+ ast.Add: operator.add,
21
+ ast.Sub: operator.sub,
22
+ ast.Mult: operator.mul,
23
+ ast.Div: operator.truediv,
24
+ ast.Pow: operator.pow,
25
+ ast.Mod: operator.mod,
26
+ ast.Eq: operator.eq,
27
+ ast.NotEq: operator.ne,
28
+ ast.Lt: operator.lt,
29
+ ast.LtE: operator.le,
30
+ ast.Gt: operator.gt,
31
+ ast.GtE: operator.ge,
32
+ ast.And: lambda x, y: x and y,
33
+ ast.Or: lambda x, y: x or y,
34
+ ast.Not: operator.not_,
35
+ ast.USub: operator.neg,
36
+ }
37
+ self.cache: Dict[Tuple[str, Tuple], Any] = {}
38
+
39
+ def evaluate(self, expression: str, context: Dict[str, Any]) -> Any:
40
+ """
41
+ Evaluates a given expression string using the provided context.
42
+
43
+ Args:
44
+ expression (str): The expression to evaluate.
45
+ context (Dict[str, Any]): A dictionary mapping variable names to their values.
46
+
47
+ Returns:
48
+ Any: The result of the evaluated expression.
49
+
50
+ Raises:
51
+ ValueError: If the expression cannot be evaluated.
52
+ """
53
+ cache_key = (expression, tuple(sorted(context.items())))
54
+ if cache_key in self.cache:
55
+ return self.cache[cache_key]
56
+
57
+ try:
58
+ tree = ast.parse(expression, mode="eval")
59
+ result = self._evaluate_node(tree.body, context)
60
+ self.cache[cache_key] = result
61
+ return result
62
+ except Exception as e:
63
+ raise ValueError(f"Failed to evaluate expression: {expression}. Error: {e}")
64
+
65
+ def _evaluate_node(self, node: ast.AST, context: Dict[str, Any]) -> Any:
66
+ """Recursively evaluates an AST node."""
67
+ if isinstance(node, ast.BinOp):
68
+ left = self._evaluate_node(node.left, context)
69
+ op_func = self.allowed_operators[type(node.op)]
70
+ right = self._evaluate_node(node.right, context)
71
+ result = op_func(left, right)
72
+ elif isinstance(node, ast.UnaryOp):
73
+ operand = self._evaluate_node(node.operand, context)
74
+ result = self.allowed_operators[type(node.op)](operand)
75
+ elif isinstance(node, ast.Name):
76
+ result = context.get(node.id, None)
77
+ elif isinstance(node, ast.Constant):
78
+ result = node.value
79
+ elif isinstance(node, ast.Compare):
80
+ left = self._evaluate_node(node.left, context)
81
+ result = True
82
+ for operation, comparator in zip(node.ops, node.comparators):
83
+ op_func = self.allowed_operators[type(operation)]
84
+ right = self._evaluate_node(comparator, context)
85
+ result = result and op_func(left, right)
86
+ if not result:
87
+ break
88
+ left = right
89
+ elif isinstance(node, ast.BoolOp):
90
+ values = [self._evaluate_node(value, context) for value in node.values]
91
+ if isinstance(node.op, ast.And):
92
+ result = all(values)
93
+ elif isinstance(node.op, ast.Or):
94
+ result = any(values)
95
+ else:
96
+ raise ValueError("Unsupported boolean operation.")
97
+ else:
98
+ raise ValueError("Unsupported operation in condition.")
99
+ return result
100
+
101
+ def add_custom_operator(self, operator_name, operation_func):
102
+ """Adds a custom operator to the evaluator."""
103
+ custom_node_class = type(operator_name, (ast.AST,), {})
104
+ if custom_node_class not in self.allowed_operators:
105
+ self.allowed_operators[custom_node_class] = operation_func
106
+ else:
107
+ raise ValueError(f"Custom operator '{operator_name}' is already defined.")
108
+
109
+ def evaluate_file(self, file_path, context, format="line"):
110
+ """Evaluates expressions from a file."""
111
+ if format == "line":
112
+ with open(file_path, "r") as file:
113
+ last_result = None
114
+ for line in file:
115
+ line = line.strip()
116
+ if line:
117
+ last_result = self.evaluate(line, context)
118
+ return last_result
119
+ elif format == "json":
120
+ with open(file_path, "r") as file:
121
+ data = to_dict(file)
122
+ last_result = None
123
+ for expression in data:
124
+ last_result = self.evaluate(expression, context)
125
+ return last_result
126
+ else:
127
+ raise ValueError(f"Unsupported file format: {format}")
128
+
129
+ def validate_expression(self, expression):
130
+ """Validates the given expression."""
131
+ try:
132
+ tree = ast.parse(expression, mode="eval")
133
+ self._validate_node(tree.body)
134
+ return True, "Expression is valid."
135
+ except Exception as e:
136
+ return False, f"Invalid expression: {str(e)}"
137
+
138
+ def _validate_node(self, node):
139
+ """Validates an AST node."""
140
+ if isinstance(
141
+ node, (ast.BinOp, ast.Compare, ast.BoolOp, ast.Name, ast.Constant)
142
+ ):
143
+ if (
144
+ isinstance(node, ast.BinOp)
145
+ and type(node.op) not in self.allowed_operators
146
+ ):
147
+ raise ValueError(
148
+ f"Operation {type(node.op).__name__} is not supported."
149
+ )
150
+ else:
151
+ raise ValueError("Unsupported node type in expression.")
152
+
153
+
154
+ class BaseEvaluationEngine:
155
+ def __init__(self) -> None:
156
+ self.variables: Dict[str, Any] = {}
157
+ self.functions: Dict[str, Callable] = {
158
+ "print": print,
159
+ }
160
+
161
+ def _evaluate_expression(self, expression: str) -> Any:
162
+ try:
163
+ return eval(expression, {}, self.variables)
164
+ except NameError as e:
165
+ raise ValueError(f"Undefined variable. {e}")
166
+
167
+ def _assign_variable(self, var_name: str, value: Any) -> None:
168
+ self.variables[var_name] = value
169
+
170
+ def _execute_function(self, func_name: str, *args: Any) -> None:
171
+ if func_name in self.functions:
172
+ self.functions[func_name](*args)
173
+ else:
174
+ raise ValueError(f"Function {func_name} not defined.")
175
+
176
+ def _execute_statement(self, stmt: ast.AST) -> None:
177
+ if isinstance(stmt, ast.Assign):
178
+ var_name = stmt.targets[0].id
179
+ value = self._evaluate_expression(ast.unparse(stmt.value))
180
+ self._assign_variable(var_name, value)
181
+ elif isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Call):
182
+ func_name = stmt.value.func.id
183
+ args = [
184
+ self._evaluate_expression(ast.unparse(arg)) for arg in stmt.value.args
185
+ ]
186
+ self._execute_function(func_name, *args)
187
+ elif isinstance(stmt, ast.For):
188
+ iter_var = stmt.target.id
189
+ if isinstance(stmt.iter, ast.Call) and stmt.iter.func.id == "range":
190
+ start, end = [
191
+ self._evaluate_expression(ast.unparse(arg))
192
+ for arg in stmt.iter.args
193
+ ]
194
+ for i in range(start, end):
195
+ self.variables[iter_var] = i
196
+ for body_stmt in stmt.body:
197
+ self._execute_statement(body_stmt)
198
+
199
+ def execute(self, script: str) -> None:
200
+ tree = ast.parse(script)
201
+ for stmt in tree.body:
202
+ self._execute_statement(stmt)
@@ -0,0 +1,14 @@
1
+ # filename: enhanced_script_engine.py
2
+ import ast
3
+
4
+
5
+ class SandboxTransformer(ast.NodeTransformer):
6
+ """AST transformer to enforce restrictions in sandbox mode."""
7
+
8
+ def visit_Import(self, node):
9
+ raise RuntimeError("Import statements are not allowed in sandbox mode.")
10
+
11
+ def visit_Exec(self, node):
12
+ raise RuntimeError("Exec statements are not allowed in sandbox mode.")
13
+
14
+ # Add other visit methods for disallowed operations or nodes
@@ -0,0 +1,83 @@
1
+ import ast
2
+ from functools import lru_cache
3
+ from lionagi.libs import AsyncUtil
4
+ from .base_evaluator import BaseEvaluator
5
+ from .sandbox_ import SandboxTransformer
6
+
7
+
8
+ class ScriptEngine:
9
+ def __init__(self):
10
+ self.variables = {}
11
+ self.safe_evaluator = BaseEvaluator()
12
+ self.functions = {
13
+ "processData": self.process_data,
14
+ }
15
+
16
+ def process_data(self, data):
17
+ return data * 2
18
+
19
+ def _evaluate_expression(self, expression):
20
+ return self.safe_evaluator.evaluate(expression, self.variables)
21
+
22
+ def _assign_variable(self, var_name, value):
23
+ self.variables[var_name] = value
24
+
25
+ def _execute_function(self, func_name, arg):
26
+ if func_name in self.functions:
27
+ return self.functions[func_name](arg)
28
+ else:
29
+ raise ValueError(f"Function {func_name} not defined.")
30
+
31
+ def execute(self, script):
32
+ tree = ast.parse(script)
33
+ for stmt in tree.body:
34
+ if isinstance(stmt, ast.Assign):
35
+ var_name = stmt.targets[0].id
36
+ value = self._evaluate_expression(ast.unparse(stmt.value))
37
+ self._assign_variable(var_name, value)
38
+ elif isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Call):
39
+ func_name = stmt.value.func.id
40
+ arg = self._evaluate_expression(ast.unparse(stmt.value.args[0]))
41
+ result = self._execute_function(func_name, arg)
42
+ # For demonstration, manually update 'x' to simulate expected behavior
43
+ if func_name == "processData":
44
+ self._assign_variable("x", result)
45
+
46
+ def add_hook(self, event_name, callback):
47
+ """Subscribe a callback function to a specific event."""
48
+ self.hooks[event_name].append(callback)
49
+
50
+ def trigger_hooks(self, event_name, *args, **kwargs):
51
+ """Trigger all callbacks attached to a specific event."""
52
+ for callback in self.hooks[event_name]:
53
+ callback(*args, **kwargs)
54
+
55
+ async def process_data(self, data):
56
+ # Example asynchronous function
57
+ return data * 2
58
+
59
+ @lru_cache(maxsize=128)
60
+ def evaluate_expression(self, expression):
61
+ return self.safe_evaluator.evaluate(expression, self.variables)
62
+
63
+ async def _execute_function_async(self, func_name, arg):
64
+ if func_name in self.functions:
65
+ func = self.functions[func_name]
66
+ if AsyncUtil.is_coroutine_func(func):
67
+ return await func(arg)
68
+ else:
69
+ return func(arg)
70
+ else:
71
+ raise ValueError(f"Function {func_name} not defined.")
72
+
73
+ def execute_sandboxed(self, script):
74
+ # Parse and sanitize the script
75
+ tree = ast.parse(script, mode="exec")
76
+ sanitized_tree = SandboxTransformer().visit(tree)
77
+ ast.fix_missing_locations(sanitized_tree)
78
+
79
+ # Compile the sanitized AST
80
+ code = compile(sanitized_tree, "<sandbox>", "exec")
81
+
82
+ # Execute the code in a restricted namespace
83
+ exec(code, {"__builtins__": None}, self.variables)
File without changes