flock-core 0.3.23__py3-none-any.whl → 0.3.31__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/__init__.py +23 -11
- flock/cli/constants.py +2 -4
- flock/cli/create_flock.py +220 -1
- flock/cli/execute_flock.py +200 -0
- flock/cli/load_flock.py +27 -7
- flock/cli/loaded_flock_cli.py +202 -0
- flock/cli/manage_agents.py +443 -0
- flock/cli/view_results.py +29 -0
- flock/cli/yaml_editor.py +283 -0
- 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 +509 -388
- flock/core/flock_agent.py +384 -121
- flock/core/flock_registry.py +532 -0
- flock/core/logging/logging.py +97 -23
- flock/core/mixin/dspy_integration.py +363 -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 +199 -0
- flock/evaluators/declarative/declarative_evaluator.py +2 -0
- flock/modules/memory/memory_module.py +17 -4
- flock/modules/output/output_module.py +9 -3
- flock/workflow/activities.py +2 -2
- {flock_core-0.3.23.dist-info → flock_core-0.3.31.dist-info}/METADATA +6 -3
- {flock_core-0.3.23.dist-info → flock_core-0.3.31.dist-info}/RECORD +36 -22
- flock/core/flock_api.py +0 -214
- flock/core/registry/agent_registry.py +0 -120
- {flock_core-0.3.23.dist-info → flock_core-0.3.31.dist-info}/WHEEL +0 -0
- {flock_core-0.3.23.dist-info → flock_core-0.3.31.dist-info}/entry_points.txt +0 -0
- {flock_core-0.3.23.dist-info → flock_core-0.3.31.dist-info}/licenses/LICENSE +0 -0
flock/core/flock.py
CHANGED
|
@@ -1,476 +1,597 @@
|
|
|
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
|
+
name: str = Field(
|
|
77
|
+
default_factory=lambda: f"flock_{uuid.uuid4().hex[:8]}",
|
|
78
|
+
description="A unique identifier for this Flock instance.",
|
|
79
|
+
)
|
|
80
|
+
model: str | None = Field(
|
|
81
|
+
default="openai/gpt-4o",
|
|
82
|
+
description="Default model identifier to be used for agents if not specified otherwise.",
|
|
83
|
+
)
|
|
84
|
+
description: str | None = Field(
|
|
85
|
+
default=None,
|
|
86
|
+
description="A brief description of the purpose of this Flock configuration.",
|
|
87
|
+
)
|
|
88
|
+
enable_temporal: bool = Field(
|
|
89
|
+
default=False,
|
|
90
|
+
description="If True, execute workflows via Temporal; otherwise, run locally.",
|
|
91
|
+
)
|
|
92
|
+
# --- Runtime Attributes (Excluded from Serialization) ---
|
|
93
|
+
# Store agents internally but don't make it part of the Pydantic model definition
|
|
94
|
+
# Use a regular attribute, initialized in __init__
|
|
95
|
+
# Pydantic V2 handles __init__ and attributes not in Field correctly
|
|
96
|
+
_agents: dict[str, FlockAgent]
|
|
97
|
+
_start_agent_name: str | None
|
|
98
|
+
_start_input: dict
|
|
99
|
+
|
|
100
|
+
# Pydantic v2 model config
|
|
101
|
+
model_config = {
|
|
102
|
+
"arbitrary_types_allowed": True,
|
|
103
|
+
"ignored_types": (
|
|
104
|
+
type(FlockRegistry),
|
|
105
|
+
), # Prevent validation issues with registry
|
|
106
|
+
# No need to exclude fields here, handled in to_dict
|
|
107
|
+
}
|
|
108
|
+
|
|
74
109
|
def __init__(
|
|
75
110
|
self,
|
|
76
|
-
model: str = "openai/gpt-4o",
|
|
111
|
+
model: str | None = "openai/gpt-4o",
|
|
112
|
+
description: str | None = None,
|
|
77
113
|
enable_temporal: bool = False,
|
|
78
|
-
enable_logging: bool
|
|
114
|
+
enable_logging: bool
|
|
115
|
+
| list[str] = False, # Keep logging control at init
|
|
116
|
+
agents: list[FlockAgent] | None = None, # Allow passing agents at init
|
|
117
|
+
**kwargs, # Allow extra fields during init if needed, Pydantic handles it
|
|
79
118
|
):
|
|
80
|
-
"""Initialize the Flock orchestrator.
|
|
119
|
+
"""Initialize the Flock orchestrator."""
|
|
120
|
+
# Initialize Pydantic fields
|
|
121
|
+
super().__init__(
|
|
122
|
+
model=model,
|
|
123
|
+
description=description,
|
|
124
|
+
enable_temporal=enable_temporal,
|
|
125
|
+
**kwargs, # Pass extra kwargs to Pydantic BaseModel
|
|
126
|
+
)
|
|
81
127
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
output_formatter (FormatterOptions): Options for formatting output results.
|
|
87
|
-
"""
|
|
88
|
-
with tracer.start_as_current_span("flock_init") as span:
|
|
89
|
-
span.set_attribute("model", model)
|
|
90
|
-
span.set_attribute("enable_temporal", enable_temporal)
|
|
91
|
-
span.set_attribute("enable_logging", enable_logging)
|
|
128
|
+
# Initialize runtime attributes AFTER super().__init__()
|
|
129
|
+
self._agents = {}
|
|
130
|
+
self._start_agent_name = None
|
|
131
|
+
self._start_input = {}
|
|
92
132
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
"Initializing Flock",
|
|
96
|
-
model=model,
|
|
97
|
-
enable_temporal=enable_temporal,
|
|
98
|
-
enable_logging=enable_logging,
|
|
99
|
-
)
|
|
100
|
-
session_id = get_baggage("session_id")
|
|
101
|
-
if not session_id:
|
|
102
|
-
session_id = str(uuid.uuid4())
|
|
103
|
-
set_baggage("session_id", session_id)
|
|
104
|
-
|
|
105
|
-
init_console()
|
|
106
|
-
|
|
107
|
-
self.agents: dict[str, FlockAgent] = {}
|
|
108
|
-
self.registry = Registry()
|
|
109
|
-
self.context = FlockContext()
|
|
110
|
-
self.model = model
|
|
111
|
-
self.enable_temporal = enable_temporal
|
|
112
|
-
self.start_agent: FlockAgent | str | None = None
|
|
113
|
-
self.input: dict = {}
|
|
114
|
-
|
|
115
|
-
if not enable_temporal:
|
|
116
|
-
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")
|
|
133
|
+
# Set up logging
|
|
134
|
+
self._configure_logging(enable_logging)
|
|
121
135
|
|
|
122
|
-
|
|
123
|
-
|
|
136
|
+
# Register passed agents
|
|
137
|
+
if agents:
|
|
138
|
+
# Ensure FlockAgent type is available for isinstance check
|
|
139
|
+
# This import might need to be deferred or handled carefully if it causes issues
|
|
140
|
+
from flock.core.flock_agent import FlockAgent as ConcreteFlockAgent
|
|
124
141
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
142
|
+
for agent in agents:
|
|
143
|
+
if isinstance(agent, ConcreteFlockAgent):
|
|
144
|
+
self.add_agent(agent)
|
|
145
|
+
else:
|
|
146
|
+
logger.warning(
|
|
147
|
+
f"Item provided in 'agents' list is not a FlockAgent: {type(agent)}"
|
|
148
|
+
)
|
|
128
149
|
|
|
129
|
-
|
|
130
|
-
|
|
150
|
+
# Initialize console if needed
|
|
151
|
+
init_console()
|
|
131
152
|
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
logger.debug(
|
|
140
|
-
f"Using default model for agent {agent.name}",
|
|
141
|
-
model=self.model,
|
|
142
|
-
)
|
|
153
|
+
# Set Temporal debug environment variable
|
|
154
|
+
self._set_temporal_debug_flag()
|
|
143
155
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
156
|
+
# Ensure session ID exists in baggage
|
|
157
|
+
self._ensure_session_id()
|
|
158
|
+
|
|
159
|
+
logger.info(
|
|
160
|
+
"Flock instance initialized",
|
|
161
|
+
model=self.model,
|
|
162
|
+
enable_temporal=self.enable_temporal,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# --- Keep _configure_logging, _set_temporal_debug_flag, _ensure_session_id ---
|
|
166
|
+
# ... (implementation as before) ...
|
|
167
|
+
def _configure_logging(self, enable_logging: bool | list[str]):
|
|
168
|
+
"""Configure logging levels based on the enable_logging flag."""
|
|
169
|
+
logger.debug(f"Configuring logging, enable_logging={enable_logging}")
|
|
170
|
+
is_enabled_globally = False
|
|
171
|
+
enabled_loggers = []
|
|
172
|
+
|
|
173
|
+
if isinstance(enable_logging, bool):
|
|
174
|
+
is_enabled_globally = enable_logging
|
|
175
|
+
elif isinstance(enable_logging, list):
|
|
176
|
+
is_enabled_globally = bool(
|
|
177
|
+
enable_logging
|
|
178
|
+
) # Enable if list is not empty
|
|
179
|
+
enabled_loggers = enable_logging
|
|
180
|
+
|
|
181
|
+
# Configure core loggers
|
|
182
|
+
for log_name in LOGGERS:
|
|
183
|
+
log_instance = get_logger(log_name)
|
|
184
|
+
if is_enabled_globally or log_name in enabled_loggers:
|
|
185
|
+
log_instance.enable_logging = True
|
|
186
|
+
else:
|
|
187
|
+
log_instance.enable_logging = False
|
|
188
|
+
|
|
189
|
+
# Configure module loggers (existing ones)
|
|
190
|
+
module_loggers = get_module_loggers()
|
|
191
|
+
for mod_log in module_loggers:
|
|
192
|
+
if is_enabled_globally or mod_log.name in enabled_loggers:
|
|
193
|
+
mod_log.enable_logging = True
|
|
194
|
+
else:
|
|
195
|
+
mod_log.enable_logging = False
|
|
196
|
+
|
|
197
|
+
def _set_temporal_debug_flag(self):
|
|
198
|
+
"""Set or remove LOCAL_DEBUG env var based on enable_temporal."""
|
|
199
|
+
if not self.enable_temporal:
|
|
200
|
+
if "LOCAL_DEBUG" not in os.environ:
|
|
201
|
+
os.environ["LOCAL_DEBUG"] = "1"
|
|
202
|
+
logger.debug(
|
|
203
|
+
"Set LOCAL_DEBUG environment variable for local execution."
|
|
147
204
|
)
|
|
148
|
-
|
|
149
|
-
|
|
205
|
+
elif "LOCAL_DEBUG" in os.environ:
|
|
206
|
+
del os.environ["LOCAL_DEBUG"]
|
|
207
|
+
logger.debug(
|
|
208
|
+
"Removed LOCAL_DEBUG environment variable for Temporal execution."
|
|
209
|
+
)
|
|
150
210
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
211
|
+
def _ensure_session_id(self):
|
|
212
|
+
"""Ensure a session_id exists in the OpenTelemetry baggage."""
|
|
213
|
+
session_id = get_baggage("session_id")
|
|
214
|
+
if not session_id:
|
|
215
|
+
session_id = str(uuid.uuid4())
|
|
216
|
+
set_baggage("session_id", session_id)
|
|
217
|
+
logger.debug(f"Generated new session_id: {session_id}")
|
|
218
|
+
|
|
219
|
+
# --- Keep add_agent, agents property, run, run_async ---
|
|
220
|
+
# ... (implementation as before, ensuring FlockAgent type hint is handled) ...
|
|
221
|
+
def add_agent(self, agent: FlockAgent) -> FlockAgent:
|
|
222
|
+
"""Adds an agent instance to this Flock configuration."""
|
|
223
|
+
# Ensure FlockAgent type is available for isinstance check
|
|
224
|
+
from flock.core.flock_agent import FlockAgent as ConcreteFlockAgent
|
|
225
|
+
|
|
226
|
+
if not isinstance(agent, ConcreteFlockAgent):
|
|
227
|
+
raise TypeError("Provided object is not a FlockAgent instance.")
|
|
228
|
+
if not agent.name:
|
|
229
|
+
raise ValueError("Agent must have a name.")
|
|
230
|
+
|
|
231
|
+
if agent.name in self._agents:
|
|
232
|
+
logger.warning(
|
|
233
|
+
f"Agent '{agent.name}' already exists in this Flock instance. Overwriting."
|
|
155
234
|
)
|
|
235
|
+
self._agents[agent.name] = agent
|
|
236
|
+
FlockRegistry.register_agent(agent) # Also register globally
|
|
156
237
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
238
|
+
# Set default model if agent doesn't have one
|
|
239
|
+
if agent.model is None:
|
|
240
|
+
# agent.set_model(self.model) # Use Flock's default model
|
|
241
|
+
if self.model: # Ensure Flock has a model defined
|
|
242
|
+
agent.set_model(self.model)
|
|
243
|
+
logger.debug(
|
|
244
|
+
f"Agent '{agent.name}' using Flock default model: {self.model}"
|
|
245
|
+
)
|
|
246
|
+
else:
|
|
247
|
+
logger.warning(
|
|
248
|
+
f"Agent '{agent.name}' has no model and Flock default model is not set."
|
|
249
|
+
)
|
|
166
250
|
|
|
167
|
-
|
|
168
|
-
|
|
251
|
+
logger.info(f"Agent '{agent.name}' added to Flock.")
|
|
252
|
+
return agent
|
|
169
253
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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")
|
|
254
|
+
@property
|
|
255
|
+
def agents(self) -> dict[str, FlockAgent]:
|
|
256
|
+
"""Returns the dictionary of agents managed by this Flock instance."""
|
|
257
|
+
return self._agents
|
|
180
258
|
|
|
181
259
|
def run(
|
|
182
260
|
self,
|
|
183
261
|
start_agent: FlockAgent | str | None = None,
|
|
184
262
|
input: dict = {},
|
|
185
|
-
context: FlockContext
|
|
263
|
+
context: FlockContext
|
|
264
|
+
| None = None, # Allow passing initial context state
|
|
186
265
|
run_id: str = "",
|
|
187
|
-
box_result: bool =
|
|
188
|
-
agents: list[FlockAgent] =
|
|
266
|
+
box_result: bool = False, # Changed default to False for raw dict
|
|
267
|
+
agents: list[FlockAgent] | None = None, # Allow adding agents via run
|
|
189
268
|
) -> dict:
|
|
190
269
|
"""Entry point for running an agent system synchronously."""
|
|
191
270
|
return asyncio.run(
|
|
192
271
|
self.run_async(
|
|
193
|
-
start_agent,
|
|
272
|
+
start_agent=start_agent,
|
|
273
|
+
input=input,
|
|
274
|
+
context=context,
|
|
275
|
+
run_id=run_id,
|
|
276
|
+
box_result=box_result,
|
|
277
|
+
agents=agents,
|
|
194
278
|
)
|
|
195
279
|
)
|
|
196
280
|
|
|
197
|
-
def
|
|
281
|
+
async def run_async(
|
|
198
282
|
self,
|
|
199
|
-
|
|
200
|
-
start_agent: str | None = None,
|
|
283
|
+
start_agent: FlockAgent | str | None = None,
|
|
201
284
|
input: dict | None = None,
|
|
202
|
-
|
|
203
|
-
|
|
285
|
+
context: FlockContext | None = None,
|
|
286
|
+
run_id: str = "",
|
|
287
|
+
box_result: bool = False, # Changed default
|
|
288
|
+
agents: list[FlockAgent] | None = None, # Allow adding agents via run
|
|
289
|
+
) -> dict:
|
|
290
|
+
"""Entry point for running an agent system asynchronously."""
|
|
291
|
+
# This import needs to be here or handled carefully due to potential cycles
|
|
292
|
+
from flock.core.flock_agent import FlockAgent as ConcreteFlockAgent
|
|
293
|
+
|
|
294
|
+
with tracer.start_as_current_span("flock.run_async") as span:
|
|
295
|
+
# Add passed agents first
|
|
296
|
+
if agents:
|
|
297
|
+
for agent_obj in agents:
|
|
298
|
+
if isinstance(agent_obj, ConcreteFlockAgent):
|
|
299
|
+
self.add_agent(
|
|
300
|
+
agent_obj
|
|
301
|
+
) # Adds to self._agents and registry
|
|
302
|
+
else:
|
|
303
|
+
logger.warning(
|
|
304
|
+
f"Item in 'agents' list is not a FlockAgent: {type(agent_obj)}"
|
|
305
|
+
)
|
|
204
306
|
|
|
205
|
-
|
|
206
|
-
|
|
307
|
+
# Determine starting agent name
|
|
308
|
+
start_agent_name: str | None = None
|
|
309
|
+
if isinstance(start_agent, ConcreteFlockAgent):
|
|
310
|
+
start_agent_name = start_agent.name
|
|
311
|
+
if start_agent_name not in self._agents:
|
|
312
|
+
self.add_agent(
|
|
313
|
+
start_agent
|
|
314
|
+
) # Add if instance was passed but not added
|
|
315
|
+
elif isinstance(start_agent, str):
|
|
316
|
+
start_agent_name = start_agent
|
|
317
|
+
else:
|
|
318
|
+
start_agent_name = (
|
|
319
|
+
self._start_agent_name
|
|
320
|
+
) # Use pre-configured if any
|
|
321
|
+
|
|
322
|
+
# Default to first agent if only one exists and none specified
|
|
323
|
+
if not start_agent_name and len(self._agents) == 1:
|
|
324
|
+
start_agent_name = list(self._agents.keys())[0]
|
|
325
|
+
elif not start_agent_name:
|
|
326
|
+
raise ValueError(
|
|
327
|
+
"No start_agent specified and multiple agents exist or none are added."
|
|
328
|
+
)
|
|
207
329
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
"""
|
|
211
|
-
hex_str = cloudpickle.dumps(self).hex()
|
|
330
|
+
# Get starting input
|
|
331
|
+
run_input = input if input is not None else self._start_input
|
|
212
332
|
|
|
213
|
-
|
|
214
|
-
"start_agent"
|
|
215
|
-
"input"
|
|
216
|
-
"
|
|
217
|
-
|
|
333
|
+
# Log and trace start info
|
|
334
|
+
span.set_attribute("start_agent", start_agent_name)
|
|
335
|
+
span.set_attribute("input", str(run_input))
|
|
336
|
+
span.set_attribute("run_id", run_id)
|
|
337
|
+
span.set_attribute("enable_temporal", self.enable_temporal)
|
|
338
|
+
logger.info(
|
|
339
|
+
f"Initiating Flock run. Start Agent: '{start_agent_name}'. Temporal: {self.enable_temporal}."
|
|
340
|
+
)
|
|
218
341
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
342
|
+
try:
|
|
343
|
+
# Resolve start agent instance from internal dict
|
|
344
|
+
resolved_start_agent = self._agents.get(start_agent_name)
|
|
345
|
+
if not resolved_start_agent:
|
|
346
|
+
# Maybe it's only in the global registry? (Less common)
|
|
347
|
+
resolved_start_agent = FlockRegistry.get_agent(
|
|
348
|
+
start_agent_name
|
|
349
|
+
)
|
|
350
|
+
if not resolved_start_agent:
|
|
351
|
+
raise ValueError(
|
|
352
|
+
f"Start agent '{start_agent_name}' not found in Flock instance or registry."
|
|
353
|
+
)
|
|
354
|
+
else:
|
|
355
|
+
# If found globally, add it to this instance for consistency during run
|
|
356
|
+
self.add_agent(resolved_start_agent)
|
|
222
357
|
|
|
223
|
-
|
|
224
|
-
|
|
358
|
+
# Create or use provided context
|
|
359
|
+
run_context = context if context else FlockContext()
|
|
360
|
+
if not run_id:
|
|
361
|
+
run_id = f"flockrun_{uuid.uuid4().hex[:8]}"
|
|
362
|
+
set_baggage("run_id", run_id) # Ensure run_id is in baggage
|
|
225
363
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
364
|
+
# Initialize context
|
|
365
|
+
initialize_context(
|
|
366
|
+
run_context,
|
|
367
|
+
start_agent_name,
|
|
368
|
+
run_input,
|
|
369
|
+
run_id,
|
|
370
|
+
not self.enable_temporal,
|
|
371
|
+
self.model
|
|
372
|
+
or resolved_start_agent.model
|
|
373
|
+
or "default-model-missing", # Pass effective model
|
|
374
|
+
)
|
|
229
375
|
|
|
230
|
-
|
|
231
|
-
|
|
376
|
+
# Execute workflow
|
|
377
|
+
logger.info(
|
|
378
|
+
"Starting agent execution",
|
|
379
|
+
agent=start_agent_name,
|
|
380
|
+
enable_temporal=self.enable_temporal,
|
|
381
|
+
)
|
|
232
382
|
|
|
233
|
-
|
|
234
|
-
|
|
383
|
+
if not self.enable_temporal:
|
|
384
|
+
result = await run_local_workflow(
|
|
385
|
+
run_context, box_result=False
|
|
386
|
+
) # Get raw dict
|
|
387
|
+
else:
|
|
388
|
+
result = await run_temporal_workflow(
|
|
389
|
+
run_context, box_result=False
|
|
390
|
+
) # Get raw dict
|
|
391
|
+
|
|
392
|
+
span.set_attribute("result.type", str(type(result)))
|
|
393
|
+
# Avoid overly large results in trace attributes
|
|
394
|
+
result_str = str(result)
|
|
395
|
+
if len(result_str) > 1000:
|
|
396
|
+
result_str = result_str[:1000] + "... (truncated)"
|
|
397
|
+
span.set_attribute("result.preview", result_str)
|
|
398
|
+
|
|
399
|
+
# Optionally box result before returning
|
|
400
|
+
if box_result:
|
|
401
|
+
try:
|
|
402
|
+
from box import Box
|
|
403
|
+
|
|
404
|
+
logger.debug("Boxing final result.")
|
|
405
|
+
return Box(result)
|
|
406
|
+
except ImportError:
|
|
407
|
+
logger.warning(
|
|
408
|
+
"Box library not installed, returning raw dict. Install with 'pip install python-box'"
|
|
409
|
+
)
|
|
410
|
+
return result
|
|
411
|
+
else:
|
|
412
|
+
return result
|
|
235
413
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
return flock
|
|
414
|
+
except Exception as e:
|
|
415
|
+
logger.error(f"Flock run failed: {e}", exc_info=True)
|
|
416
|
+
span.record_exception(e)
|
|
417
|
+
span.set_status(trace.Status(trace.StatusCode.ERROR, str(e)))
|
|
418
|
+
# Depending on desired behavior, either raise or return an error dict
|
|
419
|
+
# raise # Option 1: Let the exception propagate
|
|
420
|
+
return {
|
|
421
|
+
"error": str(e),
|
|
422
|
+
"details": "Flock run failed.",
|
|
423
|
+
} # Option 2: Return error dict
|
|
424
|
+
|
|
425
|
+
# --- ADDED Serialization Methods ---
|
|
249
426
|
|
|
250
427
|
def to_dict(self) -> dict[str, Any]:
|
|
251
|
-
"""
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
"""
|
|
428
|
+
"""Convert Flock instance to dictionary representation."""
|
|
429
|
+
logger.debug("Serializing Flock instance to dict.")
|
|
430
|
+
# Use Pydantic's dump for base fields
|
|
431
|
+
data = self.model_dump(mode="json", exclude_none=True)
|
|
432
|
+
|
|
433
|
+
# Manually add serialized agents
|
|
434
|
+
data["agents"] = {}
|
|
435
|
+
for name, agent_instance in self._agents.items():
|
|
436
|
+
try:
|
|
437
|
+
# Agents handle their own serialization via their to_dict
|
|
438
|
+
data["agents"][name] = agent_instance.to_dict()
|
|
439
|
+
except Exception as e:
|
|
440
|
+
logger.error(
|
|
441
|
+
f"Failed to serialize agent '{name}' within Flock: {e}"
|
|
442
|
+
)
|
|
443
|
+
# Optionally skip problematic agents or raise error
|
|
444
|
+
# data["agents"][name] = {"error": f"Serialization failed: {e}"}
|
|
285
445
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
|
446
|
+
# Exclude runtime fields that shouldn't be serialized
|
|
447
|
+
# These are not Pydantic fields, so they aren't dumped by model_dump
|
|
448
|
+
# No need to explicitly remove _start_agent_name, _start_input unless added manually
|
|
294
449
|
|
|
295
|
-
|
|
296
|
-
return
|
|
450
|
+
# Filter final dict (optional, Pydantic's exclude_none helps)
|
|
451
|
+
# return self._filter_none_values(data)
|
|
452
|
+
return data
|
|
297
453
|
|
|
298
|
-
|
|
299
|
-
|
|
454
|
+
@classmethod
|
|
455
|
+
def from_dict(cls: type[T], data: dict[str, Any]) -> T:
|
|
456
|
+
"""Create Flock instance from dictionary representation."""
|
|
457
|
+
logger.debug(
|
|
458
|
+
f"Deserializing Flock from dict. Provided keys: {list(data.keys())}"
|
|
459
|
+
)
|
|
300
460
|
|
|
301
|
-
|
|
302
|
-
|
|
461
|
+
# Ensure FlockAgent is importable for type checking later
|
|
462
|
+
try:
|
|
463
|
+
from flock.core.flock_agent import FlockAgent as ConcreteFlockAgent
|
|
464
|
+
except ImportError:
|
|
465
|
+
logger.error(
|
|
466
|
+
"Cannot import FlockAgent, deserialization may fail for agents."
|
|
467
|
+
)
|
|
468
|
+
ConcreteFlockAgent = Any # Fallback
|
|
469
|
+
|
|
470
|
+
# Extract agent data before initializing Flock base model
|
|
471
|
+
agents_data = data.pop("agents", {})
|
|
472
|
+
|
|
473
|
+
# Create Flock instance using Pydantic constructor for basic fields
|
|
474
|
+
try:
|
|
475
|
+
# Pass only fields defined in Flock's Pydantic model
|
|
476
|
+
init_data = {k: v for k, v in data.items() if k in cls.model_fields}
|
|
477
|
+
flock_instance = cls(**init_data)
|
|
478
|
+
except Exception as e:
|
|
479
|
+
logger.error(
|
|
480
|
+
f"Pydantic validation/init failed for Flock: {e}", exc_info=True
|
|
481
|
+
)
|
|
482
|
+
raise ValueError(
|
|
483
|
+
f"Failed to initialize Flock from dict: {e}"
|
|
484
|
+
) from e
|
|
303
485
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
486
|
+
# Deserialize and add agents AFTER Flock instance exists
|
|
487
|
+
for name, agent_data in agents_data.items():
|
|
488
|
+
try:
|
|
489
|
+
# Ensure agent_data has the name, or add it from the key
|
|
490
|
+
agent_data.setdefault("name", name)
|
|
491
|
+
# Use FlockAgent's from_dict method
|
|
492
|
+
agent_instance = ConcreteFlockAgent.from_dict(agent_data)
|
|
493
|
+
flock_instance.add_agent(
|
|
494
|
+
agent_instance
|
|
495
|
+
) # Adds to _agents and registers
|
|
496
|
+
except Exception as e:
|
|
497
|
+
logger.error(
|
|
498
|
+
f"Failed to deserialize or add agent '{name}' during Flock deserialization: {e}",
|
|
499
|
+
exc_info=True,
|
|
500
|
+
)
|
|
501
|
+
# Decide: skip agent or raise error?
|
|
309
502
|
|
|
310
|
-
|
|
311
|
-
|
|
503
|
+
logger.info("Successfully deserialized Flock instance.")
|
|
504
|
+
return flock_instance
|
|
312
505
|
|
|
313
|
-
|
|
314
|
-
def
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
"""
|
|
506
|
+
# --- API Start Method ---
|
|
507
|
+
def start_api(
|
|
508
|
+
self,
|
|
509
|
+
host: str = "127.0.0.1",
|
|
510
|
+
port: int = 8344,
|
|
511
|
+
server_name: str = "Flock API",
|
|
512
|
+
create_ui: bool = False,
|
|
513
|
+
) -> None:
|
|
514
|
+
"""Start a REST API server for this Flock instance."""
|
|
515
|
+
# Import locally to avoid making API components a hard dependency
|
|
516
|
+
try:
|
|
517
|
+
from flock.core.api import FlockAPI
|
|
518
|
+
except ImportError:
|
|
519
|
+
logger.error(
|
|
520
|
+
"API components not found. Cannot start API. "
|
|
521
|
+
"Ensure 'fastapi' and 'uvicorn' are installed."
|
|
522
|
+
)
|
|
523
|
+
return
|
|
347
524
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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)
|
|
525
|
+
logger.info(
|
|
526
|
+
f"Preparing to start API server on {host}:{port} {'with UI' if create_ui else 'without UI'}"
|
|
527
|
+
)
|
|
528
|
+
api_instance = FlockAPI(self) # Pass the current Flock instance
|
|
529
|
+
# Use the start method of FlockAPI
|
|
530
|
+
api_instance.start(
|
|
531
|
+
host=host, port=port, server_name=server_name, create_ui=create_ui
|
|
532
|
+
)
|
|
362
533
|
|
|
363
|
-
|
|
534
|
+
# --- CLI Start Method ---
|
|
535
|
+
def start_cli(
|
|
364
536
|
self,
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
agents: list[FlockAgent] = [],
|
|
371
|
-
) -> dict:
|
|
372
|
-
"""Entry point for running an agent system asynchronously.
|
|
537
|
+
server_name: str = "Flock CLI",
|
|
538
|
+
show_results: bool = False,
|
|
539
|
+
edit_mode: bool = False,
|
|
540
|
+
) -> None:
|
|
541
|
+
"""Start a CLI interface for this Flock instance.
|
|
373
542
|
|
|
374
|
-
This method
|
|
375
|
-
|
|
376
|
-
2. Optionally uses a provided global context.
|
|
377
|
-
3. Generates a unique run ID if one is not provided.
|
|
378
|
-
4. Initializes the context with standard variables (like agent name, input data, run ID, and debug flag).
|
|
379
|
-
5. Executes the agent workflow either locally (for debugging) or via Temporal (for production).
|
|
543
|
+
This method loads the CLI with the current Flock instance already available,
|
|
544
|
+
allowing users to execute, edit, or manage agents from the existing configuration.
|
|
380
545
|
|
|
381
546
|
Args:
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
run_id (str, optional): A unique identifier for this run. If empty, one is generated automatically.
|
|
386
|
-
box_result (bool, optional): If True, wraps the output in a Box for nicer formatting. Defaults to True.
|
|
387
|
-
agents (list, optional): additional way to add agents to flock instead of add_agent
|
|
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.
|
|
547
|
+
server_name: Optional name for the CLI interface
|
|
548
|
+
show_results: Whether to initially show results of previous runs
|
|
549
|
+
edit_mode: Whether to open directly in edit mode
|
|
395
550
|
"""
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
else start_agent,
|
|
551
|
+
# Import locally to avoid circular imports
|
|
552
|
+
try:
|
|
553
|
+
from flock.cli.loaded_flock_cli import start_loaded_flock_cli
|
|
554
|
+
except ImportError:
|
|
555
|
+
logger.error(
|
|
556
|
+
"CLI components not found. Cannot start CLI. "
|
|
557
|
+
"Ensure the CLI modules are properly installed."
|
|
404
558
|
)
|
|
405
|
-
|
|
406
|
-
self.add_agent(agent)
|
|
407
|
-
|
|
408
|
-
if start_agent:
|
|
409
|
-
self.start_agent = start_agent
|
|
410
|
-
if input:
|
|
411
|
-
self.input = input
|
|
412
|
-
|
|
413
|
-
span.set_attribute("input", str(self.input))
|
|
414
|
-
span.set_attribute("context", str(context))
|
|
415
|
-
span.set_attribute("run_id", run_id)
|
|
416
|
-
span.set_attribute("box_result", box_result)
|
|
417
|
-
|
|
418
|
-
try:
|
|
419
|
-
if isinstance(self.start_agent, str):
|
|
420
|
-
logger.debug(
|
|
421
|
-
f"Looking up agent '{self.start_agent.name}' in registry",
|
|
422
|
-
agent_name=self.start_agent,
|
|
423
|
-
)
|
|
424
|
-
self.start_agent = self.registry.get_agent(self.start_agent)
|
|
425
|
-
if not self.start_agent:
|
|
426
|
-
logger.error(
|
|
427
|
-
"Agent not found", agent_name=self.start_agent
|
|
428
|
-
)
|
|
429
|
-
raise ValueError(
|
|
430
|
-
f"Agent '{self.start_agent}' not found in registry"
|
|
431
|
-
)
|
|
432
|
-
self.start_agent.resolve_callables(context=self.context)
|
|
433
|
-
if context:
|
|
434
|
-
logger.debug("Using provided context")
|
|
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
|
|
559
|
+
return
|
|
449
560
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
561
|
+
logger.info(
|
|
562
|
+
f"Starting CLI interface with loaded Flock instance ({len(self._agents)} agents)"
|
|
563
|
+
)
|
|
453
564
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
self.model,
|
|
462
|
-
)
|
|
565
|
+
# Pass the current Flock instance to the CLI
|
|
566
|
+
start_loaded_flock_cli(
|
|
567
|
+
flock=self,
|
|
568
|
+
server_name=server_name,
|
|
569
|
+
show_results=show_results,
|
|
570
|
+
edit_mode=edit_mode,
|
|
571
|
+
)
|
|
463
572
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
573
|
+
# --- Static Method Loaders (Keep for convenience) ---
|
|
574
|
+
@staticmethod
|
|
575
|
+
def load_from_file(file_path: str) -> Flock:
|
|
576
|
+
"""Load a Flock instance from various file formats (detects type)."""
|
|
577
|
+
p = Path(file_path)
|
|
578
|
+
if not p.exists():
|
|
579
|
+
raise FileNotFoundError(f"Flock file not found: {file_path}")
|
|
580
|
+
|
|
581
|
+
if p.suffix in [".yaml", ".yml"]:
|
|
582
|
+
return Flock.from_yaml_file(p)
|
|
583
|
+
elif p.suffix == ".json":
|
|
584
|
+
return Flock.from_json(p.read_text())
|
|
585
|
+
elif p.suffix == ".msgpack":
|
|
586
|
+
return Flock.from_msgpack_file(p)
|
|
587
|
+
elif p.suffix == ".pkl":
|
|
588
|
+
if PICKLE_AVAILABLE:
|
|
589
|
+
return Flock.from_pickle_file(p)
|
|
590
|
+
else:
|
|
591
|
+
raise RuntimeError(
|
|
592
|
+
"Cannot load Pickle file: cloudpickle not installed."
|
|
468
593
|
)
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
return await run_temporal_workflow(self.context, box_result)
|
|
474
|
-
except Exception as e:
|
|
475
|
-
logger.exception("Execution failed", error=str(e))
|
|
476
|
-
raise
|
|
594
|
+
else:
|
|
595
|
+
raise ValueError(
|
|
596
|
+
f"Unsupported file extension: {p.suffix}. Use .yaml, .json, .msgpack, or .pkl."
|
|
597
|
+
)
|