flock-core 0.4.520__py3-none-any.whl → 0.5.0b2__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/cli/manage_agents.py +3 -3
- flock/components/__init__.py +28 -0
- flock/components/evaluation/__init__.py +9 -0
- flock/components/evaluation/declarative_evaluation_component.py +198 -0
- flock/components/routing/__init__.py +15 -0
- flock/{routers/conditional/conditional_router.py → components/routing/conditional_routing_component.py} +60 -49
- flock/components/routing/default_routing_component.py +103 -0
- flock/components/routing/llm_routing_component.py +208 -0
- flock/components/utility/__init__.py +15 -0
- flock/{modules/enterprise_memory/enterprise_memory_module.py → components/utility/memory_utility_component.py} +195 -173
- flock/{modules/performance/metrics_module.py → components/utility/metrics_utility_component.py} +101 -86
- flock/{modules/output/output_module.py → components/utility/output_utility_component.py} +49 -49
- flock/core/__init__.py +2 -8
- flock/core/agent/__init__.py +16 -0
- flock/core/agent/flock_agent_components.py +104 -0
- flock/core/agent/flock_agent_execution.py +101 -0
- flock/core/agent/flock_agent_integration.py +147 -0
- flock/core/agent/flock_agent_lifecycle.py +177 -0
- flock/core/agent/flock_agent_serialization.py +378 -0
- flock/core/component/__init__.py +15 -0
- flock/core/{flock_module.py → component/agent_component_base.py} +136 -35
- flock/core/component/evaluation_component_base.py +56 -0
- flock/core/component/routing_component_base.py +75 -0
- flock/core/component/utility_component_base.py +69 -0
- flock/core/config/flock_agent_config.py +49 -2
- flock/core/evaluation/utils.py +1 -1
- flock/core/execution/evaluation_executor.py +1 -1
- flock/core/flock.py +137 -483
- flock/core/flock_agent.py +151 -1018
- flock/core/flock_factory.py +94 -73
- flock/core/{flock_registry.py → flock_registry.py.backup} +3 -17
- flock/core/logging/logging.py +1 -0
- flock/core/mcp/flock_mcp_server.py +42 -37
- flock/core/mixin/dspy_integration.py +5 -5
- flock/core/orchestration/__init__.py +18 -0
- flock/core/orchestration/flock_batch_processor.py +94 -0
- flock/core/orchestration/flock_evaluator.py +113 -0
- flock/core/orchestration/flock_execution.py +288 -0
- flock/core/orchestration/flock_initialization.py +125 -0
- flock/core/orchestration/flock_server_manager.py +65 -0
- flock/core/orchestration/flock_web_server.py +117 -0
- flock/core/registry/__init__.py +39 -0
- flock/core/registry/agent_registry.py +69 -0
- flock/core/registry/callable_registry.py +139 -0
- flock/core/registry/component_discovery.py +142 -0
- flock/core/registry/component_registry.py +64 -0
- flock/core/registry/config_mapping.py +64 -0
- flock/core/registry/decorators.py +137 -0
- flock/core/registry/registry_hub.py +202 -0
- flock/core/registry/server_registry.py +57 -0
- flock/core/registry/type_registry.py +86 -0
- flock/core/serialization/flock_serializer.py +33 -30
- flock/core/serialization/serialization_utils.py +28 -25
- flock/core/util/input_resolver.py +29 -2
- flock/platform/docker_tools.py +3 -3
- flock/tools/markdown_tools.py +1 -2
- flock/tools/text_tools.py +1 -2
- flock/webapp/app/main.py +9 -5
- flock/workflow/activities.py +59 -84
- flock/workflow/activities_unified.py +230 -0
- flock/workflow/agent_execution_activity.py +6 -6
- flock/workflow/flock_workflow.py +1 -1
- {flock_core-0.4.520.dist-info → flock_core-0.5.0b2.dist-info}/METADATA +2 -2
- {flock_core-0.4.520.dist-info → flock_core-0.5.0b2.dist-info}/RECORD +67 -68
- flock/core/flock_evaluator.py +0 -60
- flock/core/flock_router.py +0 -83
- flock/evaluators/__init__.py +0 -1
- flock/evaluators/declarative/__init__.py +0 -1
- flock/evaluators/declarative/declarative_evaluator.py +0 -194
- flock/evaluators/memory/memory_evaluator.py +0 -90
- flock/evaluators/test/test_case_evaluator.py +0 -38
- flock/evaluators/zep/zep_evaluator.py +0 -59
- flock/modules/__init__.py +0 -1
- flock/modules/assertion/__init__.py +0 -1
- flock/modules/assertion/assertion_module.py +0 -286
- flock/modules/callback/__init__.py +0 -1
- flock/modules/callback/callback_module.py +0 -91
- flock/modules/enterprise_memory/README.md +0 -99
- flock/modules/mem0/__init__.py +0 -1
- flock/modules/mem0/mem0_module.py +0 -126
- flock/modules/mem0_async/__init__.py +0 -1
- flock/modules/mem0_async/async_mem0_module.py +0 -126
- flock/modules/memory/__init__.py +0 -1
- flock/modules/memory/memory_module.py +0 -429
- flock/modules/memory/memory_parser.py +0 -125
- flock/modules/memory/memory_storage.py +0 -736
- flock/modules/output/__init__.py +0 -1
- flock/modules/performance/__init__.py +0 -1
- flock/modules/zep/__init__.py +0 -1
- flock/modules/zep/zep_module.py +0 -192
- flock/routers/__init__.py +0 -1
- flock/routers/agent/__init__.py +0 -1
- flock/routers/agent/agent_router.py +0 -236
- flock/routers/agent/handoff_agent.py +0 -58
- flock/routers/default/__init__.py +0 -1
- flock/routers/default/default_router.py +0 -80
- flock/routers/feedback/feedback_router.py +0 -114
- flock/routers/list_generator/list_generator_router.py +0 -166
- flock/routers/llm/__init__.py +0 -1
- flock/routers/llm/llm_router.py +0 -365
- {flock_core-0.4.520.dist-info → flock_core-0.5.0b2.dist-info}/WHEEL +0 -0
- {flock_core-0.4.520.dist-info → flock_core-0.5.0b2.dist-info}/entry_points.txt +0 -0
- {flock_core-0.4.520.dist-info → flock_core-0.5.0b2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# src/flock/core/agent/flock_agent_serialization.py
|
|
2
|
+
"""Serialization functionality for FlockAgent."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
8
|
+
|
|
9
|
+
# Legacy component imports removed
|
|
10
|
+
from flock.core.logging.logging import get_logger
|
|
11
|
+
from flock.core.mcp.flock_mcp_server import FlockMCPServerBase
|
|
12
|
+
from flock.core.serialization.json_encoder import FlockJSONEncoder
|
|
13
|
+
from flock.core.serialization.serialization_utils import (
|
|
14
|
+
deserialize_component,
|
|
15
|
+
serialize_item,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from flock.core.flock_agent import FlockAgent
|
|
20
|
+
|
|
21
|
+
logger = get_logger("agent.serialization")
|
|
22
|
+
T = TypeVar("T", bound="FlockAgent")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FlockAgentSerialization:
|
|
26
|
+
"""Handles serialization and deserialization for FlockAgent."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, agent: "FlockAgent"):
|
|
29
|
+
self.agent = agent
|
|
30
|
+
|
|
31
|
+
def _save_output(self, agent_name: str, result: dict[str, Any]) -> None:
|
|
32
|
+
"""Save output to file if configured."""
|
|
33
|
+
if not self.agent.config.write_to_file:
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
37
|
+
filename = f"{agent_name}_output_{timestamp}.json"
|
|
38
|
+
filepath = os.path.join(".flock/output/", filename)
|
|
39
|
+
os.makedirs(".flock/output/", exist_ok=True)
|
|
40
|
+
|
|
41
|
+
output_data = {
|
|
42
|
+
"agent": agent_name,
|
|
43
|
+
"timestamp": timestamp,
|
|
44
|
+
"output": result,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
with open(filepath, "w") as f:
|
|
49
|
+
json.dump(output_data, f, indent=2, cls=FlockJSONEncoder)
|
|
50
|
+
except Exception as e:
|
|
51
|
+
logger.warning(f"Failed to save output to file: {e}")
|
|
52
|
+
|
|
53
|
+
def to_dict(self) -> dict[str, Any]:
|
|
54
|
+
"""Convert instance to dictionary representation suitable for serialization."""
|
|
55
|
+
from flock.core.registry import get_registry
|
|
56
|
+
|
|
57
|
+
registry = get_registry()
|
|
58
|
+
|
|
59
|
+
exclude = [
|
|
60
|
+
"context",
|
|
61
|
+
"components",
|
|
62
|
+
"tools",
|
|
63
|
+
"servers",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
is_description_callable = False
|
|
67
|
+
is_input_callable = False
|
|
68
|
+
is_output_callable = False
|
|
69
|
+
is_next_agent_callable = False
|
|
70
|
+
# if self.agent.description is a callable, exclude it
|
|
71
|
+
if callable(self.agent.description):
|
|
72
|
+
is_description_callable = True
|
|
73
|
+
exclude.append("description")
|
|
74
|
+
# if self.agent.input is a callable, exclude it
|
|
75
|
+
if callable(self.agent.input):
|
|
76
|
+
is_input_callable = True
|
|
77
|
+
exclude.append("input")
|
|
78
|
+
# if self.agent.output is a callable, exclude it
|
|
79
|
+
if callable(self.agent.output):
|
|
80
|
+
is_output_callable = True
|
|
81
|
+
exclude.append("output")
|
|
82
|
+
if callable(self.agent.next_agent):
|
|
83
|
+
is_next_agent_callable = True
|
|
84
|
+
exclude.append("next_agent")
|
|
85
|
+
|
|
86
|
+
logger.debug(f"Serializing agent '{self.agent.name}' to dict.")
|
|
87
|
+
# Use Pydantic's dump, exclude manually handled fields and runtime context
|
|
88
|
+
data = self.agent.model_dump(
|
|
89
|
+
exclude=exclude,
|
|
90
|
+
mode="json", # Use json mode for better handling of standard types by Pydantic
|
|
91
|
+
exclude_none=True, # Exclude None values for cleaner output
|
|
92
|
+
)
|
|
93
|
+
logger.debug(f"Base agent data for '{self.agent.name}': {list(data.keys())}")
|
|
94
|
+
|
|
95
|
+
# Serialize components list using unified architecture
|
|
96
|
+
if self.agent.components:
|
|
97
|
+
serialized_components = []
|
|
98
|
+
for component in self.agent.components:
|
|
99
|
+
try:
|
|
100
|
+
comp_type = type(component)
|
|
101
|
+
type_name = registry.get_component_type_name(comp_type)
|
|
102
|
+
if type_name:
|
|
103
|
+
component_data = serialize_item(component)
|
|
104
|
+
if isinstance(component_data, dict):
|
|
105
|
+
component_data["type"] = type_name
|
|
106
|
+
serialized_components.append(component_data)
|
|
107
|
+
else:
|
|
108
|
+
logger.warning(f"Component {component.name} serialization failed")
|
|
109
|
+
else:
|
|
110
|
+
logger.warning(f"Component {component.name} type not registered")
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.error(f"Failed to serialize component {component.name}: {e}")
|
|
113
|
+
|
|
114
|
+
if serialized_components:
|
|
115
|
+
data["components"] = serialized_components
|
|
116
|
+
logger.debug(f"Added {len(serialized_components)} components to agent '{self.agent.name}'")
|
|
117
|
+
|
|
118
|
+
# --- Serialize Servers ---
|
|
119
|
+
if self.agent.servers:
|
|
120
|
+
logger.debug(
|
|
121
|
+
f"Serializing {len(self.agent.servers)} servers for agent '{self.agent.name}'"
|
|
122
|
+
)
|
|
123
|
+
serialized_servers = []
|
|
124
|
+
for server in self.agent.servers:
|
|
125
|
+
if isinstance(server, FlockMCPServerBase):
|
|
126
|
+
serialized_servers.append(server.config.name)
|
|
127
|
+
else:
|
|
128
|
+
# Write it down as a list of server names.
|
|
129
|
+
serialized_servers.append(server)
|
|
130
|
+
|
|
131
|
+
if serialized_servers:
|
|
132
|
+
data["mcp_servers"] = serialized_servers
|
|
133
|
+
logger.debug(
|
|
134
|
+
f"Added {len(serialized_servers)} servers to agent '{self.agent.name}'"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# --- Serialize Tools (Callables) ---
|
|
138
|
+
if self.agent.tools:
|
|
139
|
+
logger.debug(
|
|
140
|
+
f"Serializing {len(self.agent.tools)} tools for agent '{self.agent.name}'"
|
|
141
|
+
)
|
|
142
|
+
serialized_tools = []
|
|
143
|
+
for tool in self.agent.tools:
|
|
144
|
+
if callable(tool) and not isinstance(tool, type):
|
|
145
|
+
path_str = registry.get_callable_path_string(tool)
|
|
146
|
+
if path_str:
|
|
147
|
+
# Get just the function name from the path string
|
|
148
|
+
# If it's a namespaced path like module.submodule.function_name
|
|
149
|
+
# Just use the function_name part
|
|
150
|
+
func_name = path_str.split(".")[-1]
|
|
151
|
+
serialized_tools.append(func_name)
|
|
152
|
+
logger.debug(
|
|
153
|
+
f"Added tool '{func_name}' (from path '{path_str}') to agent '{self.agent.name}'"
|
|
154
|
+
)
|
|
155
|
+
else:
|
|
156
|
+
logger.warning(
|
|
157
|
+
f"Could not get path string for tool {tool} in agent '{self.agent.name}'. Skipping."
|
|
158
|
+
)
|
|
159
|
+
else:
|
|
160
|
+
logger.warning(
|
|
161
|
+
f"Non-callable item found in tools list for agent '{self.agent.name}': {tool}. Skipping."
|
|
162
|
+
)
|
|
163
|
+
if serialized_tools:
|
|
164
|
+
data["tools"] = serialized_tools
|
|
165
|
+
logger.debug(
|
|
166
|
+
f"Added {len(serialized_tools)} tools to agent '{self.agent.name}'"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
if is_description_callable:
|
|
170
|
+
path_str = registry.get_callable_path_string(self.agent.description)
|
|
171
|
+
if path_str:
|
|
172
|
+
func_name = path_str.split(".")[-1]
|
|
173
|
+
data["description_callable"] = func_name
|
|
174
|
+
logger.debug(
|
|
175
|
+
f"Added description '{func_name}' (from path '{path_str}') to agent '{self.agent.name}'"
|
|
176
|
+
)
|
|
177
|
+
else:
|
|
178
|
+
logger.warning(
|
|
179
|
+
f"Could not get path string for description {self.agent.description} in agent '{self.agent.name}'. Skipping."
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if is_input_callable:
|
|
183
|
+
path_str = registry.get_callable_path_string(self.agent.input)
|
|
184
|
+
if path_str:
|
|
185
|
+
func_name = path_str.split(".")[-1]
|
|
186
|
+
data["input_callable"] = func_name
|
|
187
|
+
logger.debug(
|
|
188
|
+
f"Added input '{func_name}' (from path '{path_str}') to agent '{self.agent.name}'"
|
|
189
|
+
)
|
|
190
|
+
else:
|
|
191
|
+
logger.warning(
|
|
192
|
+
f"Could not get path string for input {self.agent.input} in agent '{self.agent.name}'. Skipping."
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if is_output_callable:
|
|
196
|
+
path_str = registry.get_callable_path_string(self.agent.output)
|
|
197
|
+
if path_str:
|
|
198
|
+
func_name = path_str.split(".")[-1]
|
|
199
|
+
data["output_callable"] = func_name
|
|
200
|
+
logger.debug(
|
|
201
|
+
f"Added output '{func_name}' (from path '{path_str}') to agent '{self.agent.name}'"
|
|
202
|
+
)
|
|
203
|
+
else:
|
|
204
|
+
logger.warning(
|
|
205
|
+
f"Could not get path string for output {self.agent.output} in agent '{self.agent.name}'. Skipping."
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if is_next_agent_callable:
|
|
209
|
+
path_str = registry.get_callable_path_string(self.agent.next_agent)
|
|
210
|
+
if path_str:
|
|
211
|
+
func_name = path_str.split(".")[-1]
|
|
212
|
+
data["next_agent"] = func_name
|
|
213
|
+
logger.debug(
|
|
214
|
+
f"Added next_agent '{func_name}' (from path '{path_str}') to agent '{self.agent.name}'"
|
|
215
|
+
)
|
|
216
|
+
else:
|
|
217
|
+
logger.warning(
|
|
218
|
+
f"Could not get path string for next_agent {self.agent.next_agent} in agent '{self.agent.name}'. Skipping."
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
logger.info(
|
|
223
|
+
f"Serialization of agent '{self.agent.name}' complete with {len(data)} fields"
|
|
224
|
+
)
|
|
225
|
+
return data
|
|
226
|
+
|
|
227
|
+
@classmethod
|
|
228
|
+
def from_dict(cls, agent_class: type[T], data: dict[str, Any]) -> T:
|
|
229
|
+
"""Deserialize the agent from a dictionary, including components, tools, and callables."""
|
|
230
|
+
from flock.core.component.agent_component_base import AgentComponent
|
|
231
|
+
from flock.core.registry import get_registry
|
|
232
|
+
|
|
233
|
+
registry = get_registry()
|
|
234
|
+
logger.debug(
|
|
235
|
+
f"Deserializing agent from dict. Keys: {list(data.keys())}"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# --- Separate Data ---
|
|
239
|
+
components_data = data.pop("components", [])
|
|
240
|
+
callable_configs = {}
|
|
241
|
+
tool_config = data.pop("tools", [])
|
|
242
|
+
servers_config = data.pop("mcp_servers", [])
|
|
243
|
+
|
|
244
|
+
callable_keys = [
|
|
245
|
+
"description_callable",
|
|
246
|
+
"input_callable",
|
|
247
|
+
"output_callable",
|
|
248
|
+
]
|
|
249
|
+
|
|
250
|
+
for key in callable_keys:
|
|
251
|
+
if key in data and data[key] is not None:
|
|
252
|
+
callable_configs[key] = data.pop(key)
|
|
253
|
+
|
|
254
|
+
# --- Deserialize Base Agent ---
|
|
255
|
+
# Ensure required fields like 'name' are present if needed by __init__
|
|
256
|
+
if "name" not in data:
|
|
257
|
+
raise ValueError(
|
|
258
|
+
"Agent data must include a 'name' field for deserialization."
|
|
259
|
+
)
|
|
260
|
+
agent_name_log = data["name"] # For logging
|
|
261
|
+
logger.info(f"Deserializing base agent data for '{agent_name_log}'")
|
|
262
|
+
|
|
263
|
+
# Pydantic should handle base fields based on type hints in __init__
|
|
264
|
+
agent = agent_class(**data)
|
|
265
|
+
logger.debug(f"Base agent '{agent.name}' instantiated.")
|
|
266
|
+
|
|
267
|
+
# --- Deserialize Components ---
|
|
268
|
+
logger.debug(f"Deserializing components for '{agent.name}'")
|
|
269
|
+
if components_data:
|
|
270
|
+
for component_data in components_data:
|
|
271
|
+
try:
|
|
272
|
+
# Use the existing deserialize_component function
|
|
273
|
+
component = deserialize_component(component_data, AgentComponent)
|
|
274
|
+
if component:
|
|
275
|
+
agent.add_component(component)
|
|
276
|
+
logger.debug(f"Deserialized and added component '{component.name}' for '{agent.name}'")
|
|
277
|
+
except Exception as e:
|
|
278
|
+
logger.error(f"Failed to deserialize component: {e}")
|
|
279
|
+
|
|
280
|
+
# --- Deserialize Tools ---
|
|
281
|
+
agent.tools = [] # Initialize tools list
|
|
282
|
+
if tool_config:
|
|
283
|
+
logger.debug(
|
|
284
|
+
f"Deserializing {len(tool_config)} tools for '{agent.name}'"
|
|
285
|
+
)
|
|
286
|
+
# Use get_callable to find each tool
|
|
287
|
+
for tool_name_or_path in tool_config:
|
|
288
|
+
try:
|
|
289
|
+
found_tool = registry.get_callable(tool_name_or_path)
|
|
290
|
+
if found_tool and callable(found_tool):
|
|
291
|
+
agent.tools.append(found_tool)
|
|
292
|
+
logger.debug(
|
|
293
|
+
f"Resolved and added tool '{tool_name_or_path}' for agent '{agent.name}'"
|
|
294
|
+
)
|
|
295
|
+
else:
|
|
296
|
+
# Should not happen if get_callable returns successfully but just in case
|
|
297
|
+
logger.warning(
|
|
298
|
+
f"Registry returned non-callable for tool '{tool_name_or_path}' for agent '{agent.name}'. Skipping."
|
|
299
|
+
)
|
|
300
|
+
except (
|
|
301
|
+
ValueError
|
|
302
|
+
) as e: # get_callable raises ValueError if not found/ambiguous
|
|
303
|
+
logger.warning(
|
|
304
|
+
f"Could not resolve tool '{tool_name_or_path}' for agent '{agent.name}': {e}. Skipping."
|
|
305
|
+
)
|
|
306
|
+
except Exception as e:
|
|
307
|
+
logger.error(
|
|
308
|
+
f"Unexpected error resolving tool '{tool_name_or_path}' for agent '{agent.name}': {e}. Skipping.",
|
|
309
|
+
exc_info=True,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# --- Deserialize Servers ---
|
|
313
|
+
agent.servers = [] # Initialize Servers list.
|
|
314
|
+
if servers_config:
|
|
315
|
+
logger.debug(
|
|
316
|
+
f"Deserializing {len(servers_config)} servers for '{agent.name}'"
|
|
317
|
+
)
|
|
318
|
+
# Agents keep track of server by getting a list of server names.
|
|
319
|
+
# The server instances will be retrieved during runtime from the registry. (default behavior)
|
|
320
|
+
|
|
321
|
+
for server_name in servers_config:
|
|
322
|
+
if isinstance(server_name, str):
|
|
323
|
+
# Case 1 (default behavior): A server name is passed.
|
|
324
|
+
agent.servers.append(server_name)
|
|
325
|
+
elif isinstance(server_name, FlockMCPServerBase):
|
|
326
|
+
# Case 2 (highly unlikely): If someone somehow manages to pass
|
|
327
|
+
# an instance of a server during the deserialization step (however that might be achieved)
|
|
328
|
+
# check the registry, if the server is already registered, if not, register it
|
|
329
|
+
# and store the name in the servers list
|
|
330
|
+
server_exists = (
|
|
331
|
+
registry.get_server(server_name.config.name)
|
|
332
|
+
is not None
|
|
333
|
+
)
|
|
334
|
+
if server_exists:
|
|
335
|
+
agent.servers.append(server_name.config.name)
|
|
336
|
+
else:
|
|
337
|
+
registry.register_server(
|
|
338
|
+
server=server_name
|
|
339
|
+
) # register it.
|
|
340
|
+
agent.servers.append(server_name.config.name)
|
|
341
|
+
|
|
342
|
+
# --- Deserialize Callables ---
|
|
343
|
+
logger.debug(f"Deserializing callable fields for '{agent.name}'")
|
|
344
|
+
|
|
345
|
+
def resolve_and_assign(field_name: str, callable_key: str):
|
|
346
|
+
if callable_key in callable_configs:
|
|
347
|
+
callable_name = callable_configs[callable_key]
|
|
348
|
+
try:
|
|
349
|
+
# Use get_callable to find the signature function
|
|
350
|
+
found_callable = registry.get_callable(callable_name)
|
|
351
|
+
if found_callable and callable(found_callable):
|
|
352
|
+
setattr(agent, field_name, found_callable)
|
|
353
|
+
logger.debug(
|
|
354
|
+
f"Resolved callable '{callable_name}' for field '{field_name}' on agent '{agent.name}'"
|
|
355
|
+
)
|
|
356
|
+
else:
|
|
357
|
+
logger.warning(
|
|
358
|
+
f"Registry returned non-callable for name '{callable_name}' for field '{field_name}' on agent '{agent.name}'. Field remains default."
|
|
359
|
+
)
|
|
360
|
+
except (
|
|
361
|
+
ValueError
|
|
362
|
+
) as e: # get_callable raises ValueError if not found/ambiguous
|
|
363
|
+
logger.warning(
|
|
364
|
+
f"Could not resolve callable '{callable_name}' in registry for field '{field_name}' on agent '{agent.name}': {e}. Field remains default."
|
|
365
|
+
)
|
|
366
|
+
except Exception as e:
|
|
367
|
+
logger.error(
|
|
368
|
+
f"Unexpected error resolving callable '{callable_name}' for field '{field_name}' on agent '{agent.name}': {e}. Field remains default.",
|
|
369
|
+
exc_info=True,
|
|
370
|
+
)
|
|
371
|
+
# Else: key not present, field retains its default value from __init__
|
|
372
|
+
|
|
373
|
+
resolve_and_assign("description", "description_callable")
|
|
374
|
+
resolve_and_assign("input", "input_callable")
|
|
375
|
+
resolve_and_assign("output", "output_callable")
|
|
376
|
+
|
|
377
|
+
logger.info(f"Successfully deserialized agent '{agent.name}'.")
|
|
378
|
+
return agent
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# src/flock/core/component/__init__.py
|
|
2
|
+
"""Unified component system for Flock agents."""
|
|
3
|
+
|
|
4
|
+
from .agent_component_base import AgentComponent, AgentComponentConfig
|
|
5
|
+
from .evaluation_component_base import EvaluationComponentBase
|
|
6
|
+
from .routing_component_base import RoutingComponentBase
|
|
7
|
+
from .utility_component_base import UtilityComponentBase
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"AgentComponent",
|
|
11
|
+
"AgentComponentConfig",
|
|
12
|
+
"EvaluationComponentBase",
|
|
13
|
+
"RoutingComponentBase",
|
|
14
|
+
"UtilityComponentBase",
|
|
15
|
+
]
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
# src/flock/core/component/agent_component_base.py
|
|
2
|
+
"""Base classes for the unified Flock component system."""
|
|
2
3
|
|
|
3
4
|
from abc import ABC
|
|
4
5
|
from typing import Any, TypeVar
|
|
@@ -6,64 +7,86 @@ from typing import Any, TypeVar
|
|
|
6
7
|
from pydantic import BaseModel, Field, create_model
|
|
7
8
|
|
|
8
9
|
from flock.core.context.context import FlockContext
|
|
10
|
+
# HandOffRequest removed - using agent.next_agent directly
|
|
9
11
|
|
|
10
|
-
T = TypeVar("T", bound="
|
|
12
|
+
T = TypeVar("T", bound="AgentComponentConfig")
|
|
11
13
|
|
|
12
14
|
|
|
13
|
-
class
|
|
14
|
-
"""Base configuration class for Flock
|
|
15
|
-
|
|
16
|
-
This
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Example:
|
|
20
|
-
class MemoryModuleConfig(FlockModuleConfig):
|
|
21
|
-
file_path: str = Field(default="memory.json")
|
|
22
|
-
save_after_update: bool = Field(default=True)
|
|
15
|
+
class AgentComponentConfig(BaseModel):
|
|
16
|
+
"""Base configuration class for all Flock agent components.
|
|
17
|
+
|
|
18
|
+
This unified config class replaces FlockModuleConfig, FlockEvaluatorConfig,
|
|
19
|
+
and FlockRouterConfig, providing common functionality for all component types.
|
|
23
20
|
"""
|
|
24
21
|
|
|
25
22
|
enabled: bool = Field(
|
|
26
|
-
default=True,
|
|
23
|
+
default=True,
|
|
24
|
+
description="Whether this component is currently enabled"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
model: str | None = Field(
|
|
28
|
+
default=None,
|
|
29
|
+
description="Model to use for this component (if applicable)"
|
|
27
30
|
)
|
|
28
31
|
|
|
29
32
|
@classmethod
|
|
30
33
|
def with_fields(cls: type[T], **field_definitions) -> type[T]:
|
|
31
|
-
"""Create a new config class with additional fields.
|
|
34
|
+
"""Create a new config class with additional fields.
|
|
35
|
+
|
|
36
|
+
This allows dynamic config creation for components with custom configuration needs.
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
CustomConfig = AgentComponentConfig.with_fields(
|
|
40
|
+
temperature=Field(default=0.7, description="LLM temperature"),
|
|
41
|
+
max_tokens=Field(default=1000, description="Max tokens to generate")
|
|
42
|
+
)
|
|
43
|
+
"""
|
|
32
44
|
return create_model(
|
|
33
|
-
f"Dynamic{cls.__name__}",
|
|
45
|
+
f"Dynamic{cls.__name__}",
|
|
46
|
+
__base__=cls,
|
|
47
|
+
**field_definitions
|
|
34
48
|
)
|
|
35
49
|
|
|
36
50
|
|
|
37
|
-
class
|
|
38
|
-
"""Base class for all Flock
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
51
|
+
class AgentComponent(BaseModel, ABC):
|
|
52
|
+
"""Base class for all Flock agent components.
|
|
53
|
+
|
|
54
|
+
This unified base class replaces the separate FlockModule, FlockEvaluator,
|
|
55
|
+
and FlockRouter base classes. All agent extensions now inherit from this
|
|
56
|
+
single base class and use the unified lifecycle hooks.
|
|
57
|
+
|
|
58
|
+
Components can specialize by:
|
|
59
|
+
- EvaluationComponentBase: Implements evaluate_core() for agent intelligence
|
|
60
|
+
- RoutingComponentBase: Implements determine_next_step() for workflow routing
|
|
61
|
+
- UtilityComponentBase: Uses standard lifecycle hooks for cross-cutting concerns
|
|
46
62
|
"""
|
|
47
63
|
|
|
48
64
|
name: str = Field(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
config: FlockModuleConfig = Field(
|
|
52
|
-
default_factory=FlockModuleConfig, description="Module configuration"
|
|
65
|
+
...,
|
|
66
|
+
description="Unique identifier for this component"
|
|
53
67
|
)
|
|
54
68
|
|
|
55
|
-
|
|
69
|
+
config: AgentComponentConfig = Field(
|
|
70
|
+
default_factory=AgentComponentConfig,
|
|
71
|
+
description="Component configuration"
|
|
72
|
+
)
|
|
56
73
|
|
|
57
74
|
def __init__(self, **data):
|
|
58
75
|
super().__init__(**data)
|
|
59
76
|
|
|
77
|
+
# --- Standard Lifecycle Hooks ---
|
|
78
|
+
# These are called for ALL components during agent execution
|
|
79
|
+
|
|
60
80
|
async def on_initialize(
|
|
61
81
|
self,
|
|
62
82
|
agent: Any,
|
|
63
83
|
inputs: dict[str, Any],
|
|
64
84
|
context: FlockContext | None = None,
|
|
65
85
|
) -> None:
|
|
66
|
-
"""Called when the agent starts running.
|
|
86
|
+
"""Called when the agent starts running.
|
|
87
|
+
|
|
88
|
+
Use this for component initialization, resource setup, etc.
|
|
89
|
+
"""
|
|
67
90
|
pass
|
|
68
91
|
|
|
69
92
|
async def on_pre_evaluate(
|
|
@@ -72,7 +95,16 @@ class FlockModule(BaseModel, ABC):
|
|
|
72
95
|
inputs: dict[str, Any],
|
|
73
96
|
context: FlockContext | None = None,
|
|
74
97
|
) -> dict[str, Any]:
|
|
75
|
-
"""Called before agent evaluation, can modify inputs.
|
|
98
|
+
"""Called before agent evaluation, can modify inputs.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
agent: The agent being executed
|
|
102
|
+
inputs: Current input data
|
|
103
|
+
context: Execution context
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Modified input data (or original if no changes)
|
|
107
|
+
"""
|
|
76
108
|
return inputs
|
|
77
109
|
|
|
78
110
|
async def on_post_evaluate(
|
|
@@ -82,7 +114,17 @@ class FlockModule(BaseModel, ABC):
|
|
|
82
114
|
context: FlockContext | None = None,
|
|
83
115
|
result: dict[str, Any] | None = None,
|
|
84
116
|
) -> dict[str, Any] | None:
|
|
85
|
-
"""Called after agent evaluation, can modify results.
|
|
117
|
+
"""Called after agent evaluation, can modify results.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
agent: The agent that was executed
|
|
121
|
+
inputs: Original input data
|
|
122
|
+
context: Execution context
|
|
123
|
+
result: Evaluation result
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Modified result data (or original if no changes)
|
|
127
|
+
"""
|
|
86
128
|
return result
|
|
87
129
|
|
|
88
130
|
async def on_terminate(
|
|
@@ -92,7 +134,10 @@ class FlockModule(BaseModel, ABC):
|
|
|
92
134
|
context: FlockContext | None = None,
|
|
93
135
|
result: dict[str, Any] | None = None,
|
|
94
136
|
) -> dict[str, Any] | None:
|
|
95
|
-
"""Called when the agent finishes running.
|
|
137
|
+
"""Called when the agent finishes running.
|
|
138
|
+
|
|
139
|
+
Use this for cleanup, final result processing, etc.
|
|
140
|
+
"""
|
|
96
141
|
return result
|
|
97
142
|
|
|
98
143
|
async def on_error(
|
|
@@ -102,15 +147,71 @@ class FlockModule(BaseModel, ABC):
|
|
|
102
147
|
context: FlockContext | None = None,
|
|
103
148
|
error: Exception | None = None,
|
|
104
149
|
) -> None:
|
|
105
|
-
"""Called when an error occurs during agent execution.
|
|
150
|
+
"""Called when an error occurs during agent execution.
|
|
151
|
+
|
|
152
|
+
Use this for error handling, logging, recovery, etc.
|
|
153
|
+
"""
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
# --- Specialized Hooks ---
|
|
157
|
+
# These are overridden by specialized component types
|
|
158
|
+
|
|
159
|
+
async def evaluate_core(
|
|
160
|
+
self,
|
|
161
|
+
agent: Any,
|
|
162
|
+
inputs: dict[str, Any],
|
|
163
|
+
context: FlockContext | None = None,
|
|
164
|
+
tools: list[Any] | None = None,
|
|
165
|
+
mcp_tools: list[Any] | None = None,
|
|
166
|
+
) -> dict[str, Any]:
|
|
167
|
+
"""Core evaluation logic - override in EvaluationComponentBase.
|
|
168
|
+
|
|
169
|
+
This is where the main "intelligence" of the agent happens.
|
|
170
|
+
Only one component per agent should implement this meaningfully.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
agent: The agent being executed
|
|
174
|
+
inputs: Input data for evaluation
|
|
175
|
+
context: Execution context
|
|
176
|
+
tools: Available tools for the agent
|
|
177
|
+
mcp_tools: Available MCP tools
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Evaluation result
|
|
181
|
+
"""
|
|
182
|
+
# Default implementation is pass-through
|
|
183
|
+
return inputs
|
|
184
|
+
|
|
185
|
+
async def determine_next_step(
|
|
186
|
+
self,
|
|
187
|
+
agent: Any,
|
|
188
|
+
result: dict[str, Any],
|
|
189
|
+
context: FlockContext | None = None,
|
|
190
|
+
) -> None:
|
|
191
|
+
"""Determine the next step in the workflow - override in RoutingComponentBase.
|
|
192
|
+
|
|
193
|
+
This is where routing decisions are made. Sets agent.next_agent directly.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
agent: The agent that just completed
|
|
197
|
+
result: Result from the agent's evaluation
|
|
198
|
+
context: Execution context
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
None - routing components set agent.next_agent directly
|
|
202
|
+
"""
|
|
203
|
+
# Default implementation provides no routing
|
|
106
204
|
pass
|
|
107
205
|
|
|
206
|
+
# --- MCP Server Lifecycle Hooks ---
|
|
207
|
+
# For components that interact directly with MCP servers
|
|
208
|
+
|
|
108
209
|
async def on_pre_server_init(self, server: Any) -> None:
|
|
109
210
|
"""Called before a server initializes."""
|
|
110
211
|
pass
|
|
111
212
|
|
|
112
213
|
async def on_post_server_init(self, server: Any) -> None:
|
|
113
|
-
"""Called after a server
|
|
214
|
+
"""Called after a server initializes."""
|
|
114
215
|
pass
|
|
115
216
|
|
|
116
217
|
async def on_pre_server_terminate(self, server: Any) -> None:
|