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
flock/core/flock.py
CHANGED
|
@@ -1,476 +1,554 @@
|
|
|
1
|
+
# src/flock/core/flock.py
|
|
1
2
|
"""High-level orchestrator for creating and executing agents."""
|
|
2
3
|
|
|
4
|
+
from __future__ import annotations # Ensure forward references work
|
|
5
|
+
|
|
3
6
|
import asyncio
|
|
4
|
-
import json
|
|
5
7
|
import os
|
|
6
8
|
import uuid
|
|
7
|
-
from
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
8
11
|
|
|
9
|
-
import cloudpickle
|
|
10
12
|
from opentelemetry import trace
|
|
11
13
|
from opentelemetry.baggage import get_baggage, set_baggage
|
|
12
14
|
|
|
15
|
+
# Pydantic and OpenTelemetry
|
|
16
|
+
from pydantic import BaseModel, Field # Using Pydantic directly now
|
|
17
|
+
|
|
18
|
+
# Flock core components & utilities
|
|
13
19
|
from flock.config import TELEMETRY
|
|
14
20
|
from flock.core.context.context import FlockContext
|
|
15
21
|
from flock.core.context.context_manager import initialize_context
|
|
16
22
|
from flock.core.execution.local_executor import run_local_workflow
|
|
17
23
|
from flock.core.execution.temporal_executor import run_temporal_workflow
|
|
18
|
-
from flock.core.flock_agent import FlockAgent
|
|
19
24
|
from flock.core.logging.logging import LOGGERS, get_logger, get_module_loggers
|
|
20
|
-
|
|
25
|
+
|
|
26
|
+
# Import FlockAgent using TYPE_CHECKING to avoid circular import at runtime
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from flock.core.flock_agent import FlockAgent
|
|
29
|
+
else:
|
|
30
|
+
# Provide a forward reference string or Any for runtime if FlockAgent is used in hints here
|
|
31
|
+
FlockAgent = "FlockAgent" # Forward reference string for Pydantic/runtime
|
|
32
|
+
|
|
33
|
+
# Registry and Serialization
|
|
34
|
+
from flock.core.flock_registry import (
|
|
35
|
+
get_registry, # Use the unified registry
|
|
36
|
+
)
|
|
37
|
+
from flock.core.serialization.serializable import (
|
|
38
|
+
Serializable, # Import Serializable base
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# NOTE: Flock.to_dict/from_dict primarily orchestrates agent serialization.
|
|
42
|
+
# It doesn't usually need serialize_item/deserialize_item directly,
|
|
43
|
+
# relying on FlockAgent's implementation instead.
|
|
44
|
+
# from flock.core.serialization.serialization_utils import serialize_item, deserialize_item
|
|
45
|
+
# CLI Helper (if still used directly, otherwise can be removed)
|
|
21
46
|
from flock.core.util.cli_helper import init_console
|
|
22
|
-
from flock.core.util.input_resolver import top_level_to_keys
|
|
23
47
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
tracer = trace.get_tracer(__name__)
|
|
48
|
+
# Cloudpickle for fallback/direct serialization if needed
|
|
49
|
+
try:
|
|
50
|
+
import cloudpickle
|
|
28
51
|
|
|
52
|
+
PICKLE_AVAILABLE = True
|
|
53
|
+
except ImportError:
|
|
54
|
+
PICKLE_AVAILABLE = False
|
|
29
55
|
|
|
30
|
-
def init_loggers(enable_logging: bool | list[str] = False):
|
|
31
|
-
"""Initialize the loggers for the Flock system.
|
|
32
56
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
for logger in LOGGERS:
|
|
38
|
-
if logger in enable_logging:
|
|
39
|
-
other_loggers = get_logger(logger)
|
|
40
|
-
other_loggers.enable_logging = True
|
|
41
|
-
else:
|
|
42
|
-
other_loggers = get_logger(logger)
|
|
43
|
-
other_loggers.enable_logging = False
|
|
44
|
-
else:
|
|
45
|
-
logger = get_logger("flock")
|
|
46
|
-
logger.enable_logging = enable_logging
|
|
47
|
-
other_loggers = get_logger("interpreter")
|
|
48
|
-
other_loggers.enable_logging = enable_logging
|
|
49
|
-
other_loggers = get_logger("memory")
|
|
50
|
-
other_loggers.enable_logging = enable_logging
|
|
51
|
-
other_loggers = get_logger("activities")
|
|
52
|
-
other_loggers.enable_logging = enable_logging
|
|
53
|
-
other_loggers = get_logger("context")
|
|
54
|
-
other_loggers.enable_logging = enable_logging
|
|
55
|
-
other_loggers = get_logger("registry")
|
|
56
|
-
other_loggers.enable_logging = enable_logging
|
|
57
|
-
other_loggers = get_logger("tools")
|
|
58
|
-
other_loggers.enable_logging = enable_logging
|
|
59
|
-
other_loggers = get_logger("agent")
|
|
60
|
-
other_loggers.enable_logging = enable_logging
|
|
57
|
+
logger = get_logger("flock")
|
|
58
|
+
TELEMETRY.setup_tracing() # Setup OpenTelemetry
|
|
59
|
+
tracer = trace.get_tracer(__name__)
|
|
60
|
+
FlockRegistry = get_registry() # Get the registry instance
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
module_logger.enable_logging = enable_logging
|
|
62
|
+
# Define TypeVar for generic methods like from_dict
|
|
63
|
+
T = TypeVar("T", bound="Flock")
|
|
65
64
|
|
|
66
65
|
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
# Inherit from Serializable for YAML/JSON/etc. methods
|
|
67
|
+
# Use BaseModel directly for Pydantic features
|
|
68
|
+
class Flock(BaseModel, Serializable):
|
|
69
|
+
"""High-level orchestrator for creating and executing agent systems.
|
|
69
70
|
|
|
70
|
-
Flock manages
|
|
71
|
-
|
|
71
|
+
Flock manages agent definitions, context, and execution flow, supporting
|
|
72
|
+
both local debugging and robust distributed execution via Temporal.
|
|
73
|
+
It is serializable to various formats like YAML and JSON.
|
|
72
74
|
"""
|
|
73
75
|
|
|
76
|
+
model: str | None = Field(
|
|
77
|
+
default="openai/gpt-4o",
|
|
78
|
+
description="Default model identifier to be used for agents if not specified otherwise.",
|
|
79
|
+
)
|
|
80
|
+
description: str | None = Field(
|
|
81
|
+
default=None,
|
|
82
|
+
description="A brief description of the purpose of this Flock configuration.",
|
|
83
|
+
)
|
|
84
|
+
enable_temporal: bool = Field(
|
|
85
|
+
default=False,
|
|
86
|
+
description="If True, execute workflows via Temporal; otherwise, run locally.",
|
|
87
|
+
)
|
|
88
|
+
# --- Runtime Attributes (Excluded from Serialization) ---
|
|
89
|
+
# Store agents internally but don't make it part of the Pydantic model definition
|
|
90
|
+
# Use a regular attribute, initialized in __init__
|
|
91
|
+
# Pydantic V2 handles __init__ and attributes not in Field correctly
|
|
92
|
+
_agents: dict[str, FlockAgent]
|
|
93
|
+
_start_agent_name: str | None
|
|
94
|
+
_start_input: dict
|
|
95
|
+
|
|
96
|
+
# Pydantic v2 model config
|
|
97
|
+
model_config = {
|
|
98
|
+
"arbitrary_types_allowed": True,
|
|
99
|
+
"ignored_types": (
|
|
100
|
+
type(FlockRegistry),
|
|
101
|
+
), # Prevent validation issues with registry
|
|
102
|
+
# No need to exclude fields here, handled in to_dict
|
|
103
|
+
}
|
|
104
|
+
|
|
74
105
|
def __init__(
|
|
75
106
|
self,
|
|
76
|
-
model: str = "openai/gpt-4o",
|
|
107
|
+
model: str | None = "openai/gpt-4o",
|
|
108
|
+
description: str | None = None,
|
|
77
109
|
enable_temporal: bool = False,
|
|
78
|
-
enable_logging: bool
|
|
110
|
+
enable_logging: bool
|
|
111
|
+
| list[str] = False, # Keep logging control at init
|
|
112
|
+
agents: list[FlockAgent] | None = None, # Allow passing agents at init
|
|
113
|
+
**kwargs, # Allow extra fields during init if needed, Pydantic handles it
|
|
79
114
|
):
|
|
80
|
-
"""Initialize the Flock orchestrator.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
model
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
"""Initialize the Flock orchestrator."""
|
|
116
|
+
# Initialize Pydantic fields
|
|
117
|
+
super().__init__(
|
|
118
|
+
model=model,
|
|
119
|
+
description=description,
|
|
120
|
+
enable_temporal=enable_temporal,
|
|
121
|
+
**kwargs, # Pass extra kwargs to Pydantic BaseModel
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Initialize runtime attributes AFTER super().__init__()
|
|
125
|
+
self._agents = {}
|
|
126
|
+
self._start_agent_name = None
|
|
127
|
+
self._start_input = {}
|
|
128
|
+
|
|
129
|
+
# Set up logging
|
|
130
|
+
self._configure_logging(enable_logging)
|
|
131
|
+
|
|
132
|
+
# Register passed agents
|
|
133
|
+
if agents:
|
|
134
|
+
# Ensure FlockAgent type is available for isinstance check
|
|
135
|
+
# This import might need to be deferred or handled carefully if it causes issues
|
|
136
|
+
from flock.core.flock_agent import FlockAgent as ConcreteFlockAgent
|
|
137
|
+
|
|
138
|
+
for agent in agents:
|
|
139
|
+
if isinstance(agent, ConcreteFlockAgent):
|
|
140
|
+
self.add_agent(agent)
|
|
141
|
+
else:
|
|
142
|
+
logger.warning(
|
|
143
|
+
f"Item provided in 'agents' list is not a FlockAgent: {type(agent)}"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Initialize console if needed
|
|
147
|
+
init_console()
|
|
148
|
+
|
|
149
|
+
# Set Temporal debug environment variable
|
|
150
|
+
self._set_temporal_debug_flag()
|
|
151
|
+
|
|
152
|
+
# Ensure session ID exists in baggage
|
|
153
|
+
self._ensure_session_id()
|
|
154
|
+
|
|
155
|
+
logger.info(
|
|
156
|
+
"Flock instance initialized",
|
|
157
|
+
model=self.model,
|
|
158
|
+
enable_temporal=self.enable_temporal,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# --- Keep _configure_logging, _set_temporal_debug_flag, _ensure_session_id ---
|
|
162
|
+
# ... (implementation as before) ...
|
|
163
|
+
def _configure_logging(self, enable_logging: bool | list[str]):
|
|
164
|
+
"""Configure logging levels based on the enable_logging flag."""
|
|
165
|
+
logger.debug(f"Configuring logging, enable_logging={enable_logging}")
|
|
166
|
+
is_enabled_globally = False
|
|
167
|
+
enabled_loggers = []
|
|
168
|
+
|
|
169
|
+
if isinstance(enable_logging, bool):
|
|
170
|
+
is_enabled_globally = enable_logging
|
|
171
|
+
elif isinstance(enable_logging, list):
|
|
172
|
+
is_enabled_globally = bool(
|
|
173
|
+
enable_logging
|
|
174
|
+
) # Enable if list is not empty
|
|
175
|
+
enabled_loggers = enable_logging
|
|
176
|
+
|
|
177
|
+
# Configure core loggers
|
|
178
|
+
for log_name in LOGGERS:
|
|
179
|
+
log_instance = get_logger(log_name)
|
|
180
|
+
if is_enabled_globally or log_name in enabled_loggers:
|
|
181
|
+
log_instance.enable_logging = True
|
|
182
|
+
else:
|
|
183
|
+
log_instance.enable_logging = False
|
|
184
|
+
|
|
185
|
+
# Configure module loggers (existing ones)
|
|
186
|
+
module_loggers = get_module_loggers()
|
|
187
|
+
for mod_log in module_loggers:
|
|
188
|
+
if is_enabled_globally or mod_log.name in enabled_loggers:
|
|
189
|
+
mod_log.enable_logging = True
|
|
190
|
+
else:
|
|
191
|
+
mod_log.enable_logging = False
|
|
192
|
+
|
|
193
|
+
def _set_temporal_debug_flag(self):
|
|
194
|
+
"""Set or remove LOCAL_DEBUG env var based on enable_temporal."""
|
|
195
|
+
if not self.enable_temporal:
|
|
196
|
+
if "LOCAL_DEBUG" not in os.environ:
|
|
116
197
|
os.environ["LOCAL_DEBUG"] = "1"
|
|
117
|
-
logger.debug("Set LOCAL_DEBUG environment variable")
|
|
118
|
-
elif "LOCAL_DEBUG" in os.environ:
|
|
119
|
-
del os.environ["LOCAL_DEBUG"]
|
|
120
|
-
logger.debug("Removed LOCAL_DEBUG environment variable")
|
|
121
|
-
|
|
122
|
-
def add_agent(self, agent: T) -> T:
|
|
123
|
-
"""Add a new agent to the Flock system.
|
|
124
|
-
|
|
125
|
-
This method registers the agent, updates the internal registry and global context, and
|
|
126
|
-
sets default values if needed. If an agent with the same name already exists, the existing
|
|
127
|
-
agent is returned.
|
|
128
|
-
|
|
129
|
-
Args:
|
|
130
|
-
agent (FlockAgent): The agent instance to add.
|
|
131
|
-
|
|
132
|
-
Returns:
|
|
133
|
-
FlockAgent: The registered agent instance.
|
|
134
|
-
"""
|
|
135
|
-
with tracer.start_as_current_span("add_agent") as span:
|
|
136
|
-
span.set_attribute("agent_name", agent.name)
|
|
137
|
-
if not agent.model:
|
|
138
|
-
agent.set_model(self.model)
|
|
139
198
|
logger.debug(
|
|
140
|
-
|
|
141
|
-
model=self.model,
|
|
199
|
+
"Set LOCAL_DEBUG environment variable for local execution."
|
|
142
200
|
)
|
|
201
|
+
elif "LOCAL_DEBUG" in os.environ:
|
|
202
|
+
del os.environ["LOCAL_DEBUG"]
|
|
203
|
+
logger.debug(
|
|
204
|
+
"Removed LOCAL_DEBUG environment variable for Temporal execution."
|
|
205
|
+
)
|
|
143
206
|
|
|
144
|
-
|
|
207
|
+
def _ensure_session_id(self):
|
|
208
|
+
"""Ensure a session_id exists in the OpenTelemetry baggage."""
|
|
209
|
+
session_id = get_baggage("session_id")
|
|
210
|
+
if not session_id:
|
|
211
|
+
session_id = str(uuid.uuid4())
|
|
212
|
+
set_baggage("session_id", session_id)
|
|
213
|
+
logger.debug(f"Generated new session_id: {session_id}")
|
|
214
|
+
|
|
215
|
+
# --- Keep add_agent, agents property, run, run_async ---
|
|
216
|
+
# ... (implementation as before, ensuring FlockAgent type hint is handled) ...
|
|
217
|
+
def add_agent(self, agent: FlockAgent) -> FlockAgent:
|
|
218
|
+
"""Adds an agent instance to this Flock configuration."""
|
|
219
|
+
# Ensure FlockAgent type is available for isinstance check
|
|
220
|
+
from flock.core.flock_agent import FlockAgent as ConcreteFlockAgent
|
|
221
|
+
|
|
222
|
+
if not isinstance(agent, ConcreteFlockAgent):
|
|
223
|
+
raise TypeError("Provided object is not a FlockAgent instance.")
|
|
224
|
+
if not agent.name:
|
|
225
|
+
raise ValueError("Agent must have a name.")
|
|
226
|
+
|
|
227
|
+
if agent.name in self._agents:
|
|
228
|
+
logger.warning(
|
|
229
|
+
f"Agent '{agent.name}' already exists in this Flock instance. Overwriting."
|
|
230
|
+
)
|
|
231
|
+
self._agents[agent.name] = agent
|
|
232
|
+
FlockRegistry.register_agent(agent) # Also register globally
|
|
233
|
+
|
|
234
|
+
# Set default model if agent doesn't have one
|
|
235
|
+
if agent.model is None:
|
|
236
|
+
# agent.set_model(self.model) # Use Flock's default model
|
|
237
|
+
if self.model: # Ensure Flock has a model defined
|
|
238
|
+
agent.set_model(self.model)
|
|
239
|
+
logger.debug(
|
|
240
|
+
f"Agent '{agent.name}' using Flock default model: {self.model}"
|
|
241
|
+
)
|
|
242
|
+
else:
|
|
145
243
|
logger.warning(
|
|
146
|
-
f"Agent {agent.name}
|
|
244
|
+
f"Agent '{agent.name}' has no model and Flock default model is not set."
|
|
147
245
|
)
|
|
148
|
-
return self.agents[agent.name]
|
|
149
|
-
logger.info(f"Adding new agent '{agent.name}'")
|
|
150
246
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
self.context.add_agent_definition(
|
|
154
|
-
type(agent), agent.name, agent.to_dict()
|
|
155
|
-
)
|
|
247
|
+
logger.info(f"Agent '{agent.name}' added to Flock.")
|
|
248
|
+
return agent
|
|
156
249
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
f"Registered tool '{tool.__name__}'",
|
|
162
|
-
tool_name=tool.__name__,
|
|
163
|
-
)
|
|
164
|
-
logger.success(f"'{agent.name}' added successfully")
|
|
165
|
-
return agent
|
|
166
|
-
|
|
167
|
-
def add_tool(self, tool_name: str, tool: callable):
|
|
168
|
-
"""Register a tool with the Flock system.
|
|
169
|
-
|
|
170
|
-
Args:
|
|
171
|
-
tool_name (str): The name under which the tool will be registered.
|
|
172
|
-
tool (callable): The tool function to register.
|
|
173
|
-
"""
|
|
174
|
-
with tracer.start_as_current_span("add_tool") as span:
|
|
175
|
-
span.set_attribute("tool_name", tool_name)
|
|
176
|
-
span.set_attribute("tool", tool.__name__)
|
|
177
|
-
logger.info("Registering tool", tool_name=tool_name)
|
|
178
|
-
self.registry.register_tool(tool_name, tool)
|
|
179
|
-
logger.debug("Tool registered successfully")
|
|
250
|
+
@property
|
|
251
|
+
def agents(self) -> dict[str, FlockAgent]:
|
|
252
|
+
"""Returns the dictionary of agents managed by this Flock instance."""
|
|
253
|
+
return self._agents
|
|
180
254
|
|
|
181
255
|
def run(
|
|
182
256
|
self,
|
|
183
257
|
start_agent: FlockAgent | str | None = None,
|
|
184
258
|
input: dict = {},
|
|
185
|
-
context: FlockContext
|
|
259
|
+
context: FlockContext
|
|
260
|
+
| None = None, # Allow passing initial context state
|
|
186
261
|
run_id: str = "",
|
|
187
|
-
box_result: bool =
|
|
188
|
-
agents: list[FlockAgent] =
|
|
262
|
+
box_result: bool = False, # Changed default to False for raw dict
|
|
263
|
+
agents: list[FlockAgent] | None = None, # Allow adding agents via run
|
|
189
264
|
) -> dict:
|
|
190
265
|
"""Entry point for running an agent system synchronously."""
|
|
191
266
|
return asyncio.run(
|
|
192
267
|
self.run_async(
|
|
193
|
-
start_agent,
|
|
268
|
+
start_agent=start_agent,
|
|
269
|
+
input=input,
|
|
270
|
+
context=context,
|
|
271
|
+
run_id=run_id,
|
|
272
|
+
box_result=box_result,
|
|
273
|
+
agents=agents,
|
|
194
274
|
)
|
|
195
275
|
)
|
|
196
276
|
|
|
197
|
-
def save_to_file(
|
|
198
|
-
self,
|
|
199
|
-
file_path: str,
|
|
200
|
-
start_agent: str | None = None,
|
|
201
|
-
input: dict | None = None,
|
|
202
|
-
) -> None:
|
|
203
|
-
"""Save the Flock instance to a file.
|
|
204
|
-
|
|
205
|
-
This method serializes the Flock instance to a dictionary using the `to_dict()` method and saves it to a file.
|
|
206
|
-
The saved file can be reloaded later using the `from_file()` method.
|
|
207
|
-
|
|
208
|
-
Args:
|
|
209
|
-
file_path (str): The path to the file where the Flock instance should be saved.
|
|
210
|
-
"""
|
|
211
|
-
hex_str = cloudpickle.dumps(self).hex()
|
|
212
|
-
|
|
213
|
-
result = {
|
|
214
|
-
"start_agent": start_agent,
|
|
215
|
-
"input": input,
|
|
216
|
-
"flock": hex_str,
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
path = os.path.dirname(file_path)
|
|
220
|
-
if path:
|
|
221
|
-
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
|
222
|
-
|
|
223
|
-
with open(file_path, "w") as file:
|
|
224
|
-
file.write(json.dumps(result))
|
|
225
|
-
|
|
226
|
-
@staticmethod
|
|
227
|
-
def load_from_file(file_path: str) -> "Flock":
|
|
228
|
-
"""Load a Flock instance from a file.
|
|
229
|
-
|
|
230
|
-
This class method deserializes a Flock instance from a file that was previously saved using the `save_to_file()`
|
|
231
|
-
method. It reads the file, converts the hexadecimal string back into a Flock instance, and returns it.
|
|
232
|
-
|
|
233
|
-
Args:
|
|
234
|
-
file_path (str): The path to the file containing the serialized Flock instance.
|
|
235
|
-
|
|
236
|
-
Returns:
|
|
237
|
-
Flock: A new Flock instance reconstructed from the saved file.
|
|
238
|
-
"""
|
|
239
|
-
with open(file_path) as file:
|
|
240
|
-
json_flock = json.load(file)
|
|
241
|
-
hex_str = json_flock["flock"]
|
|
242
|
-
flock = cloudpickle.loads(bytes.fromhex(hex_str))
|
|
243
|
-
if json_flock["start_agent"]:
|
|
244
|
-
agent = flock.registry.get_agent(json_flock["start_agent"])
|
|
245
|
-
flock.start_agent = agent
|
|
246
|
-
if json_flock["input"]:
|
|
247
|
-
flock.input = json_flock["input"]
|
|
248
|
-
return flock
|
|
249
|
-
|
|
250
|
-
def to_dict(self) -> dict[str, Any]:
|
|
251
|
-
"""Serialize the FlockAgent instance to a dictionary.
|
|
252
|
-
|
|
253
|
-
This method converts the entire agent instance—including its configuration, state, and lifecycle hooks—
|
|
254
|
-
into a dictionary format. It uses cloudpickle to serialize any callable objects (such as functions or
|
|
255
|
-
methods), converting them into hexadecimal string representations. This ensures that the agent can be
|
|
256
|
-
easily persisted, transmitted, or logged as JSON.
|
|
257
|
-
|
|
258
|
-
The serialization process is recursive:
|
|
259
|
-
- If a field is a callable (and not a class), it is serialized using cloudpickle.
|
|
260
|
-
- Lists and dictionaries are processed recursively to ensure that all nested callables are properly handled.
|
|
261
|
-
|
|
262
|
-
**Returns:**
|
|
263
|
-
dict[str, Any]: A dictionary representing the FlockAgent, which includes all of its configuration data.
|
|
264
|
-
This dictionary is suitable for storage, debugging, or transmission over the network.
|
|
265
|
-
|
|
266
|
-
**Example:**
|
|
267
|
-
For an agent defined as:
|
|
268
|
-
name = "idea_agent",
|
|
269
|
-
model = "openai/gpt-4o",
|
|
270
|
-
input = "query: str | The search query, context: dict | The full conversation context",
|
|
271
|
-
output = "idea: str | The generated idea"
|
|
272
|
-
Calling `agent.to_dict()` might produce:
|
|
273
|
-
{
|
|
274
|
-
"name": "idea_agent",
|
|
275
|
-
"model": "openai/gpt-4o",
|
|
276
|
-
"input": "query: str | The search query, context: dict | The full conversation context",
|
|
277
|
-
"output": "idea: str | The generated idea",
|
|
278
|
-
"tools": ["<serialized tool representation>"],
|
|
279
|
-
"use_cache": False,
|
|
280
|
-
"hand_off": None,
|
|
281
|
-
"termination": None,
|
|
282
|
-
...
|
|
283
|
-
}
|
|
284
|
-
"""
|
|
285
|
-
|
|
286
|
-
def convert_callable(obj: Any) -> Any:
|
|
287
|
-
if callable(obj) and not isinstance(obj, type):
|
|
288
|
-
return cloudpickle.dumps(obj).hex()
|
|
289
|
-
if isinstance(obj, list):
|
|
290
|
-
return [convert_callable(item) for item in obj]
|
|
291
|
-
if isinstance(obj, dict):
|
|
292
|
-
return {k: convert_callable(v) for k, v in obj.items()}
|
|
293
|
-
return obj
|
|
294
|
-
|
|
295
|
-
data = self.model_dump()
|
|
296
|
-
return convert_callable(data)
|
|
297
|
-
|
|
298
|
-
def start_api(self, host: str = "0.0.0.0", port: int = 8344) -> None:
|
|
299
|
-
"""Start a REST API server for this Flock instance.
|
|
300
|
-
|
|
301
|
-
This method creates a FlockAPI instance for the current Flock and starts the API server.
|
|
302
|
-
It provides an easier alternative to manually creating and starting the API.
|
|
303
|
-
|
|
304
|
-
Args:
|
|
305
|
-
host (str): The host to bind the server to. Defaults to "0.0.0.0".
|
|
306
|
-
port (int): The port to bind the server to. Defaults to 8344.
|
|
307
|
-
"""
|
|
308
|
-
from flock.core.flock_api import FlockAPI
|
|
309
|
-
|
|
310
|
-
api = FlockAPI(self)
|
|
311
|
-
api.start(host=host, port=port)
|
|
312
|
-
|
|
313
|
-
@classmethod
|
|
314
|
-
def from_dict(cls: type[T], data: dict[str, Any]) -> T:
|
|
315
|
-
"""Deserialize a FlockAgent instance from a dictionary.
|
|
316
|
-
|
|
317
|
-
This class method reconstructs a FlockAgent from its serialized dictionary representation, as produced
|
|
318
|
-
by the `to_dict()` method. It recursively processes the dictionary to convert any serialized callables
|
|
319
|
-
(stored as hexadecimal strings via cloudpickle) back into executable callable objects.
|
|
320
|
-
|
|
321
|
-
**Arguments:**
|
|
322
|
-
data (dict[str, Any]): A dictionary representation of a FlockAgent, typically produced by `to_dict()`.
|
|
323
|
-
The dictionary should contain all configuration fields and state information necessary to fully
|
|
324
|
-
reconstruct the agent.
|
|
325
|
-
|
|
326
|
-
**Returns:**
|
|
327
|
-
FlockAgent: An instance of FlockAgent reconstructed from the provided dictionary. The deserialized agent
|
|
328
|
-
will have the same configuration, state, and behavior as the original instance.
|
|
329
|
-
|
|
330
|
-
**Example:**
|
|
331
|
-
Suppose you have the following dictionary:
|
|
332
|
-
{
|
|
333
|
-
"name": "idea_agent",
|
|
334
|
-
"model": "openai/gpt-4o",
|
|
335
|
-
"input": "query: str | The search query, context: dict | The full conversation context",
|
|
336
|
-
"output": "idea: str | The generated idea",
|
|
337
|
-
"tools": ["<serialized tool representation>"],
|
|
338
|
-
"use_cache": False,
|
|
339
|
-
"hand_off": None,
|
|
340
|
-
"termination": None,
|
|
341
|
-
...
|
|
342
|
-
}
|
|
343
|
-
Then, calling:
|
|
344
|
-
agent = FlockAgent.from_dict(data)
|
|
345
|
-
will return a FlockAgent instance with the same properties and behavior as when it was originally serialized.
|
|
346
|
-
"""
|
|
347
|
-
|
|
348
|
-
def convert_callable(obj: Any) -> Any:
|
|
349
|
-
if isinstance(obj, str) and len(obj) > 2:
|
|
350
|
-
try:
|
|
351
|
-
return cloudpickle.loads(bytes.fromhex(obj))
|
|
352
|
-
except Exception:
|
|
353
|
-
return obj
|
|
354
|
-
if isinstance(obj, list):
|
|
355
|
-
return [convert_callable(item) for item in obj]
|
|
356
|
-
if isinstance(obj, dict):
|
|
357
|
-
return {k: convert_callable(v) for k, v in obj.items()}
|
|
358
|
-
return obj
|
|
359
|
-
|
|
360
|
-
converted = convert_callable(data)
|
|
361
|
-
return cls(**converted)
|
|
362
|
-
|
|
363
277
|
async def run_async(
|
|
364
278
|
self,
|
|
365
279
|
start_agent: FlockAgent | str | None = None,
|
|
366
|
-
input: dict =
|
|
367
|
-
context: FlockContext = None,
|
|
280
|
+
input: dict | None = None,
|
|
281
|
+
context: FlockContext | None = None,
|
|
368
282
|
run_id: str = "",
|
|
369
|
-
box_result: bool =
|
|
370
|
-
agents: list[FlockAgent] =
|
|
283
|
+
box_result: bool = False, # Changed default
|
|
284
|
+
agents: list[FlockAgent] | None = None, # Allow adding agents via run
|
|
371
285
|
) -> dict:
|
|
372
|
-
"""Entry point for running an agent system asynchronously.
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
Returns:
|
|
390
|
-
dict: A dictionary containing the result of the agent workflow execution.
|
|
391
|
-
|
|
392
|
-
Raises:
|
|
393
|
-
ValueError: If the specified agent is not found in the registry.
|
|
394
|
-
Exception: For any other errors encountered during execution.
|
|
395
|
-
"""
|
|
396
|
-
with tracer.start_as_current_span("run_async") as span:
|
|
397
|
-
if isinstance(start_agent, str):
|
|
398
|
-
start_agent = self.registry.get_agent(start_agent)
|
|
399
|
-
span.set_attribute(
|
|
400
|
-
"start_agent",
|
|
401
|
-
start_agent.name
|
|
402
|
-
if hasattr(start_agent, "name")
|
|
403
|
-
else start_agent,
|
|
404
|
-
)
|
|
405
|
-
for agent in agents:
|
|
406
|
-
self.add_agent(agent)
|
|
286
|
+
"""Entry point for running an agent system asynchronously."""
|
|
287
|
+
# This import needs to be here or handled carefully due to potential cycles
|
|
288
|
+
from flock.core.flock_agent import FlockAgent as ConcreteFlockAgent
|
|
289
|
+
|
|
290
|
+
with tracer.start_as_current_span("flock.run_async") as span:
|
|
291
|
+
# Add passed agents first
|
|
292
|
+
if agents:
|
|
293
|
+
for agent_obj in agents:
|
|
294
|
+
if isinstance(agent_obj, ConcreteFlockAgent):
|
|
295
|
+
self.add_agent(
|
|
296
|
+
agent_obj
|
|
297
|
+
) # Adds to self._agents and registry
|
|
298
|
+
else:
|
|
299
|
+
logger.warning(
|
|
300
|
+
f"Item in 'agents' list is not a FlockAgent: {type(agent_obj)}"
|
|
301
|
+
)
|
|
407
302
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
if
|
|
411
|
-
|
|
303
|
+
# Determine starting agent name
|
|
304
|
+
start_agent_name: str | None = None
|
|
305
|
+
if isinstance(start_agent, ConcreteFlockAgent):
|
|
306
|
+
start_agent_name = start_agent.name
|
|
307
|
+
if start_agent_name not in self._agents:
|
|
308
|
+
self.add_agent(
|
|
309
|
+
start_agent
|
|
310
|
+
) # Add if instance was passed but not added
|
|
311
|
+
elif isinstance(start_agent, str):
|
|
312
|
+
start_agent_name = start_agent
|
|
313
|
+
else:
|
|
314
|
+
start_agent_name = (
|
|
315
|
+
self._start_agent_name
|
|
316
|
+
) # Use pre-configured if any
|
|
317
|
+
|
|
318
|
+
# Default to first agent if only one exists and none specified
|
|
319
|
+
if not start_agent_name and len(self._agents) == 1:
|
|
320
|
+
start_agent_name = list(self._agents.keys())[0]
|
|
321
|
+
elif not start_agent_name:
|
|
322
|
+
raise ValueError(
|
|
323
|
+
"No start_agent specified and multiple agents exist or none are added."
|
|
324
|
+
)
|
|
412
325
|
|
|
413
|
-
|
|
414
|
-
|
|
326
|
+
# Get starting input
|
|
327
|
+
run_input = input if input is not None else self._start_input
|
|
328
|
+
|
|
329
|
+
# Log and trace start info
|
|
330
|
+
span.set_attribute("start_agent", start_agent_name)
|
|
331
|
+
span.set_attribute("input", str(run_input))
|
|
415
332
|
span.set_attribute("run_id", run_id)
|
|
416
|
-
span.set_attribute("
|
|
333
|
+
span.set_attribute("enable_temporal", self.enable_temporal)
|
|
334
|
+
logger.info(
|
|
335
|
+
f"Initiating Flock run. Start Agent: '{start_agent_name}'. Temporal: {self.enable_temporal}."
|
|
336
|
+
)
|
|
417
337
|
|
|
418
338
|
try:
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
339
|
+
# Resolve start agent instance from internal dict
|
|
340
|
+
resolved_start_agent = self._agents.get(start_agent_name)
|
|
341
|
+
if not resolved_start_agent:
|
|
342
|
+
# Maybe it's only in the global registry? (Less common)
|
|
343
|
+
resolved_start_agent = FlockRegistry.get_agent(
|
|
344
|
+
start_agent_name
|
|
423
345
|
)
|
|
424
|
-
|
|
425
|
-
if not self.start_agent:
|
|
426
|
-
logger.error(
|
|
427
|
-
"Agent not found", agent_name=self.start_agent
|
|
428
|
-
)
|
|
346
|
+
if not resolved_start_agent:
|
|
429
347
|
raise ValueError(
|
|
430
|
-
f"
|
|
348
|
+
f"Start agent '{start_agent_name}' not found in Flock instance or registry."
|
|
431
349
|
)
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
self.context = context
|
|
436
|
-
if not run_id:
|
|
437
|
-
run_id = f"flock_{uuid.uuid4().hex[:4]}"
|
|
438
|
-
logger.debug(f"Generated run ID '{run_id}'", run_id=run_id)
|
|
439
|
-
|
|
440
|
-
set_baggage("run_id", run_id)
|
|
441
|
-
|
|
442
|
-
# TODO - Add a check for required input keys
|
|
443
|
-
input_keys = top_level_to_keys(self.start_agent.input)
|
|
444
|
-
for key in input_keys:
|
|
445
|
-
if key.startswith("flock."):
|
|
446
|
-
key = key[6:] # Remove the "flock." prefix
|
|
447
|
-
if key not in self.input:
|
|
448
|
-
from rich.prompt import Prompt
|
|
350
|
+
else:
|
|
351
|
+
# If found globally, add it to this instance for consistency during run
|
|
352
|
+
self.add_agent(resolved_start_agent)
|
|
449
353
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
354
|
+
# Create or use provided context
|
|
355
|
+
run_context = context if context else FlockContext()
|
|
356
|
+
if not run_id:
|
|
357
|
+
run_id = f"flockrun_{uuid.uuid4().hex[:8]}"
|
|
358
|
+
set_baggage("run_id", run_id) # Ensure run_id is in baggage
|
|
453
359
|
|
|
454
|
-
# Initialize
|
|
360
|
+
# Initialize context
|
|
455
361
|
initialize_context(
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
362
|
+
run_context,
|
|
363
|
+
start_agent_name,
|
|
364
|
+
run_input,
|
|
459
365
|
run_id,
|
|
460
366
|
not self.enable_temporal,
|
|
461
|
-
self.model
|
|
367
|
+
self.model
|
|
368
|
+
or resolved_start_agent.model
|
|
369
|
+
or "default-model-missing", # Pass effective model
|
|
462
370
|
)
|
|
463
371
|
|
|
372
|
+
# Execute workflow
|
|
464
373
|
logger.info(
|
|
465
374
|
"Starting agent execution",
|
|
466
|
-
agent=
|
|
375
|
+
agent=start_agent_name,
|
|
467
376
|
enable_temporal=self.enable_temporal,
|
|
468
377
|
)
|
|
469
378
|
|
|
470
379
|
if not self.enable_temporal:
|
|
471
|
-
|
|
380
|
+
result = await run_local_workflow(
|
|
381
|
+
run_context, box_result=False
|
|
382
|
+
) # Get raw dict
|
|
472
383
|
else:
|
|
473
|
-
|
|
384
|
+
result = await run_temporal_workflow(
|
|
385
|
+
run_context, box_result=False
|
|
386
|
+
) # Get raw dict
|
|
387
|
+
|
|
388
|
+
span.set_attribute("result.type", str(type(result)))
|
|
389
|
+
# Avoid overly large results in trace attributes
|
|
390
|
+
result_str = str(result)
|
|
391
|
+
if len(result_str) > 1000:
|
|
392
|
+
result_str = result_str[:1000] + "... (truncated)"
|
|
393
|
+
span.set_attribute("result.preview", result_str)
|
|
394
|
+
|
|
395
|
+
# Optionally box result before returning
|
|
396
|
+
if box_result:
|
|
397
|
+
try:
|
|
398
|
+
from box import Box
|
|
399
|
+
|
|
400
|
+
logger.debug("Boxing final result.")
|
|
401
|
+
return Box(result)
|
|
402
|
+
except ImportError:
|
|
403
|
+
logger.warning(
|
|
404
|
+
"Box library not installed, returning raw dict. Install with 'pip install python-box'"
|
|
405
|
+
)
|
|
406
|
+
return result
|
|
407
|
+
else:
|
|
408
|
+
return result
|
|
409
|
+
|
|
410
|
+
except Exception as e:
|
|
411
|
+
logger.error(f"Flock run failed: {e}", exc_info=True)
|
|
412
|
+
span.record_exception(e)
|
|
413
|
+
span.set_status(trace.Status(trace.StatusCode.ERROR, str(e)))
|
|
414
|
+
# Depending on desired behavior, either raise or return an error dict
|
|
415
|
+
# raise # Option 1: Let the exception propagate
|
|
416
|
+
return {
|
|
417
|
+
"error": str(e),
|
|
418
|
+
"details": "Flock run failed.",
|
|
419
|
+
} # Option 2: Return error dict
|
|
420
|
+
|
|
421
|
+
# --- ADDED Serialization Methods ---
|
|
422
|
+
|
|
423
|
+
def to_dict(self) -> dict[str, Any]:
|
|
424
|
+
"""Convert Flock instance to dictionary representation."""
|
|
425
|
+
logger.debug("Serializing Flock instance to dict.")
|
|
426
|
+
# Use Pydantic's dump for base fields
|
|
427
|
+
data = self.model_dump(mode="json", exclude_none=True)
|
|
428
|
+
|
|
429
|
+
# Manually add serialized agents
|
|
430
|
+
data["agents"] = {}
|
|
431
|
+
for name, agent_instance in self._agents.items():
|
|
432
|
+
try:
|
|
433
|
+
# Agents handle their own serialization via their to_dict
|
|
434
|
+
data["agents"][name] = agent_instance.to_dict()
|
|
474
435
|
except Exception as e:
|
|
475
|
-
logger.
|
|
476
|
-
|
|
436
|
+
logger.error(
|
|
437
|
+
f"Failed to serialize agent '{name}' within Flock: {e}"
|
|
438
|
+
)
|
|
439
|
+
# Optionally skip problematic agents or raise error
|
|
440
|
+
# data["agents"][name] = {"error": f"Serialization failed: {e}"}
|
|
441
|
+
|
|
442
|
+
# Exclude runtime fields that shouldn't be serialized
|
|
443
|
+
# These are not Pydantic fields, so they aren't dumped by model_dump
|
|
444
|
+
# No need to explicitly remove _start_agent_name, _start_input unless added manually
|
|
445
|
+
|
|
446
|
+
# Filter final dict (optional, Pydantic's exclude_none helps)
|
|
447
|
+
# return self._filter_none_values(data)
|
|
448
|
+
return data
|
|
449
|
+
|
|
450
|
+
@classmethod
|
|
451
|
+
def from_dict(cls: type[T], data: dict[str, Any]) -> T:
|
|
452
|
+
"""Create Flock instance from dictionary representation."""
|
|
453
|
+
logger.debug(
|
|
454
|
+
f"Deserializing Flock from dict. Provided keys: {list(data.keys())}"
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
# Ensure FlockAgent is importable for type checking later
|
|
458
|
+
try:
|
|
459
|
+
from flock.core.flock_agent import FlockAgent as ConcreteFlockAgent
|
|
460
|
+
except ImportError:
|
|
461
|
+
logger.error(
|
|
462
|
+
"Cannot import FlockAgent, deserialization may fail for agents."
|
|
463
|
+
)
|
|
464
|
+
ConcreteFlockAgent = Any # Fallback
|
|
465
|
+
|
|
466
|
+
# Extract agent data before initializing Flock base model
|
|
467
|
+
agents_data = data.pop("agents", {})
|
|
468
|
+
|
|
469
|
+
# Create Flock instance using Pydantic constructor for basic fields
|
|
470
|
+
try:
|
|
471
|
+
# Pass only fields defined in Flock's Pydantic model
|
|
472
|
+
init_data = {k: v for k, v in data.items() if k in cls.model_fields}
|
|
473
|
+
flock_instance = cls(**init_data)
|
|
474
|
+
except Exception as e:
|
|
475
|
+
logger.error(
|
|
476
|
+
f"Pydantic validation/init failed for Flock: {e}", exc_info=True
|
|
477
|
+
)
|
|
478
|
+
raise ValueError(
|
|
479
|
+
f"Failed to initialize Flock from dict: {e}"
|
|
480
|
+
) from e
|
|
481
|
+
|
|
482
|
+
# Deserialize and add agents AFTER Flock instance exists
|
|
483
|
+
for name, agent_data in agents_data.items():
|
|
484
|
+
try:
|
|
485
|
+
# Ensure agent_data has the name, or add it from the key
|
|
486
|
+
agent_data.setdefault("name", name)
|
|
487
|
+
# Use FlockAgent's from_dict method
|
|
488
|
+
agent_instance = ConcreteFlockAgent.from_dict(agent_data)
|
|
489
|
+
flock_instance.add_agent(
|
|
490
|
+
agent_instance
|
|
491
|
+
) # Adds to _agents and registers
|
|
492
|
+
except Exception as e:
|
|
493
|
+
logger.error(
|
|
494
|
+
f"Failed to deserialize or add agent '{name}' during Flock deserialization: {e}",
|
|
495
|
+
exc_info=True,
|
|
496
|
+
)
|
|
497
|
+
# Decide: skip agent or raise error?
|
|
498
|
+
|
|
499
|
+
logger.info("Successfully deserialized Flock instance.")
|
|
500
|
+
return flock_instance
|
|
501
|
+
|
|
502
|
+
# --- API Start Method ---
|
|
503
|
+
def start_api(
|
|
504
|
+
self,
|
|
505
|
+
host: str = "127.0.0.1",
|
|
506
|
+
port: int = 8344,
|
|
507
|
+
server_name: str = "Flock API",
|
|
508
|
+
create_ui: bool = False,
|
|
509
|
+
) -> None:
|
|
510
|
+
"""Start a REST API server for this Flock instance."""
|
|
511
|
+
# Import locally to avoid making API components a hard dependency
|
|
512
|
+
try:
|
|
513
|
+
from flock.core.api import FlockAPI
|
|
514
|
+
except ImportError:
|
|
515
|
+
logger.error(
|
|
516
|
+
"API components not found. Cannot start API. "
|
|
517
|
+
"Ensure 'fastapi' and 'uvicorn' are installed."
|
|
518
|
+
)
|
|
519
|
+
return
|
|
520
|
+
|
|
521
|
+
logger.info(
|
|
522
|
+
f"Preparing to start API server on {host}:{port} {'with UI' if create_ui else 'without UI'}"
|
|
523
|
+
)
|
|
524
|
+
api_instance = FlockAPI(self) # Pass the current Flock instance
|
|
525
|
+
# Use the start method of FlockAPI
|
|
526
|
+
api_instance.start(
|
|
527
|
+
host=host, port=port, server_name=server_name, create_ui=create_ui
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
# --- Static Method Loaders (Keep for convenience) ---
|
|
531
|
+
@staticmethod
|
|
532
|
+
def load_from_file(file_path: str) -> Flock:
|
|
533
|
+
"""Load a Flock instance from various file formats (detects type)."""
|
|
534
|
+
p = Path(file_path)
|
|
535
|
+
if not p.exists():
|
|
536
|
+
raise FileNotFoundError(f"Flock file not found: {file_path}")
|
|
537
|
+
|
|
538
|
+
if p.suffix in [".yaml", ".yml"]:
|
|
539
|
+
return Flock.from_yaml_file(p)
|
|
540
|
+
elif p.suffix == ".json":
|
|
541
|
+
return Flock.from_json(p.read_text())
|
|
542
|
+
elif p.suffix == ".msgpack":
|
|
543
|
+
return Flock.from_msgpack_file(p)
|
|
544
|
+
elif p.suffix == ".pkl":
|
|
545
|
+
if PICKLE_AVAILABLE:
|
|
546
|
+
return Flock.from_pickle_file(p)
|
|
547
|
+
else:
|
|
548
|
+
raise RuntimeError(
|
|
549
|
+
"Cannot load Pickle file: cloudpickle not installed."
|
|
550
|
+
)
|
|
551
|
+
else:
|
|
552
|
+
raise ValueError(
|
|
553
|
+
f"Unsupported file extension: {p.suffix}. Use .yaml, .json, .msgpack, or .pkl."
|
|
554
|
+
)
|