flock-core 0.4.528__py3-none-any.whl → 0.5.0b0__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/execute_flock.py +1 -1
- flock/cli/manage_agents.py +6 -6
- flock/components/__init__.py +30 -0
- flock/components/evaluation/__init__.py +9 -0
- flock/components/evaluation/declarative_evaluation_component.py +222 -0
- flock/components/routing/__init__.py +15 -0
- flock/{routers/conditional/conditional_router.py → components/routing/conditional_routing_component.py} +61 -53
- flock/components/routing/default_routing_component.py +103 -0
- flock/components/routing/llm_routing_component.py +206 -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} +110 -95
- flock/{modules/output/output_module.py → components/utility/output_utility_component.py} +47 -45
- flock/core/__init__.py +26 -18
- 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 +206 -0
- flock/core/agent/flock_agent_lifecycle.py +177 -0
- flock/core/agent/flock_agent_serialization.py +381 -0
- flock/core/api/endpoints.py +2 -2
- flock/core/api/service.py +2 -2
- flock/core/component/__init__.py +15 -0
- flock/core/{flock_module.py → component/agent_component_base.py} +136 -34
- flock/core/component/evaluation_component.py +56 -0
- flock/core/component/routing_component.py +74 -0
- flock/core/component/utility_component.py +69 -0
- flock/core/config/flock_agent_config.py +49 -2
- flock/core/evaluation/utils.py +3 -2
- flock/core/execution/batch_executor.py +1 -1
- flock/core/execution/evaluation_executor.py +2 -2
- flock/core/execution/opik_executor.py +1 -1
- flock/core/flock.py +147 -493
- flock/core/flock_agent.py +195 -1032
- flock/core/flock_factory.py +114 -90
- flock/core/flock_scheduler.py +1 -1
- flock/core/flock_server_manager.py +8 -8
- flock/core/logging/logging.py +1 -0
- flock/core/mcp/flock_mcp_server.py +53 -48
- flock/core/mcp/{flock_mcp_tool_base.py → flock_mcp_tool.py} +2 -2
- flock/core/mcp/mcp_client.py +9 -9
- flock/core/mcp/mcp_client_manager.py +9 -9
- flock/core/mcp/mcp_config.py +24 -24
- 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 +67 -0
- flock/core/orchestration/flock_web_server.py +117 -0
- flock/core/registry/__init__.py +45 -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 +205 -0
- flock/core/registry/server_registry.py +57 -0
- flock/core/registry/type_registry.py +86 -0
- flock/core/serialization/flock_serializer.py +36 -32
- flock/core/serialization/serialization_utils.py +28 -25
- flock/core/util/hydrator.py +1 -1
- flock/core/util/input_resolver.py +29 -2
- flock/mcp/servers/sse/flock_sse_server.py +10 -10
- flock/mcp/servers/stdio/flock_stdio_server.py +10 -10
- flock/mcp/servers/streamable_http/flock_streamable_http_server.py +10 -10
- flock/mcp/servers/websockets/flock_websocket_server.py +10 -10
- flock/platform/docker_tools.py +3 -3
- flock/webapp/app/chat.py +1 -1
- flock/webapp/app/main.py +9 -5
- flock/webapp/app/services/flock_service.py +1 -1
- flock/webapp/app/services/sharing_store.py +1 -0
- flock/workflow/activities.py +67 -92
- flock/workflow/agent_execution_activity.py +6 -6
- flock/workflow/flock_workflow.py +1 -1
- flock_core-0.5.0b0.dist-info/METADATA +272 -0
- {flock_core-0.4.528.dist-info → flock_core-0.5.0b0.dist-info}/RECORD +82 -95
- flock/core/flock_evaluator.py +0 -60
- flock/core/flock_registry.py +0 -702
- 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 -217
- 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/tools/__init__.py +0 -0
- flock/tools/azure_tools.py +0 -781
- flock/tools/code_tools.py +0 -167
- flock/tools/file_tools.py +0 -149
- flock/tools/github_tools.py +0 -157
- flock/tools/markdown_tools.py +0 -205
- flock/tools/system_tools.py +0 -9
- flock/tools/text_tools.py +0 -810
- flock/tools/web_tools.py +0 -90
- flock/tools/zendesk_tools.py +0 -147
- flock_core-0.4.528.dist-info/METADATA +0 -675
- {flock_core-0.4.528.dist-info → flock_core-0.5.0b0.dist-info}/WHEEL +0 -0
- {flock_core-0.4.528.dist-info → flock_core-0.5.0b0.dist-info}/entry_points.txt +0 -0
- {flock_core-0.4.528.dist-info → flock_core-0.5.0b0.dist-info}/licenses/LICENSE +0 -0
flock/core/flock_agent.py
CHANGED
|
@@ -1,70 +1,54 @@
|
|
|
1
1
|
# src/flock/core/flock_agent.py
|
|
2
|
-
"""FlockAgent
|
|
2
|
+
"""FlockAgent with unified component architecture."""
|
|
3
3
|
|
|
4
|
-
import asyncio
|
|
5
|
-
import json
|
|
6
|
-
import os
|
|
7
4
|
import uuid
|
|
8
5
|
from abc import ABC
|
|
9
6
|
from collections.abc import Callable
|
|
10
|
-
from
|
|
11
|
-
from typing import TYPE_CHECKING, Any, TypeVar
|
|
7
|
+
from typing import Any, TypeVar
|
|
12
8
|
|
|
13
|
-
from flock.core.config.flock_agent_config import FlockAgentConfig
|
|
14
|
-
from flock.core.mcp.flock_mcp_server import FlockMCPServerBase
|
|
15
|
-
from flock.core.serialization.json_encoder import FlockJSONEncoder
|
|
16
|
-
from flock.workflow.temporal_config import TemporalActivityConfig
|
|
17
|
-
|
|
18
|
-
if TYPE_CHECKING:
|
|
19
|
-
from flock.core.context.context import FlockContext
|
|
20
|
-
from flock.core.flock_evaluator import FlockEvaluator
|
|
21
|
-
from flock.core.flock_module import FlockModule
|
|
22
|
-
from flock.core.flock_router import FlockRouter
|
|
23
|
-
|
|
24
|
-
from opentelemetry import trace
|
|
25
9
|
from pydantic import BaseModel, Field
|
|
26
10
|
|
|
27
|
-
|
|
11
|
+
from flock.core.agent.flock_agent_execution import FlockAgentExecution
|
|
12
|
+
from flock.core.agent.flock_agent_integration import FlockAgentIntegration
|
|
13
|
+
from flock.core.agent.flock_agent_serialization import FlockAgentSerialization
|
|
14
|
+
from flock.core.component.agent_component_base import AgentComponent
|
|
15
|
+
from flock.core.component.evaluation_component import (
|
|
16
|
+
EvaluationComponent,
|
|
17
|
+
)
|
|
18
|
+
from flock.core.component.routing_component import RoutingComponent
|
|
19
|
+
from flock.core.config.flock_agent_config import FlockAgentConfig
|
|
28
20
|
from flock.core.context.context import FlockContext
|
|
29
|
-
from flock.core.flock_evaluator import FlockEvaluator, FlockEvaluatorConfig
|
|
30
|
-
from flock.core.flock_module import FlockModule, FlockModuleConfig
|
|
31
|
-
from flock.core.flock_router import FlockRouter, FlockRouterConfig
|
|
32
21
|
from flock.core.logging.logging import get_logger
|
|
22
|
+
from flock.core.mcp.flock_mcp_server import FlockMCPServer
|
|
33
23
|
|
|
34
24
|
# Mixins and Serialization components
|
|
35
25
|
from flock.core.mixin.dspy_integration import DSPyIntegrationMixin
|
|
36
|
-
from flock.core.serialization.serializable import
|
|
37
|
-
|
|
38
|
-
)
|
|
39
|
-
from flock.core.serialization.serialization_utils import (
|
|
40
|
-
deserialize_component,
|
|
41
|
-
serialize_item,
|
|
42
|
-
)
|
|
26
|
+
from flock.core.serialization.serializable import Serializable
|
|
27
|
+
from flock.workflow.temporal_config import TemporalActivityConfig
|
|
43
28
|
|
|
44
|
-
logger = get_logger("agent")
|
|
45
|
-
tracer = trace.get_tracer(__name__)
|
|
46
|
-
T = TypeVar("T", bound="FlockAgent")
|
|
29
|
+
logger = get_logger("agent.unified")
|
|
47
30
|
|
|
31
|
+
T = TypeVar("T", bound="FlockAgent")
|
|
48
32
|
|
|
49
|
-
SignatureType = (
|
|
50
|
-
str
|
|
51
|
-
| Callable[..., str]
|
|
52
|
-
| type[BaseModel]
|
|
53
|
-
| Callable[..., type[BaseModel]]
|
|
54
|
-
| None
|
|
55
|
-
)
|
|
56
33
|
|
|
34
|
+
DynamicStr = str | Callable[[FlockContext], str]
|
|
57
35
|
|
|
58
|
-
# Make FlockAgent inherit from Serializable
|
|
59
36
|
class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
60
|
-
"""
|
|
61
|
-
|
|
62
|
-
|
|
37
|
+
"""Unified FlockAgent using the new component architecture.
|
|
38
|
+
|
|
39
|
+
This is the next-generation FlockAgent that uses a single components list
|
|
40
|
+
instead of separate evaluator, router, and modules. All agent functionality
|
|
41
|
+
is now provided through AgentComponent instances.
|
|
42
|
+
|
|
43
|
+
Key changes:
|
|
44
|
+
- components: list[AgentComponent] - unified component list
|
|
45
|
+
- next_agent: str | None - explicit workflow state
|
|
46
|
+
- evaluator/router properties - convenience access to primary components
|
|
63
47
|
"""
|
|
64
48
|
|
|
65
49
|
agent_id: str = Field(
|
|
66
50
|
default_factory=lambda: str(uuid.uuid4()),
|
|
67
|
-
description="Internal, Unique UUID4 for this agent instance.
|
|
51
|
+
description="Internal, Unique UUID4 for this agent instance.",
|
|
68
52
|
)
|
|
69
53
|
|
|
70
54
|
name: str = Field(..., description="Unique identifier for the agent.")
|
|
@@ -73,73 +57,61 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
73
57
|
None,
|
|
74
58
|
description="The model identifier to use (e.g., 'openai/gpt-4o'). If None, uses Flock's default.",
|
|
75
59
|
)
|
|
76
|
-
|
|
77
|
-
"",
|
|
60
|
+
description_spec: DynamicStr | None = Field(
|
|
61
|
+
default="",
|
|
62
|
+
alias="description",
|
|
63
|
+
validation_alias="description",
|
|
78
64
|
description="A human-readable description or a callable returning one.",
|
|
79
65
|
)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
),
|
|
66
|
+
input_spec: DynamicStr | None = Field(
|
|
67
|
+
default="",
|
|
68
|
+
alias="input",
|
|
69
|
+
validation_alias="input",
|
|
70
|
+
description="Signature for input keys. Supports type hints (:) and descriptions (|).",
|
|
86
71
|
)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
),
|
|
72
|
+
output_spec: DynamicStr | None = Field(
|
|
73
|
+
default="",
|
|
74
|
+
alias="output",
|
|
75
|
+
validation_alias="output",
|
|
76
|
+
description="Signature for output keys. Supports type hints (:) and descriptions (|).",
|
|
93
77
|
)
|
|
94
|
-
tools: list[Callable[..., Any]] | None = (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
description="List of callable tools the agent can use. These must be registered.",
|
|
98
|
-
)
|
|
78
|
+
tools: list[Callable[..., Any]] | None = Field(
|
|
79
|
+
default=None,
|
|
80
|
+
description="List of callable tools the agent can use. These must be registered.",
|
|
99
81
|
)
|
|
100
|
-
servers: list[str |
|
|
82
|
+
servers: list[str | FlockMCPServer] | None = Field(
|
|
101
83
|
default=None,
|
|
102
|
-
description="List of MCP Servers the agent can use to enhance its capabilities.
|
|
84
|
+
description="List of MCP Servers the agent can use to enhance its capabilities.",
|
|
103
85
|
)
|
|
104
86
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
wait_for_input: bool = Field(
|
|
110
|
-
default=False,
|
|
111
|
-
description="Wait for user input after the agent's output is displayed.",
|
|
87
|
+
# --- UNIFIED COMPONENT SYSTEM ---
|
|
88
|
+
components: list[AgentComponent] = Field(
|
|
89
|
+
default_factory=list,
|
|
90
|
+
description="List of all agent components (evaluators, routers, modules).",
|
|
112
91
|
)
|
|
113
92
|
|
|
114
|
-
# ---
|
|
115
|
-
|
|
116
|
-
default=None,
|
|
117
|
-
description="The evaluator instance defining the agent's core logic.",
|
|
118
|
-
)
|
|
119
|
-
handoff_router: FlockRouter | None = Field( # Make optional, allow None
|
|
93
|
+
# --- EXPLICIT WORKFLOW STATE ---
|
|
94
|
+
next_agent_spec: DynamicStr | None = Field(
|
|
120
95
|
default=None,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
default_factory=dict,
|
|
125
|
-
description="Dictionary of FlockModules attached to this agent.",
|
|
96
|
+
alias="next_agent",
|
|
97
|
+
validation_alias="next_agent",
|
|
98
|
+
description="Next agent in workflow - set by user or routing components.",
|
|
126
99
|
)
|
|
127
100
|
|
|
128
101
|
config: FlockAgentConfig = Field(
|
|
129
102
|
default_factory=lambda: FlockAgentConfig(),
|
|
130
|
-
description="Configuration for this agent
|
|
103
|
+
description="Configuration for this agent.",
|
|
131
104
|
)
|
|
132
105
|
|
|
133
|
-
# --- Temporal Configuration (Optional) ---
|
|
134
106
|
temporal_activity_config: TemporalActivityConfig | None = Field(
|
|
135
107
|
default=None,
|
|
136
|
-
description="Optional Temporal settings specific to this agent
|
|
108
|
+
description="Optional Temporal settings specific to this agent.",
|
|
137
109
|
)
|
|
138
110
|
|
|
139
111
|
# --- Runtime State (Excluded from Serialization) ---
|
|
140
112
|
context: FlockContext | None = Field(
|
|
141
113
|
default=None,
|
|
142
|
-
exclude=True,
|
|
114
|
+
exclude=True,
|
|
143
115
|
description="Runtime context associated with the flock execution.",
|
|
144
116
|
)
|
|
145
117
|
|
|
@@ -147,349 +119,143 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
147
119
|
self,
|
|
148
120
|
name: str,
|
|
149
121
|
model: str | None = None,
|
|
150
|
-
description:
|
|
151
|
-
input:
|
|
152
|
-
output:
|
|
122
|
+
description: DynamicStr | None = None,
|
|
123
|
+
input: DynamicStr | None = None,
|
|
124
|
+
output: DynamicStr | None = None,
|
|
153
125
|
tools: list[Callable[..., Any]] | None = None,
|
|
154
|
-
servers: list[str |
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
modules: dict[str, "FlockModule"] | None = None,
|
|
159
|
-
write_to_file: bool = False,
|
|
160
|
-
wait_for_input: bool = False,
|
|
126
|
+
servers: list[str | FlockMCPServer] | None = None,
|
|
127
|
+
components: list[AgentComponent] | None = None,
|
|
128
|
+
config: FlockAgentConfig | None = None,
|
|
129
|
+
next_agent: DynamicStr | None = None,
|
|
161
130
|
temporal_activity_config: TemporalActivityConfig | None = None,
|
|
162
|
-
**kwargs,
|
|
163
131
|
):
|
|
132
|
+
"""Initialize the unified FlockAgent with components and configuration."""
|
|
133
|
+
if config is None:
|
|
134
|
+
config = FlockAgentConfig()
|
|
164
135
|
super().__init__(
|
|
165
136
|
name=name,
|
|
166
137
|
model=model,
|
|
167
138
|
description=description,
|
|
168
|
-
input=input,
|
|
169
|
-
output=output,
|
|
139
|
+
input=input,
|
|
140
|
+
output=output,
|
|
170
141
|
tools=tools,
|
|
171
142
|
servers=servers,
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
evaluator=evaluator,
|
|
175
|
-
handoff_router=handoff_router,
|
|
176
|
-
modules=modules
|
|
177
|
-
if modules is not None
|
|
178
|
-
else {}, # Ensure modules is a dict
|
|
143
|
+
components=components if components is not None else [],
|
|
144
|
+
config=config,
|
|
179
145
|
temporal_activity_config=temporal_activity_config,
|
|
180
|
-
|
|
146
|
+
next_agent=next_agent,
|
|
181
147
|
)
|
|
182
148
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
149
|
+
# Initialize helper systems (reuse existing logic)
|
|
150
|
+
self._execution = FlockAgentExecution(self)
|
|
151
|
+
self._integration = FlockAgentIntegration(self)
|
|
152
|
+
self._serialization = FlockAgentSerialization(self)
|
|
153
|
+
# Lifecycle will be lazy-loaded when needed
|
|
187
154
|
|
|
188
|
-
# ---
|
|
189
|
-
#
|
|
190
|
-
def add_module(self, module: FlockModule) -> None:
|
|
191
|
-
"""Add a module to this agent."""
|
|
192
|
-
if not module.name:
|
|
193
|
-
logger.error("Module must have a name to be added.")
|
|
194
|
-
return
|
|
195
|
-
if module.name in self.modules:
|
|
196
|
-
logger.warning(f"Overwriting existing module: {module.name}")
|
|
197
|
-
self.modules[module.name] = module
|
|
198
|
-
logger.debug(f"Added module '{module.name}' to agent '{self.name}'")
|
|
199
|
-
|
|
200
|
-
def remove_module(self, module_name: str) -> None:
|
|
201
|
-
"""Remove a module from this agent."""
|
|
202
|
-
if module_name in self.modules:
|
|
203
|
-
del self.modules[module_name]
|
|
204
|
-
logger.debug(
|
|
205
|
-
f"Removed module '{module_name}' from agent '{self.name}'"
|
|
206
|
-
)
|
|
207
|
-
else:
|
|
208
|
-
logger.warning(
|
|
209
|
-
f"Module '{module_name}' not found on agent '{self.name}'."
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
def get_module(self, module_name: str) -> FlockModule | None:
|
|
213
|
-
"""Get a module by name."""
|
|
214
|
-
return self.modules.get(module_name)
|
|
215
|
-
|
|
216
|
-
def get_enabled_modules(self) -> list[FlockModule]:
|
|
217
|
-
"""Get a list of currently enabled modules attached to this agent."""
|
|
218
|
-
return [m for m in self.modules.values() if m.config.enabled]
|
|
155
|
+
# --- CONVENIENCE PROPERTIES ---
|
|
156
|
+
# These provide familiar access patterns while using the unified model
|
|
219
157
|
|
|
220
158
|
@property
|
|
221
|
-
def
|
|
222
|
-
"""
|
|
223
|
-
|
|
224
|
-
Returns None if the description is None or a callable that fails.
|
|
225
|
-
"""
|
|
226
|
-
if callable(self.description):
|
|
227
|
-
try:
|
|
228
|
-
# Attempt to call without context first.
|
|
229
|
-
# If callables consistently need context, this might need adjustment
|
|
230
|
-
# or the template-facing property might need to be simpler,
|
|
231
|
-
# relying on prior resolution via resolve_callables.
|
|
232
|
-
return self.description()
|
|
233
|
-
except TypeError:
|
|
234
|
-
# Log a warning that context might be needed?
|
|
235
|
-
# For now, treat as unresolvable in this simple property.
|
|
236
|
-
logger.warning(
|
|
237
|
-
f"Callable description for agent '{self.name}' could not be resolved "
|
|
238
|
-
f"without context via the simple 'resolved_description' property. "
|
|
239
|
-
f"Consider calling 'agent.resolve_callables(context)' beforehand if context is required."
|
|
240
|
-
)
|
|
241
|
-
return None # Or a placeholder like "[Callable Description]"
|
|
242
|
-
except Exception as e:
|
|
243
|
-
logger.error(
|
|
244
|
-
f"Error resolving callable description for agent '{self.name}': {e}"
|
|
245
|
-
)
|
|
246
|
-
return None
|
|
247
|
-
elif isinstance(self.description, str):
|
|
248
|
-
return self.description
|
|
249
|
-
return None
|
|
250
|
-
|
|
251
|
-
# --- Lifecycle Hooks (Keep as they were) ---
|
|
252
|
-
async def initialize(self, inputs: dict[str, Any]) -> None:
|
|
253
|
-
"""Initialize agent and run module initializers."""
|
|
254
|
-
logger.debug(f"Initializing agent '{self.name}'")
|
|
255
|
-
with tracer.start_as_current_span("agent.initialize") as span:
|
|
256
|
-
span.set_attribute("agent.name", self.name)
|
|
257
|
-
span.set_attribute("inputs", str(inputs))
|
|
258
|
-
logger.info(
|
|
259
|
-
f"agent.initialize",
|
|
260
|
-
agent=self.name,
|
|
261
|
-
)
|
|
262
|
-
try:
|
|
263
|
-
for module in self.get_enabled_modules():
|
|
264
|
-
await module.on_initialize(self, inputs, self.context)
|
|
265
|
-
except Exception as module_error:
|
|
266
|
-
logger.error(
|
|
267
|
-
"Error during initialize",
|
|
268
|
-
agent=self.name,
|
|
269
|
-
error=str(module_error),
|
|
270
|
-
)
|
|
271
|
-
span.record_exception(module_error)
|
|
272
|
-
|
|
273
|
-
async def terminate(
|
|
274
|
-
self, inputs: dict[str, Any], result: dict[str, Any]
|
|
275
|
-
) -> None:
|
|
276
|
-
"""Terminate agent and run module terminators."""
|
|
277
|
-
logger.debug(f"Terminating agent '{self.name}'")
|
|
278
|
-
with tracer.start_as_current_span("agent.terminate") as span:
|
|
279
|
-
span.set_attribute("agent.name", self.name)
|
|
280
|
-
span.set_attribute("inputs", str(inputs))
|
|
281
|
-
span.set_attribute("result", str(result))
|
|
282
|
-
logger.info(
|
|
283
|
-
f"agent.terminate",
|
|
284
|
-
agent=self.name,
|
|
285
|
-
)
|
|
286
|
-
try:
|
|
287
|
-
current_result = result
|
|
288
|
-
for module in self.get_enabled_modules():
|
|
289
|
-
tmp_result = await module.on_terminate(
|
|
290
|
-
self, inputs, self.context, current_result
|
|
291
|
-
)
|
|
292
|
-
# If the module returns a result, use it
|
|
293
|
-
if tmp_result:
|
|
294
|
-
current_result = tmp_result
|
|
295
|
-
|
|
296
|
-
if self.write_to_file:
|
|
297
|
-
self._save_output(self.name, current_result)
|
|
298
|
-
|
|
299
|
-
if self.wait_for_input:
|
|
300
|
-
# simple input prompt
|
|
301
|
-
input("Press Enter to continue...")
|
|
159
|
+
def evaluator(self) -> EvaluationComponent | None:
|
|
160
|
+
"""Get the primary evaluation component for this agent."""
|
|
161
|
+
return self._components.get_primary_evaluator()
|
|
302
162
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
error=str(module_error),
|
|
308
|
-
)
|
|
309
|
-
span.record_exception(module_error)
|
|
163
|
+
@property
|
|
164
|
+
def router(self) -> RoutingComponent | None:
|
|
165
|
+
"""Get the primary routing component for this agent."""
|
|
166
|
+
return self._components.get_primary_router()
|
|
310
167
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
span.set_attribute("agent.name", self.name)
|
|
316
|
-
span.set_attribute("inputs", str(inputs))
|
|
317
|
-
try:
|
|
318
|
-
for module in self.get_enabled_modules():
|
|
319
|
-
await module.on_error(self, inputs, self.context, error)
|
|
320
|
-
except Exception as module_error:
|
|
321
|
-
logger.error(
|
|
322
|
-
"Error during on_error",
|
|
323
|
-
agent=self.name,
|
|
324
|
-
error=str(module_error),
|
|
325
|
-
)
|
|
326
|
-
span.record_exception(module_error)
|
|
168
|
+
@property
|
|
169
|
+
def modules(self) -> list[AgentComponent]:
|
|
170
|
+
"""Get all components (for backward compatibility with module-style access)."""
|
|
171
|
+
return self.components.copy()
|
|
327
172
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
with tracer.start_as_current_span("agent.evaluate") as span:
|
|
335
|
-
span.set_attribute("agent.name", self.name)
|
|
336
|
-
span.set_attribute("inputs", str(inputs))
|
|
337
|
-
logger.info(
|
|
338
|
-
f"agent.evaluate",
|
|
339
|
-
agent=self.name,
|
|
173
|
+
@property
|
|
174
|
+
def _components(self):
|
|
175
|
+
"""Get the component management helper."""
|
|
176
|
+
if not hasattr(self, '_components_helper'):
|
|
177
|
+
from flock.core.agent.flock_agent_components import (
|
|
178
|
+
FlockAgentComponents,
|
|
340
179
|
)
|
|
180
|
+
self._components_helper = FlockAgentComponents(self)
|
|
181
|
+
return self._components_helper
|
|
341
182
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
for module in self.get_enabled_modules():
|
|
347
|
-
current_inputs = await module.on_pre_evaluate(
|
|
348
|
-
self, current_inputs, self.context
|
|
349
|
-
)
|
|
183
|
+
# Component management delegated to _components
|
|
184
|
+
def add_component(self, component: AgentComponent) -> None:
|
|
185
|
+
"""Add a component to this agent."""
|
|
186
|
+
self._components.add_component(component)
|
|
350
187
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
registered_tools = []
|
|
355
|
-
if self.tools:
|
|
356
|
-
# Ensure tools are actually retrieved/validated if needed by evaluator type
|
|
357
|
-
# For now, assume evaluator handles tool resolution if necessary
|
|
358
|
-
registered_tools = self.tools
|
|
188
|
+
def remove_component(self, component_name: str) -> None:
|
|
189
|
+
"""Remove a component from this agent."""
|
|
190
|
+
self._components.remove_component(component_name)
|
|
359
191
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
from flock.core.flock_registry import get_registry
|
|
192
|
+
def get_component(self, component_name: str) -> AgentComponent | None:
|
|
193
|
+
"""Get a component by name."""
|
|
194
|
+
return self._components.get_component(component_name)
|
|
364
195
|
|
|
365
|
-
FlockRegistry = get_registry() # Get the registry
|
|
366
|
-
for server in self.servers:
|
|
367
|
-
registered_server: FlockMCPServerBase | None = None
|
|
368
|
-
server_tools = []
|
|
369
|
-
if isinstance(server, FlockMCPServerBase):
|
|
370
|
-
# check if registered
|
|
371
|
-
server_name = server.config.name
|
|
372
|
-
registered_server = FlockRegistry.get_server(
|
|
373
|
-
server_name
|
|
374
|
-
)
|
|
375
|
-
else:
|
|
376
|
-
# servers must be registered.
|
|
377
|
-
registered_server = FlockRegistry.get_server(
|
|
378
|
-
name=server
|
|
379
|
-
)
|
|
380
|
-
if registered_server:
|
|
381
|
-
server_tools = await registered_server.get_tools(
|
|
382
|
-
agent_id=self.agent_id,
|
|
383
|
-
run_id=self.context.run_id,
|
|
384
|
-
)
|
|
385
|
-
else:
|
|
386
|
-
logger.warning(
|
|
387
|
-
f"No Server with name '{server.config.name}' registered! Skipping."
|
|
388
|
-
)
|
|
389
|
-
mcp_tools = mcp_tools + server_tools
|
|
390
196
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
container = None
|
|
395
|
-
if self.context is not None:
|
|
396
|
-
container = self.context.get_variable("di.container")
|
|
197
|
+
def get_enabled_components(self) -> list[AgentComponent]:
|
|
198
|
+
"""Get enabled components (backward compatibility)."""
|
|
199
|
+
return self._components.get_enabled_components()
|
|
397
200
|
|
|
398
|
-
|
|
399
|
-
|
|
201
|
+
# --- LIFECYCLE DELEGATION ---
|
|
202
|
+
# Delegate lifecycle methods to the composition objects
|
|
400
203
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
204
|
+
@property
|
|
205
|
+
def _lifecycle(self):
|
|
206
|
+
"""Get the lifecycle management helper (lazy-loaded)."""
|
|
207
|
+
if not hasattr(self, '_lifecycle_helper'):
|
|
208
|
+
from flock.core.agent.flock_agent_lifecycle import (
|
|
209
|
+
FlockAgentLifecycle,
|
|
210
|
+
)
|
|
211
|
+
self._lifecycle_helper = FlockAgentLifecycle(self)
|
|
212
|
+
return self._lifecycle_helper
|
|
406
213
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
except Exception:
|
|
411
|
-
pipeline = None
|
|
214
|
+
async def initialize(self, inputs: dict[str, Any]) -> None:
|
|
215
|
+
"""Initialize agent and run component initializers."""
|
|
216
|
+
return await self._lifecycle.initialize(inputs)
|
|
412
217
|
|
|
413
|
-
|
|
414
|
-
|
|
218
|
+
async def evaluate(self, inputs: dict[str, Any]) -> dict[str, Any]:
|
|
219
|
+
"""Core evaluation logic using unified component system."""
|
|
220
|
+
return await self._lifecycle.evaluate(inputs)
|
|
415
221
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
)
|
|
222
|
+
async def terminate(self, inputs: dict[str, Any], result: dict[str, Any]) -> None:
|
|
223
|
+
"""Terminate agent and run component terminators."""
|
|
224
|
+
return await self._lifecycle.terminate(inputs, result)
|
|
420
225
|
|
|
421
|
-
|
|
226
|
+
async def on_error(self, error: Exception, inputs: dict[str, Any]) -> None:
|
|
227
|
+
"""Handle errors and run component error handlers."""
|
|
228
|
+
return await self._lifecycle.on_error(error, inputs)
|
|
422
229
|
|
|
423
|
-
|
|
424
|
-
|
|
230
|
+
# --- EXECUTION METHODS ---
|
|
231
|
+
# Delegate to the execution system
|
|
425
232
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
return await mw(self.context, _invoke_next) # type: ignore[arg-type]
|
|
430
|
-
return await _final_handler()
|
|
233
|
+
def run(self, inputs: dict[str, Any]) -> dict[str, Any]:
|
|
234
|
+
"""Synchronous wrapper for run_async."""
|
|
235
|
+
return self._execution.run(inputs)
|
|
431
236
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
# No pipeline registered, direct evaluation
|
|
436
|
-
result = await self.evaluator.evaluate(
|
|
437
|
-
self, current_inputs, registered_tools
|
|
438
|
-
)
|
|
439
|
-
except ImportError:
|
|
440
|
-
# wd.di not installed – fall back
|
|
441
|
-
result = await self.evaluator.evaluate(
|
|
442
|
-
self, current_inputs, registered_tools
|
|
443
|
-
)
|
|
444
|
-
else:
|
|
445
|
-
# No DI container – standard execution
|
|
446
|
-
result = await self.evaluator.evaluate(
|
|
447
|
-
self,
|
|
448
|
-
current_inputs,
|
|
449
|
-
registered_tools,
|
|
450
|
-
mcp_tools=mcp_tools,
|
|
451
|
-
)
|
|
452
|
-
except Exception as eval_error:
|
|
453
|
-
logger.error(
|
|
454
|
-
"Error during evaluate",
|
|
455
|
-
agent=self.name,
|
|
456
|
-
error=str(eval_error),
|
|
457
|
-
)
|
|
458
|
-
span.record_exception(eval_error)
|
|
459
|
-
await self.on_error(
|
|
460
|
-
eval_error, current_inputs
|
|
461
|
-
) # Call error hook
|
|
462
|
-
raise # Re-raise the exception
|
|
237
|
+
async def run_async(self, inputs: dict[str, Any]) -> dict[str, Any]:
|
|
238
|
+
"""Asynchronous execution logic with unified lifecycle."""
|
|
239
|
+
return await self._execution.run_async(inputs)
|
|
463
240
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
for module in self.get_enabled_modules():
|
|
467
|
-
tmp_result = await module.on_post_evaluate(
|
|
468
|
-
self,
|
|
469
|
-
current_inputs,
|
|
470
|
-
self.context,
|
|
471
|
-
current_result,
|
|
472
|
-
)
|
|
473
|
-
# If the module returns a result, use it
|
|
474
|
-
if tmp_result:
|
|
475
|
-
current_result = tmp_result
|
|
241
|
+
# --- SERIALIZATION ---
|
|
242
|
+
# Delegate to the serialization system
|
|
476
243
|
|
|
477
|
-
|
|
478
|
-
|
|
244
|
+
def to_dict(self) -> dict[str, Any]:
|
|
245
|
+
"""Convert to dictionary using unified component serialization."""
|
|
246
|
+
return self._serialization.to_dict()
|
|
479
247
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
except (
|
|
485
|
-
RuntimeError
|
|
486
|
-
): # 'RuntimeError: There is no current event loop...'
|
|
487
|
-
loop = asyncio.new_event_loop()
|
|
488
|
-
asyncio.set_event_loop(loop)
|
|
489
|
-
return loop.run_until_complete(self.run_async(inputs))
|
|
248
|
+
@classmethod
|
|
249
|
+
def from_dict(cls: type[T], data: dict[str, Any]) -> T:
|
|
250
|
+
"""Deserialize from dictionary using unified component deserialization."""
|
|
251
|
+
return FlockAgentSerialization.from_dict(cls, data)
|
|
490
252
|
|
|
491
253
|
def set_model(self, model: str):
|
|
492
|
-
"""Set the model for the agent and its evaluator.
|
|
254
|
+
"""Set the model for the agent and its evaluator.
|
|
255
|
+
|
|
256
|
+
This method updates both the agent's model property and propagates
|
|
257
|
+
the model to the evaluator component if it has a config with a model field.
|
|
258
|
+
"""
|
|
493
259
|
self.model = model
|
|
494
260
|
if self.evaluator and hasattr(self.evaluator, "config"):
|
|
495
261
|
self.evaluator.config.model = model
|
|
@@ -505,654 +271,51 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
505
271
|
f"Agent '{self.name}' has no evaluator to set model for."
|
|
506
272
|
)
|
|
507
273
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
span.set_attribute("inputs", str(inputs))
|
|
513
|
-
try:
|
|
514
|
-
await self.initialize(inputs)
|
|
515
|
-
result = await self.evaluate(inputs)
|
|
516
|
-
await self.terminate(inputs, result)
|
|
517
|
-
span.set_attribute("result", str(result))
|
|
518
|
-
logger.info("Agent run completed", agent=self.name)
|
|
519
|
-
return result
|
|
520
|
-
except Exception as run_error:
|
|
521
|
-
logger.error(
|
|
522
|
-
"Error running agent", agent=self.name, error=str(run_error)
|
|
523
|
-
)
|
|
524
|
-
if "evaluate" not in str(
|
|
525
|
-
run_error
|
|
526
|
-
): # Simple check, might need refinement
|
|
527
|
-
await self.on_error(run_error, inputs)
|
|
528
|
-
logger.error(
|
|
529
|
-
f"Agent '{self.name}' run failed: {run_error}",
|
|
530
|
-
exc_info=True,
|
|
531
|
-
)
|
|
532
|
-
span.record_exception(run_error)
|
|
533
|
-
raise # Re-raise after handling
|
|
534
|
-
|
|
535
|
-
async def run_temporal(self, inputs: dict[str, Any]) -> dict[str, Any]:
|
|
536
|
-
with tracer.start_as_current_span("agent.run_temporal") as span:
|
|
537
|
-
span.set_attribute("agent.name", self.name)
|
|
538
|
-
span.set_attribute("inputs", str(inputs))
|
|
539
|
-
try:
|
|
540
|
-
from temporalio.client import Client
|
|
541
|
-
|
|
542
|
-
from flock.workflow.agent_activities import (
|
|
543
|
-
run_flock_agent_activity,
|
|
544
|
-
)
|
|
545
|
-
from flock.workflow.temporal_setup import run_activity
|
|
546
|
-
|
|
547
|
-
client = await Client.connect(
|
|
548
|
-
"localhost:7233", namespace="default"
|
|
549
|
-
)
|
|
550
|
-
agent_data = self.to_dict()
|
|
551
|
-
inputs_data = inputs
|
|
552
|
-
|
|
553
|
-
result = await run_activity(
|
|
554
|
-
client,
|
|
555
|
-
self.name,
|
|
556
|
-
run_flock_agent_activity,
|
|
557
|
-
{"agent_data": agent_data, "inputs": inputs_data},
|
|
558
|
-
)
|
|
559
|
-
span.set_attribute("result", str(result))
|
|
560
|
-
logger.info("Temporal run successful", agent=self.name)
|
|
561
|
-
return result
|
|
562
|
-
except Exception as temporal_error:
|
|
563
|
-
logger.error(
|
|
564
|
-
"Error in Temporal workflow",
|
|
565
|
-
agent=self.name,
|
|
566
|
-
error=str(temporal_error),
|
|
567
|
-
)
|
|
568
|
-
span.record_exception(temporal_error)
|
|
569
|
-
raise
|
|
570
|
-
|
|
571
|
-
def add_component(
|
|
572
|
-
self,
|
|
573
|
-
config_instance: FlockModuleConfig
|
|
574
|
-
| FlockRouterConfig
|
|
575
|
-
| FlockEvaluatorConfig,
|
|
576
|
-
component_name: str | None = None,
|
|
577
|
-
) -> "FlockAgent":
|
|
578
|
-
"""Adds or replaces a component (Evaluator, Router, Module) based on its configuration object.
|
|
579
|
-
|
|
580
|
-
Args:
|
|
581
|
-
config_instance: An instance of a config class inheriting from
|
|
582
|
-
FlockModuleConfig, FlockRouterConfig, or FlockEvaluatorConfig.
|
|
583
|
-
component_name: Explicit name for the component (required for Modules if not in config).
|
|
584
|
-
|
|
585
|
-
Returns:
|
|
586
|
-
self for potential chaining.
|
|
587
|
-
"""
|
|
588
|
-
from flock.core.flock_registry import get_registry
|
|
589
|
-
|
|
590
|
-
config_type = type(config_instance)
|
|
591
|
-
registry = get_registry() # Get registry instance
|
|
592
|
-
logger.debug(
|
|
593
|
-
f"Attempting to add component via config: {config_type.__name__}"
|
|
594
|
-
)
|
|
595
|
-
|
|
596
|
-
# --- 1. Find Component Class using Registry Map ---
|
|
597
|
-
ComponentClass = registry.get_component_class_for_config(config_type)
|
|
598
|
-
|
|
599
|
-
if not ComponentClass:
|
|
600
|
-
logger.error(
|
|
601
|
-
f"No component class registered for config type {config_type.__name__}. Use @flock_component(config_class=...) on the component."
|
|
602
|
-
)
|
|
603
|
-
raise TypeError(
|
|
604
|
-
f"Cannot find component class for config {config_type.__name__}"
|
|
605
|
-
)
|
|
606
|
-
|
|
607
|
-
component_class_name = ComponentClass.__name__
|
|
608
|
-
logger.debug(
|
|
609
|
-
f"Found component class '{component_class_name}' mapped to config '{config_type.__name__}'"
|
|
610
|
-
)
|
|
611
|
-
|
|
612
|
-
# --- 2. Determine Assignment Target and Name (Same as before) ---
|
|
613
|
-
instance_name = component_name
|
|
614
|
-
attribute_name: str = ""
|
|
615
|
-
|
|
616
|
-
if issubclass(ComponentClass, FlockEvaluator):
|
|
617
|
-
attribute_name = "evaluator"
|
|
618
|
-
if not instance_name:
|
|
619
|
-
instance_name = getattr(
|
|
620
|
-
config_instance, "name", component_class_name.lower()
|
|
621
|
-
)
|
|
622
|
-
|
|
623
|
-
elif issubclass(ComponentClass, FlockRouter):
|
|
624
|
-
attribute_name = "handoff_router"
|
|
625
|
-
if not instance_name:
|
|
626
|
-
instance_name = getattr(
|
|
627
|
-
config_instance, "name", component_class_name.lower()
|
|
628
|
-
)
|
|
629
|
-
|
|
630
|
-
elif issubclass(ComponentClass, FlockModule):
|
|
631
|
-
attribute_name = "modules"
|
|
632
|
-
if not instance_name:
|
|
633
|
-
instance_name = getattr(
|
|
634
|
-
config_instance, "name", component_class_name.lower()
|
|
635
|
-
)
|
|
636
|
-
if not instance_name:
|
|
637
|
-
raise ValueError(
|
|
638
|
-
"Module name must be provided either in config or as component_name argument."
|
|
639
|
-
)
|
|
640
|
-
# Ensure config has name if module expects it
|
|
641
|
-
if hasattr(config_instance, "name") and not getattr(
|
|
642
|
-
config_instance, "name", None
|
|
643
|
-
):
|
|
644
|
-
setattr(config_instance, "name", instance_name)
|
|
274
|
+
@property
|
|
275
|
+
def description(self) -> str | None:
|
|
276
|
+
"""Returns the resolved agent description."""
|
|
277
|
+
return self._integration.resolve_description(self.context)
|
|
645
278
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
279
|
+
@property
|
|
280
|
+
def input(self) -> str | None:
|
|
281
|
+
"""Returns the resolved agent input."""
|
|
282
|
+
return self._integration.resolve_input(self.context)
|
|
650
283
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
284
|
+
@property
|
|
285
|
+
def output(self) -> str | None:
|
|
286
|
+
"""Returns the resolved agent output."""
|
|
287
|
+
return self._integration.resolve_output(self.context)
|
|
654
288
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
exc_info=True,
|
|
660
|
-
)
|
|
661
|
-
raise RuntimeError(f"Component instantiation failed: {e}") from e
|
|
289
|
+
@property
|
|
290
|
+
def next_agent(self) -> str | None:
|
|
291
|
+
"""Returns the resolved agent next agent."""
|
|
292
|
+
return self._integration.resolve_next_agent(self.context)
|
|
662
293
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
self.modules = {}
|
|
667
|
-
self.modules[instance_name] = component_instance
|
|
668
|
-
logger.info(
|
|
669
|
-
f"Added/Updated module '{instance_name}' (type: {ComponentClass.__name__}) to agent '{self.name}'"
|
|
670
|
-
)
|
|
671
|
-
else:
|
|
672
|
-
setattr(self, attribute_name, component_instance)
|
|
673
|
-
logger.info(
|
|
674
|
-
f"Set {attribute_name} to {ComponentClass.__name__} (instance name: '{instance_name}') for agent '{self.name}'"
|
|
675
|
-
)
|
|
294
|
+
@description.setter
|
|
295
|
+
def description(self, value: DynamicStr) -> None:
|
|
296
|
+
self.description_spec = value
|
|
676
297
|
|
|
677
|
-
|
|
298
|
+
@input.setter
|
|
299
|
+
def input(self, value: DynamicStr) -> None:
|
|
300
|
+
self.input_spec = value
|
|
678
301
|
|
|
679
|
-
|
|
680
|
-
def
|
|
681
|
-
|
|
682
|
-
if callable(self.description):
|
|
683
|
-
self.description = self.description(
|
|
684
|
-
context
|
|
685
|
-
) # Pass context if needed by callable
|
|
686
|
-
if callable(self.input):
|
|
687
|
-
self.input = self.input(context)
|
|
688
|
-
if callable(self.output):
|
|
689
|
-
self.output = self.output(context)
|
|
302
|
+
@output.setter
|
|
303
|
+
def output(self, value: DynamicStr) -> None:
|
|
304
|
+
self.output_spec = value
|
|
690
305
|
|
|
691
|
-
|
|
306
|
+
@next_agent.setter
|
|
307
|
+
def next_agent(self, value: DynamicStr) -> None:
|
|
308
|
+
self.next_agent_spec = value
|
|
692
309
|
|
|
693
310
|
def _save_output(self, agent_name: str, result: dict[str, Any]) -> None:
|
|
694
|
-
"""Save output to file if configured."""
|
|
695
|
-
|
|
696
|
-
return
|
|
697
|
-
|
|
698
|
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
699
|
-
filename = f"{agent_name}_output_{timestamp}.json"
|
|
700
|
-
filepath = os.path.join(".flock/output/", filename)
|
|
701
|
-
os.makedirs(".flock/output/", exist_ok=True)
|
|
702
|
-
|
|
703
|
-
output_data = {
|
|
704
|
-
"agent": agent_name,
|
|
705
|
-
"timestamp": timestamp,
|
|
706
|
-
"output": result,
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
try:
|
|
710
|
-
with open(filepath, "w") as f:
|
|
711
|
-
json.dump(output_data, f, indent=2, cls=FlockJSONEncoder)
|
|
712
|
-
except Exception as e:
|
|
713
|
-
logger.warning(f"Failed to save output to file: {e}")
|
|
714
|
-
|
|
715
|
-
def to_dict(self) -> dict[str, Any]:
|
|
716
|
-
"""Convert instance to dictionary representation suitable for serialization."""
|
|
717
|
-
from flock.core.flock_registry import get_registry
|
|
718
|
-
|
|
719
|
-
FlockRegistry = get_registry()
|
|
720
|
-
|
|
721
|
-
exclude = [
|
|
722
|
-
"context",
|
|
723
|
-
"evaluator",
|
|
724
|
-
"modules",
|
|
725
|
-
"handoff_router",
|
|
726
|
-
"tools",
|
|
727
|
-
"servers",
|
|
728
|
-
]
|
|
729
|
-
|
|
730
|
-
is_descrition_callable = False
|
|
731
|
-
is_input_callable = False
|
|
732
|
-
is_output_callable = False
|
|
733
|
-
|
|
734
|
-
# if self.description is a callable, exclude it
|
|
735
|
-
if callable(self.description):
|
|
736
|
-
is_descrition_callable = True
|
|
737
|
-
exclude.append("description")
|
|
738
|
-
# if self.input is a callable, exclude it
|
|
739
|
-
if callable(self.input):
|
|
740
|
-
is_input_callable = True
|
|
741
|
-
exclude.append("input")
|
|
742
|
-
# if self.output is a callable, exclude it
|
|
743
|
-
if callable(self.output):
|
|
744
|
-
is_output_callable = True
|
|
745
|
-
exclude.append("output")
|
|
746
|
-
|
|
747
|
-
logger.debug(f"Serializing agent '{self.name}' to dict.")
|
|
748
|
-
# Use Pydantic's dump, exclude manually handled fields and runtime context
|
|
749
|
-
data = self.model_dump(
|
|
750
|
-
exclude=exclude,
|
|
751
|
-
mode="json", # Use json mode for better handling of standard types by Pydantic
|
|
752
|
-
exclude_none=True, # Exclude None values for cleaner output
|
|
753
|
-
)
|
|
754
|
-
logger.debug(f"Base agent data for '{self.name}': {list(data.keys())}")
|
|
755
|
-
serialized_modules = {}
|
|
756
|
-
|
|
757
|
-
def add_serialized_component(component: Any, field_name: str):
|
|
758
|
-
if component:
|
|
759
|
-
comp_type = type(component)
|
|
760
|
-
type_name = FlockRegistry.get_component_type_name(
|
|
761
|
-
comp_type
|
|
762
|
-
) # Get registered name
|
|
763
|
-
if type_name:
|
|
764
|
-
try:
|
|
765
|
-
serialized_component_data = serialize_item(component)
|
|
766
|
-
|
|
767
|
-
if not isinstance(serialized_component_data, dict):
|
|
768
|
-
logger.error(
|
|
769
|
-
f"Serialization of component {type_name} for field '{field_name}' did not result in a dictionary. Got: {type(serialized_component_data)}"
|
|
770
|
-
)
|
|
771
|
-
serialized_modules[field_name] = {
|
|
772
|
-
"type": type_name,
|
|
773
|
-
"name": getattr(component, "name", "unknown"),
|
|
774
|
-
"error": "serialization_failed_non_dict",
|
|
775
|
-
}
|
|
776
|
-
else:
|
|
777
|
-
serialized_component_data["type"] = type_name
|
|
778
|
-
serialized_modules[field_name] = (
|
|
779
|
-
serialized_component_data
|
|
780
|
-
)
|
|
781
|
-
logger.debug(
|
|
782
|
-
f"Successfully serialized component for field '{field_name}' (type: {type_name})"
|
|
783
|
-
)
|
|
784
|
-
|
|
785
|
-
except Exception as e:
|
|
786
|
-
logger.error(
|
|
787
|
-
f"Failed to serialize component {type_name} for field '{field_name}': {e}",
|
|
788
|
-
exc_info=True,
|
|
789
|
-
)
|
|
790
|
-
serialized_modules[field_name] = {
|
|
791
|
-
"type": type_name,
|
|
792
|
-
"name": getattr(component, "name", "unknown"),
|
|
793
|
-
"error": "serialization_failed",
|
|
794
|
-
}
|
|
795
|
-
else:
|
|
796
|
-
logger.warning(
|
|
797
|
-
f"Cannot serialize unregistered component {comp_type.__name__} for field '{field_name}'"
|
|
798
|
-
)
|
|
799
|
-
|
|
800
|
-
add_serialized_component(self.evaluator, "evaluator")
|
|
801
|
-
if serialized_modules:
|
|
802
|
-
data["evaluator"] = serialized_modules["evaluator"]
|
|
803
|
-
logger.debug(f"Added evaluator to agent '{self.name}'")
|
|
804
|
-
|
|
805
|
-
serialized_modules = {}
|
|
806
|
-
add_serialized_component(self.handoff_router, "handoff_router")
|
|
807
|
-
if serialized_modules:
|
|
808
|
-
data["handoff_router"] = serialized_modules["handoff_router"]
|
|
809
|
-
logger.debug(f"Added handoff_router to agent '{self.name}'")
|
|
810
|
-
|
|
811
|
-
serialized_modules = {}
|
|
812
|
-
for module in self.modules.values():
|
|
813
|
-
add_serialized_component(module, module.name)
|
|
814
|
-
|
|
815
|
-
if serialized_modules:
|
|
816
|
-
data["modules"] = serialized_modules
|
|
817
|
-
logger.debug(
|
|
818
|
-
f"Added {len(serialized_modules)} modules to agent '{self.name}'"
|
|
819
|
-
)
|
|
820
|
-
|
|
821
|
-
# --- Serialize Servers ---
|
|
822
|
-
if self.servers:
|
|
823
|
-
logger.debug(
|
|
824
|
-
f"Serializing {len(self.servers)} servers for agent '{self.name}'"
|
|
825
|
-
)
|
|
826
|
-
serialized_servers = []
|
|
827
|
-
for server in self.servers:
|
|
828
|
-
if isinstance(server, FlockMCPServerBase):
|
|
829
|
-
serialized_servers.append(server.config.name)
|
|
830
|
-
else:
|
|
831
|
-
# Write it down as a list of server names.
|
|
832
|
-
serialized_servers.append(server)
|
|
833
|
-
|
|
834
|
-
if serialized_servers:
|
|
835
|
-
data["mcp_servers"] = serialized_servers
|
|
836
|
-
logger.debug(
|
|
837
|
-
f"Added {len(serialized_servers)} servers to agent '{self.name}'"
|
|
838
|
-
)
|
|
839
|
-
|
|
840
|
-
# --- Serialize Tools (Callables) ---
|
|
841
|
-
if self.tools:
|
|
842
|
-
logger.debug(
|
|
843
|
-
f"Serializing {len(self.tools)} tools for agent '{self.name}'"
|
|
844
|
-
)
|
|
845
|
-
serialized_tools = []
|
|
846
|
-
for tool in self.tools:
|
|
847
|
-
if callable(tool) and not isinstance(tool, type):
|
|
848
|
-
path_str = FlockRegistry.get_callable_path_string(tool)
|
|
849
|
-
if path_str:
|
|
850
|
-
# Get just the function name from the path string
|
|
851
|
-
# If it's a namespaced path like module.submodule.function_name
|
|
852
|
-
# Just use the function_name part
|
|
853
|
-
func_name = path_str.split(".")[-1]
|
|
854
|
-
serialized_tools.append(func_name)
|
|
855
|
-
logger.debug(
|
|
856
|
-
f"Added tool '{func_name}' (from path '{path_str}') to agent '{self.name}'"
|
|
857
|
-
)
|
|
858
|
-
else:
|
|
859
|
-
logger.warning(
|
|
860
|
-
f"Could not get path string for tool {tool} in agent '{self.name}'. Skipping."
|
|
861
|
-
)
|
|
862
|
-
else:
|
|
863
|
-
logger.warning(
|
|
864
|
-
f"Non-callable item found in tools list for agent '{self.name}': {tool}. Skipping."
|
|
865
|
-
)
|
|
866
|
-
if serialized_tools:
|
|
867
|
-
data["tools"] = serialized_tools
|
|
868
|
-
logger.debug(
|
|
869
|
-
f"Added {len(serialized_tools)} tools to agent '{self.name}'"
|
|
870
|
-
)
|
|
871
|
-
|
|
872
|
-
if is_descrition_callable:
|
|
873
|
-
path_str = FlockRegistry.get_callable_path_string(self.description)
|
|
874
|
-
if path_str:
|
|
875
|
-
func_name = path_str.split(".")[-1]
|
|
876
|
-
data["description_callable"] = func_name
|
|
877
|
-
logger.debug(
|
|
878
|
-
f"Added description '{func_name}' (from path '{path_str}') to agent '{self.name}'"
|
|
879
|
-
)
|
|
880
|
-
else:
|
|
881
|
-
logger.warning(
|
|
882
|
-
f"Could not get path string for description {self.description} in agent '{self.name}'. Skipping."
|
|
883
|
-
)
|
|
884
|
-
|
|
885
|
-
if is_input_callable:
|
|
886
|
-
path_str = FlockRegistry.get_callable_path_string(self.input)
|
|
887
|
-
if path_str:
|
|
888
|
-
func_name = path_str.split(".")[-1]
|
|
889
|
-
data["input_callable"] = func_name
|
|
890
|
-
logger.debug(
|
|
891
|
-
f"Added input '{func_name}' (from path '{path_str}') to agent '{self.name}'"
|
|
892
|
-
)
|
|
893
|
-
else:
|
|
894
|
-
logger.warning(
|
|
895
|
-
f"Could not get path string for input {self.input} in agent '{self.name}'. Skipping."
|
|
896
|
-
)
|
|
897
|
-
|
|
898
|
-
if is_output_callable:
|
|
899
|
-
path_str = FlockRegistry.get_callable_path_string(self.output)
|
|
900
|
-
if path_str:
|
|
901
|
-
func_name = path_str.split(".")[-1]
|
|
902
|
-
data["output_callable"] = func_name
|
|
903
|
-
logger.debug(
|
|
904
|
-
f"Added output '{func_name}' (from path '{path_str}') to agent '{self.name}'"
|
|
905
|
-
)
|
|
906
|
-
else:
|
|
907
|
-
logger.warning(
|
|
908
|
-
f"Could not get path string for output {self.output} in agent '{self.name}'. Skipping."
|
|
909
|
-
)
|
|
910
|
-
|
|
911
|
-
# No need to call _filter_none_values here as model_dump(exclude_none=True) handles it
|
|
912
|
-
logger.info(
|
|
913
|
-
f"Serialization of agent '{self.name}' complete with {len(data)} fields"
|
|
914
|
-
)
|
|
915
|
-
return data
|
|
916
|
-
|
|
917
|
-
@classmethod
|
|
918
|
-
def from_dict(cls: type[T], data: dict[str, Any]) -> T:
|
|
919
|
-
"""Deserialize the agent from a dictionary, including components, tools, and callables."""
|
|
920
|
-
from flock.core.flock_registry import (
|
|
921
|
-
get_registry, # Import registry locally
|
|
922
|
-
)
|
|
923
|
-
|
|
924
|
-
registry = get_registry()
|
|
925
|
-
logger.debug(
|
|
926
|
-
f"Deserializing agent from dict. Keys: {list(data.keys())}"
|
|
927
|
-
)
|
|
928
|
-
|
|
929
|
-
# --- Separate Data ---
|
|
930
|
-
component_configs = {}
|
|
931
|
-
callable_configs = {}
|
|
932
|
-
tool_config = []
|
|
933
|
-
servers_config = []
|
|
934
|
-
agent_data = {}
|
|
935
|
-
|
|
936
|
-
component_keys = [
|
|
937
|
-
"evaluator",
|
|
938
|
-
"handoff_router",
|
|
939
|
-
"modules",
|
|
940
|
-
"temporal_activity_config",
|
|
941
|
-
]
|
|
942
|
-
callable_keys = [
|
|
943
|
-
"description_callable",
|
|
944
|
-
"input_callable",
|
|
945
|
-
"output_callable",
|
|
946
|
-
]
|
|
947
|
-
tool_key = "tools"
|
|
948
|
-
|
|
949
|
-
servers_key = "mcp_servers"
|
|
950
|
-
|
|
951
|
-
for key, value in data.items():
|
|
952
|
-
if key in component_keys and value is not None:
|
|
953
|
-
component_configs[key] = value
|
|
954
|
-
elif key in callable_keys and value is not None:
|
|
955
|
-
callable_configs[key] = value
|
|
956
|
-
elif key == tool_key and value is not None:
|
|
957
|
-
tool_config = value # Expecting a list of names
|
|
958
|
-
elif key == servers_key and value is not None:
|
|
959
|
-
servers_config = value # Expecting a list of names
|
|
960
|
-
elif key not in component_keys + callable_keys + [
|
|
961
|
-
tool_key,
|
|
962
|
-
servers_key,
|
|
963
|
-
]: # Avoid double adding
|
|
964
|
-
agent_data[key] = value
|
|
965
|
-
# else: ignore keys that are None or already handled
|
|
966
|
-
|
|
967
|
-
# --- Deserialize Base Agent ---
|
|
968
|
-
# Ensure required fields like 'name' are present if needed by __init__
|
|
969
|
-
if "name" not in agent_data:
|
|
970
|
-
raise ValueError(
|
|
971
|
-
"Agent data must include a 'name' field for deserialization."
|
|
972
|
-
)
|
|
973
|
-
agent_name_log = agent_data["name"] # For logging
|
|
974
|
-
logger.info(f"Deserializing base agent data for '{agent_name_log}'")
|
|
975
|
-
|
|
976
|
-
# Pydantic should handle base fields based on type hints in __init__
|
|
977
|
-
agent = cls(**agent_data)
|
|
978
|
-
logger.debug(f"Base agent '{agent.name}' instantiated.")
|
|
979
|
-
|
|
980
|
-
# --- Deserialize Components ---
|
|
981
|
-
logger.debug(f"Deserializing components for '{agent.name}'")
|
|
982
|
-
# Evaluator
|
|
983
|
-
if "evaluator" in component_configs:
|
|
984
|
-
try:
|
|
985
|
-
agent.evaluator = deserialize_component(
|
|
986
|
-
component_configs["evaluator"], FlockEvaluator
|
|
987
|
-
)
|
|
988
|
-
logger.debug(f"Deserialized evaluator for '{agent.name}'")
|
|
989
|
-
except Exception as e:
|
|
990
|
-
logger.error(
|
|
991
|
-
f"Failed to deserialize evaluator for '{agent.name}': {e}",
|
|
992
|
-
exc_info=True,
|
|
993
|
-
)
|
|
994
|
-
|
|
995
|
-
# Handoff Router
|
|
996
|
-
if "handoff_router" in component_configs:
|
|
997
|
-
try:
|
|
998
|
-
agent.handoff_router = deserialize_component(
|
|
999
|
-
component_configs["handoff_router"], FlockRouter
|
|
1000
|
-
)
|
|
1001
|
-
logger.debug(f"Deserialized handoff_router for '{agent.name}'")
|
|
1002
|
-
except Exception as e:
|
|
1003
|
-
logger.error(
|
|
1004
|
-
f"Failed to deserialize handoff_router for '{agent.name}': {e}",
|
|
1005
|
-
exc_info=True,
|
|
1006
|
-
)
|
|
1007
|
-
|
|
1008
|
-
# Modules
|
|
1009
|
-
if "modules" in component_configs:
|
|
1010
|
-
agent.modules = {} # Initialize
|
|
1011
|
-
for module_name, module_data in component_configs[
|
|
1012
|
-
"modules"
|
|
1013
|
-
].items():
|
|
1014
|
-
try:
|
|
1015
|
-
module_instance = deserialize_component(
|
|
1016
|
-
module_data, FlockModule
|
|
1017
|
-
)
|
|
1018
|
-
if module_instance:
|
|
1019
|
-
# Use add_module for potential logic within it
|
|
1020
|
-
agent.add_module(module_instance)
|
|
1021
|
-
logger.debug(
|
|
1022
|
-
f"Deserialized and added module '{module_name}' for '{agent.name}'"
|
|
1023
|
-
)
|
|
1024
|
-
except Exception as e:
|
|
1025
|
-
logger.error(
|
|
1026
|
-
f"Failed to deserialize module '{module_name}' for '{agent.name}': {e}",
|
|
1027
|
-
exc_info=True,
|
|
1028
|
-
)
|
|
1029
|
-
|
|
1030
|
-
# Temporal Activity Config
|
|
1031
|
-
if "temporal_activity_config" in component_configs:
|
|
1032
|
-
try:
|
|
1033
|
-
agent.temporal_activity_config = TemporalActivityConfig(
|
|
1034
|
-
**component_configs["temporal_activity_config"]
|
|
1035
|
-
)
|
|
1036
|
-
logger.debug(
|
|
1037
|
-
f"Deserialized temporal_activity_config for '{agent.name}'"
|
|
1038
|
-
)
|
|
1039
|
-
except Exception as e:
|
|
1040
|
-
logger.error(
|
|
1041
|
-
f"Failed to deserialize temporal_activity_config for '{agent.name}': {e}",
|
|
1042
|
-
exc_info=True,
|
|
1043
|
-
)
|
|
1044
|
-
agent.temporal_activity_config = None
|
|
1045
|
-
|
|
1046
|
-
# --- Deserialize Tools ---
|
|
1047
|
-
agent.tools = [] # Initialize tools list
|
|
1048
|
-
if tool_config:
|
|
1049
|
-
logger.debug(
|
|
1050
|
-
f"Deserializing {len(tool_config)} tools for '{agent.name}'"
|
|
1051
|
-
)
|
|
1052
|
-
# Use get_callable to find each tool
|
|
1053
|
-
for tool_name_or_path in tool_config:
|
|
1054
|
-
try:
|
|
1055
|
-
found_tool = registry.get_callable(tool_name_or_path)
|
|
1056
|
-
if found_tool and callable(found_tool):
|
|
1057
|
-
agent.tools.append(found_tool)
|
|
1058
|
-
logger.debug(
|
|
1059
|
-
f"Resolved and added tool '{tool_name_or_path}' for agent '{agent.name}'"
|
|
1060
|
-
)
|
|
1061
|
-
else:
|
|
1062
|
-
# Should not happen if get_callable returns successfully but just in case
|
|
1063
|
-
logger.warning(
|
|
1064
|
-
f"Registry returned non-callable for tool '{tool_name_or_path}' for agent '{agent.name}'. Skipping."
|
|
1065
|
-
)
|
|
1066
|
-
except (
|
|
1067
|
-
ValueError
|
|
1068
|
-
) as e: # get_callable raises ValueError if not found/ambiguous
|
|
1069
|
-
logger.warning(
|
|
1070
|
-
f"Could not resolve tool '{tool_name_or_path}' for agent '{agent.name}': {e}. Skipping."
|
|
1071
|
-
)
|
|
1072
|
-
except Exception as e:
|
|
1073
|
-
logger.error(
|
|
1074
|
-
f"Unexpected error resolving tool '{tool_name_or_path}' for agent '{agent.name}': {e}. Skipping.",
|
|
1075
|
-
exc_info=True,
|
|
1076
|
-
)
|
|
1077
|
-
|
|
1078
|
-
# --- Deserialize Servers ---
|
|
1079
|
-
agent.servers = [] # Initialize Servers list.
|
|
1080
|
-
if servers_config:
|
|
1081
|
-
logger.debug(
|
|
1082
|
-
f"Deserializing {len(servers_config)} servers for '{agent.name}'"
|
|
1083
|
-
)
|
|
1084
|
-
# Agents keep track of server by getting a list of server names.
|
|
1085
|
-
# The server instances will be retrieved during runtime from the registry. (default behavior)
|
|
1086
|
-
|
|
1087
|
-
for server_name in servers_config:
|
|
1088
|
-
if isinstance(server_name, str):
|
|
1089
|
-
# Case 1 (default behavior): A server name is passe.
|
|
1090
|
-
agent.servers.append(server_name)
|
|
1091
|
-
elif isinstance(server_name, FlockMCPServerBase):
|
|
1092
|
-
# Case 2 (highly unlikely): If someone somehow manages to pass
|
|
1093
|
-
# an instance of a server during the deserialization step (however that might be achieved)
|
|
1094
|
-
# check the registry, if the server is already registered, if not, register it
|
|
1095
|
-
# and store the name in the servers list
|
|
1096
|
-
FlockRegistry = get_registry()
|
|
1097
|
-
server_exists = (
|
|
1098
|
-
FlockRegistry.get_server(server_name.config.name)
|
|
1099
|
-
is not None
|
|
1100
|
-
)
|
|
1101
|
-
if server_exists:
|
|
1102
|
-
agent.servers.append(server_name.config.name)
|
|
1103
|
-
else:
|
|
1104
|
-
FlockRegistry.register_server(
|
|
1105
|
-
server=server_name
|
|
1106
|
-
) # register it.
|
|
1107
|
-
agent.servers.append(server_name.config.name)
|
|
1108
|
-
|
|
1109
|
-
# --- Deserialize Callables ---
|
|
1110
|
-
logger.debug(f"Deserializing callable fields for '{agent.name}'")
|
|
1111
|
-
# available_callables = registry.get_all_callables() # Incorrect
|
|
1112
|
-
|
|
1113
|
-
def resolve_and_assign(field_name: str, callable_key: str):
|
|
1114
|
-
if callable_key in callable_configs:
|
|
1115
|
-
callable_name = callable_configs[callable_key]
|
|
1116
|
-
try:
|
|
1117
|
-
# Use get_callable to find the signature function
|
|
1118
|
-
found_callable = registry.get_callable(callable_name)
|
|
1119
|
-
if found_callable and callable(found_callable):
|
|
1120
|
-
setattr(agent, field_name, found_callable)
|
|
1121
|
-
logger.debug(
|
|
1122
|
-
f"Resolved callable '{callable_name}' for field '{field_name}' on agent '{agent.name}'"
|
|
1123
|
-
)
|
|
1124
|
-
else:
|
|
1125
|
-
logger.warning(
|
|
1126
|
-
f"Registry returned non-callable for name '{callable_name}' for field '{field_name}' on agent '{agent.name}'. Field remains default."
|
|
1127
|
-
)
|
|
1128
|
-
except (
|
|
1129
|
-
ValueError
|
|
1130
|
-
) as e: # get_callable raises ValueError if not found/ambiguous
|
|
1131
|
-
logger.warning(
|
|
1132
|
-
f"Could not resolve callable '{callable_name}' in registry for field '{field_name}' on agent '{agent.name}': {e}. Field remains default."
|
|
1133
|
-
)
|
|
1134
|
-
except Exception as e:
|
|
1135
|
-
logger.error(
|
|
1136
|
-
f"Unexpected error resolving callable '{callable_name}' for field '{field_name}' on agent '{agent.name}': {e}. Field remains default.",
|
|
1137
|
-
exc_info=True,
|
|
1138
|
-
)
|
|
1139
|
-
# Else: key not present, field retains its default value from __init__
|
|
1140
|
-
|
|
1141
|
-
resolve_and_assign("description", "description_callable")
|
|
1142
|
-
resolve_and_assign("input", "input_callable")
|
|
1143
|
-
resolve_and_assign("output", "output_callable")
|
|
1144
|
-
|
|
1145
|
-
logger.info(f"Successfully deserialized agent '{agent.name}'.")
|
|
1146
|
-
return agent
|
|
311
|
+
"""Save output to file if configured (delegated to serialization)."""
|
|
312
|
+
return self._serialization._save_output(agent_name, result)
|
|
1147
313
|
|
|
1148
314
|
# --- Pydantic v2 Configuration ---
|
|
1149
|
-
|
|
1150
|
-
arbitrary_types_allowed
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
#
|
|
1154
|
-
#
|
|
1155
|
-
|
|
1156
|
-
# FlockRouter: lambda v: v.to_dict() if v else None,
|
|
1157
|
-
# FlockModule: lambda v: v.to_dict() if v else None,
|
|
1158
|
-
# }
|
|
315
|
+
model_config = {
|
|
316
|
+
"arbitrary_types_allowed": True,
|
|
317
|
+
"populate_by_name": True,
|
|
318
|
+
# "json_encoders": {
|
|
319
|
+
# Callable: lambda f: f"{f.__module__}.{f.__qualname__}",
|
|
320
|
+
# },
|
|
321
|
+
}
|