flock-core 0.4.3__py3-none-any.whl → 0.4.503__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/create_flock.py +0 -7
- flock/cli/execute_flock.py +19 -9
- flock/core/__init__.py +11 -0
- flock/core/flock.py +145 -80
- flock/core/flock_agent.py +117 -4
- flock/core/flock_evaluator.py +1 -1
- flock/core/flock_factory.py +290 -2
- flock/core/flock_module.py +101 -0
- flock/core/flock_registry.py +40 -3
- flock/core/flock_server_manager.py +136 -0
- flock/core/logging/__init__.py +4 -0
- flock/core/logging/logging.py +98 -11
- flock/core/logging/telemetry.py +1 -1
- flock/core/mcp/__init__.py +1 -0
- flock/core/mcp/flock_mcp_server.py +614 -0
- flock/core/mcp/flock_mcp_tool_base.py +201 -0
- flock/core/mcp/mcp_client.py +658 -0
- flock/core/mcp/mcp_client_manager.py +201 -0
- flock/core/mcp/mcp_config.py +237 -0
- flock/core/mcp/types/__init__.py +1 -0
- flock/core/mcp/types/callbacks.py +86 -0
- flock/core/mcp/types/factories.py +111 -0
- flock/core/mcp/types/handlers.py +240 -0
- flock/core/mcp/types/types.py +157 -0
- flock/core/mcp/util/__init__.py +0 -0
- flock/core/mcp/util/helpers.py +23 -0
- flock/core/mixin/dspy_integration.py +45 -12
- flock/core/serialization/flock_serializer.py +52 -1
- flock/core/util/hydrator.py +0 -1
- flock/core/util/spliter.py +4 -0
- flock/evaluators/declarative/declarative_evaluator.py +4 -3
- flock/mcp/servers/sse/__init__.py +1 -0
- flock/mcp/servers/sse/flock_sse_server.py +139 -0
- flock/mcp/servers/stdio/__init__.py +1 -0
- flock/mcp/servers/stdio/flock_stdio_server.py +138 -0
- flock/mcp/servers/websockets/__init__.py +1 -0
- flock/mcp/servers/websockets/flock_websocket_server.py +119 -0
- flock/modules/performance/metrics_module.py +159 -1
- flock/webapp/app/main.py +1 -1
- flock/webapp/app/services/flock_service.py +0 -1
- {flock_core-0.4.3.dist-info → flock_core-0.4.503.dist-info}/METADATA +42 -4
- {flock_core-0.4.3.dist-info → flock_core-0.4.503.dist-info}/RECORD +45 -25
- {flock_core-0.4.3.dist-info → flock_core-0.4.503.dist-info}/WHEEL +0 -0
- {flock_core-0.4.3.dist-info → flock_core-0.4.503.dist-info}/entry_points.txt +0 -0
- {flock_core-0.4.3.dist-info → flock_core-0.4.503.dist-info}/licenses/LICENSE +0 -0
flock/cli/create_flock.py
CHANGED
|
@@ -74,19 +74,12 @@ def create_flock():
|
|
|
74
74
|
# ).ask()
|
|
75
75
|
enable_temporal = False
|
|
76
76
|
|
|
77
|
-
# Logging configuration
|
|
78
|
-
enable_logging = questionary.confirm(
|
|
79
|
-
"Enable logging?",
|
|
80
|
-
default=True,
|
|
81
|
-
).ask()
|
|
82
|
-
|
|
83
77
|
# Create the Flock instance
|
|
84
78
|
flock = Flock(
|
|
85
79
|
name=flock_name,
|
|
86
80
|
model=model,
|
|
87
81
|
description=description,
|
|
88
82
|
enable_temporal=enable_temporal,
|
|
89
|
-
enable_logging=enable_logging,
|
|
90
83
|
)
|
|
91
84
|
|
|
92
85
|
console.print("\n[green]✓[/] Flock created successfully!")
|
flock/cli/execute_flock.py
CHANGED
|
@@ -12,6 +12,7 @@ from rich.console import Console
|
|
|
12
12
|
from rich.panel import Panel
|
|
13
13
|
|
|
14
14
|
from flock.core.flock import Flock
|
|
15
|
+
from flock.core.logging.logging import configure_logging
|
|
15
16
|
from flock.core.util.cli_helper import init_console
|
|
16
17
|
|
|
17
18
|
# Create console instance
|
|
@@ -109,9 +110,16 @@ def execute_flock(flock: Flock):
|
|
|
109
110
|
|
|
110
111
|
# Logging options
|
|
111
112
|
enable_logging = questionary.confirm(
|
|
112
|
-
"Enable
|
|
113
|
+
"Enable logging?",
|
|
113
114
|
default=False,
|
|
114
115
|
).ask()
|
|
116
|
+
if enable_logging:
|
|
117
|
+
log_level = questionary.select(
|
|
118
|
+
"Minimum log level:",
|
|
119
|
+
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
|
120
|
+
default="ERROR",
|
|
121
|
+
).ask()
|
|
122
|
+
configure_logging(flock_level=log_level, external_level=log_level)
|
|
115
123
|
|
|
116
124
|
# Preview input
|
|
117
125
|
console.print("\n[bold]Input Preview:[/]")
|
|
@@ -130,10 +138,7 @@ def execute_flock(flock: Flock):
|
|
|
130
138
|
console.print("\n[bold]Executing Flock...[/]")
|
|
131
139
|
|
|
132
140
|
try:
|
|
133
|
-
#
|
|
134
|
-
if enable_logging:
|
|
135
|
-
# Enable logging through the logging configuration method
|
|
136
|
-
flock._configure_logging(True)
|
|
141
|
+
# Logging was configured earlier if enabled
|
|
137
142
|
|
|
138
143
|
# Run the Flock
|
|
139
144
|
result = flock.run(
|
|
@@ -509,9 +514,16 @@ def execute_flock_batch(flock: Flock):
|
|
|
509
514
|
|
|
510
515
|
# Logging options
|
|
511
516
|
enable_logging = questionary.confirm(
|
|
512
|
-
"Enable
|
|
517
|
+
"Enable logging?",
|
|
513
518
|
default=False,
|
|
514
519
|
).ask()
|
|
520
|
+
if enable_logging:
|
|
521
|
+
log_level = questionary.select(
|
|
522
|
+
"Minimum log level:",
|
|
523
|
+
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
|
524
|
+
default="ERROR",
|
|
525
|
+
).ask()
|
|
526
|
+
configure_logging(log_level)
|
|
515
527
|
|
|
516
528
|
# Preview configuration
|
|
517
529
|
console.print("\n[bold]Batch Configuration Preview:[/]")
|
|
@@ -554,9 +566,7 @@ def execute_flock_batch(flock: Flock):
|
|
|
554
566
|
console.print("\n[bold]Executing Batch...[/]")
|
|
555
567
|
|
|
556
568
|
try:
|
|
557
|
-
#
|
|
558
|
-
if enable_logging:
|
|
559
|
-
flock._configure_logging(True)
|
|
569
|
+
# Logging was already configured above if enabled
|
|
560
570
|
|
|
561
571
|
# Run the batch
|
|
562
572
|
results = flock.run_batch(
|
flock/core/__init__.py
CHANGED
|
@@ -14,6 +14,12 @@ from flock.core.flock_registry import (
|
|
|
14
14
|
flock_type,
|
|
15
15
|
get_registry,
|
|
16
16
|
)
|
|
17
|
+
from flock.core.mcp.flock_mcp_server import (
|
|
18
|
+
FlockMCPServerBase,
|
|
19
|
+
)
|
|
20
|
+
from flock.core.mcp.flock_mcp_tool_base import FlockMCPToolBase
|
|
21
|
+
from flock.core.mcp.mcp_client import FlockMCPClientBase
|
|
22
|
+
from flock.core.mcp.mcp_client_manager import FlockMCPClientManagerBase
|
|
17
23
|
|
|
18
24
|
__all__ = [
|
|
19
25
|
"Flock",
|
|
@@ -22,6 +28,11 @@ __all__ = [
|
|
|
22
28
|
"FlockEvaluator",
|
|
23
29
|
"FlockEvaluatorConfig",
|
|
24
30
|
"FlockFactory",
|
|
31
|
+
"FlockMCPClientBase",
|
|
32
|
+
"FlockMCPClientManagerBase",
|
|
33
|
+
"FlockMCPServerBase",
|
|
34
|
+
"FlockMCPServerConfig",
|
|
35
|
+
"FlockMCPToolBase",
|
|
25
36
|
"FlockModule",
|
|
26
37
|
"FlockModuleConfig",
|
|
27
38
|
"FlockRegistry",
|
flock/core/flock.py
CHANGED
|
@@ -22,6 +22,9 @@ _R = TypeVar("_R")
|
|
|
22
22
|
from box import Box
|
|
23
23
|
from temporalio import workflow
|
|
24
24
|
|
|
25
|
+
from flock.core.flock_server_manager import FlockServerManager
|
|
26
|
+
from flock.core.mcp.flock_mcp_server import FlockMCPServerBase
|
|
27
|
+
|
|
25
28
|
with workflow.unsafe.imports_passed_through():
|
|
26
29
|
from datasets import Dataset # type: ignore
|
|
27
30
|
|
|
@@ -45,7 +48,7 @@ from flock.core.context.context_manager import initialize_context
|
|
|
45
48
|
# Assuming run_temporal_workflow is correctly placed and importable
|
|
46
49
|
from flock.core.execution.temporal_executor import run_temporal_workflow
|
|
47
50
|
from flock.core.flock_evaluator import FlockEvaluator # For type hint
|
|
48
|
-
from flock.core.logging.logging import
|
|
51
|
+
from flock.core.logging.logging import get_logger
|
|
49
52
|
from flock.core.serialization.serializable import Serializable
|
|
50
53
|
from flock.core.util.cli_helper import init_console
|
|
51
54
|
from flock.workflow.temporal_config import TemporalWorkflowConfig
|
|
@@ -100,10 +103,6 @@ class Flock(BaseModel, Serializable):
|
|
|
100
103
|
default=False,
|
|
101
104
|
description="If True, execute workflows via Temporal; otherwise, run locally.",
|
|
102
105
|
)
|
|
103
|
-
enable_logging: bool = Field(
|
|
104
|
-
default=False,
|
|
105
|
-
description="If True, enable logging for the Flock instance.",
|
|
106
|
-
)
|
|
107
106
|
show_flock_banner: bool = Field(
|
|
108
107
|
default=True,
|
|
109
108
|
description="If True, show the Flock banner on console interactions.",
|
|
@@ -135,7 +134,14 @@ class Flock(BaseModel, Serializable):
|
|
|
135
134
|
# Marked with underscore to indicate it's managed internally and accessed via property
|
|
136
135
|
_agents: dict[str, FlockAgent]
|
|
137
136
|
_start_agent_name: str | None = None # For potential pre-configuration
|
|
138
|
-
_start_input: dict = {} #
|
|
137
|
+
_start_input: dict = {} # For potential pre-configuration
|
|
138
|
+
|
|
139
|
+
# Internal server storage - not part of the Pydantic model for direct serialization
|
|
140
|
+
_servers: dict[str, FlockMCPServerBase]
|
|
141
|
+
|
|
142
|
+
# Async context-manager for startup and teardown of servers
|
|
143
|
+
# Not part of the pydantic model
|
|
144
|
+
_mgr: FlockServerManager
|
|
139
145
|
|
|
140
146
|
# Pydantic v2 model config
|
|
141
147
|
model_config = {
|
|
@@ -173,8 +179,8 @@ class Flock(BaseModel, Serializable):
|
|
|
173
179
|
description: str | None = None,
|
|
174
180
|
show_flock_banner: bool = True,
|
|
175
181
|
enable_temporal: bool = False,
|
|
176
|
-
enable_logging: bool | list[str] = False,
|
|
177
182
|
agents: list[FlockAgent] | None = None,
|
|
183
|
+
servers: list[FlockMCPServerBase] | None = None,
|
|
178
184
|
temporal_config: TemporalWorkflowConfig | None = None,
|
|
179
185
|
temporal_start_in_process_worker: bool = True,
|
|
180
186
|
**kwargs,
|
|
@@ -189,7 +195,6 @@ class Flock(BaseModel, Serializable):
|
|
|
189
195
|
model=model,
|
|
190
196
|
description=description,
|
|
191
197
|
enable_temporal=enable_temporal,
|
|
192
|
-
enable_logging=bool(enable_logging), # Store as bool, specific loggers handled by _configure
|
|
193
198
|
show_flock_banner=show_flock_banner,
|
|
194
199
|
temporal_config=temporal_config,
|
|
195
200
|
temporal_start_in_process_worker=temporal_start_in_process_worker,
|
|
@@ -198,11 +203,28 @@ class Flock(BaseModel, Serializable):
|
|
|
198
203
|
|
|
199
204
|
# Initialize runtime attributes AFTER super().__init__()
|
|
200
205
|
self._agents = {}
|
|
206
|
+
self._servers = {}
|
|
201
207
|
self._start_agent_name = None
|
|
202
208
|
self._start_input = {}
|
|
209
|
+
self._mgr = FlockServerManager()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# Register passed servers
|
|
213
|
+
# (need to be registered first so that agents can retrieve them from the registry)
|
|
214
|
+
# This will also add them to the managed list of self._mgr
|
|
215
|
+
if servers:
|
|
216
|
+
from flock.core.mcp.flock_mcp_server import (
|
|
217
|
+
FlockMCPServerBase as ConcreteFlockMCPServer,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
for server in servers:
|
|
221
|
+
if isinstance(server, ConcreteFlockMCPServer):
|
|
222
|
+
self.add_server(server)
|
|
223
|
+
else:
|
|
224
|
+
logger.warning(
|
|
225
|
+
f"Item provided in 'servers' list is not a FlockMCPServer: {type(server)}"
|
|
226
|
+
)
|
|
203
227
|
|
|
204
|
-
# Set up logging based on the enable_logging flag
|
|
205
|
-
self._configure_logging(enable_logging) # Pass original value to _configure_logging
|
|
206
228
|
|
|
207
229
|
# Register passed agents
|
|
208
230
|
if agents:
|
|
@@ -277,35 +299,6 @@ class Flock(BaseModel, Serializable):
|
|
|
277
299
|
|
|
278
300
|
return run
|
|
279
301
|
|
|
280
|
-
def _configure_logging(self, enable_logging_config: bool | list[str]):
|
|
281
|
-
"""Configure logging levels based on the enable_logging flag."""
|
|
282
|
-
is_enabled_globally = False
|
|
283
|
-
specific_loggers_to_enable = []
|
|
284
|
-
|
|
285
|
-
if isinstance(enable_logging_config, bool):
|
|
286
|
-
is_enabled_globally = enable_logging_config
|
|
287
|
-
elif isinstance(enable_logging_config, list):
|
|
288
|
-
is_enabled_globally = bool(enable_logging_config) # True if list is not empty
|
|
289
|
-
specific_loggers_to_enable = enable_logging_config
|
|
290
|
-
|
|
291
|
-
# Configure core loggers
|
|
292
|
-
for log_name in LOGGERS: # Assuming LOGGERS is a list of known logger names
|
|
293
|
-
log_instance = get_logger(log_name)
|
|
294
|
-
if is_enabled_globally or log_name in specific_loggers_to_enable:
|
|
295
|
-
log_instance.enable_logging = True
|
|
296
|
-
else:
|
|
297
|
-
log_instance.enable_logging = False
|
|
298
|
-
|
|
299
|
-
# Configure module loggers (existing ones)
|
|
300
|
-
module_loggers = get_module_loggers() # Assuming this returns list of FlockLogger instances
|
|
301
|
-
for mod_log in module_loggers:
|
|
302
|
-
if is_enabled_globally or mod_log.name in specific_loggers_to_enable:
|
|
303
|
-
mod_log.enable_logging = True
|
|
304
|
-
else:
|
|
305
|
-
mod_log.enable_logging = False
|
|
306
|
-
|
|
307
|
-
# Update the instance's Pydantic field
|
|
308
|
-
self.enable_logging = is_enabled_globally or bool(specific_loggers_to_enable)
|
|
309
302
|
|
|
310
303
|
|
|
311
304
|
def _set_temporal_debug_flag(self):
|
|
@@ -330,11 +323,47 @@ class Flock(BaseModel, Serializable):
|
|
|
330
323
|
set_baggage("session_id", session_id)
|
|
331
324
|
logger.debug(f"Generated new session_id: {session_id}")
|
|
332
325
|
|
|
333
|
-
def
|
|
334
|
-
"""Adds
|
|
335
|
-
from flock.core.
|
|
336
|
-
|
|
326
|
+
def add_server(self, server: FlockMCPServerBase) -> FlockMCPServerBase:
|
|
327
|
+
"""Adds a server instance to this Flock configuration and registry as well as set it up to be managed by self._mgr."""
|
|
328
|
+
from flock.core.mcp.flock_mcp_server import (
|
|
329
|
+
FlockMCPServerBase as ConcreteFlockMCPServer,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
if not isinstance(server, ConcreteFlockMCPServer):
|
|
333
|
+
raise TypeError("Provided object is not a FlockMCPServer instance.")
|
|
334
|
+
if not server.config.name:
|
|
335
|
+
raise ValueError("Server must have a name.")
|
|
336
|
+
|
|
337
|
+
if server.config.name in self.servers:
|
|
338
|
+
raise ValueError(
|
|
339
|
+
f"Server with this name already exists. Name: '{server.config.name}'"
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
self._servers[server.config.name] = server
|
|
343
|
+
FlockRegistry.register_server(server) # Register globally.
|
|
344
|
+
|
|
345
|
+
# Make sure that the server is also added to
|
|
346
|
+
# the server_list managed by FlockServerManager
|
|
347
|
+
if not self._mgr:
|
|
348
|
+
self._mgr = FlockServerManager()
|
|
349
|
+
|
|
350
|
+
# Prepare server to be managed by the FlockServerManager
|
|
351
|
+
logger.info(f"Adding server '{server.config.name}' to managed list.")
|
|
352
|
+
self._mgr.add_server_sync(server=server)
|
|
353
|
+
logger.info(f"Server '{server.config.name}' is now on managed list.")
|
|
354
|
+
|
|
355
|
+
logger.info(
|
|
356
|
+
f"Server '{server.config.name}' added to Flock '{self.name}'"
|
|
337
357
|
)
|
|
358
|
+
return server
|
|
359
|
+
|
|
360
|
+
def add_agent(self, agent: FlockAgent) -> FlockAgent:
|
|
361
|
+
"""Adds an agent instance to this Flock configuration and registry.
|
|
362
|
+
|
|
363
|
+
This also registers all servers attached to the agent, if they have not been registered
|
|
364
|
+
beforehand.
|
|
365
|
+
"""
|
|
366
|
+
from flock.core.flock_agent import FlockAgent as ConcreteFlockAgent
|
|
338
367
|
|
|
339
368
|
if not isinstance(agent, ConcreteFlockAgent):
|
|
340
369
|
raise TypeError("Provided object is not a FlockAgent instance.")
|
|
@@ -372,6 +401,11 @@ class Flock(BaseModel, Serializable):
|
|
|
372
401
|
"""Returns the dictionary of agents managed by this Flock instance."""
|
|
373
402
|
return self._agents
|
|
374
403
|
|
|
404
|
+
@property
|
|
405
|
+
def servers(self) -> dict[str, FlockMCPServerBase]:
|
|
406
|
+
"""Returns the dictionary of servers managed by this Flock instance."""
|
|
407
|
+
return self._servers
|
|
408
|
+
|
|
375
409
|
def run(
|
|
376
410
|
self,
|
|
377
411
|
start_agent: FlockAgent | str | None = None,
|
|
@@ -380,6 +414,7 @@ class Flock(BaseModel, Serializable):
|
|
|
380
414
|
run_id: str = "",
|
|
381
415
|
box_result: bool = True,
|
|
382
416
|
agents: list[FlockAgent] | None = None,
|
|
417
|
+
servers: list[FlockMCPServerBase] | None = None,
|
|
383
418
|
memo: dict[str, Any] | None = None
|
|
384
419
|
) -> Box | dict:
|
|
385
420
|
return self._run_sync(
|
|
@@ -390,6 +425,7 @@ class Flock(BaseModel, Serializable):
|
|
|
390
425
|
run_id=run_id,
|
|
391
426
|
box_result=box_result,
|
|
392
427
|
agents=agents,
|
|
428
|
+
servers=servers,
|
|
393
429
|
memo=memo,
|
|
394
430
|
)
|
|
395
431
|
)
|
|
@@ -403,15 +439,28 @@ class Flock(BaseModel, Serializable):
|
|
|
403
439
|
run_id: str = "",
|
|
404
440
|
box_result: bool = True,
|
|
405
441
|
agents: list[FlockAgent] | None = None,
|
|
442
|
+
servers: list[FlockMCPServerBase] | None = None,
|
|
406
443
|
memo: dict[str, Any] | None = None,
|
|
407
444
|
) -> Box | dict:
|
|
408
445
|
"""Entry point for running an agent system asynchronously."""
|
|
409
|
-
|
|
410
|
-
|
|
446
|
+
# Import here to allow forward reference resolution
|
|
447
|
+
from flock.core.flock_agent import FlockAgent as ConcreteFlockAgent
|
|
448
|
+
from flock.core.mcp.flock_mcp_server import (
|
|
449
|
+
FlockMCPServerBase as ConcreteFlockServer,
|
|
411
450
|
)
|
|
412
451
|
|
|
413
452
|
with tracer.start_as_current_span("flock.run_async") as span:
|
|
414
|
-
# Add passed agents
|
|
453
|
+
# Add passed servers so that agents have access to them.
|
|
454
|
+
if servers:
|
|
455
|
+
for server_obj in servers:
|
|
456
|
+
if isinstance(server_obj, ConcreteFlockServer):
|
|
457
|
+
self.add_server(server=server_obj)
|
|
458
|
+
else:
|
|
459
|
+
logger.warning(
|
|
460
|
+
f"Item in 'servers' list is not a FlockMCPServer: {type(server_obj)}"
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
# Add passed agents
|
|
415
464
|
if agents:
|
|
416
465
|
for agent_obj in agents:
|
|
417
466
|
if isinstance(agent_obj, ConcreteFlockAgent):
|
|
@@ -434,7 +483,7 @@ class Flock(BaseModel, Serializable):
|
|
|
434
483
|
|
|
435
484
|
# Default to first agent if only one exists and none specified
|
|
436
485
|
if not start_agent_name and len(self._agents) == 1:
|
|
437
|
-
start_agent_name =
|
|
486
|
+
start_agent_name = next(iter(self._agents.keys()))
|
|
438
487
|
elif not start_agent_name:
|
|
439
488
|
raise ValueError(
|
|
440
489
|
"No start_agent specified and multiple/no agents exist in the Flock instance."
|
|
@@ -497,44 +546,60 @@ class Flock(BaseModel, Serializable):
|
|
|
497
546
|
self.temporal_config.model_dump(mode="json"),
|
|
498
547
|
)
|
|
499
548
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
else:
|
|
512
|
-
result = await run_temporal_workflow(
|
|
513
|
-
self, # Pass the Flock instance
|
|
514
|
-
run_context,
|
|
515
|
-
box_result=False, # Boxing handled below
|
|
516
|
-
memo=memo,
|
|
549
|
+
# At this point, initial setup is done
|
|
550
|
+
# and flock is ready to execute it's agent_workflow.
|
|
551
|
+
# Befor that happens, the ServerManager needs to
|
|
552
|
+
# get the Servers up and running (Populate pools, build connections, start scripts, etc.)
|
|
553
|
+
async with self._mgr:
|
|
554
|
+
# Enter the manager's async context,
|
|
555
|
+
# running it's __aenter__ method and starting all registered servers
|
|
556
|
+
# after this block ends, self._mgr's __aexit__ will be called
|
|
557
|
+
# all servers will be torn down.
|
|
558
|
+
logger.info(
|
|
559
|
+
f"Entering managed server context. Servers starting up."
|
|
517
560
|
)
|
|
518
561
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
+ ("..." if len(result_str) > 1000 else ""),
|
|
525
|
-
)
|
|
562
|
+
logger.info(
|
|
563
|
+
"Starting agent execution",
|
|
564
|
+
agent=start_agent_name,
|
|
565
|
+
enable_temporal=self.enable_temporal,
|
|
566
|
+
)
|
|
526
567
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
568
|
+
# Execute workflow
|
|
569
|
+
if not self.enable_temporal:
|
|
570
|
+
result = await run_local_workflow(
|
|
571
|
+
run_context, box_result=False # Boxing handled below
|
|
572
|
+
)
|
|
573
|
+
else:
|
|
574
|
+
result = await run_temporal_workflow(
|
|
575
|
+
self, # Pass the Flock instance
|
|
576
|
+
run_context,
|
|
577
|
+
box_result=False, # Boxing handled below
|
|
578
|
+
memo=memo,
|
|
534
579
|
)
|
|
580
|
+
|
|
581
|
+
span.set_attribute("result.type", str(type(result)))
|
|
582
|
+
result_str = str(result)
|
|
583
|
+
span.set_attribute(
|
|
584
|
+
"result.preview",
|
|
585
|
+
result_str[:1000]
|
|
586
|
+
+ ("..." if len(result_str) > 1000 else ""),
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
if box_result:
|
|
590
|
+
try:
|
|
591
|
+
logger.debug("Boxing final result.")
|
|
592
|
+
return Box(result)
|
|
593
|
+
except ImportError:
|
|
594
|
+
logger.warning(
|
|
595
|
+
"Box library not installed, returning raw dict."
|
|
596
|
+
)
|
|
597
|
+
return result
|
|
598
|
+
else:
|
|
535
599
|
return result
|
|
536
|
-
|
|
537
|
-
|
|
600
|
+
|
|
601
|
+
# The context of self._mgr ends here, meaning, that servers will
|
|
602
|
+
# be cleaned up and shut down.
|
|
538
603
|
|
|
539
604
|
except Exception as e:
|
|
540
605
|
logger.error(
|
flock/core/flock_agent.py
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
import asyncio
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
|
+
import uuid
|
|
7
8
|
from abc import ABC
|
|
8
9
|
from collections.abc import Callable
|
|
9
10
|
from datetime import datetime
|
|
10
11
|
from typing import TYPE_CHECKING, Any, TypeVar
|
|
11
12
|
|
|
13
|
+
from flock.core.mcp.flock_mcp_server import FlockMCPServerBase
|
|
12
14
|
from flock.core.serialization.json_encoder import FlockJSONEncoder
|
|
13
15
|
from flock.workflow.temporal_config import TemporalActivityConfig
|
|
14
16
|
|
|
@@ -59,7 +61,13 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
59
61
|
Inherits from Pydantic BaseModel, ABC, DSPyIntegrationMixin, and Serializable.
|
|
60
62
|
"""
|
|
61
63
|
|
|
64
|
+
agent_id: str = Field(
|
|
65
|
+
default_factory=lambda: str(uuid.uuid4()),
|
|
66
|
+
description="Internal, Unique UUID4 for this agent instance. No need to set it manually. Used for MCP features.",
|
|
67
|
+
)
|
|
68
|
+
|
|
62
69
|
name: str = Field(..., description="Unique identifier for the agent.")
|
|
70
|
+
|
|
63
71
|
model: str | None = Field(
|
|
64
72
|
None,
|
|
65
73
|
description="The model identifier to use (e.g., 'openai/gpt-4o'). If None, uses Flock's default.",
|
|
@@ -88,6 +96,11 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
88
96
|
description="List of callable tools the agent can use. These must be registered.",
|
|
89
97
|
)
|
|
90
98
|
)
|
|
99
|
+
servers: list[str | FlockMCPServerBase] | None = Field(
|
|
100
|
+
default=None,
|
|
101
|
+
description="List of MCP Servers the agent can use to enhance its capabilities. These must be registered.",
|
|
102
|
+
)
|
|
103
|
+
|
|
91
104
|
write_to_file: bool = Field(
|
|
92
105
|
default=False,
|
|
93
106
|
description="Write the agent's output to a file.",
|
|
@@ -132,9 +145,11 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
132
145
|
input: SignatureType = None,
|
|
133
146
|
output: SignatureType = None,
|
|
134
147
|
tools: list[Callable[..., Any]] | None = None,
|
|
148
|
+
servers: list[str | FlockMCPServerBase] | None = None,
|
|
135
149
|
evaluator: "FlockEvaluator | None" = None,
|
|
136
150
|
handoff_router: "FlockRouter | None" = None,
|
|
137
|
-
|
|
151
|
+
# Use dict for modules
|
|
152
|
+
modules: dict[str, "FlockModule"] | None = None,
|
|
138
153
|
write_to_file: bool = False,
|
|
139
154
|
wait_for_input: bool = False,
|
|
140
155
|
temporal_activity_config: TemporalActivityConfig | None = None,
|
|
@@ -147,6 +162,7 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
147
162
|
input=input, # Store the raw input spec
|
|
148
163
|
output=output, # Store the raw output spec
|
|
149
164
|
tools=tools,
|
|
165
|
+
servers=servers,
|
|
150
166
|
write_to_file=write_to_file,
|
|
151
167
|
wait_for_input=wait_for_input,
|
|
152
168
|
evaluator=evaluator,
|
|
@@ -335,6 +351,37 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
335
351
|
# For now, assume evaluator handles tool resolution if necessary
|
|
336
352
|
registered_tools = self.tools
|
|
337
353
|
|
|
354
|
+
# Retrieve available mcp_tools if the evaluator needs them
|
|
355
|
+
mcp_tools = []
|
|
356
|
+
if self.servers:
|
|
357
|
+
from flock.core.flock_registry import get_registry
|
|
358
|
+
|
|
359
|
+
FlockRegistry = get_registry() # Get the registry
|
|
360
|
+
for server in self.servers:
|
|
361
|
+
registered_server: FlockMCPServerBase | None = None
|
|
362
|
+
server_tools = []
|
|
363
|
+
if isinstance(server, FlockMCPServerBase):
|
|
364
|
+
# check if registered
|
|
365
|
+
server_name = server.config.name
|
|
366
|
+
registered_server = FlockRegistry.get_server(
|
|
367
|
+
server_name
|
|
368
|
+
)
|
|
369
|
+
else:
|
|
370
|
+
# servers must be registered.
|
|
371
|
+
registered_server = FlockRegistry.get_server(
|
|
372
|
+
name=server
|
|
373
|
+
)
|
|
374
|
+
if registered_server:
|
|
375
|
+
server_tools = await registered_server.get_tools(
|
|
376
|
+
agent_id=self.agent_id,
|
|
377
|
+
run_id=self.context.run_id,
|
|
378
|
+
)
|
|
379
|
+
else:
|
|
380
|
+
logger.warning(
|
|
381
|
+
f"No Server with name '{server}' registered! Skipping."
|
|
382
|
+
)
|
|
383
|
+
mcp_tools = mcp_tools + server_tools
|
|
384
|
+
|
|
338
385
|
# --------------------------------------------------
|
|
339
386
|
# Optional DI middleware pipeline
|
|
340
387
|
# --------------------------------------------------
|
|
@@ -391,7 +438,10 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
391
438
|
else:
|
|
392
439
|
# No DI container – standard execution
|
|
393
440
|
result = await self.evaluator.evaluate(
|
|
394
|
-
self,
|
|
441
|
+
self,
|
|
442
|
+
current_inputs,
|
|
443
|
+
registered_tools,
|
|
444
|
+
mcp_tools=mcp_tools,
|
|
395
445
|
)
|
|
396
446
|
except Exception as eval_error:
|
|
397
447
|
logger.error(
|
|
@@ -662,7 +712,14 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
662
712
|
|
|
663
713
|
FlockRegistry = get_registry()
|
|
664
714
|
|
|
665
|
-
exclude = [
|
|
715
|
+
exclude = [
|
|
716
|
+
"context",
|
|
717
|
+
"evaluator",
|
|
718
|
+
"modules",
|
|
719
|
+
"handoff_router",
|
|
720
|
+
"tools",
|
|
721
|
+
"servers",
|
|
722
|
+
]
|
|
666
723
|
|
|
667
724
|
is_descrition_callable = False
|
|
668
725
|
is_input_callable = False
|
|
@@ -755,6 +812,25 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
755
812
|
f"Added {len(serialized_modules)} modules to agent '{self.name}'"
|
|
756
813
|
)
|
|
757
814
|
|
|
815
|
+
# --- Serialize Servers ---
|
|
816
|
+
if self.servers:
|
|
817
|
+
logger.debug(
|
|
818
|
+
f"Serializing {len(self.servers)} servers for agent '{self.name}'"
|
|
819
|
+
)
|
|
820
|
+
serialized_servers = []
|
|
821
|
+
for server in self.servers:
|
|
822
|
+
if isinstance(server, FlockMCPServerBase):
|
|
823
|
+
serialized_servers.append(server.config.name)
|
|
824
|
+
else:
|
|
825
|
+
# Write it down as a list of server names.
|
|
826
|
+
serialized_servers.append(server)
|
|
827
|
+
|
|
828
|
+
if serialized_servers:
|
|
829
|
+
data["mcp_servers"] = serialized_servers
|
|
830
|
+
logger.debug(
|
|
831
|
+
f"Added {len(serialized_servers)} servers to agent '{self.name}'"
|
|
832
|
+
)
|
|
833
|
+
|
|
758
834
|
# --- Serialize Tools (Callables) ---
|
|
759
835
|
if self.tools:
|
|
760
836
|
logger.debug(
|
|
@@ -848,6 +924,7 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
848
924
|
component_configs = {}
|
|
849
925
|
callable_configs = {}
|
|
850
926
|
tool_config = []
|
|
927
|
+
servers_config = []
|
|
851
928
|
agent_data = {}
|
|
852
929
|
|
|
853
930
|
component_keys = [
|
|
@@ -863,6 +940,8 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
863
940
|
]
|
|
864
941
|
tool_key = "tools"
|
|
865
942
|
|
|
943
|
+
servers_key = "mcp_servers"
|
|
944
|
+
|
|
866
945
|
for key, value in data.items():
|
|
867
946
|
if key in component_keys and value is not None:
|
|
868
947
|
component_configs[key] = value
|
|
@@ -870,8 +949,11 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
870
949
|
callable_configs[key] = value
|
|
871
950
|
elif key == tool_key and value is not None:
|
|
872
951
|
tool_config = value # Expecting a list of names
|
|
952
|
+
elif key == servers_key and value is not None:
|
|
953
|
+
servers_config = value # Expecting a list of names
|
|
873
954
|
elif key not in component_keys + callable_keys + [
|
|
874
|
-
tool_key
|
|
955
|
+
tool_key,
|
|
956
|
+
servers_key,
|
|
875
957
|
]: # Avoid double adding
|
|
876
958
|
agent_data[key] = value
|
|
877
959
|
# else: ignore keys that are None or already handled
|
|
@@ -987,6 +1069,37 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
987
1069
|
exc_info=True,
|
|
988
1070
|
)
|
|
989
1071
|
|
|
1072
|
+
# --- Deserialize Servers ---
|
|
1073
|
+
agent.servers = [] # Initialize Servers list.
|
|
1074
|
+
if servers_config:
|
|
1075
|
+
logger.debug(
|
|
1076
|
+
f"Deserializing {len(servers_config)} servers for '{agent.name}'"
|
|
1077
|
+
)
|
|
1078
|
+
# Agents keep track of server by getting a list of server names.
|
|
1079
|
+
# The server instances will be retrieved during runtime from the registry. (default behavior)
|
|
1080
|
+
|
|
1081
|
+
for server_name in servers_config:
|
|
1082
|
+
if isinstance(server_name, str):
|
|
1083
|
+
# Case 1 (default behavior): A server name is passe.
|
|
1084
|
+
agent.servers.append(server_name)
|
|
1085
|
+
elif isinstance(server_name, FlockMCPServerBase):
|
|
1086
|
+
# Case 2 (highly unlikely): If someone somehow manages to pass
|
|
1087
|
+
# an instance of a server during the deserialization step (however that might be achieved)
|
|
1088
|
+
# check the registry, if the server is already registered, if not, register it
|
|
1089
|
+
# and store the name in the servers list
|
|
1090
|
+
FlockRegistry = get_registry()
|
|
1091
|
+
server_exists = (
|
|
1092
|
+
FlockRegistry.get_server(server_name.config.name)
|
|
1093
|
+
is not None
|
|
1094
|
+
)
|
|
1095
|
+
if server_exists:
|
|
1096
|
+
agent.servers.append(server_name.config.name)
|
|
1097
|
+
else:
|
|
1098
|
+
FlockRegistry.register_server(
|
|
1099
|
+
server=server_name
|
|
1100
|
+
) # register it.
|
|
1101
|
+
agent.servers.append(server_name.config.name)
|
|
1102
|
+
|
|
990
1103
|
# --- Deserialize Callables ---
|
|
991
1104
|
logger.debug(f"Deserializing callable fields for '{agent.name}'")
|
|
992
1105
|
# available_callables = registry.get_all_callables() # Incorrect
|
flock/core/flock_evaluator.py
CHANGED
|
@@ -47,7 +47,7 @@ class FlockEvaluator(ABC, BaseModel):
|
|
|
47
47
|
|
|
48
48
|
@abstractmethod
|
|
49
49
|
async def evaluate(
|
|
50
|
-
self, agent: Any, inputs: dict[str, Any], tools: list[Any]
|
|
50
|
+
self, agent: Any, inputs: dict[str, Any], tools: list[Any], mcp_tools: list[Any] | None = None
|
|
51
51
|
) -> dict[str, Any]:
|
|
52
52
|
"""Evaluate inputs to produce outputs."""
|
|
53
53
|
pass
|