flatmachines 1.0.0__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.
- flatmachines/__init__.py +136 -0
- flatmachines/actions.py +408 -0
- flatmachines/adapters/__init__.py +38 -0
- flatmachines/adapters/flatagent.py +86 -0
- flatmachines/adapters/pi_agent_bridge.py +127 -0
- flatmachines/adapters/pi_agent_runner.mjs +99 -0
- flatmachines/adapters/smolagents.py +125 -0
- flatmachines/agents.py +144 -0
- flatmachines/assets/MACHINES.md +141 -0
- flatmachines/assets/README.md +11 -0
- flatmachines/assets/__init__.py +0 -0
- flatmachines/assets/flatagent.d.ts +219 -0
- flatmachines/assets/flatagent.schema.json +271 -0
- flatmachines/assets/flatagent.slim.d.ts +58 -0
- flatmachines/assets/flatagents-runtime.d.ts +523 -0
- flatmachines/assets/flatagents-runtime.schema.json +281 -0
- flatmachines/assets/flatagents-runtime.slim.d.ts +187 -0
- flatmachines/assets/flatmachine.d.ts +403 -0
- flatmachines/assets/flatmachine.schema.json +620 -0
- flatmachines/assets/flatmachine.slim.d.ts +106 -0
- flatmachines/assets/profiles.d.ts +140 -0
- flatmachines/assets/profiles.schema.json +93 -0
- flatmachines/assets/profiles.slim.d.ts +26 -0
- flatmachines/backends.py +222 -0
- flatmachines/distributed.py +835 -0
- flatmachines/distributed_hooks.py +351 -0
- flatmachines/execution.py +638 -0
- flatmachines/expressions/__init__.py +60 -0
- flatmachines/expressions/cel.py +101 -0
- flatmachines/expressions/simple.py +166 -0
- flatmachines/flatmachine.py +1263 -0
- flatmachines/hooks.py +381 -0
- flatmachines/locking.py +69 -0
- flatmachines/monitoring.py +505 -0
- flatmachines/persistence.py +213 -0
- flatmachines/run.py +117 -0
- flatmachines/utils.py +166 -0
- flatmachines/validation.py +79 -0
- flatmachines-1.0.0.dist-info/METADATA +390 -0
- flatmachines-1.0.0.dist-info/RECORD +41 -0
- flatmachines-1.0.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CEL expression engine for flatmachines.
|
|
3
|
+
|
|
4
|
+
Wraps cel-python to provide full CEL support including:
|
|
5
|
+
- List macros (all, exists, filter, map)
|
|
6
|
+
- String methods (startsWith, contains, endsWith)
|
|
7
|
+
- Timestamps and durations
|
|
8
|
+
- Type coercion
|
|
9
|
+
|
|
10
|
+
Requires: pip install flatmachines[cel]
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Any, Dict
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
import celpy
|
|
17
|
+
from celpy import celtypes
|
|
18
|
+
CEL_AVAILABLE = True
|
|
19
|
+
except ImportError:
|
|
20
|
+
CEL_AVAILABLE = False
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CELExpressionEngine:
|
|
24
|
+
"""
|
|
25
|
+
CEL expression engine using cel-python.
|
|
26
|
+
|
|
27
|
+
Provides full CEL support for advanced expressions.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
if not CEL_AVAILABLE:
|
|
32
|
+
raise ImportError(
|
|
33
|
+
"CEL expression engine requires cel-python. "
|
|
34
|
+
"Install with: pip install flatmachines[cel]"
|
|
35
|
+
)
|
|
36
|
+
self._env = celpy.Environment()
|
|
37
|
+
|
|
38
|
+
def evaluate(self, expression: str, variables: Dict[str, Any]) -> Any:
|
|
39
|
+
"""
|
|
40
|
+
Evaluate a CEL expression with the given variables.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
expression: The CEL expression string to evaluate
|
|
44
|
+
variables: Dictionary of variable names to values
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
The result of evaluating the expression
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
ValueError: If expression syntax is invalid
|
|
51
|
+
"""
|
|
52
|
+
if not expression or not expression.strip():
|
|
53
|
+
return True # Empty expression is always true
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
# Parse the expression
|
|
57
|
+
ast = self._env.compile(expression)
|
|
58
|
+
|
|
59
|
+
# Create the program
|
|
60
|
+
prog = self._env.program(ast)
|
|
61
|
+
|
|
62
|
+
# Convert Python values to CEL types
|
|
63
|
+
cel_vars = self._to_cel_types(variables)
|
|
64
|
+
|
|
65
|
+
# Evaluate
|
|
66
|
+
result = prog.evaluate(cel_vars)
|
|
67
|
+
|
|
68
|
+
# Convert result back to Python
|
|
69
|
+
return self._from_cel_type(result)
|
|
70
|
+
|
|
71
|
+
except Exception as e:
|
|
72
|
+
raise ValueError(f"CEL expression error: {expression} - {e}") from e
|
|
73
|
+
|
|
74
|
+
def _to_cel_types(self, obj: Any) -> Any:
|
|
75
|
+
"""Convert Python types to CEL types."""
|
|
76
|
+
if isinstance(obj, dict):
|
|
77
|
+
return {k: self._to_cel_types(v) for k, v in obj.items()}
|
|
78
|
+
if isinstance(obj, list):
|
|
79
|
+
return [self._to_cel_types(v) for v in obj]
|
|
80
|
+
# Primitives pass through
|
|
81
|
+
return obj
|
|
82
|
+
|
|
83
|
+
def _from_cel_type(self, obj: Any) -> Any:
|
|
84
|
+
"""Convert CEL types back to Python types."""
|
|
85
|
+
if CEL_AVAILABLE:
|
|
86
|
+
if isinstance(obj, celtypes.BoolType):
|
|
87
|
+
return bool(obj)
|
|
88
|
+
if isinstance(obj, celtypes.IntType):
|
|
89
|
+
return int(obj)
|
|
90
|
+
if isinstance(obj, celtypes.DoubleType):
|
|
91
|
+
return float(obj)
|
|
92
|
+
if isinstance(obj, celtypes.StringType):
|
|
93
|
+
return str(obj)
|
|
94
|
+
if isinstance(obj, celtypes.ListType):
|
|
95
|
+
return [self._from_cel_type(v) for v in obj]
|
|
96
|
+
if isinstance(obj, celtypes.MapType):
|
|
97
|
+
return {k: self._from_cel_type(v) for k, v in obj.items()}
|
|
98
|
+
return obj
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
__all__ = ["CELExpressionEngine", "CEL_AVAILABLE"]
|
|
@@ -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"]
|