flock-core 0.4.3__py3-none-any.whl → 0.4.5__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/core/__init__.py +11 -0
- flock/core/flock.py +144 -42
- 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 +39 -2
- flock/core/flock_server_manager.py +136 -0
- 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/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_core-0.4.3.dist-info → flock_core-0.4.5.dist-info}/METADATA +4 -2
- {flock_core-0.4.3.dist-info → flock_core-0.4.5.dist-info}/RECORD +38 -18
- {flock_core-0.4.3.dist-info → flock_core-0.4.5.dist-info}/WHEEL +0 -0
- {flock_core-0.4.3.dist-info → flock_core-0.4.5.dist-info}/entry_points.txt +0 -0
- {flock_core-0.4.3.dist-info → flock_core-0.4.5.dist-info}/licenses/LICENSE +0 -0
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
|
|
|
@@ -135,7 +138,14 @@ class Flock(BaseModel, Serializable):
|
|
|
135
138
|
# Marked with underscore to indicate it's managed internally and accessed via property
|
|
136
139
|
_agents: dict[str, FlockAgent]
|
|
137
140
|
_start_agent_name: str | None = None # For potential pre-configuration
|
|
138
|
-
_start_input: dict = {} #
|
|
141
|
+
_start_input: dict = {} # For potential pre-configuration
|
|
142
|
+
|
|
143
|
+
# Internal server storage - not part of the Pydantic model for direct serialization
|
|
144
|
+
_servers: dict[str, FlockMCPServerBase]
|
|
145
|
+
|
|
146
|
+
# Async context-manager for startup and teardown of servers
|
|
147
|
+
# Not part of the pydantic model
|
|
148
|
+
_mgr: FlockServerManager
|
|
139
149
|
|
|
140
150
|
# Pydantic v2 model config
|
|
141
151
|
model_config = {
|
|
@@ -175,6 +185,7 @@ class Flock(BaseModel, Serializable):
|
|
|
175
185
|
enable_temporal: bool = False,
|
|
176
186
|
enable_logging: bool | list[str] = False,
|
|
177
187
|
agents: list[FlockAgent] | None = None,
|
|
188
|
+
servers: list[FlockMCPServerBase] | None = None,
|
|
178
189
|
temporal_config: TemporalWorkflowConfig | None = None,
|
|
179
190
|
temporal_start_in_process_worker: bool = True,
|
|
180
191
|
**kwargs,
|
|
@@ -198,12 +209,31 @@ class Flock(BaseModel, Serializable):
|
|
|
198
209
|
|
|
199
210
|
# Initialize runtime attributes AFTER super().__init__()
|
|
200
211
|
self._agents = {}
|
|
212
|
+
self._servers = {}
|
|
201
213
|
self._start_agent_name = None
|
|
202
214
|
self._start_input = {}
|
|
215
|
+
self._mgr = FlockServerManager()
|
|
203
216
|
|
|
204
217
|
# Set up logging based on the enable_logging flag
|
|
205
218
|
self._configure_logging(enable_logging) # Pass original value to _configure_logging
|
|
206
219
|
|
|
220
|
+
# Register passed servers
|
|
221
|
+
# (need to be registered first so that agents can retrieve them from the registry)
|
|
222
|
+
# This will also add them to the managed list of self._mgr
|
|
223
|
+
if servers:
|
|
224
|
+
from flock.core.mcp.flock_mcp_server import (
|
|
225
|
+
FlockMCPServerBase as ConcreteFlockMCPServer,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
for server in servers:
|
|
229
|
+
if isinstance(server, ConcreteFlockMCPServer):
|
|
230
|
+
self.add_server(server)
|
|
231
|
+
else:
|
|
232
|
+
logger.warning(
|
|
233
|
+
f"Item provided in 'servers' list is not a FlockMCPServer: {type(server)}"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
207
237
|
# Register passed agents
|
|
208
238
|
if agents:
|
|
209
239
|
from flock.core.flock_agent import (
|
|
@@ -330,12 +360,48 @@ class Flock(BaseModel, Serializable):
|
|
|
330
360
|
set_baggage("session_id", session_id)
|
|
331
361
|
logger.debug(f"Generated new session_id: {session_id}")
|
|
332
362
|
|
|
333
|
-
def
|
|
334
|
-
"""Adds
|
|
335
|
-
from flock.core.
|
|
336
|
-
|
|
363
|
+
def add_server(self, server: FlockMCPServerBase) -> FlockMCPServerBase:
|
|
364
|
+
"""Adds a server instance to this Flock configuration and registry as well as set it up to be managed by self._mgr."""
|
|
365
|
+
from flock.core.mcp.flock_mcp_server import (
|
|
366
|
+
FlockMCPServerBase as ConcreteFlockMCPServer,
|
|
337
367
|
)
|
|
338
368
|
|
|
369
|
+
if not isinstance(server, ConcreteFlockMCPServer):
|
|
370
|
+
raise TypeError("Provided object is not a FlockMCPServer instance.")
|
|
371
|
+
if not server.config.name:
|
|
372
|
+
raise ValueError("Server must have a name.")
|
|
373
|
+
|
|
374
|
+
if server.config.name in self.servers:
|
|
375
|
+
raise ValueError(
|
|
376
|
+
f"Server with this name already exists. Name: '{server.config.name}'"
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
self._servers[server.config.name] = server
|
|
380
|
+
FlockRegistry.register_server(server) # Register globally.
|
|
381
|
+
|
|
382
|
+
# Make sure that the server is also added to
|
|
383
|
+
# the server_list managed by FlockServerManager
|
|
384
|
+
if not self._mgr:
|
|
385
|
+
self._mgr = FlockServerManager()
|
|
386
|
+
|
|
387
|
+
# Prepare server to be managed by the FlockServerManager
|
|
388
|
+
logger.info(f"Adding server '{server.config.name}' to managed list.")
|
|
389
|
+
self._mgr.add_server_sync(server=server)
|
|
390
|
+
logger.info(f"Server '{server.config.name}' is now on managed list.")
|
|
391
|
+
|
|
392
|
+
logger.info(
|
|
393
|
+
f"Server '{server.config.name}' added to Flock '{self.name}'"
|
|
394
|
+
)
|
|
395
|
+
return server
|
|
396
|
+
|
|
397
|
+
def add_agent(self, agent: FlockAgent) -> FlockAgent:
|
|
398
|
+
"""Adds an agent instance to this Flock configuration and registry.
|
|
399
|
+
|
|
400
|
+
This also registers all servers attached to the agent, if they have not been registered
|
|
401
|
+
beforehand.
|
|
402
|
+
"""
|
|
403
|
+
from flock.core.flock_agent import FlockAgent as ConcreteFlockAgent
|
|
404
|
+
|
|
339
405
|
if not isinstance(agent, ConcreteFlockAgent):
|
|
340
406
|
raise TypeError("Provided object is not a FlockAgent instance.")
|
|
341
407
|
if not agent.name:
|
|
@@ -372,6 +438,11 @@ class Flock(BaseModel, Serializable):
|
|
|
372
438
|
"""Returns the dictionary of agents managed by this Flock instance."""
|
|
373
439
|
return self._agents
|
|
374
440
|
|
|
441
|
+
@property
|
|
442
|
+
def servers(self) -> dict[str, FlockMCPServerBase]:
|
|
443
|
+
"""Returns the dictionary of servers managed by this Flock instance."""
|
|
444
|
+
return self._servers
|
|
445
|
+
|
|
375
446
|
def run(
|
|
376
447
|
self,
|
|
377
448
|
start_agent: FlockAgent | str | None = None,
|
|
@@ -380,6 +451,7 @@ class Flock(BaseModel, Serializable):
|
|
|
380
451
|
run_id: str = "",
|
|
381
452
|
box_result: bool = True,
|
|
382
453
|
agents: list[FlockAgent] | None = None,
|
|
454
|
+
servers: list[FlockMCPServerBase] | None = None,
|
|
383
455
|
memo: dict[str, Any] | None = None
|
|
384
456
|
) -> Box | dict:
|
|
385
457
|
return self._run_sync(
|
|
@@ -390,6 +462,7 @@ class Flock(BaseModel, Serializable):
|
|
|
390
462
|
run_id=run_id,
|
|
391
463
|
box_result=box_result,
|
|
392
464
|
agents=agents,
|
|
465
|
+
servers=servers,
|
|
393
466
|
memo=memo,
|
|
394
467
|
)
|
|
395
468
|
)
|
|
@@ -403,15 +476,28 @@ class Flock(BaseModel, Serializable):
|
|
|
403
476
|
run_id: str = "",
|
|
404
477
|
box_result: bool = True,
|
|
405
478
|
agents: list[FlockAgent] | None = None,
|
|
479
|
+
servers: list[FlockMCPServerBase] | None = None,
|
|
406
480
|
memo: dict[str, Any] | None = None,
|
|
407
481
|
) -> Box | dict:
|
|
408
482
|
"""Entry point for running an agent system asynchronously."""
|
|
409
|
-
|
|
410
|
-
|
|
483
|
+
# Import here to allow forward reference resolution
|
|
484
|
+
from flock.core.flock_agent import FlockAgent as ConcreteFlockAgent
|
|
485
|
+
from flock.core.mcp.flock_mcp_server import (
|
|
486
|
+
FlockMCPServerBase as ConcreteFlockServer,
|
|
411
487
|
)
|
|
412
488
|
|
|
413
489
|
with tracer.start_as_current_span("flock.run_async") as span:
|
|
414
|
-
# Add passed agents
|
|
490
|
+
# Add passed servers so that agents have access to them.
|
|
491
|
+
if servers:
|
|
492
|
+
for server_obj in servers:
|
|
493
|
+
if isinstance(server_obj, ConcreteFlockServer):
|
|
494
|
+
self.add_server(server=server_obj)
|
|
495
|
+
else:
|
|
496
|
+
logger.warning(
|
|
497
|
+
f"Item in 'servers' list is not a FlockMCPServer: {type(server_obj)}"
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
# Add passed agents
|
|
415
501
|
if agents:
|
|
416
502
|
for agent_obj in agents:
|
|
417
503
|
if isinstance(agent_obj, ConcreteFlockAgent):
|
|
@@ -434,7 +520,7 @@ class Flock(BaseModel, Serializable):
|
|
|
434
520
|
|
|
435
521
|
# Default to first agent if only one exists and none specified
|
|
436
522
|
if not start_agent_name and len(self._agents) == 1:
|
|
437
|
-
start_agent_name =
|
|
523
|
+
start_agent_name = next(iter(self._agents.keys()))
|
|
438
524
|
elif not start_agent_name:
|
|
439
525
|
raise ValueError(
|
|
440
526
|
"No start_agent specified and multiple/no agents exist in the Flock instance."
|
|
@@ -497,44 +583,60 @@ class Flock(BaseModel, Serializable):
|
|
|
497
583
|
self.temporal_config.model_dump(mode="json"),
|
|
498
584
|
)
|
|
499
585
|
|
|
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,
|
|
586
|
+
# At this point, initial setup is done
|
|
587
|
+
# and flock is ready to execute it's agent_workflow.
|
|
588
|
+
# Befor that happens, the ServerManager needs to
|
|
589
|
+
# get the Servers up and running (Populate pools, build connections, start scripts, etc.)
|
|
590
|
+
async with self._mgr:
|
|
591
|
+
# Enter the manager's async context,
|
|
592
|
+
# running it's __aenter__ method and starting all registered servers
|
|
593
|
+
# after this block ends, self._mgr's __aexit__ will be called
|
|
594
|
+
# all servers will be torn down.
|
|
595
|
+
logger.info(
|
|
596
|
+
f"Entering managed server context. Servers starting up."
|
|
517
597
|
)
|
|
518
598
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
+ ("..." if len(result_str) > 1000 else ""),
|
|
525
|
-
)
|
|
599
|
+
logger.info(
|
|
600
|
+
"Starting agent execution",
|
|
601
|
+
agent=start_agent_name,
|
|
602
|
+
enable_temporal=self.enable_temporal,
|
|
603
|
+
)
|
|
526
604
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
605
|
+
# Execute workflow
|
|
606
|
+
if not self.enable_temporal:
|
|
607
|
+
result = await run_local_workflow(
|
|
608
|
+
run_context, box_result=False # Boxing handled below
|
|
609
|
+
)
|
|
610
|
+
else:
|
|
611
|
+
result = await run_temporal_workflow(
|
|
612
|
+
self, # Pass the Flock instance
|
|
613
|
+
run_context,
|
|
614
|
+
box_result=False, # Boxing handled below
|
|
615
|
+
memo=memo,
|
|
534
616
|
)
|
|
617
|
+
|
|
618
|
+
span.set_attribute("result.type", str(type(result)))
|
|
619
|
+
result_str = str(result)
|
|
620
|
+
span.set_attribute(
|
|
621
|
+
"result.preview",
|
|
622
|
+
result_str[:1000]
|
|
623
|
+
+ ("..." if len(result_str) > 1000 else ""),
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
if box_result:
|
|
627
|
+
try:
|
|
628
|
+
logger.debug("Boxing final result.")
|
|
629
|
+
return Box(result)
|
|
630
|
+
except ImportError:
|
|
631
|
+
logger.warning(
|
|
632
|
+
"Box library not installed, returning raw dict."
|
|
633
|
+
)
|
|
634
|
+
return result
|
|
635
|
+
else:
|
|
535
636
|
return result
|
|
536
|
-
|
|
537
|
-
|
|
637
|
+
|
|
638
|
+
# The context of self._mgr ends here, meaning, that servers will
|
|
639
|
+
# be cleaned up and shut down.
|
|
538
640
|
|
|
539
641
|
except Exception as e:
|
|
540
642
|
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
|