flock-core 0.5.0b1__py3-none-any.whl → 0.5.0b3__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/evaluation/declarative_evaluation_component.py +10 -10
- flock/components/routing/conditional_routing_component.py +7 -6
- flock/components/routing/default_routing_component.py +3 -3
- flock/components/routing/llm_routing_component.py +24 -26
- flock/components/utility/memory_utility_component.py +3 -3
- flock/components/utility/metrics_utility_component.py +11 -11
- flock/components/utility/output_utility_component.py +11 -9
- flock/core/__init__.py +24 -10
- flock/core/agent/flock_agent_components.py +16 -16
- flock/core/agent/flock_agent_integration.py +88 -29
- flock/core/agent/flock_agent_serialization.py +23 -20
- flock/core/api/endpoints.py +1 -1
- flock/core/component/__init__.py +7 -7
- flock/core/component/{evaluation_component_base.py → evaluation_component.py} +2 -2
- flock/core/component/{routing_component_base.py → routing_component.py} +3 -4
- flock/core/component/{utility_component_base.py → utility_component.py} +3 -3
- flock/core/flock.py +7 -7
- flock/core/flock_agent.py +68 -38
- flock/core/flock_factory.py +21 -18
- flock/core/flock_server_manager.py +8 -8
- flock/core/mcp/flock_mcp_server.py +11 -11
- 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/orchestration/flock_execution.py +3 -3
- flock/core/orchestration/flock_initialization.py +6 -6
- flock/core/orchestration/flock_server_manager.py +8 -6
- flock/core/registry/__init__.py +16 -10
- flock/core/registry/registry_hub.py +7 -4
- flock/core/registry/server_registry.py +6 -6
- flock/core/serialization/flock_serializer.py +3 -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/workflow/activities.py +10 -10
- {flock_core-0.5.0b1.dist-info → flock_core-0.5.0b3.dist-info}/METADATA +1 -1
- {flock_core-0.5.0b1.dist-info → flock_core-0.5.0b3.dist-info}/RECORD +43 -45
- flock/core/flock_registry.py.backup +0 -688
- flock/workflow/activities_unified.py +0 -230
- {flock_core-0.5.0b1.dist-info → flock_core-0.5.0b3.dist-info}/WHEEL +0 -0
- {flock_core-0.5.0b1.dist-info → flock_core-0.5.0b3.dist-info}/entry_points.txt +0 -0
- {flock_core-0.5.0b1.dist-info → flock_core-0.5.0b3.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,17 +1,80 @@
|
|
|
1
1
|
# src/flock/core/agent/flock_agent_integration.py
|
|
2
2
|
"""Tool and server integration functionality for FlockAgent."""
|
|
3
3
|
|
|
4
|
-
from
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from functools import wraps
|
|
6
|
+
from inspect import Parameter, signature
|
|
7
|
+
from typing import TYPE_CHECKING, Any, TypeVar, cast
|
|
5
8
|
|
|
6
9
|
from flock.core.context.context import FlockContext
|
|
7
10
|
from flock.core.logging.logging import get_logger
|
|
8
|
-
from flock.core.mcp.flock_mcp_server import
|
|
11
|
+
from flock.core.mcp.flock_mcp_server import FlockMCPServer
|
|
9
12
|
|
|
10
13
|
if TYPE_CHECKING:
|
|
11
14
|
from flock.core.flock_agent import FlockAgent
|
|
12
15
|
|
|
13
16
|
logger = get_logger("agent.integration")
|
|
14
17
|
|
|
18
|
+
R = TypeVar("R", bound=str)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def adapt(prop_name: str, fn: Callable[..., R]) -> Callable[[FlockContext], R]:
|
|
22
|
+
"""Coerce *fn* into the canonical ``(ctx: FlockContext) -> str`` form.
|
|
23
|
+
|
|
24
|
+
Acceptable signatures
|
|
25
|
+
---------------------
|
|
26
|
+
1. ``() -> str`` (no parameters)
|
|
27
|
+
2. ``(ctx: FlockContext) -> str`` (exactly one positional parameter)
|
|
28
|
+
|
|
29
|
+
Anything else raises ``TypeError``.
|
|
30
|
+
|
|
31
|
+
The wrapper also enforces at runtime that the result is ``str``.
|
|
32
|
+
"""
|
|
33
|
+
if not callable(fn):
|
|
34
|
+
raise TypeError(f"{prop_name} must be a callable, got {type(fn).__name__}")
|
|
35
|
+
|
|
36
|
+
sig = signature(fn)
|
|
37
|
+
params = list(sig.parameters.values())
|
|
38
|
+
|
|
39
|
+
def _validate_result(res: object) -> R:
|
|
40
|
+
if not isinstance(res, str):
|
|
41
|
+
raise TypeError(
|
|
42
|
+
f"{prop_name} callable must return str, got {type(res).__name__}"
|
|
43
|
+
)
|
|
44
|
+
return cast(R, res)
|
|
45
|
+
|
|
46
|
+
# ── Case 1: () -> str ────────────────────────────────────────────────────
|
|
47
|
+
if len(params) == 0:
|
|
48
|
+
|
|
49
|
+
@wraps(fn)
|
|
50
|
+
def _wrapped(ctx: FlockContext) -> R:
|
|
51
|
+
return _validate_result(fn())
|
|
52
|
+
|
|
53
|
+
return _wrapped
|
|
54
|
+
|
|
55
|
+
# ── Case 2: (ctx) -> str ────────────────────────────────────────────────
|
|
56
|
+
if len(params) == 1:
|
|
57
|
+
p: Parameter = params[0]
|
|
58
|
+
valid_kind = p.kind in (
|
|
59
|
+
Parameter.POSITIONAL_ONLY,
|
|
60
|
+
Parameter.POSITIONAL_OR_KEYWORD,
|
|
61
|
+
)
|
|
62
|
+
valid_annotation = p.annotation in (Parameter.empty, FlockContext)
|
|
63
|
+
has_no_default = p.default is Parameter.empty
|
|
64
|
+
|
|
65
|
+
if valid_kind and valid_annotation and has_no_default:
|
|
66
|
+
|
|
67
|
+
@wraps(fn)
|
|
68
|
+
def _wrapped(ctx: FlockContext) -> R:
|
|
69
|
+
return _validate_result(fn(ctx)) # type: ignore[arg-type]
|
|
70
|
+
|
|
71
|
+
return _wrapped
|
|
72
|
+
|
|
73
|
+
# ── Anything else: reject ───────────────────────────────────────────────
|
|
74
|
+
raise TypeError(
|
|
75
|
+
f"{prop_name} callable must be () -> str or (ctx: FlockContext) -> str; "
|
|
76
|
+
f"got signature {sig}"
|
|
77
|
+
)
|
|
15
78
|
|
|
16
79
|
class FlockAgentIntegration:
|
|
17
80
|
"""Handles tool and server integration for FlockAgent including MCP servers and callable tools."""
|
|
@@ -19,30 +82,26 @@ class FlockAgentIntegration:
|
|
|
19
82
|
def __init__(self, agent: "FlockAgent"):
|
|
20
83
|
self.agent = agent
|
|
21
84
|
|
|
22
|
-
def
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return None
|
|
43
|
-
elif isinstance(self.agent.description, str):
|
|
44
|
-
return self.agent.description
|
|
45
|
-
return None
|
|
85
|
+
def _resolve(self, raw: str | Callable[..., str], name: str, ctx: FlockContext | None) -> str | None:
|
|
86
|
+
if callable(raw):
|
|
87
|
+
raw = adapt(name, raw)(ctx or FlockContext())
|
|
88
|
+
return raw
|
|
89
|
+
|
|
90
|
+
def resolve_description(self, context: FlockContext | None = None) -> str | None:
|
|
91
|
+
"""Resolve the agent's description, handling callable descriptions."""
|
|
92
|
+
return self._resolve(self.agent.description_spec, "description", context)
|
|
93
|
+
|
|
94
|
+
def resolve_input(self, context: FlockContext | None = None) -> str | None:
|
|
95
|
+
"""Resolve the agent's input, handling callable inputs."""
|
|
96
|
+
return self._resolve(self.agent.input_spec, "input", context)
|
|
97
|
+
|
|
98
|
+
def resolve_output(self, context: FlockContext | None = None) -> str | None:
|
|
99
|
+
"""Resolve the agent's output, handling callable outputs."""
|
|
100
|
+
return self._resolve(self.agent.output_spec, "output", context)
|
|
101
|
+
|
|
102
|
+
def resolve_next_agent(self, context: FlockContext | None = None) -> str | None:
|
|
103
|
+
"""Resolve the next agent, handling callable next agents."""
|
|
104
|
+
return self._resolve(self.agent.next_agent_spec, "next_agent", context)
|
|
46
105
|
|
|
47
106
|
async def get_mcp_tools(self) -> list[Any]:
|
|
48
107
|
"""Get tools from registered MCP servers."""
|
|
@@ -52,9 +111,9 @@ class FlockAgentIntegration:
|
|
|
52
111
|
|
|
53
112
|
registry = get_registry() # Get the registry
|
|
54
113
|
for server in self.agent.servers:
|
|
55
|
-
registered_server:
|
|
114
|
+
registered_server: FlockMCPServer | None = None
|
|
56
115
|
server_tools = []
|
|
57
|
-
if isinstance(server,
|
|
116
|
+
if isinstance(server, FlockMCPServer):
|
|
58
117
|
# check if registered
|
|
59
118
|
server_name = server.config.name
|
|
60
119
|
registered_server = registry.get_server(
|
|
@@ -72,7 +131,7 @@ class FlockAgentIntegration:
|
|
|
72
131
|
)
|
|
73
132
|
else:
|
|
74
133
|
logger.warning(
|
|
75
|
-
f"No Server with name '{server.config.name if isinstance(server,
|
|
134
|
+
f"No Server with name '{server.config.name if isinstance(server, FlockMCPServer) else server}' registered! Skipping."
|
|
76
135
|
)
|
|
77
136
|
mcp_tools = mcp_tools + server_tools
|
|
78
137
|
return mcp_tools
|
|
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any, TypeVar
|
|
|
8
8
|
|
|
9
9
|
# Legacy component imports removed
|
|
10
10
|
from flock.core.logging.logging import get_logger
|
|
11
|
-
from flock.core.mcp.flock_mcp_server import
|
|
11
|
+
from flock.core.mcp.flock_mcp_server import FlockMCPServer
|
|
12
12
|
from flock.core.serialization.json_encoder import FlockJSONEncoder
|
|
13
13
|
from flock.core.serialization.serialization_utils import (
|
|
14
14
|
deserialize_component,
|
|
@@ -68,20 +68,20 @@ class FlockAgentSerialization:
|
|
|
68
68
|
is_output_callable = False
|
|
69
69
|
is_next_agent_callable = False
|
|
70
70
|
# if self.agent.description is a callable, exclude it
|
|
71
|
-
if callable(self.agent.
|
|
71
|
+
if callable(self.agent.description_spec):
|
|
72
72
|
is_description_callable = True
|
|
73
|
-
exclude.append("
|
|
73
|
+
exclude.append("description_spec")
|
|
74
74
|
# if self.agent.input is a callable, exclude it
|
|
75
|
-
if callable(self.agent.
|
|
75
|
+
if callable(self.agent.input_spec):
|
|
76
76
|
is_input_callable = True
|
|
77
|
-
exclude.append("
|
|
77
|
+
exclude.append("input_spec")
|
|
78
78
|
# if self.agent.output is a callable, exclude it
|
|
79
|
-
if callable(self.agent.
|
|
79
|
+
if callable(self.agent.output_spec):
|
|
80
80
|
is_output_callable = True
|
|
81
|
-
exclude.append("
|
|
82
|
-
if callable(self.agent.
|
|
81
|
+
exclude.append("output_spec")
|
|
82
|
+
if callable(self.agent.next_agent_spec):
|
|
83
83
|
is_next_agent_callable = True
|
|
84
|
-
exclude.append("
|
|
84
|
+
exclude.append("next_agent_spec")
|
|
85
85
|
|
|
86
86
|
logger.debug(f"Serializing agent '{self.agent.name}' to dict.")
|
|
87
87
|
# Use Pydantic's dump, exclude manually handled fields and runtime context
|
|
@@ -122,7 +122,7 @@ class FlockAgentSerialization:
|
|
|
122
122
|
)
|
|
123
123
|
serialized_servers = []
|
|
124
124
|
for server in self.agent.servers:
|
|
125
|
-
if isinstance(server,
|
|
125
|
+
if isinstance(server, FlockMCPServer):
|
|
126
126
|
serialized_servers.append(server.config.name)
|
|
127
127
|
else:
|
|
128
128
|
# Write it down as a list of server names.
|
|
@@ -167,7 +167,7 @@ class FlockAgentSerialization:
|
|
|
167
167
|
)
|
|
168
168
|
|
|
169
169
|
if is_description_callable:
|
|
170
|
-
path_str = registry.get_callable_path_string(self.agent.
|
|
170
|
+
path_str = registry.get_callable_path_string(self.agent.description_spec)
|
|
171
171
|
if path_str:
|
|
172
172
|
func_name = path_str.split(".")[-1]
|
|
173
173
|
data["description_callable"] = func_name
|
|
@@ -176,11 +176,11 @@ class FlockAgentSerialization:
|
|
|
176
176
|
)
|
|
177
177
|
else:
|
|
178
178
|
logger.warning(
|
|
179
|
-
f"Could not get path string for description {self.agent.
|
|
179
|
+
f"Could not get path string for description {self.agent.description_spec} in agent '{self.agent.name}'. Skipping."
|
|
180
180
|
)
|
|
181
181
|
|
|
182
182
|
if is_input_callable:
|
|
183
|
-
path_str = registry.get_callable_path_string(self.agent.
|
|
183
|
+
path_str = registry.get_callable_path_string(self.agent.input_spec)
|
|
184
184
|
if path_str:
|
|
185
185
|
func_name = path_str.split(".")[-1]
|
|
186
186
|
data["input_callable"] = func_name
|
|
@@ -189,11 +189,11 @@ class FlockAgentSerialization:
|
|
|
189
189
|
)
|
|
190
190
|
else:
|
|
191
191
|
logger.warning(
|
|
192
|
-
f"Could not get path string for input {self.agent.
|
|
192
|
+
f"Could not get path string for input {self.agent.input_spec} in agent '{self.agent.name}'. Skipping."
|
|
193
193
|
)
|
|
194
194
|
|
|
195
195
|
if is_output_callable:
|
|
196
|
-
path_str = registry.get_callable_path_string(self.agent.
|
|
196
|
+
path_str = registry.get_callable_path_string(self.agent.output_spec)
|
|
197
197
|
if path_str:
|
|
198
198
|
func_name = path_str.split(".")[-1]
|
|
199
199
|
data["output_callable"] = func_name
|
|
@@ -202,20 +202,20 @@ class FlockAgentSerialization:
|
|
|
202
202
|
)
|
|
203
203
|
else:
|
|
204
204
|
logger.warning(
|
|
205
|
-
f"Could not get path string for output {self.agent.
|
|
205
|
+
f"Could not get path string for output {self.agent.output_spec} in agent '{self.agent.name}'. Skipping."
|
|
206
206
|
)
|
|
207
207
|
|
|
208
208
|
if is_next_agent_callable:
|
|
209
|
-
path_str = registry.get_callable_path_string(self.agent.
|
|
209
|
+
path_str = registry.get_callable_path_string(self.agent.next_agent_spec)
|
|
210
210
|
if path_str:
|
|
211
211
|
func_name = path_str.split(".")[-1]
|
|
212
|
-
data["
|
|
212
|
+
data["next_agent_callable"] = func_name
|
|
213
213
|
logger.debug(
|
|
214
214
|
f"Added next_agent '{func_name}' (from path '{path_str}') to agent '{self.agent.name}'"
|
|
215
215
|
)
|
|
216
216
|
else:
|
|
217
217
|
logger.warning(
|
|
218
|
-
f"Could not get path string for next_agent {self.agent.
|
|
218
|
+
f"Could not get path string for next_agent {self.agent.next_agent_spec} in agent '{self.agent.name}'. Skipping."
|
|
219
219
|
)
|
|
220
220
|
|
|
221
221
|
|
|
@@ -245,6 +245,7 @@ class FlockAgentSerialization:
|
|
|
245
245
|
"description_callable",
|
|
246
246
|
"input_callable",
|
|
247
247
|
"output_callable",
|
|
248
|
+
"next_agent_callable",
|
|
248
249
|
]
|
|
249
250
|
|
|
250
251
|
for key in callable_keys:
|
|
@@ -322,7 +323,7 @@ class FlockAgentSerialization:
|
|
|
322
323
|
if isinstance(server_name, str):
|
|
323
324
|
# Case 1 (default behavior): A server name is passed.
|
|
324
325
|
agent.servers.append(server_name)
|
|
325
|
-
elif isinstance(server_name,
|
|
326
|
+
elif isinstance(server_name, FlockMCPServer):
|
|
326
327
|
# Case 2 (highly unlikely): If someone somehow manages to pass
|
|
327
328
|
# an instance of a server during the deserialization step (however that might be achieved)
|
|
328
329
|
# check the registry, if the server is already registered, if not, register it
|
|
@@ -373,6 +374,8 @@ class FlockAgentSerialization:
|
|
|
373
374
|
resolve_and_assign("description", "description_callable")
|
|
374
375
|
resolve_and_assign("input", "input_callable")
|
|
375
376
|
resolve_and_assign("output", "output_callable")
|
|
377
|
+
resolve_and_assign("next_agent", "next_agent_callable")
|
|
378
|
+
# --- Finalize ---
|
|
376
379
|
|
|
377
380
|
logger.info(f"Successfully deserialized agent '{agent.name}'.")
|
|
378
381
|
return agent
|
flock/core/api/endpoints.py
CHANGED
|
@@ -246,7 +246,7 @@ def create_api_router() -> APIRouter:
|
|
|
246
246
|
"""List all available agents in the currently loaded Flock."""
|
|
247
247
|
logger.debug("API request: list agents")
|
|
248
248
|
agents_list = [
|
|
249
|
-
|
|
249
|
+
agent.to_dict()
|
|
250
250
|
for agent in flock_instance.agents.values()
|
|
251
251
|
]
|
|
252
252
|
return {"agents": agents_list}
|
flock/core/component/__init__.py
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
"""Unified component system for Flock agents."""
|
|
3
3
|
|
|
4
4
|
from .agent_component_base import AgentComponent, AgentComponentConfig
|
|
5
|
-
from .
|
|
6
|
-
from .
|
|
7
|
-
from .
|
|
5
|
+
from .evaluation_component import EvaluationComponent
|
|
6
|
+
from .routing_component import RoutingComponent
|
|
7
|
+
from .utility_component import UtilityComponent
|
|
8
8
|
|
|
9
9
|
__all__ = [
|
|
10
10
|
"AgentComponent",
|
|
11
|
-
"AgentComponentConfig",
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
11
|
+
"AgentComponentConfig",
|
|
12
|
+
"EvaluationComponent",
|
|
13
|
+
"RoutingComponent",
|
|
14
|
+
"UtilityComponent",
|
|
15
15
|
]
|
|
@@ -9,7 +9,7 @@ from flock.core.context.context import FlockContext
|
|
|
9
9
|
from .agent_component_base import AgentComponent
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class
|
|
12
|
+
class EvaluationComponent(AgentComponent):
|
|
13
13
|
"""Base class for evaluation components.
|
|
14
14
|
|
|
15
15
|
Evaluation components implement the core intelligence/logic of an agent.
|
|
@@ -23,7 +23,7 @@ class EvaluationComponentBase(AgentComponent):
|
|
|
23
23
|
- ScriptEvaluationComponent (Python script-based)
|
|
24
24
|
- LLMEvaluationComponent (direct LLM API)
|
|
25
25
|
"""
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
@abstractmethod
|
|
28
28
|
async def evaluate_core(
|
|
29
29
|
self,
|
|
@@ -6,11 +6,10 @@ from typing import Any
|
|
|
6
6
|
|
|
7
7
|
from flock.core.context.context import FlockContext
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
from .agent_component_base import AgentComponent
|
|
11
10
|
|
|
12
11
|
|
|
13
|
-
class
|
|
12
|
+
class RoutingComponent(AgentComponent):
|
|
14
13
|
"""Base class for routing components.
|
|
15
14
|
|
|
16
15
|
Routing components determine the next step in a workflow based on the
|
|
@@ -26,7 +25,7 @@ class RoutingComponentBase(AgentComponent):
|
|
|
26
25
|
- DefaultRoutingModule (simple next-agent routing)
|
|
27
26
|
- ListGeneratorRoutingModule (dynamic agent creation)
|
|
28
27
|
"""
|
|
29
|
-
|
|
28
|
+
|
|
30
29
|
@abstractmethod
|
|
31
30
|
async def determine_next_step(
|
|
32
31
|
self,
|
|
@@ -54,7 +53,7 @@ class RoutingComponentBase(AgentComponent):
|
|
|
54
53
|
raise NotImplementedError(
|
|
55
54
|
f"{self.__class__.__name__} must implement determine_next_step()"
|
|
56
55
|
)
|
|
57
|
-
|
|
56
|
+
|
|
58
57
|
async def evaluate_core(
|
|
59
58
|
self,
|
|
60
59
|
agent: Any,
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
6
|
from flock.core.context.context import FlockContext
|
|
7
|
-
# HandOffRequest removed - using agent.next_agent directly
|
|
8
7
|
|
|
8
|
+
# HandOffRequest removed - using agent.next_agent directly
|
|
9
9
|
from .agent_component_base import AgentComponent
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class
|
|
12
|
+
class UtilityComponent(AgentComponent):
|
|
13
13
|
"""Base class for utility/enhancement components.
|
|
14
14
|
|
|
15
15
|
Utility components add cross-cutting concerns to agents without being
|
|
@@ -32,7 +32,7 @@ class UtilityComponentBase(AgentComponent):
|
|
|
32
32
|
- MetricsUtilityModule (performance tracking)
|
|
33
33
|
- AssertionUtilityModule (result validation)
|
|
34
34
|
"""
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
async def evaluate_core(
|
|
37
37
|
self,
|
|
38
38
|
agent: Any,
|
flock/core/flock.py
CHANGED
|
@@ -17,7 +17,7 @@ from typing import (
|
|
|
17
17
|
from box import Box
|
|
18
18
|
from temporalio import workflow
|
|
19
19
|
|
|
20
|
-
from flock.core.mcp.flock_mcp_server import
|
|
20
|
+
from flock.core.mcp.flock_mcp_server import FlockMCPServer
|
|
21
21
|
|
|
22
22
|
with workflow.unsafe.imports_passed_through():
|
|
23
23
|
from datasets import Dataset # type: ignore
|
|
@@ -125,7 +125,7 @@ class Flock(BaseModel, Serializable):
|
|
|
125
125
|
_start_input: dict = {} # For potential pre-configuration
|
|
126
126
|
|
|
127
127
|
# Internal server storage - not part of the Pydantic model for direct serialization
|
|
128
|
-
_servers: dict[str,
|
|
128
|
+
_servers: dict[str, FlockMCPServer]
|
|
129
129
|
|
|
130
130
|
# Note: _mgr is now handled by the server manager helper
|
|
131
131
|
|
|
@@ -207,7 +207,7 @@ class Flock(BaseModel, Serializable):
|
|
|
207
207
|
enable_temporal: bool = False,
|
|
208
208
|
enable_opik: bool = False,
|
|
209
209
|
agents: list[FlockAgent] | None = None,
|
|
210
|
-
servers: list[
|
|
210
|
+
servers: list[FlockMCPServer] | None = None,
|
|
211
211
|
temporal_config: TemporalWorkflowConfig | None = None,
|
|
212
212
|
temporal_start_in_process_worker: bool = True,
|
|
213
213
|
**kwargs,
|
|
@@ -296,7 +296,7 @@ class Flock(BaseModel, Serializable):
|
|
|
296
296
|
|
|
297
297
|
|
|
298
298
|
|
|
299
|
-
def add_server(self, server:
|
|
299
|
+
def add_server(self, server: FlockMCPServer) -> FlockMCPServer:
|
|
300
300
|
"""Adds a server instance to this Flock configuration and registry."""
|
|
301
301
|
return self._server_manager.add_server(server)
|
|
302
302
|
|
|
@@ -349,7 +349,7 @@ class Flock(BaseModel, Serializable):
|
|
|
349
349
|
return self._agents
|
|
350
350
|
|
|
351
351
|
@property
|
|
352
|
-
def servers(self) -> dict[str,
|
|
352
|
+
def servers(self) -> dict[str, FlockMCPServer]:
|
|
353
353
|
"""Returns the dictionary of servers managed by this Flock instance."""
|
|
354
354
|
return self._server_manager.servers
|
|
355
355
|
|
|
@@ -361,7 +361,7 @@ class Flock(BaseModel, Serializable):
|
|
|
361
361
|
run_id: str = "",
|
|
362
362
|
box_result: bool = True,
|
|
363
363
|
agents: list[FlockAgent] | None = None,
|
|
364
|
-
servers: list[
|
|
364
|
+
servers: list[FlockMCPServer] | None = None,
|
|
365
365
|
memo: dict[str, Any] | None = None,
|
|
366
366
|
) -> Box | dict:
|
|
367
367
|
"""Synchronous execution wrapper."""
|
|
@@ -384,7 +384,7 @@ class Flock(BaseModel, Serializable):
|
|
|
384
384
|
run_id: str = "",
|
|
385
385
|
box_result: bool = True,
|
|
386
386
|
agents: list[FlockAgent] | None = None,
|
|
387
|
-
servers: list[
|
|
387
|
+
servers: list[FlockMCPServer] | None = None,
|
|
388
388
|
memo: dict[str, Any] | None = None,
|
|
389
389
|
) -> Box | dict:
|
|
390
390
|
"""Entry point for running an agent system asynchronously."""
|
flock/core/flock_agent.py
CHANGED
|
@@ -12,14 +12,14 @@ from flock.core.agent.flock_agent_execution import FlockAgentExecution
|
|
|
12
12
|
from flock.core.agent.flock_agent_integration import FlockAgentIntegration
|
|
13
13
|
from flock.core.agent.flock_agent_serialization import FlockAgentSerialization
|
|
14
14
|
from flock.core.component.agent_component_base import AgentComponent
|
|
15
|
-
from flock.core.component.
|
|
16
|
-
|
|
15
|
+
from flock.core.component.evaluation_component import (
|
|
16
|
+
EvaluationComponent,
|
|
17
17
|
)
|
|
18
|
-
from flock.core.component.
|
|
18
|
+
from flock.core.component.routing_component import RoutingComponent
|
|
19
19
|
from flock.core.config.flock_agent_config import FlockAgentConfig
|
|
20
20
|
from flock.core.context.context import FlockContext
|
|
21
21
|
from flock.core.logging.logging import get_logger
|
|
22
|
-
from flock.core.mcp.flock_mcp_server import
|
|
22
|
+
from flock.core.mcp.flock_mcp_server import FlockMCPServer
|
|
23
23
|
|
|
24
24
|
# Mixins and Serialization components
|
|
25
25
|
from flock.core.mixin.dspy_integration import DSPyIntegrationMixin
|
|
@@ -30,14 +30,8 @@ logger = get_logger("agent.unified")
|
|
|
30
30
|
|
|
31
31
|
T = TypeVar("T", bound="FlockAgent")
|
|
32
32
|
|
|
33
|
-
SignatureType = (
|
|
34
|
-
str
|
|
35
|
-
| Callable[..., str]
|
|
36
|
-
| type[BaseModel]
|
|
37
|
-
| Callable[..., type[BaseModel]]
|
|
38
|
-
| None
|
|
39
|
-
)
|
|
40
33
|
|
|
34
|
+
DynamicStr = str | Callable[[FlockContext], str]
|
|
41
35
|
|
|
42
36
|
class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
43
37
|
"""Unified FlockAgent using the new component architecture.
|
|
@@ -63,23 +57,29 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
63
57
|
None,
|
|
64
58
|
description="The model identifier to use (e.g., 'openai/gpt-4o'). If None, uses Flock's default.",
|
|
65
59
|
)
|
|
66
|
-
|
|
67
|
-
"",
|
|
60
|
+
description_spec: DynamicStr | None = Field(
|
|
61
|
+
default="",
|
|
62
|
+
alias="description",
|
|
63
|
+
validation_alias="description",
|
|
68
64
|
description="A human-readable description or a callable returning one.",
|
|
69
65
|
)
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
input_spec: DynamicStr | None = Field(
|
|
67
|
+
default="",
|
|
68
|
+
alias="input",
|
|
69
|
+
validation_alias="input",
|
|
72
70
|
description="Signature for input keys. Supports type hints (:) and descriptions (|).",
|
|
73
71
|
)
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
output_spec: DynamicStr | None = Field(
|
|
73
|
+
default="",
|
|
74
|
+
alias="output",
|
|
75
|
+
validation_alias="output",
|
|
76
76
|
description="Signature for output keys. Supports type hints (:) and descriptions (|).",
|
|
77
77
|
)
|
|
78
78
|
tools: list[Callable[..., Any]] | None = Field(
|
|
79
79
|
default=None,
|
|
80
80
|
description="List of callable tools the agent can use. These must be registered.",
|
|
81
81
|
)
|
|
82
|
-
servers: list[str |
|
|
82
|
+
servers: list[str | FlockMCPServer] | None = Field(
|
|
83
83
|
default=None,
|
|
84
84
|
description="List of MCP Servers the agent can use to enhance its capabilities.",
|
|
85
85
|
)
|
|
@@ -91,9 +91,10 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
91
91
|
)
|
|
92
92
|
|
|
93
93
|
# --- EXPLICIT WORKFLOW STATE ---
|
|
94
|
-
|
|
94
|
+
next_agent_spec: DynamicStr | None = Field(
|
|
95
95
|
default=None,
|
|
96
|
-
|
|
96
|
+
alias="next_agent",
|
|
97
|
+
validation_alias="next_agent",
|
|
97
98
|
description="Next agent in workflow - set by user or routing components.",
|
|
98
99
|
)
|
|
99
100
|
|
|
@@ -118,14 +119,14 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
118
119
|
self,
|
|
119
120
|
name: str,
|
|
120
121
|
model: str | None = None,
|
|
121
|
-
description:
|
|
122
|
-
input:
|
|
123
|
-
output:
|
|
122
|
+
description: DynamicStr | None = None,
|
|
123
|
+
input: DynamicStr | None = None,
|
|
124
|
+
output: DynamicStr | None = None,
|
|
124
125
|
tools: list[Callable[..., Any]] | None = None,
|
|
125
|
-
servers: list[str |
|
|
126
|
+
servers: list[str | FlockMCPServer] | None = None,
|
|
126
127
|
components: list[AgentComponent] | None = None,
|
|
127
128
|
config: FlockAgentConfig | None = None,
|
|
128
|
-
next_agent:
|
|
129
|
+
next_agent: DynamicStr | None = None,
|
|
129
130
|
temporal_activity_config: TemporalActivityConfig | None = None,
|
|
130
131
|
):
|
|
131
132
|
"""Initialize the unified FlockAgent with components and configuration."""
|
|
@@ -155,12 +156,12 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
155
156
|
# These provide familiar access patterns while using the unified model
|
|
156
157
|
|
|
157
158
|
@property
|
|
158
|
-
def evaluator(self) ->
|
|
159
|
+
def evaluator(self) -> EvaluationComponent | None:
|
|
159
160
|
"""Get the primary evaluation component for this agent."""
|
|
160
161
|
return self._components.get_primary_evaluator()
|
|
161
162
|
|
|
162
163
|
@property
|
|
163
|
-
def router(self) ->
|
|
164
|
+
def router(self) -> RoutingComponent | None:
|
|
164
165
|
"""Get the primary routing component for this agent."""
|
|
165
166
|
return self._components.get_primary_router()
|
|
166
167
|
|
|
@@ -270,22 +271,51 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
270
271
|
f"Agent '{self.name}' has no evaluator to set model for."
|
|
271
272
|
)
|
|
272
273
|
|
|
273
|
-
def resolve_callables(self, context: FlockContext | None = None) -> None:
|
|
274
|
-
"""Resolves callable fields (description, input, output) using context."""
|
|
275
|
-
self.context = context or self.context
|
|
276
|
-
return self._integration.resolve_callables(self.context)
|
|
277
|
-
|
|
278
274
|
@property
|
|
279
|
-
def
|
|
280
|
-
"""Returns the resolved agent description.
|
|
281
|
-
If the description is a callable, it attempts to call it.
|
|
282
|
-
Returns None if the description is None or a callable that fails.
|
|
283
|
-
"""
|
|
275
|
+
def description(self) -> str | None:
|
|
276
|
+
"""Returns the resolved agent description."""
|
|
284
277
|
return self._integration.resolve_description(self.context)
|
|
285
278
|
|
|
279
|
+
@property
|
|
280
|
+
def input(self) -> str | None:
|
|
281
|
+
"""Returns the resolved agent input."""
|
|
282
|
+
return self._integration.resolve_input(self.context)
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
def output(self) -> str | None:
|
|
286
|
+
"""Returns the resolved agent output."""
|
|
287
|
+
return self._integration.resolve_output(self.context)
|
|
288
|
+
|
|
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)
|
|
293
|
+
|
|
294
|
+
@description.setter
|
|
295
|
+
def description(self, value: DynamicStr) -> None:
|
|
296
|
+
self.description_spec = value
|
|
297
|
+
|
|
298
|
+
@input.setter
|
|
299
|
+
def input(self, value: DynamicStr) -> None:
|
|
300
|
+
self.input_spec = value
|
|
301
|
+
|
|
302
|
+
@output.setter
|
|
303
|
+
def output(self, value: DynamicStr) -> None:
|
|
304
|
+
self.output_spec = value
|
|
305
|
+
|
|
306
|
+
@next_agent.setter
|
|
307
|
+
def next_agent(self, value: DynamicStr) -> None:
|
|
308
|
+
self.next_agent_spec = value
|
|
309
|
+
|
|
286
310
|
def _save_output(self, agent_name: str, result: dict[str, Any]) -> None:
|
|
287
311
|
"""Save output to file if configured (delegated to serialization)."""
|
|
288
312
|
return self._serialization._save_output(agent_name, result)
|
|
289
313
|
|
|
290
314
|
# --- Pydantic v2 Configuration ---
|
|
291
|
-
model_config = {
|
|
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
|
+
}
|