flock-core 0.3.22__py3-none-any.whl → 0.3.30__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.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/core/__init__.py +2 -2
- flock/core/api/__init__.py +11 -0
- flock/core/api/endpoints.py +222 -0
- flock/core/api/main.py +237 -0
- flock/core/api/models.py +34 -0
- flock/core/api/run_store.py +72 -0
- flock/core/api/ui/__init__.py +0 -0
- flock/core/api/ui/routes.py +271 -0
- flock/core/api/ui/utils.py +119 -0
- flock/core/flock.py +475 -397
- flock/core/flock_agent.py +384 -121
- flock/core/flock_registry.py +614 -0
- flock/core/logging/logging.py +97 -23
- flock/core/mixin/dspy_integration.py +360 -158
- flock/core/serialization/__init__.py +7 -1
- flock/core/serialization/callable_registry.py +52 -0
- flock/core/serialization/serializable.py +259 -37
- flock/core/serialization/serialization_utils.py +184 -0
- flock/workflow/activities.py +2 -2
- {flock_core-0.3.22.dist-info → flock_core-0.3.30.dist-info}/METADATA +7 -3
- {flock_core-0.3.22.dist-info → flock_core-0.3.30.dist-info}/RECORD +24 -15
- flock/core/flock_api.py +0 -214
- flock/core/registry/agent_registry.py +0 -120
- {flock_core-0.3.22.dist-info → flock_core-0.3.30.dist-info}/WHEEL +0 -0
- {flock_core-0.3.22.dist-info → flock_core-0.3.30.dist-info}/entry_points.txt +0 -0
- {flock_core-0.3.22.dist-info → flock_core-0.3.30.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,12 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# src/flock/core/serialization/serializable.py
|
|
3
2
|
import json
|
|
4
3
|
from abc import ABC, abstractmethod
|
|
5
4
|
from pathlib import Path
|
|
6
5
|
from typing import Any, TypeVar
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
# Use yaml if available, otherwise skip yaml methods
|
|
8
|
+
try:
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
YAML_AVAILABLE = True
|
|
12
|
+
except ImportError:
|
|
13
|
+
YAML_AVAILABLE = False
|
|
14
|
+
|
|
15
|
+
# Use msgpack if available
|
|
16
|
+
try:
|
|
17
|
+
import msgpack
|
|
18
|
+
|
|
19
|
+
MSGPACK_AVAILABLE = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
MSGPACK_AVAILABLE = False
|
|
22
|
+
|
|
23
|
+
# Use cloudpickle
|
|
24
|
+
try:
|
|
25
|
+
import cloudpickle
|
|
26
|
+
|
|
27
|
+
PICKLE_AVAILABLE = True
|
|
28
|
+
except ImportError:
|
|
29
|
+
PICKLE_AVAILABLE = False
|
|
30
|
+
|
|
10
31
|
|
|
11
32
|
T = TypeVar("T", bound="Serializable")
|
|
12
33
|
|
|
@@ -15,79 +36,280 @@ class Serializable(ABC):
|
|
|
15
36
|
"""Base class for all serializable objects in the system.
|
|
16
37
|
|
|
17
38
|
Provides methods for serializing/deserializing objects to various formats.
|
|
39
|
+
Subclasses MUST implement to_dict and from_dict.
|
|
18
40
|
"""
|
|
19
41
|
|
|
20
42
|
@abstractmethod
|
|
21
43
|
def to_dict(self) -> dict[str, Any]:
|
|
22
|
-
"""Convert instance to dictionary representation.
|
|
44
|
+
"""Convert instance to a dictionary representation suitable for serialization.
|
|
45
|
+
This method should handle converting nested Serializable objects and callables.
|
|
46
|
+
"""
|
|
23
47
|
pass
|
|
24
48
|
|
|
25
49
|
@classmethod
|
|
26
50
|
@abstractmethod
|
|
27
51
|
def from_dict(cls: type[T], data: dict[str, Any]) -> T:
|
|
28
|
-
"""Create instance from dictionary representation.
|
|
52
|
+
"""Create instance from a dictionary representation.
|
|
53
|
+
This method should handle reconstructing nested Serializable objects and callables.
|
|
54
|
+
"""
|
|
29
55
|
pass
|
|
30
56
|
|
|
31
|
-
|
|
57
|
+
# --- JSON Methods ---
|
|
58
|
+
def to_json(self, indent: int | None = 2) -> str:
|
|
32
59
|
"""Serialize to JSON string."""
|
|
60
|
+
# Import encoder locally to avoid making it a hard dependency if JSON isn't used
|
|
61
|
+
from .json_encoder import FlockJSONEncoder
|
|
62
|
+
|
|
33
63
|
try:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
64
|
+
# Note: to_dict should ideally prepare the structure fully.
|
|
65
|
+
# FlockJSONEncoder is a fallback for types missed by to_dict.
|
|
66
|
+
return json.dumps(
|
|
67
|
+
self.to_dict(), cls=FlockJSONEncoder, indent=indent
|
|
68
|
+
)
|
|
69
|
+
except Exception as e:
|
|
70
|
+
raise RuntimeError(
|
|
71
|
+
f"Failed to serialize {self.__class__.__name__} to JSON: {e}"
|
|
72
|
+
) from e
|
|
37
73
|
|
|
38
74
|
@classmethod
|
|
39
75
|
def from_json(cls: type[T], json_str: str) -> T:
|
|
40
76
|
"""Create instance from JSON string."""
|
|
41
77
|
try:
|
|
42
|
-
|
|
43
|
-
|
|
78
|
+
data = json.loads(json_str)
|
|
79
|
+
return cls.from_dict(data)
|
|
80
|
+
except json.JSONDecodeError as e:
|
|
81
|
+
raise ValueError(f"Invalid JSON string: {e}") from e
|
|
82
|
+
except Exception as e:
|
|
83
|
+
raise RuntimeError(
|
|
84
|
+
f"Failed to deserialize {cls.__name__} from JSON: {e}"
|
|
85
|
+
) from e
|
|
86
|
+
|
|
87
|
+
# --- YAML Methods ---
|
|
88
|
+
def to_yaml(self, sort_keys=False, default_flow_style=False) -> str:
|
|
89
|
+
"""Serialize to YAML string."""
|
|
90
|
+
if not YAML_AVAILABLE:
|
|
91
|
+
raise NotImplementedError(
|
|
92
|
+
"YAML support requires PyYAML: pip install pyyaml"
|
|
93
|
+
)
|
|
94
|
+
try:
|
|
95
|
+
# to_dict should prepare a structure suitable for YAML dumping
|
|
96
|
+
return yaml.dump(
|
|
97
|
+
self.to_dict(),
|
|
98
|
+
sort_keys=sort_keys,
|
|
99
|
+
default_flow_style=default_flow_style,
|
|
100
|
+
allow_unicode=True,
|
|
101
|
+
)
|
|
102
|
+
except Exception as e:
|
|
103
|
+
raise RuntimeError(
|
|
104
|
+
f"Failed to serialize {self.__class__.__name__} to YAML: {e}"
|
|
105
|
+
) from e
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def from_yaml(cls: type[T], yaml_str: str) -> T:
|
|
109
|
+
"""Create instance from YAML string."""
|
|
110
|
+
if not YAML_AVAILABLE:
|
|
111
|
+
raise NotImplementedError(
|
|
112
|
+
"YAML support requires PyYAML: pip install pyyaml"
|
|
113
|
+
)
|
|
114
|
+
try:
|
|
115
|
+
data = yaml.safe_load(yaml_str)
|
|
116
|
+
if not isinstance(data, dict):
|
|
117
|
+
raise TypeError(
|
|
118
|
+
f"YAML did not yield a dictionary for {cls.__name__}"
|
|
119
|
+
)
|
|
120
|
+
return cls.from_dict(data)
|
|
121
|
+
except yaml.YAMLError as e:
|
|
122
|
+
raise ValueError(f"Invalid YAML string: {e}") from e
|
|
123
|
+
except Exception as e:
|
|
124
|
+
raise RuntimeError(
|
|
125
|
+
f"Failed to deserialize {cls.__name__} from YAML: {e}"
|
|
126
|
+
) from e
|
|
127
|
+
|
|
128
|
+
def to_yaml_file(self, path: Path | str, **yaml_dump_kwargs) -> None:
|
|
129
|
+
"""Serialize to YAML file."""
|
|
130
|
+
if not YAML_AVAILABLE:
|
|
131
|
+
raise NotImplementedError(
|
|
132
|
+
"YAML support requires PyYAML: pip install pyyaml"
|
|
133
|
+
)
|
|
134
|
+
path = Path(path)
|
|
135
|
+
try:
|
|
136
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
137
|
+
yaml_str = self.to_yaml(**yaml_dump_kwargs)
|
|
138
|
+
path.write_text(yaml_str, encoding="utf-8")
|
|
139
|
+
except Exception as e:
|
|
140
|
+
raise RuntimeError(
|
|
141
|
+
f"Failed to write {self.__class__.__name__} to YAML file {path}: {e}"
|
|
142
|
+
) from e
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
def from_yaml_file(cls: type[T], path: Path | str) -> T:
|
|
146
|
+
"""Create instance from YAML file."""
|
|
147
|
+
if not YAML_AVAILABLE:
|
|
148
|
+
raise NotImplementedError(
|
|
149
|
+
"YAML support requires PyYAML: pip install pyyaml"
|
|
150
|
+
)
|
|
151
|
+
path = Path(path)
|
|
152
|
+
try:
|
|
153
|
+
yaml_str = path.read_text(encoding="utf-8")
|
|
154
|
+
return cls.from_yaml(yaml_str)
|
|
155
|
+
except FileNotFoundError:
|
|
44
156
|
raise
|
|
157
|
+
except Exception as e:
|
|
158
|
+
raise RuntimeError(
|
|
159
|
+
f"Failed to read {cls.__name__} from YAML file {path}: {e}"
|
|
160
|
+
) from e
|
|
45
161
|
|
|
46
|
-
|
|
162
|
+
# --- MsgPack Methods ---
|
|
163
|
+
def to_msgpack(self) -> bytes:
|
|
47
164
|
"""Serialize to msgpack bytes."""
|
|
165
|
+
if not MSGPACK_AVAILABLE:
|
|
166
|
+
raise NotImplementedError(
|
|
167
|
+
"MsgPack support requires msgpack: pip install msgpack"
|
|
168
|
+
)
|
|
48
169
|
try:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
170
|
+
# Use default hook for complex types if needed, or rely on to_dict
|
|
171
|
+
return msgpack.packb(self.to_dict(), use_bin_type=True)
|
|
172
|
+
except Exception as e:
|
|
173
|
+
raise RuntimeError(
|
|
174
|
+
f"Failed to serialize {self.__class__.__name__} to MsgPack: {e}"
|
|
175
|
+
) from e
|
|
55
176
|
|
|
56
177
|
@classmethod
|
|
57
178
|
def from_msgpack(cls: type[T], msgpack_bytes: bytes) -> T:
|
|
58
179
|
"""Create instance from msgpack bytes."""
|
|
180
|
+
if not MSGPACK_AVAILABLE:
|
|
181
|
+
raise NotImplementedError(
|
|
182
|
+
"MsgPack support requires msgpack: pip install msgpack"
|
|
183
|
+
)
|
|
59
184
|
try:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
185
|
+
# Use object_hook if custom deserialization is needed beyond from_dict
|
|
186
|
+
data = msgpack.unpackb(msgpack_bytes, raw=False)
|
|
187
|
+
if not isinstance(data, dict):
|
|
188
|
+
raise TypeError(
|
|
189
|
+
f"MsgPack did not yield a dictionary for {cls.__name__}"
|
|
190
|
+
)
|
|
191
|
+
return cls.from_dict(data)
|
|
192
|
+
except Exception as e:
|
|
193
|
+
raise RuntimeError(
|
|
194
|
+
f"Failed to deserialize {cls.__name__} from MsgPack: {e}"
|
|
195
|
+
) from e
|
|
196
|
+
|
|
197
|
+
def to_msgpack_file(self, path: Path | str) -> None:
|
|
198
|
+
"""Serialize to msgpack file."""
|
|
199
|
+
if not MSGPACK_AVAILABLE:
|
|
200
|
+
raise NotImplementedError(
|
|
201
|
+
"MsgPack support requires msgpack: pip install msgpack"
|
|
202
|
+
)
|
|
203
|
+
path = Path(path)
|
|
204
|
+
try:
|
|
205
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
206
|
+
msgpack_bytes = self.to_msgpack()
|
|
207
|
+
path.write_bytes(msgpack_bytes)
|
|
208
|
+
except Exception as e:
|
|
209
|
+
raise RuntimeError(
|
|
210
|
+
f"Failed to write {self.__class__.__name__} to MsgPack file {path}: {e}"
|
|
211
|
+
) from e
|
|
63
212
|
|
|
64
213
|
@classmethod
|
|
65
|
-
def from_msgpack_file(cls: type[T], path: Path) -> T:
|
|
214
|
+
def from_msgpack_file(cls: type[T], path: Path | str) -> T:
|
|
66
215
|
"""Create instance from msgpack file."""
|
|
216
|
+
if not MSGPACK_AVAILABLE:
|
|
217
|
+
raise NotImplementedError(
|
|
218
|
+
"MsgPack support requires msgpack: pip install msgpack"
|
|
219
|
+
)
|
|
220
|
+
path = Path(path)
|
|
67
221
|
try:
|
|
68
|
-
|
|
69
|
-
|
|
222
|
+
msgpack_bytes = path.read_bytes()
|
|
223
|
+
return cls.from_msgpack(msgpack_bytes)
|
|
224
|
+
except FileNotFoundError:
|
|
70
225
|
raise
|
|
226
|
+
except Exception as e:
|
|
227
|
+
raise RuntimeError(
|
|
228
|
+
f"Failed to read {cls.__name__} from MsgPack file {path}: {e}"
|
|
229
|
+
) from e
|
|
71
230
|
|
|
231
|
+
# --- Pickle Methods (Use with caution due to security risks) ---
|
|
72
232
|
def to_pickle(self) -> bytes:
|
|
73
|
-
"""Serialize to pickle bytes."""
|
|
233
|
+
"""Serialize to pickle bytes using cloudpickle."""
|
|
234
|
+
if not PICKLE_AVAILABLE:
|
|
235
|
+
raise NotImplementedError(
|
|
236
|
+
"Pickle support requires cloudpickle: pip install cloudpickle"
|
|
237
|
+
)
|
|
74
238
|
try:
|
|
75
239
|
return cloudpickle.dumps(self)
|
|
76
|
-
except Exception:
|
|
77
|
-
raise
|
|
240
|
+
except Exception as e:
|
|
241
|
+
raise RuntimeError(
|
|
242
|
+
f"Failed to serialize {self.__class__.__name__} to Pickle: {e}"
|
|
243
|
+
) from e
|
|
78
244
|
|
|
79
245
|
@classmethod
|
|
80
|
-
def from_pickle(cls, pickle_bytes: bytes) -> T:
|
|
81
|
-
"""Create instance from pickle bytes."""
|
|
246
|
+
def from_pickle(cls: type[T], pickle_bytes: bytes) -> T:
|
|
247
|
+
"""Create instance from pickle bytes using cloudpickle."""
|
|
248
|
+
if not PICKLE_AVAILABLE:
|
|
249
|
+
raise NotImplementedError(
|
|
250
|
+
"Pickle support requires cloudpickle: pip install cloudpickle"
|
|
251
|
+
)
|
|
82
252
|
try:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
253
|
+
instance = cloudpickle.loads(pickle_bytes)
|
|
254
|
+
if not isinstance(instance, cls):
|
|
255
|
+
raise TypeError(
|
|
256
|
+
f"Deserialized object is not of type {cls.__name__}"
|
|
257
|
+
)
|
|
258
|
+
return instance
|
|
259
|
+
except Exception as e:
|
|
260
|
+
raise RuntimeError(
|
|
261
|
+
f"Failed to deserialize {cls.__name__} from Pickle: {e}"
|
|
262
|
+
) from e
|
|
263
|
+
|
|
264
|
+
def to_pickle_file(self, path: Path | str) -> None:
|
|
265
|
+
"""Serialize to pickle file using cloudpickle."""
|
|
266
|
+
if not PICKLE_AVAILABLE:
|
|
267
|
+
raise NotImplementedError(
|
|
268
|
+
"Pickle support requires cloudpickle: pip install cloudpickle"
|
|
269
|
+
)
|
|
270
|
+
path = Path(path)
|
|
271
|
+
try:
|
|
272
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
273
|
+
pickle_bytes = self.to_pickle()
|
|
274
|
+
path.write_bytes(pickle_bytes)
|
|
275
|
+
except Exception as e:
|
|
276
|
+
raise RuntimeError(
|
|
277
|
+
f"Failed to write {self.__class__.__name__} to Pickle file {path}: {e}"
|
|
278
|
+
) from e
|
|
86
279
|
|
|
87
280
|
@classmethod
|
|
88
|
-
def from_pickle_file(cls: type[T], path: Path) -> T:
|
|
89
|
-
"""Create instance from pickle file."""
|
|
281
|
+
def from_pickle_file(cls: type[T], path: Path | str) -> T:
|
|
282
|
+
"""Create instance from pickle file using cloudpickle."""
|
|
283
|
+
if not PICKLE_AVAILABLE:
|
|
284
|
+
raise NotImplementedError(
|
|
285
|
+
"Pickle support requires cloudpickle: pip install cloudpickle"
|
|
286
|
+
)
|
|
287
|
+
path = Path(path)
|
|
90
288
|
try:
|
|
91
|
-
|
|
92
|
-
|
|
289
|
+
pickle_bytes = path.read_bytes()
|
|
290
|
+
return cls.from_pickle(pickle_bytes)
|
|
291
|
+
except FileNotFoundError:
|
|
93
292
|
raise
|
|
293
|
+
except Exception as e:
|
|
294
|
+
raise RuntimeError(
|
|
295
|
+
f"Failed to read {cls.__name__} from Pickle file {path}: {e}"
|
|
296
|
+
) from e
|
|
297
|
+
|
|
298
|
+
# _filter_none_values remains unchanged
|
|
299
|
+
@staticmethod
|
|
300
|
+
def _filter_none_values(data: Any) -> Any:
|
|
301
|
+
"""Filter out None values from dictionaries and lists recursively."""
|
|
302
|
+
if isinstance(data, dict):
|
|
303
|
+
return {
|
|
304
|
+
k: Serializable._filter_none_values(v)
|
|
305
|
+
for k, v in data.items()
|
|
306
|
+
if v is not None
|
|
307
|
+
}
|
|
308
|
+
elif isinstance(data, list):
|
|
309
|
+
# Filter None from list items AND recursively filter within items
|
|
310
|
+
return [
|
|
311
|
+
Serializable._filter_none_values(item)
|
|
312
|
+
for item in data
|
|
313
|
+
if item is not None
|
|
314
|
+
]
|
|
315
|
+
return data
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# src/flock/core/serialization/serialization_utils.py
|
|
2
|
+
"""Utilities for recursive serialization/deserialization with callable handling."""
|
|
3
|
+
|
|
4
|
+
import importlib
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
# Import the registry
|
|
11
|
+
# from .callable_registry import CallableRegistry # Old way
|
|
12
|
+
from flock.core.flock_registry import (
|
|
13
|
+
COMPONENT_BASE_TYPES,
|
|
14
|
+
get_registry, # New way
|
|
15
|
+
)
|
|
16
|
+
from flock.core.logging.logging import get_logger
|
|
17
|
+
|
|
18
|
+
logger = get_logger("serialization.utils")
|
|
19
|
+
FlockRegistry = get_registry() # Get singleton instance
|
|
20
|
+
|
|
21
|
+
# --- Serialization Helper ---
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def serialize_item(item: Any) -> Any:
|
|
25
|
+
"""Recursively prepares an item for serialization (e.g., to dict for YAML/JSON).
|
|
26
|
+
Converts known callables to their path strings using FlockRegistry.
|
|
27
|
+
Converts Pydantic models using model_dump.
|
|
28
|
+
"""
|
|
29
|
+
if isinstance(item, BaseModel):
|
|
30
|
+
dumped = item.model_dump(mode="json", exclude_none=True)
|
|
31
|
+
return serialize_item(dumped)
|
|
32
|
+
elif callable(item) and not isinstance(item, type):
|
|
33
|
+
path_str = FlockRegistry.get_callable_path_string(
|
|
34
|
+
item
|
|
35
|
+
) # Use registry helper
|
|
36
|
+
if path_str:
|
|
37
|
+
return {"__callable_ref__": path_str}
|
|
38
|
+
else:
|
|
39
|
+
logger.warning(
|
|
40
|
+
f"Could not get path string for callable {item}, storing as string."
|
|
41
|
+
)
|
|
42
|
+
return str(item)
|
|
43
|
+
elif isinstance(item, Mapping):
|
|
44
|
+
return {key: serialize_item(value) for key, value in item.items()}
|
|
45
|
+
elif isinstance(item, Sequence) and not isinstance(item, str):
|
|
46
|
+
return [serialize_item(sub_item) for sub_item in item]
|
|
47
|
+
elif isinstance(
|
|
48
|
+
item, type
|
|
49
|
+
): # Handle type objects themselves (e.g. if stored directly)
|
|
50
|
+
type_name = FlockRegistry.get_component_type_name(
|
|
51
|
+
item
|
|
52
|
+
) # Check components first
|
|
53
|
+
if type_name:
|
|
54
|
+
return {"__component_ref__": type_name}
|
|
55
|
+
type_name = FlockRegistry._get_path_string(
|
|
56
|
+
item
|
|
57
|
+
) # Check regular types/classes by path
|
|
58
|
+
if type_name:
|
|
59
|
+
return {"__type_ref__": type_name}
|
|
60
|
+
logger.warning(
|
|
61
|
+
f"Could not serialize type object {item}, storing as string."
|
|
62
|
+
)
|
|
63
|
+
return str(item)
|
|
64
|
+
else:
|
|
65
|
+
# Return basic types as is
|
|
66
|
+
return item
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# --- Deserialization Helper ---
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def deserialize_item(item: Any) -> Any:
|
|
73
|
+
"""Recursively processes a deserialized item (e.g., from YAML/JSON dict).
|
|
74
|
+
Converts reference dicts back to actual callables or types using FlockRegistry.
|
|
75
|
+
Handles nested lists and dicts.
|
|
76
|
+
"""
|
|
77
|
+
if isinstance(item, Mapping):
|
|
78
|
+
if "__callable_ref__" in item and len(item) == 1:
|
|
79
|
+
path_str = item["__callable_ref__"]
|
|
80
|
+
try:
|
|
81
|
+
return FlockRegistry.get_callable(path_str)
|
|
82
|
+
except KeyError:
|
|
83
|
+
logger.error(
|
|
84
|
+
f"Callable reference '{path_str}' not found during deserialization."
|
|
85
|
+
)
|
|
86
|
+
return None
|
|
87
|
+
elif "__component_ref__" in item and len(item) == 1:
|
|
88
|
+
type_name = item["__component_ref__"]
|
|
89
|
+
try:
|
|
90
|
+
return FlockRegistry.get_component(type_name)
|
|
91
|
+
except KeyError:
|
|
92
|
+
logger.error(
|
|
93
|
+
f"Component reference '{type_name}' not found during deserialization."
|
|
94
|
+
)
|
|
95
|
+
return None
|
|
96
|
+
elif "__type_ref__" in item and len(item) == 1:
|
|
97
|
+
type_name = item["__type_ref__"]
|
|
98
|
+
try:
|
|
99
|
+
# For general types, use get_type or fallback to dynamic import like get_callable
|
|
100
|
+
# Using get_type for now, assuming it needs registration
|
|
101
|
+
return FlockRegistry.get_type(type_name)
|
|
102
|
+
except KeyError:
|
|
103
|
+
# Attempt dynamic import as fallback if get_type fails (similar to get_callable)
|
|
104
|
+
try:
|
|
105
|
+
if "." not in type_name: # Builtins?
|
|
106
|
+
mod = importlib.import_module("builtins")
|
|
107
|
+
else:
|
|
108
|
+
module_name, class_name = type_name.rsplit(".", 1)
|
|
109
|
+
mod = importlib.import_module(module_name)
|
|
110
|
+
type_obj = getattr(mod, class_name)
|
|
111
|
+
if isinstance(type_obj, type):
|
|
112
|
+
FlockRegistry.register_type(
|
|
113
|
+
type_obj, type_name
|
|
114
|
+
) # Cache it
|
|
115
|
+
return type_obj
|
|
116
|
+
else:
|
|
117
|
+
raise TypeError()
|
|
118
|
+
except Exception:
|
|
119
|
+
logger.error(
|
|
120
|
+
f"Type reference '{type_name}' not found in registry or via dynamic import."
|
|
121
|
+
)
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
else:
|
|
125
|
+
# Recursively deserialize dictionary values
|
|
126
|
+
return {key: deserialize_item(value) for key, value in item.items()}
|
|
127
|
+
elif isinstance(item, Sequence) and not isinstance(item, str):
|
|
128
|
+
return [deserialize_item(sub_item) for sub_item in item]
|
|
129
|
+
else:
|
|
130
|
+
# Return basic types as is
|
|
131
|
+
return item
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# --- Component Deserialization Helper ---
|
|
135
|
+
def deserialize_component(
|
|
136
|
+
data: dict | None, expected_base_type: type
|
|
137
|
+
) -> Any | None:
|
|
138
|
+
"""Deserializes a component (Module, Evaluator, Router) from its dict representation.
|
|
139
|
+
Uses the 'type' field to find the correct class via FlockRegistry.
|
|
140
|
+
"""
|
|
141
|
+
if data is None:
|
|
142
|
+
return None
|
|
143
|
+
if not isinstance(data, dict):
|
|
144
|
+
logger.error(
|
|
145
|
+
f"Expected dict for component deserialization, got {type(data)}"
|
|
146
|
+
)
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
type_name = data.get(
|
|
150
|
+
"type"
|
|
151
|
+
) # Assuming 'type' key holds the class name string
|
|
152
|
+
if not type_name:
|
|
153
|
+
logger.error(f"Component data missing 'type' field: {data}")
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
ComponentClass = FlockRegistry.get_component(type_name) # Use registry
|
|
158
|
+
# Optional: Keep the base type check
|
|
159
|
+
if COMPONENT_BASE_TYPES and not issubclass(
|
|
160
|
+
ComponentClass, expected_base_type
|
|
161
|
+
):
|
|
162
|
+
raise TypeError(
|
|
163
|
+
f"Deserialized class {type_name} is not a subclass of {expected_base_type.__name__}"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Recursively deserialize the data *before* passing to Pydantic constructor
|
|
167
|
+
# This handles nested callables/types within the component's config/data
|
|
168
|
+
deserialized_data_for_init = {}
|
|
169
|
+
for k, v in data.items():
|
|
170
|
+
# Don't pass the 'type' field itself to the constructor if it matches class name
|
|
171
|
+
if k == "type" and v == ComponentClass.__name__:
|
|
172
|
+
continue
|
|
173
|
+
deserialized_data_for_init[k] = deserialize_item(v)
|
|
174
|
+
|
|
175
|
+
# Use Pydantic constructor directly. Assumes keys match field names.
|
|
176
|
+
# from_dict could be added to components for more complex logic if needed.
|
|
177
|
+
return ComponentClass(**deserialized_data_for_init)
|
|
178
|
+
|
|
179
|
+
except (KeyError, TypeError, Exception) as e:
|
|
180
|
+
logger.error(
|
|
181
|
+
f"Failed to deserialize component of type '{type_name}': {e}",
|
|
182
|
+
exc_info=True,
|
|
183
|
+
)
|
|
184
|
+
return None
|
flock/workflow/activities.py
CHANGED
|
@@ -8,9 +8,9 @@ from temporalio import activity
|
|
|
8
8
|
from flock.core.context.context import FlockContext
|
|
9
9
|
from flock.core.context.context_vars import FLOCK_CURRENT_AGENT, FLOCK_MODEL
|
|
10
10
|
from flock.core.flock_agent import FlockAgent
|
|
11
|
+
from flock.core.flock_registry import get_registry
|
|
11
12
|
from flock.core.flock_router import HandOffRequest
|
|
12
13
|
from flock.core.logging.logging import get_logger
|
|
13
|
-
from flock.core.registry.agent_registry import Registry
|
|
14
14
|
from flock.core.util.input_resolver import resolve_inputs
|
|
15
15
|
|
|
16
16
|
logger = get_logger("activities")
|
|
@@ -26,7 +26,7 @@ async def run_agent(context: FlockContext) -> dict:
|
|
|
26
26
|
"""
|
|
27
27
|
# Start a top-level span for the entire run_agent activity.
|
|
28
28
|
with tracer.start_as_current_span("run_agent") as span:
|
|
29
|
-
registry =
|
|
29
|
+
registry = get_registry()
|
|
30
30
|
previous_agent_name = ""
|
|
31
31
|
if isinstance(context, dict):
|
|
32
32
|
context = FlockContext.from_dict(context)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flock-core
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.30
|
|
4
4
|
Summary: Declarative LLM Orchestration at Scale
|
|
5
5
|
Author-email: Andre Ratzenberger <andre.ratzenberger@whiteduck.de>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -12,10 +12,11 @@ Requires-Dist: azure-search-documents>=11.5.2
|
|
|
12
12
|
Requires-Dist: chromadb>=0.6.3
|
|
13
13
|
Requires-Dist: cloudpickle>=3.1.1
|
|
14
14
|
Requires-Dist: devtools>=0.12.2
|
|
15
|
-
Requires-Dist: dspy==2.
|
|
15
|
+
Requires-Dist: dspy==2.6.16
|
|
16
16
|
Requires-Dist: duckduckgo-search>=7.3.2
|
|
17
17
|
Requires-Dist: fastapi>=0.115.8
|
|
18
18
|
Requires-Dist: httpx>=0.28.1
|
|
19
|
+
Requires-Dist: litellm==1.63.7
|
|
19
20
|
Requires-Dist: loguru>=0.7.3
|
|
20
21
|
Requires-Dist: matplotlib>=3.10.0
|
|
21
22
|
Requires-Dist: msgpack>=1.1.0
|
|
@@ -28,10 +29,13 @@ Requires-Dist: opentelemetry-instrumentation-logging>=0.51b0
|
|
|
28
29
|
Requires-Dist: opentelemetry-sdk>=1.30.0
|
|
29
30
|
Requires-Dist: pillow>=10.4.0
|
|
30
31
|
Requires-Dist: prometheus-client>=0.21.1
|
|
32
|
+
Requires-Dist: psutil>=6.1.1
|
|
31
33
|
Requires-Dist: pydantic>=2.10.5
|
|
32
34
|
Requires-Dist: python-box>=7.3.2
|
|
33
35
|
Requires-Dist: python-decouple>=3.8
|
|
34
36
|
Requires-Dist: python-dotenv>=1.0.1
|
|
37
|
+
Requires-Dist: python-fasthtml>=0.12.6
|
|
38
|
+
Requires-Dist: pyyaml>=6.0
|
|
35
39
|
Requires-Dist: questionary>=2.1.0
|
|
36
40
|
Requires-Dist: rich>=13.9.4
|
|
37
41
|
Requires-Dist: sentence-transformers>=3.4.1
|
|
@@ -58,7 +62,7 @@ Description-Content-Type: text/markdown
|
|
|
58
62
|
|
|
59
63
|
## Overview
|
|
60
64
|
|
|
61
|
-
Flock is a framework for orchestrating LLM-powered agents. It leverages a **declarative approach** where you simply specify what each agent needs as input and what it produces as output
|
|
65
|
+
Flock is a framework for orchestrating LLM-powered agents. It leverages a **declarative approach** where you simply specify what each agent needs as input and what it produces as output, without having to write lengthy, brittle prompts. Under the hood, Flock transforms these declarations into robust workflows, using cutting-edge components such as Temporal and DSPy to handle fault tolerance, state management, and error recovery.
|
|
62
66
|
|
|
63
67
|
|
|
64
68
|
|
|
@@ -9,14 +9,22 @@ flock/cli/load_flock.py,sha256=3JdECvt5X7uyOG2vZS3-Zk5C5SI_84_QZjcsB3oJmfA,932
|
|
|
9
9
|
flock/cli/load_release_notes.py,sha256=qFcgUrMddAE_TP6x1P-6ZywTUjTknfhTDW5LTxtg1yk,599
|
|
10
10
|
flock/cli/settings.py,sha256=Z_TXBzCYlCmSaKrJ_CQCdYy-Cj29gpI4kbC_2KzoKqg,27025
|
|
11
11
|
flock/cli/assets/release_notes.md,sha256=bqnk50jxM3w5uY44Dc7MkdT8XmRREFxrVBAG9XCOSSU,4896
|
|
12
|
-
flock/core/__init__.py,sha256=
|
|
13
|
-
flock/core/flock.py,sha256=
|
|
14
|
-
flock/core/flock_agent.py,sha256=
|
|
15
|
-
flock/core/flock_api.py,sha256=2rHnmEdtT5KPZYwGesRT7LqwbrgKClODHT-O56u7pcQ,7140
|
|
12
|
+
flock/core/__init__.py,sha256=6NmSXsdQOoOPWjWqdF8BYiUveb54CjH8Ta0WAjNM0Ps,574
|
|
13
|
+
flock/core/flock.py,sha256=O2ro_CZMKi_8ssY7BYpT6wSENcvcimO9mqwIxqENMpg,22202
|
|
14
|
+
flock/core/flock_agent.py,sha256=jkDwB7yVGYyLfg-WuwvR2X4XblXOs5DrB2dB30xBL7Q,24754
|
|
16
15
|
flock/core/flock_evaluator.py,sha256=dOXZeDOGZcAmJ9ahqq_2bdGUU1VOXY4skmwTVpAjiVw,1685
|
|
17
16
|
flock/core/flock_factory.py,sha256=MGTkJCP1WGpV614f87r1vwe0tqAvBCoH9PlqtqDyJDk,2828
|
|
18
17
|
flock/core/flock_module.py,sha256=96aFVYAgwpKN53xGbivQDUpikOYGFCxK5mqhclOcxY0,3003
|
|
18
|
+
flock/core/flock_registry.py,sha256=cqW74n6RebrQ7uqqLb6DxBNxebqJsu4WyMtL1BcS5oI,22551
|
|
19
19
|
flock/core/flock_router.py,sha256=A5GaxcGvtiFlRLHBTW7okh5RDm3BdKam2uXvRHRaj7k,2187
|
|
20
|
+
flock/core/api/__init__.py,sha256=OKlhzDWZJfA6ddBwxQUmATY0TSzESsH032u00iVGvdA,228
|
|
21
|
+
flock/core/api/endpoints.py,sha256=m-QAyizCHGh3sd5IXaDEhquSxNgTZLpUVTXLdVjiDVw,9086
|
|
22
|
+
flock/core/api/main.py,sha256=-MJClTcmkyUnaP9fmVu2CcHagFy_9lKk6CAbF05kraY,8928
|
|
23
|
+
flock/core/api/models.py,sha256=mMPCMCD52Txc56_yscwZQG0RRPCJTnIrVKQ8WbI7gts,1111
|
|
24
|
+
flock/core/api/run_store.py,sha256=kmrb0bq2Et5JiSxFWskAAf5a4jTeEFVZShTDlX5jfAk,2773
|
|
25
|
+
flock/core/api/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
flock/core/api/ui/routes.py,sha256=nS-wWO94mshE5ozWfOQZ-HOvtes_1qxDVcqpMZtU5JQ,8885
|
|
27
|
+
flock/core/api/ui/utils.py,sha256=V7PqYHNK519hFJ8jvvwf7bGpbBXCRz_HQG3BDCCqlNA,4802
|
|
20
28
|
flock/core/context/context.py,sha256=8bjRLZ74oacRNBaHmDNXdQKfB-95poF7Pp03n2k0zcQ,6437
|
|
21
29
|
flock/core/context/context_manager.py,sha256=FANSWa6DEhdhtZ7t_9Gza0v80UdpoDOhHbfVOccmjkA,1181
|
|
22
30
|
flock/core/context/context_vars.py,sha256=zYTMi9b6mNSSEHowEQUOTpEDurmAjaUcyBCgfKY6-cU,300
|
|
@@ -24,7 +32,7 @@ flock/core/execution/local_executor.py,sha256=rnIQvaJOs6zZORUcR3vvyS6LPREDJTjayg
|
|
|
24
32
|
flock/core/execution/temporal_executor.py,sha256=OF_uXgQsoUGp6U1ZkcuaidAEKyH7XDtbfrtdF10XQ_4,1675
|
|
25
33
|
flock/core/interpreter/python_interpreter.py,sha256=RaUMZuufsKBNQ4FAeSaOgUuxzs8VYu5TgUUs-xwaxxM,26376
|
|
26
34
|
flock/core/logging/__init__.py,sha256=Q8hp9-1ilPIUIV0jLgJ3_cP7COrea32cVwL7dicPnlM,82
|
|
27
|
-
flock/core/logging/logging.py,sha256
|
|
35
|
+
flock/core/logging/logging.py,sha256=-ViLNLTm5wqbvVsc6xYekOsAY38ZCIn6j1wciC7lgeM,15417
|
|
28
36
|
flock/core/logging/telemetry.py,sha256=3E9Tyj6AUR6A5RlIufcdCdWm5BAA7tbOsCa7lHoUQaU,5404
|
|
29
37
|
flock/core/logging/trace_and_logged.py,sha256=5vNrK1kxuPMoPJ0-QjQg-EDJL1oiEzvU6UNi6X8FiMs,2117
|
|
30
38
|
flock/core/logging/formatters/enum_builder.py,sha256=LgEYXUv84wK5vwHflZ5h8HBGgvLH3sByvUQe8tZiyY0,981
|
|
@@ -35,13 +43,14 @@ flock/core/logging/span_middleware/baggage_span_processor.py,sha256=gJfRl8FeB6jd
|
|
|
35
43
|
flock/core/logging/telemetry_exporter/base_exporter.py,sha256=rQJJzS6q9n2aojoSqwCnl7ZtHrh5LZZ-gkxUuI5WfrQ,1124
|
|
36
44
|
flock/core/logging/telemetry_exporter/file_exporter.py,sha256=nKAjJSZtA7FqHSTuTiFtYYepaxOq7l1rDvs8U8rSBlA,3023
|
|
37
45
|
flock/core/logging/telemetry_exporter/sqlite_exporter.py,sha256=CDsiMb9QcqeXelZ6ZqPSS56ovMPGqOu6whzBZRK__Vg,3498
|
|
38
|
-
flock/core/mixin/dspy_integration.py,sha256=
|
|
46
|
+
flock/core/mixin/dspy_integration.py,sha256=Os2jZ505t_7Mg5jllUc06uDpCNTnhGBeCbUW-yAHfXc,17366
|
|
39
47
|
flock/core/mixin/prompt_parser.py,sha256=eOqI-FK3y17gVqpc_y5GF-WmK1Jv8mFlkZxTcgweoxI,5121
|
|
40
|
-
flock/core/
|
|
41
|
-
flock/core/serialization/
|
|
48
|
+
flock/core/serialization/__init__.py,sha256=CML7fPgG6p4c0CDBlJ_uwV1aZZhJKK9uy3IoIHfO87w,431
|
|
49
|
+
flock/core/serialization/callable_registry.py,sha256=sUZECTZWsM3fJ8FDRQ-FgLNW9hF26nY17AD6fJKADMc,1419
|
|
42
50
|
flock/core/serialization/json_encoder.py,sha256=gAKj2zU_8wQiNvdkby2hksSA4fbPNwTjup_yz1Le1Vw,1229
|
|
43
51
|
flock/core/serialization/secure_serializer.py,sha256=n5-zRvvXddgJv1FFHsaQ2wuYdL3WUSGPvG_LGaffEJo,6144
|
|
44
|
-
flock/core/serialization/serializable.py,sha256=
|
|
52
|
+
flock/core/serialization/serializable.py,sha256=ymuJmm8teLbyUFMakkD7W3a_N8H4igZ1ITQnOqoLt0o,11270
|
|
53
|
+
flock/core/serialization/serialization_utils.py,sha256=UgMYYccwgfGxyMJ7qYaQ9-RWWjiE9p9qMehoY_X1zi0,7146
|
|
45
54
|
flock/core/tools/azure_tools.py,sha256=9Bi6IrB5pzBTBhBSxpCVMgx8HBud8nl4gDp8aN0NT6c,17031
|
|
46
55
|
flock/core/tools/basic_tools.py,sha256=hEG14jNZ2itVvubCHTfsWkuJK6yuNwBtuFj2Js0VHZs,9043
|
|
47
56
|
flock/core/tools/llm_tools.py,sha256=Bdt4Dpur5dGpxd2KFEQyxjfZazvW1HCDKY6ydMj6UgQ,21811
|
|
@@ -410,12 +419,12 @@ flock/themes/zenburned.toml,sha256=UEmquBbcAO3Zj652XKUwCsNoC2iQSlIh-q5c6DH-7Kc,1
|
|
|
410
419
|
flock/themes/zenwritten-dark.toml,sha256=To5l6520_3UqAGiEumpzGWsHhXxqu9ThrMildXKgIO0,1669
|
|
411
420
|
flock/themes/zenwritten-light.toml,sha256=G1iEheCPfBNsMTGaVpEVpDzYBHA_T-MV27rolUYolmE,1666
|
|
412
421
|
flock/workflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
413
|
-
flock/workflow/activities.py,sha256=
|
|
422
|
+
flock/workflow/activities.py,sha256=eVZDnxGJl_quNO-UTV3YgvTV8LrRaHN3QDAA1ANKzac,9065
|
|
414
423
|
flock/workflow/agent_activities.py,sha256=NhBZscflEf2IMfSRa_pBM_TRP7uVEF_O0ROvWZ33eDc,963
|
|
415
424
|
flock/workflow/temporal_setup.py,sha256=VWBgmBgfTBjwM5ruS_dVpA5AVxx6EZ7oFPGw4j3m0l0,1091
|
|
416
425
|
flock/workflow/workflow.py,sha256=I9MryXW_bqYVTHx-nl2epbTqeRy27CAWHHA7ZZA0nAk,1696
|
|
417
|
-
flock_core-0.3.
|
|
418
|
-
flock_core-0.3.
|
|
419
|
-
flock_core-0.3.
|
|
420
|
-
flock_core-0.3.
|
|
421
|
-
flock_core-0.3.
|
|
426
|
+
flock_core-0.3.30.dist-info/METADATA,sha256=tEQMna6bPKeaSix98h5NnqVnb8vN8IQLtiKE3DP_F2U,20745
|
|
427
|
+
flock_core-0.3.30.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
428
|
+
flock_core-0.3.30.dist-info/entry_points.txt,sha256=rWaS5KSpkTmWySURGFZk6PhbJ87TmvcFQDi2uzjlagQ,37
|
|
429
|
+
flock_core-0.3.30.dist-info/licenses/LICENSE,sha256=iYEqWy0wjULzM9GAERaybP4LBiPeu7Z1NEliLUdJKSc,1072
|
|
430
|
+
flock_core-0.3.30.dist-info/RECORD,,
|