flatagents 0.4.1__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.
- flatagents/__init__.py +136 -0
- flatagents/actions.py +239 -0
- flatagents/assets/__init__.py +0 -0
- flatagents/assets/flatagent.d.ts +189 -0
- flatagents/assets/flatagent.schema.json +210 -0
- flatagents/assets/flatagent.slim.d.ts +52 -0
- flatagents/assets/flatmachine.d.ts +363 -0
- flatagents/assets/flatmachine.schema.json +515 -0
- flatagents/assets/flatmachine.slim.d.ts +94 -0
- flatagents/backends.py +222 -0
- flatagents/baseagent.py +814 -0
- flatagents/execution.py +462 -0
- flatagents/expressions/__init__.py +60 -0
- flatagents/expressions/cel.py +101 -0
- flatagents/expressions/simple.py +166 -0
- flatagents/flatagent.py +735 -0
- flatagents/flatmachine.py +1176 -0
- flatagents/gcp/__init__.py +25 -0
- flatagents/gcp/firestore.py +227 -0
- flatagents/hooks.py +380 -0
- flatagents/locking.py +69 -0
- flatagents/monitoring.py +373 -0
- flatagents/persistence.py +200 -0
- flatagents/utils.py +46 -0
- flatagents/validation.py +141 -0
- flatagents-0.4.1.dist-info/METADATA +310 -0
- flatagents-0.4.1.dist-info/RECORD +28 -0
- flatagents-0.4.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Simple expression engine for flatmachines.
|
|
3
|
+
|
|
4
|
+
Supports:
|
|
5
|
+
- Comparisons: ==, !=, <, <=, >, >=
|
|
6
|
+
- Boolean operators: and, or, not
|
|
7
|
+
- Field access: context.field, input.field, output.field
|
|
8
|
+
- Literals: strings, numbers, booleans, null
|
|
9
|
+
|
|
10
|
+
Examples:
|
|
11
|
+
context.score >= 8
|
|
12
|
+
context.current == context.target
|
|
13
|
+
context.score >= 8 and context.round < 4
|
|
14
|
+
not context.failed
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import ast
|
|
18
|
+
import operator
|
|
19
|
+
import re
|
|
20
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SimpleExpressionEngine:
|
|
24
|
+
"""
|
|
25
|
+
Simple expression parser and evaluator.
|
|
26
|
+
|
|
27
|
+
Uses Python's ast module for safe parsing, then evaluates
|
|
28
|
+
with a restricted set of operations.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
# Supported comparison operators
|
|
32
|
+
COMPARISON_OPS = {
|
|
33
|
+
ast.Eq: operator.eq,
|
|
34
|
+
ast.NotEq: operator.ne,
|
|
35
|
+
ast.Lt: operator.lt,
|
|
36
|
+
ast.LtE: operator.le,
|
|
37
|
+
ast.Gt: operator.gt,
|
|
38
|
+
ast.GtE: operator.ge,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Supported boolean operators
|
|
42
|
+
BOOL_OPS = {
|
|
43
|
+
ast.And: lambda values: all(values),
|
|
44
|
+
ast.Or: lambda values: any(values),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
def __init__(self):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
def evaluate(self, expression: str, variables: Dict[str, Any]) -> Any:
|
|
51
|
+
"""
|
|
52
|
+
Evaluate an expression with the given variables.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
expression: The expression string to evaluate
|
|
56
|
+
variables: Dictionary of variable names to values
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
The result of evaluating the expression
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
ValueError: If expression syntax is invalid
|
|
63
|
+
KeyError: If referenced variable doesn't exist
|
|
64
|
+
"""
|
|
65
|
+
if not expression or not expression.strip():
|
|
66
|
+
return True # Empty expression is always true (default transition)
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
tree = ast.parse(expression, mode='eval')
|
|
70
|
+
except SyntaxError as e:
|
|
71
|
+
raise ValueError(f"Invalid expression syntax: {expression}") from e
|
|
72
|
+
|
|
73
|
+
return self._eval_node(tree.body, variables)
|
|
74
|
+
|
|
75
|
+
def _eval_node(self, node: ast.AST, variables: Dict[str, Any]) -> Any:
|
|
76
|
+
"""Recursively evaluate an AST node."""
|
|
77
|
+
|
|
78
|
+
# Literals
|
|
79
|
+
if isinstance(node, ast.Constant):
|
|
80
|
+
return node.value
|
|
81
|
+
|
|
82
|
+
# Name (variable reference like 'context', 'input', 'output')
|
|
83
|
+
if isinstance(node, ast.Name):
|
|
84
|
+
name = node.id
|
|
85
|
+
# Handle boolean literals
|
|
86
|
+
if name == 'true' or name == 'True':
|
|
87
|
+
return True
|
|
88
|
+
if name == 'false' or name == 'False':
|
|
89
|
+
return False
|
|
90
|
+
if name == 'null' or name == 'None':
|
|
91
|
+
return None
|
|
92
|
+
if name not in variables:
|
|
93
|
+
raise KeyError(f"Unknown variable: {name}")
|
|
94
|
+
return variables[name]
|
|
95
|
+
|
|
96
|
+
# Attribute access (e.g., context.score, context.nested.field)
|
|
97
|
+
if isinstance(node, ast.Attribute):
|
|
98
|
+
value = self._eval_node(node.value, variables)
|
|
99
|
+
attr = node.attr
|
|
100
|
+
if isinstance(value, dict):
|
|
101
|
+
if attr not in value:
|
|
102
|
+
return None # Missing fields return None
|
|
103
|
+
return value[attr]
|
|
104
|
+
return getattr(value, attr, None)
|
|
105
|
+
|
|
106
|
+
# Comparison (e.g., context.score >= 8)
|
|
107
|
+
if isinstance(node, ast.Compare):
|
|
108
|
+
left = self._eval_node(node.left, variables)
|
|
109
|
+
for op, comparator in zip(node.ops, node.comparators):
|
|
110
|
+
right = self._eval_node(comparator, variables)
|
|
111
|
+
op_func = self.COMPARISON_OPS.get(type(op))
|
|
112
|
+
if op_func is None:
|
|
113
|
+
raise ValueError(f"Unsupported comparison operator: {type(op).__name__}")
|
|
114
|
+
if not op_func(left, right):
|
|
115
|
+
return False
|
|
116
|
+
left = right
|
|
117
|
+
return True
|
|
118
|
+
|
|
119
|
+
# Boolean operators (and, or)
|
|
120
|
+
if isinstance(node, ast.BoolOp):
|
|
121
|
+
op_func = self.BOOL_OPS.get(type(node.op))
|
|
122
|
+
if op_func is None:
|
|
123
|
+
raise ValueError(f"Unsupported boolean operator: {type(node.op).__name__}")
|
|
124
|
+
# Short-circuit evaluation
|
|
125
|
+
if isinstance(node.op, ast.And):
|
|
126
|
+
for value_node in node.values:
|
|
127
|
+
if not self._eval_node(value_node, variables):
|
|
128
|
+
return False
|
|
129
|
+
return True
|
|
130
|
+
else: # Or
|
|
131
|
+
for value_node in node.values:
|
|
132
|
+
if self._eval_node(value_node, variables):
|
|
133
|
+
return True
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
# Unary operators (not)
|
|
137
|
+
if isinstance(node, ast.UnaryOp):
|
|
138
|
+
operand = self._eval_node(node.operand, variables)
|
|
139
|
+
if isinstance(node.op, ast.Not):
|
|
140
|
+
return not operand
|
|
141
|
+
raise ValueError(f"Unsupported unary operator: {type(node.op).__name__}")
|
|
142
|
+
|
|
143
|
+
# Binary operators (for arithmetic in expressions like context.round + 1)
|
|
144
|
+
if isinstance(node, ast.BinOp):
|
|
145
|
+
left = self._eval_node(node.left, variables)
|
|
146
|
+
right = self._eval_node(node.right, variables)
|
|
147
|
+
if isinstance(node.op, ast.Add):
|
|
148
|
+
return left + right
|
|
149
|
+
if isinstance(node.op, ast.Sub):
|
|
150
|
+
return left - right
|
|
151
|
+
if isinstance(node.op, ast.Mult):
|
|
152
|
+
return left * right
|
|
153
|
+
if isinstance(node.op, ast.Div):
|
|
154
|
+
return left / right
|
|
155
|
+
raise ValueError(f"Unsupported binary operator: {type(node.op).__name__}")
|
|
156
|
+
|
|
157
|
+
# Subscript (e.g., context["key"])
|
|
158
|
+
if isinstance(node, ast.Subscript):
|
|
159
|
+
value = self._eval_node(node.value, variables)
|
|
160
|
+
index = self._eval_node(node.slice, variables)
|
|
161
|
+
return value[index]
|
|
162
|
+
|
|
163
|
+
raise ValueError(f"Unsupported expression type: {type(node).__name__}")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
__all__ = ["SimpleExpressionEngine"]
|