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
flock/cli/manage_agents.py
CHANGED
|
@@ -105,7 +105,7 @@ def _list_agents(flock: Flock):
|
|
|
105
105
|
model = agent.model or flock.model or "Default"
|
|
106
106
|
|
|
107
107
|
# Format description (truncate if too long)
|
|
108
|
-
description = agent.
|
|
108
|
+
description = agent.description
|
|
109
109
|
if description and len(description) > 30:
|
|
110
110
|
description = description[:27] + "..."
|
|
111
111
|
elif not description:
|
|
@@ -165,7 +165,7 @@ def _view_agent_details(agent: FlockAgent):
|
|
|
165
165
|
|
|
166
166
|
basic_info.add_row("Name", agent.name)
|
|
167
167
|
basic_info.add_row("Model", str(agent.model or "Default"))
|
|
168
|
-
basic_info.add_row("Description", agent.
|
|
168
|
+
basic_info.add_row("Description", agent.description if agent.description else "N/A")
|
|
169
169
|
basic_info.add_row("Input", str(agent.input))
|
|
170
170
|
basic_info.add_row("Output", str(agent.output))
|
|
171
171
|
basic_info.add_row("Write to File", str(agent.config.write_to_file))
|
|
@@ -339,7 +339,7 @@ def _edit_agent(flock: Flock):
|
|
|
339
339
|
console.print(f"\n[bold underline]Details for Agent: {agent.name}[/]")
|
|
340
340
|
basic_info = Table(show_header=False, box=Box.ROUNDED, padding=(0, 2))
|
|
341
341
|
basic_info.add_row("Name", agent.name)
|
|
342
|
-
description = agent.
|
|
342
|
+
description = agent.description
|
|
343
343
|
basic_info.add_row("Description", description if description else "N/A")
|
|
344
344
|
basic_info.add_row("Model", agent.model or "Flock Default")
|
|
345
345
|
basic_info.add_row("Input Signature", str(agent.input))
|
|
@@ -12,12 +12,12 @@ with workflow.unsafe.imports_passed_through():
|
|
|
12
12
|
from pydantic import Field, PrivateAttr
|
|
13
13
|
|
|
14
14
|
from flock.core.component.agent_component_base import AgentComponentConfig
|
|
15
|
-
from flock.core.component.
|
|
15
|
+
from flock.core.component.evaluation_component import EvaluationComponent
|
|
16
16
|
from flock.core.context.context import FlockContext
|
|
17
|
-
from flock.core.registry import flock_component
|
|
18
17
|
from flock.core.logging.logging import get_logger
|
|
19
18
|
from flock.core.mixin.dspy_integration import DSPyIntegrationMixin
|
|
20
19
|
from flock.core.mixin.prompt_parser import PromptParserMixin
|
|
20
|
+
from flock.core.registry import flock_component
|
|
21
21
|
|
|
22
22
|
logger = get_logger("components.evaluation.declarative")
|
|
23
23
|
|
|
@@ -45,7 +45,7 @@ class DeclarativeEvaluationConfig(AgentComponentConfig):
|
|
|
45
45
|
|
|
46
46
|
@flock_component(config_class=DeclarativeEvaluationConfig)
|
|
47
47
|
class DeclarativeEvaluationComponent(
|
|
48
|
-
|
|
48
|
+
EvaluationComponent, DSPyIntegrationMixin, PromptParserMixin
|
|
49
49
|
):
|
|
50
50
|
"""Evaluation component that uses DSPy for generation.
|
|
51
51
|
|
|
@@ -58,7 +58,7 @@ class DeclarativeEvaluationComponent(
|
|
|
58
58
|
default_factory=DeclarativeEvaluationConfig,
|
|
59
59
|
description="Evaluation configuration",
|
|
60
60
|
)
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
_cost: float = PrivateAttr(default=0.0)
|
|
63
63
|
_lm_history: list = PrivateAttr(default_factory=list)
|
|
64
64
|
|
|
@@ -75,7 +75,7 @@ class DeclarativeEvaluationComponent(
|
|
|
75
75
|
) -> dict[str, Any]:
|
|
76
76
|
"""Core evaluation logic using DSPy - migrated from DeclarativeEvaluator."""
|
|
77
77
|
logger.debug(f"Starting declarative evaluation for component '{self.name}'")
|
|
78
|
-
|
|
78
|
+
|
|
79
79
|
# Setup DSPy context with LM (directly from original implementation)
|
|
80
80
|
with dspy.context(
|
|
81
81
|
lm=dspy.LM(
|
|
@@ -89,14 +89,14 @@ class DeclarativeEvaluationComponent(
|
|
|
89
89
|
try:
|
|
90
90
|
from rich.console import Console
|
|
91
91
|
console = Console()
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
# Create DSPy signature from agent definition
|
|
94
94
|
_dspy_signature = self.create_dspy_signature_class(
|
|
95
95
|
agent.name,
|
|
96
96
|
agent.description,
|
|
97
97
|
f"{agent.input} -> {agent.output}",
|
|
98
98
|
)
|
|
99
|
-
|
|
99
|
+
|
|
100
100
|
# Get output field names for streaming
|
|
101
101
|
output_field_names = list(_dspy_signature.output_fields.keys())
|
|
102
102
|
if not output_field_names:
|
|
@@ -113,7 +113,7 @@ class DeclarativeEvaluationComponent(
|
|
|
113
113
|
mcp_tools=mcp_tools or [],
|
|
114
114
|
kwargs=self.config.kwargs,
|
|
115
115
|
)
|
|
116
|
-
|
|
116
|
+
|
|
117
117
|
except Exception as setup_error:
|
|
118
118
|
logger.error(
|
|
119
119
|
f"Error setting up DSPy task for agent '{agent.name}': {setup_error}",
|
|
@@ -132,7 +132,7 @@ class DeclarativeEvaluationComponent(
|
|
|
132
132
|
async def _execute_streaming(self, agent_task, inputs: dict[str, Any], agent: Any, console) -> dict[str, Any]:
|
|
133
133
|
"""Execute DSPy program in streaming mode (from original implementation)."""
|
|
134
134
|
logger.info(f"Evaluating agent '{agent.name}' with async streaming.")
|
|
135
|
-
|
|
135
|
+
|
|
136
136
|
if not callable(agent_task):
|
|
137
137
|
logger.error("agent_task is not callable, cannot stream.")
|
|
138
138
|
raise TypeError("DSPy task could not be created or is not callable.")
|
|
@@ -167,7 +167,7 @@ class DeclarativeEvaluationComponent(
|
|
|
167
167
|
async def _execute_standard(self, agent_task, inputs: dict[str, Any], agent: Any) -> dict[str, Any]:
|
|
168
168
|
"""Execute DSPy program in standard mode (from original implementation)."""
|
|
169
169
|
logger.info(f"Evaluating agent '{agent.name}' without streaming.")
|
|
170
|
-
|
|
170
|
+
|
|
171
171
|
try:
|
|
172
172
|
# Ensure the call is awaited if the underlying task is async
|
|
173
173
|
result_obj = await agent_task.acall(**inputs)
|
|
@@ -8,11 +8,12 @@ from typing import TYPE_CHECKING, Any, Literal
|
|
|
8
8
|
from pydantic import Field, model_validator
|
|
9
9
|
|
|
10
10
|
from flock.core.component.agent_component_base import AgentComponentConfig
|
|
11
|
-
from flock.core.component.
|
|
11
|
+
from flock.core.component.routing_component import RoutingComponent
|
|
12
12
|
from flock.core.context.context import FlockContext
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
# HandOffRequest removed - using agent.next_agent directly
|
|
15
15
|
from flock.core.logging.logging import get_logger
|
|
16
|
+
from flock.core.registry import flock_component, get_registry
|
|
16
17
|
|
|
17
18
|
if TYPE_CHECKING:
|
|
18
19
|
from flock.core.flock_agent import FlockAgent
|
|
@@ -148,7 +149,7 @@ class ConditionalRoutingConfig(AgentComponentConfig):
|
|
|
148
149
|
|
|
149
150
|
|
|
150
151
|
@flock_component(config_class=ConditionalRoutingConfig)
|
|
151
|
-
class ConditionalRoutingComponent(
|
|
152
|
+
class ConditionalRoutingComponent(RoutingComponent):
|
|
152
153
|
"""Routes workflow based on evaluating a condition against a value in the FlockContext.
|
|
153
154
|
|
|
154
155
|
Supports various built-in checks (string, number, list, type, bool, existence)
|
|
@@ -436,7 +437,7 @@ class ConditionalRoutingComponent(RoutingComponentBase):
|
|
|
436
437
|
|
|
437
438
|
next_agent = cfg.success_agent or None # Stop chain if None
|
|
438
439
|
logger.debug(f"Success route target: '{next_agent}'")
|
|
439
|
-
|
|
440
|
+
|
|
440
441
|
agent.next_agent = next_agent # Set directly on agent
|
|
441
442
|
|
|
442
443
|
else:
|
|
@@ -481,7 +482,7 @@ class ConditionalRoutingComponent(RoutingComponentBase):
|
|
|
481
482
|
logger.debug(
|
|
482
483
|
f"Failure route target (after retries): '{next_agent}'"
|
|
483
484
|
)
|
|
484
|
-
|
|
485
|
+
|
|
485
486
|
agent.next_agent = next_agent
|
|
486
487
|
else:
|
|
487
488
|
# --- No Retry Logic ---
|
|
@@ -489,5 +490,5 @@ class ConditionalRoutingComponent(RoutingComponentBase):
|
|
|
489
490
|
cfg.failure_agent or None
|
|
490
491
|
) # Use failure agent or stop
|
|
491
492
|
logger.debug(f"Failure route target (no retry): '{next_agent}'")
|
|
492
|
-
|
|
493
|
+
|
|
493
494
|
agent.next_agent = next_agent
|
|
@@ -7,10 +7,10 @@ from typing import TYPE_CHECKING, Any
|
|
|
7
7
|
from pydantic import Field
|
|
8
8
|
|
|
9
9
|
from flock.core.component.agent_component_base import AgentComponentConfig
|
|
10
|
-
from flock.core.component.
|
|
10
|
+
from flock.core.component.routing_component import RoutingComponent
|
|
11
11
|
from flock.core.context.context import FlockContext
|
|
12
|
-
from flock.core.registry import flock_component
|
|
13
12
|
from flock.core.logging.logging import get_logger
|
|
13
|
+
from flock.core.registry import flock_component
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
16
|
from flock.core.flock_agent import FlockAgent
|
|
@@ -27,7 +27,7 @@ class DefaultRoutingConfig(AgentComponentConfig):
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
@flock_component(config_class=DefaultRoutingConfig)
|
|
30
|
-
class DefaultRoutingComponent(
|
|
30
|
+
class DefaultRoutingComponent(RoutingComponent):
|
|
31
31
|
"""Default routing component implementation.
|
|
32
32
|
|
|
33
33
|
This router simply uses the configured hand_off property to determine the next agent.
|
|
@@ -8,11 +8,12 @@ import litellm
|
|
|
8
8
|
from pydantic import Field
|
|
9
9
|
|
|
10
10
|
from flock.core.component.agent_component_base import AgentComponentConfig
|
|
11
|
-
from flock.core.component.
|
|
11
|
+
from flock.core.component.routing_component import RoutingComponent
|
|
12
12
|
from flock.core.context.context import FlockContext
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
# HandOffRequest removed - using agent.next_agent directly
|
|
15
15
|
from flock.core.logging.logging import get_logger
|
|
16
|
+
from flock.core.registry import flock_component
|
|
16
17
|
|
|
17
18
|
if TYPE_CHECKING:
|
|
18
19
|
from flock.core.flock_agent import FlockAgent
|
|
@@ -53,7 +54,7 @@ If no agent is suitable, use "next_agent": "" to end the workflow.""",
|
|
|
53
54
|
|
|
54
55
|
|
|
55
56
|
@flock_component(config_class=LLMRoutingConfig)
|
|
56
|
-
class LLMRoutingComponent(
|
|
57
|
+
class LLMRoutingComponent(RoutingComponent):
|
|
57
58
|
"""Router that uses an LLM to determine the next agent in a workflow.
|
|
58
59
|
|
|
59
60
|
This component analyzes the current agent's output and uses an LLM to
|
|
@@ -79,7 +80,7 @@ class LLMRoutingComponent(RoutingComponentBase):
|
|
|
79
80
|
) -> list[str]:
|
|
80
81
|
"""Get list of available agent names except the current one."""
|
|
81
82
|
available = []
|
|
82
|
-
for agent_name in agent_definitions
|
|
83
|
+
for agent_name in agent_definitions:
|
|
83
84
|
if agent_name != current_agent_name:
|
|
84
85
|
available.append(agent_name)
|
|
85
86
|
return available
|
|
@@ -91,17 +92,16 @@ class LLMRoutingComponent(RoutingComponentBase):
|
|
|
91
92
|
available_agents: list[str],
|
|
92
93
|
) -> str:
|
|
93
94
|
"""Create the prompt for LLM agent selection."""
|
|
94
|
-
|
|
95
95
|
# Format available agents
|
|
96
96
|
agents_list = []
|
|
97
97
|
for agent_name in available_agents:
|
|
98
98
|
agents_list.append(f"- {agent_name}")
|
|
99
|
-
|
|
99
|
+
|
|
100
100
|
available_agents_str = "\n".join(agents_list) if agents_list else "None available"
|
|
101
|
-
|
|
102
|
-
# Format current output
|
|
101
|
+
|
|
102
|
+
# Format current output
|
|
103
103
|
current_output = json.dumps(result, indent=2) if result else "No output"
|
|
104
|
-
|
|
104
|
+
|
|
105
105
|
return self.config.prompt_template.format(
|
|
106
106
|
current_agent_name=current_agent.name,
|
|
107
107
|
current_output=current_output,
|
|
@@ -115,14 +115,13 @@ class LLMRoutingComponent(RoutingComponentBase):
|
|
|
115
115
|
available_agents: list[str],
|
|
116
116
|
) -> tuple[str, float]:
|
|
117
117
|
"""Use an LLM to select the best next agent."""
|
|
118
|
-
|
|
119
118
|
if not available_agents:
|
|
120
119
|
logger.warning("No available agents for LLM routing")
|
|
121
120
|
return "", 0.0
|
|
122
121
|
|
|
123
122
|
# Create the selection prompt
|
|
124
123
|
prompt = self._create_selection_prompt(current_agent, result, available_agents)
|
|
125
|
-
|
|
124
|
+
|
|
126
125
|
try:
|
|
127
126
|
# Call the LLM
|
|
128
127
|
response = await litellm.acompletion(
|
|
@@ -133,30 +132,30 @@ class LLMRoutingComponent(RoutingComponentBase):
|
|
|
133
132
|
temperature=self.config.temperature,
|
|
134
133
|
max_tokens=self.config.max_tokens,
|
|
135
134
|
)
|
|
136
|
-
|
|
135
|
+
|
|
137
136
|
response_content = response.choices[0].message.content.strip()
|
|
138
137
|
logger.debug(f"LLM routing response: {response_content}")
|
|
139
|
-
|
|
138
|
+
|
|
140
139
|
# Parse the JSON response
|
|
141
140
|
try:
|
|
142
141
|
routing_decision = json.loads(response_content)
|
|
143
142
|
next_agent = routing_decision.get("next_agent", "")
|
|
144
143
|
confidence = routing_decision.get("confidence", 0.0)
|
|
145
144
|
reasoning = routing_decision.get("reasoning", "No reasoning provided")
|
|
146
|
-
|
|
145
|
+
|
|
147
146
|
logger.info(f"LLM routing decision: {next_agent} (confidence: {confidence}) - {reasoning}")
|
|
148
|
-
|
|
147
|
+
|
|
149
148
|
# Validate the selected agent is available
|
|
150
149
|
if next_agent and next_agent not in available_agents and next_agent != "":
|
|
151
150
|
logger.warning(f"LLM selected unavailable agent '{next_agent}', ending workflow")
|
|
152
151
|
return "", 0.0
|
|
153
|
-
|
|
152
|
+
|
|
154
153
|
return next_agent, confidence
|
|
155
|
-
|
|
154
|
+
|
|
156
155
|
except json.JSONDecodeError as e:
|
|
157
156
|
logger.error(f"Failed to parse LLM response as JSON: {e}")
|
|
158
157
|
return "", 0.0
|
|
159
|
-
|
|
158
|
+
|
|
160
159
|
except Exception as e:
|
|
161
160
|
logger.error(f"Error calling LLM for routing: {e}")
|
|
162
161
|
return "", 0.0
|
|
@@ -168,19 +167,18 @@ class LLMRoutingComponent(RoutingComponentBase):
|
|
|
168
167
|
context: FlockContext | None = None,
|
|
169
168
|
) -> None:
|
|
170
169
|
"""Use LLM to determine the next agent based on current output."""
|
|
171
|
-
|
|
172
170
|
if not context:
|
|
173
171
|
logger.warning("No context provided for LLM routing")
|
|
174
172
|
return
|
|
175
173
|
|
|
176
174
|
logger.info(f"LLM routing from agent '{agent.name}'")
|
|
177
|
-
|
|
175
|
+
|
|
178
176
|
# Get available agents from context
|
|
179
177
|
agent_definitions = getattr(context, 'agent_definitions', {})
|
|
180
178
|
available_agents = self._get_available_agents(agent_definitions, agent.name)
|
|
181
|
-
|
|
179
|
+
|
|
182
180
|
logger.debug(f"Available agents for LLM routing: {available_agents}")
|
|
183
|
-
|
|
181
|
+
|
|
184
182
|
if not available_agents:
|
|
185
183
|
logger.warning("No available agents for LLM routing")
|
|
186
184
|
return
|
|
@@ -189,20 +187,20 @@ class LLMRoutingComponent(RoutingComponentBase):
|
|
|
189
187
|
next_agent_name, confidence = await self._select_next_agent(
|
|
190
188
|
agent, result, available_agents
|
|
191
189
|
)
|
|
192
|
-
|
|
190
|
+
|
|
193
191
|
logger.info(f"LLM routing result: {next_agent_name} (confidence: {confidence})")
|
|
194
|
-
|
|
192
|
+
|
|
195
193
|
# Check confidence threshold
|
|
196
194
|
if not next_agent_name or confidence < self.config.confidence_threshold:
|
|
197
195
|
logger.warning(
|
|
198
196
|
f"LLM routing confidence {confidence} below threshold {self.config.confidence_threshold}"
|
|
199
197
|
)
|
|
200
198
|
return
|
|
201
|
-
|
|
199
|
+
|
|
202
200
|
# Validate the selected agent exists
|
|
203
201
|
if next_agent_name not in agent_definitions:
|
|
204
202
|
logger.error(f"LLM selected non-existent agent '{next_agent_name}'")
|
|
205
203
|
return
|
|
206
|
-
|
|
204
|
+
|
|
207
205
|
logger.info(f"Successfully routed to agent '{next_agent_name}' with confidence {confidence}")
|
|
208
206
|
agent.next_agent = next_agent_name
|
|
@@ -30,10 +30,10 @@ from flock.adapter.faiss_adapter import FAISSAdapter
|
|
|
30
30
|
from flock.adapter.pinecone_adapter import PineconeAdapter
|
|
31
31
|
from flock.adapter.vector_base import VectorAdapter
|
|
32
32
|
from flock.core.component.agent_component_base import AgentComponentConfig
|
|
33
|
-
from flock.core.component.
|
|
33
|
+
from flock.core.component.utility_component import UtilityComponent
|
|
34
34
|
from flock.core.context.context import FlockContext
|
|
35
|
-
from flock.core.registry import flock_component
|
|
36
35
|
from flock.core.logging.logging import get_logger
|
|
36
|
+
from flock.core.registry import flock_component
|
|
37
37
|
|
|
38
38
|
# Conditional import for MetricsUtilityComponent to avoid circular imports
|
|
39
39
|
if TYPE_CHECKING:
|
|
@@ -423,7 +423,7 @@ class MemoryStore:
|
|
|
423
423
|
|
|
424
424
|
|
|
425
425
|
@flock_component(config_class=MemoryUtilityConfig)
|
|
426
|
-
class MemoryUtilityComponent(
|
|
426
|
+
class MemoryUtilityComponent(UtilityComponent):
|
|
427
427
|
"""Enterprise-ready memory utility component using real datastores."""
|
|
428
428
|
|
|
429
429
|
config: MemoryUtilityConfig = Field(
|
|
@@ -13,10 +13,10 @@ import psutil
|
|
|
13
13
|
from pydantic import BaseModel, Field, field_validator
|
|
14
14
|
|
|
15
15
|
from flock.core.component.agent_component_base import AgentComponentConfig
|
|
16
|
-
from flock.core.component.
|
|
16
|
+
from flock.core.component.utility_component import UtilityComponent
|
|
17
17
|
from flock.core.context.context import FlockContext
|
|
18
|
+
from flock.core.mcp.flock_mcp_server import FlockMCPServer
|
|
18
19
|
from flock.core.registry import flock_component
|
|
19
|
-
from flock.core.mcp.flock_mcp_server import FlockMCPServerBase
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
22
|
from flock.core.flock_agent import FlockAgent
|
|
@@ -79,7 +79,7 @@ class MetricsUtilityConfig(AgentComponentConfig):
|
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
@flock_component(config_class=MetricsUtilityConfig)
|
|
82
|
-
class MetricsUtilityComponent(
|
|
82
|
+
class MetricsUtilityComponent(UtilityComponent):
|
|
83
83
|
"""Utility component for collecting and analyzing agent performance metrics."""
|
|
84
84
|
|
|
85
85
|
# --- Singleton holder for convenient static access ---
|
|
@@ -540,7 +540,7 @@ class MetricsUtilityComponent(UtilityComponentBase):
|
|
|
540
540
|
|
|
541
541
|
# --- MCP Server Lifecycle Hooks ---
|
|
542
542
|
async def on_server_error(
|
|
543
|
-
self, server:
|
|
543
|
+
self, server: FlockMCPServer, error: Exception
|
|
544
544
|
) -> None:
|
|
545
545
|
"""Record server error metrics."""
|
|
546
546
|
self._record_metric(
|
|
@@ -552,7 +552,7 @@ class MetricsUtilityComponent(UtilityComponentBase):
|
|
|
552
552
|
},
|
|
553
553
|
)
|
|
554
554
|
|
|
555
|
-
async def on_pre_server_init(self, server:
|
|
555
|
+
async def on_pre_server_init(self, server: FlockMCPServer):
|
|
556
556
|
"""Initialize metrics collection for server."""
|
|
557
557
|
self._server_start_time = time.time()
|
|
558
558
|
|
|
@@ -564,7 +564,7 @@ class MetricsUtilityComponent(UtilityComponentBase):
|
|
|
564
564
|
{"server": server.config.name, "phase": "pre_init"},
|
|
565
565
|
)
|
|
566
566
|
|
|
567
|
-
async def on_post_server_init(self, server:
|
|
567
|
+
async def on_post_server_init(self, server: FlockMCPServer):
|
|
568
568
|
"""Collect metrics after server starts."""
|
|
569
569
|
if self.config.collect_memory:
|
|
570
570
|
checkpoint_memory = psutil.Process().memory_info().rss
|
|
@@ -574,7 +574,7 @@ class MetricsUtilityComponent(UtilityComponentBase):
|
|
|
574
574
|
{"server": server.config.name, "phase": "post_init"},
|
|
575
575
|
)
|
|
576
576
|
|
|
577
|
-
async def on_pre_server_terminate(self, server:
|
|
577
|
+
async def on_pre_server_terminate(self, server: FlockMCPServer):
|
|
578
578
|
"""Collect metrics before server terminates."""
|
|
579
579
|
if self.config.collect_memory:
|
|
580
580
|
checkpoint_memory = psutil.Process().memory_info().rss
|
|
@@ -584,7 +584,7 @@ class MetricsUtilityComponent(UtilityComponentBase):
|
|
|
584
584
|
{"server": server.config.name, "phase": "pre_terminate"},
|
|
585
585
|
)
|
|
586
586
|
|
|
587
|
-
async def on_post_server_terminate(self, server:
|
|
587
|
+
async def on_post_server_terminate(self, server: FlockMCPServer):
|
|
588
588
|
"""Collect metrics after server terminates.
|
|
589
589
|
|
|
590
590
|
Clean up and final metric recording.
|
|
@@ -615,7 +615,7 @@ class MetricsUtilityComponent(UtilityComponentBase):
|
|
|
615
615
|
json.dump(summary, f, indent=2)
|
|
616
616
|
|
|
617
617
|
async def on_pre_mcp_call(
|
|
618
|
-
self, server:
|
|
618
|
+
self, server: FlockMCPServer, arguments: Any | None = None
|
|
619
619
|
):
|
|
620
620
|
"""Record pre-call metrics."""
|
|
621
621
|
if self.config.collect_cpu:
|
|
@@ -645,7 +645,7 @@ class MetricsUtilityComponent(UtilityComponentBase):
|
|
|
645
645
|
)
|
|
646
646
|
|
|
647
647
|
async def on_post_mcp_call(
|
|
648
|
-
self, server:
|
|
648
|
+
self, server: FlockMCPServer, result: Any | None = None
|
|
649
649
|
):
|
|
650
650
|
"""Record post-call metrics."""
|
|
651
651
|
if self.config.collect_timing and self._server_start_time:
|
|
@@ -676,7 +676,7 @@ class MetricsUtilityComponent(UtilityComponentBase):
|
|
|
676
676
|
)
|
|
677
677
|
|
|
678
678
|
async def on_connect(
|
|
679
|
-
self, server:
|
|
679
|
+
self, server: FlockMCPServer, additional_params: dict[str, Any]
|
|
680
680
|
) -> dict[str, Any]:
|
|
681
681
|
"""Collect metrics during connect."""
|
|
682
682
|
# We should track the refresh rate for clients
|
|
@@ -7,13 +7,15 @@ from typing import TYPE_CHECKING, Any
|
|
|
7
7
|
from pydantic import Field
|
|
8
8
|
|
|
9
9
|
from flock.core.component.agent_component_base import AgentComponentConfig
|
|
10
|
-
from flock.core.component.
|
|
10
|
+
from flock.core.component.utility_component import UtilityComponent
|
|
11
11
|
from flock.core.context.context import FlockContext
|
|
12
12
|
from flock.core.context.context_vars import FLOCK_BATCH_SILENT_MODE
|
|
13
|
-
from flock.core.
|
|
14
|
-
|
|
13
|
+
from flock.core.logging.formatters.themed_formatter import (
|
|
14
|
+
ThemedAgentResultFormatter,
|
|
15
|
+
)
|
|
15
16
|
from flock.core.logging.formatters.themes import OutputTheme
|
|
16
17
|
from flock.core.logging.logging import get_logger
|
|
18
|
+
from flock.core.registry import flock_component
|
|
17
19
|
|
|
18
20
|
if TYPE_CHECKING:
|
|
19
21
|
from flock.core.flock_agent import FlockAgent
|
|
@@ -58,7 +60,7 @@ class OutputUtilityConfig(AgentComponentConfig):
|
|
|
58
60
|
|
|
59
61
|
|
|
60
62
|
@flock_component(config_class=OutputUtilityConfig)
|
|
61
|
-
class OutputUtilityComponent(
|
|
63
|
+
class OutputUtilityComponent(UtilityComponent):
|
|
62
64
|
"""Utility component that handles output formatting and display."""
|
|
63
65
|
|
|
64
66
|
config: OutputUtilityConfig = Field(
|
|
@@ -97,7 +99,7 @@ class OutputUtilityComponent(UtilityComponentBase):
|
|
|
97
99
|
"""Format a dictionary with proper indentation."""
|
|
98
100
|
if not d:
|
|
99
101
|
return "{}"
|
|
100
|
-
|
|
102
|
+
|
|
101
103
|
items = []
|
|
102
104
|
prefix = " " * indent
|
|
103
105
|
for key, value in d.items():
|
|
@@ -105,17 +107,17 @@ class OutputUtilityComponent(UtilityComponentBase):
|
|
|
105
107
|
value = value[:97] + "..."
|
|
106
108
|
formatted_value = self._format_value(value, key)
|
|
107
109
|
items.append(f"{prefix} {key}: {formatted_value}")
|
|
108
|
-
|
|
110
|
+
|
|
109
111
|
return "{\n" + "\n".join(items) + f"\n{prefix}}}"
|
|
110
112
|
|
|
111
113
|
def _format_list(self, lst: list[Any]) -> str:
|
|
112
114
|
"""Format a list with proper structure."""
|
|
113
115
|
if not lst:
|
|
114
116
|
return "[]"
|
|
115
|
-
|
|
117
|
+
|
|
116
118
|
if len(lst) <= 3:
|
|
117
119
|
return str(lst)
|
|
118
|
-
|
|
120
|
+
|
|
119
121
|
# For longer lists, show first few items and count
|
|
120
122
|
preview = [str(item) for item in lst[:3]]
|
|
121
123
|
return f"[{', '.join(preview)}, ... ({len(lst)} total)]"
|
|
@@ -127,7 +129,7 @@ class OutputUtilityComponent(UtilityComponentBase):
|
|
|
127
129
|
language = match.group(1) or "text"
|
|
128
130
|
code = match.group(2)
|
|
129
131
|
return f"[CODE:{language}]\n{code}\n[/CODE]"
|
|
130
|
-
|
|
132
|
+
|
|
131
133
|
# Replace markdown-style code blocks
|
|
132
134
|
text = re.sub(
|
|
133
135
|
r"```(\w+)?\n(.*?)\n```", replace_code_block, text, flags=re.DOTALL
|
flock/core/__init__.py
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
"""This module contains the core classes of the flock package."""
|
|
2
2
|
|
|
3
|
+
from flock.core.component import (
|
|
4
|
+
AgentComponent,
|
|
5
|
+
AgentComponentConfig,
|
|
6
|
+
EvaluationComponent,
|
|
7
|
+
RoutingComponent,
|
|
8
|
+
UtilityComponent,
|
|
9
|
+
)
|
|
3
10
|
from flock.core.context.context import FlockContext
|
|
4
11
|
from flock.core.flock import Flock
|
|
5
12
|
from flock.core.flock_agent import FlockAgent
|
|
6
13
|
from flock.core.flock_factory import FlockFactory
|
|
14
|
+
from flock.core.mcp.flock_mcp_server import (
|
|
15
|
+
FlockMCPServer,
|
|
16
|
+
)
|
|
17
|
+
from flock.core.mcp.flock_mcp_tool import FlockMCPTool
|
|
18
|
+
from flock.core.mcp.mcp_client import FlockMCPClient
|
|
19
|
+
from flock.core.mcp.mcp_client_manager import FlockMCPClientManager
|
|
7
20
|
from flock.core.registry import (
|
|
8
21
|
RegistryHub as FlockRegistry, # Keep FlockRegistry name for API compatibility
|
|
9
22
|
flock_callable,
|
|
@@ -12,23 +25,24 @@ from flock.core.registry import (
|
|
|
12
25
|
flock_type,
|
|
13
26
|
get_registry,
|
|
14
27
|
)
|
|
15
|
-
from flock.core.mcp.flock_mcp_server import (
|
|
16
|
-
FlockMCPServerBase,
|
|
17
|
-
)
|
|
18
|
-
from flock.core.mcp.flock_mcp_tool_base import FlockMCPToolBase
|
|
19
|
-
from flock.core.mcp.mcp_client import FlockMCPClientBase
|
|
20
|
-
from flock.core.mcp.mcp_client_manager import FlockMCPClientManagerBase
|
|
21
28
|
|
|
22
29
|
__all__ = [
|
|
23
30
|
"Flock",
|
|
24
31
|
"FlockAgent",
|
|
25
32
|
"FlockContext",
|
|
26
33
|
"FlockFactory",
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
"
|
|
34
|
+
# Components
|
|
35
|
+
"AgentComponent",
|
|
36
|
+
"AgentComponentConfig",
|
|
37
|
+
"EvaluationComponent",
|
|
38
|
+
"RoutingComponent",
|
|
39
|
+
"UtilityComponent",
|
|
40
|
+
|
|
41
|
+
"FlockMCPClient",
|
|
42
|
+
"FlockMCPClientManager",
|
|
43
|
+
"FlockMCPServer",
|
|
30
44
|
"FlockMCPServerConfig",
|
|
31
|
-
"
|
|
45
|
+
"FlockMCPTool",
|
|
32
46
|
"FlockRegistry",
|
|
33
47
|
"flock_callable",
|
|
34
48
|
"flock_component",
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any
|
|
4
4
|
|
|
5
|
-
from flock.core.component.
|
|
6
|
-
from flock.core.component.
|
|
7
|
-
from flock.core.component.
|
|
5
|
+
from flock.core.component.evaluation_component import EvaluationComponent
|
|
6
|
+
from flock.core.component.routing_component import RoutingComponent
|
|
7
|
+
from flock.core.component.utility_component import UtilityComponent
|
|
8
8
|
from flock.core.logging.logging import get_logger
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
|
-
from flock.core.flock_agent import FlockAgent
|
|
12
11
|
from flock.core.component.agent_component_base import AgentComponent
|
|
12
|
+
from flock.core.flock_agent import FlockAgent
|
|
13
13
|
|
|
14
14
|
logger = get_logger("agent.components")
|
|
15
15
|
|
|
@@ -25,7 +25,7 @@ class FlockAgentComponents:
|
|
|
25
25
|
if not component.name:
|
|
26
26
|
logger.error("Component must have a name to be added.")
|
|
27
27
|
return
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
# Check if component with same name already exists
|
|
30
30
|
existing = self.get_component(component.name)
|
|
31
31
|
if existing:
|
|
@@ -54,29 +54,29 @@ class FlockAgentComponents:
|
|
|
54
54
|
def get_enabled_components(self) -> list["AgentComponent"]:
|
|
55
55
|
"""Get a list of currently enabled components attached to this agent."""
|
|
56
56
|
return [c for c in self.agent.components if c.config.enabled]
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
def get_components_by_type(self, component_type: type) -> list["AgentComponent"]:
|
|
59
59
|
"""Get all components of a specific type."""
|
|
60
60
|
return [c for c in self.agent.components if isinstance(c, component_type)]
|
|
61
61
|
|
|
62
|
-
def get_evaluation_components(self) -> list[
|
|
62
|
+
def get_evaluation_components(self) -> list[EvaluationComponent]:
|
|
63
63
|
"""Get all evaluation components."""
|
|
64
|
-
return self.get_components_by_type(
|
|
64
|
+
return self.get_components_by_type(EvaluationComponent)
|
|
65
65
|
|
|
66
|
-
def get_routing_components(self) -> list[
|
|
66
|
+
def get_routing_components(self) -> list[RoutingComponent]:
|
|
67
67
|
"""Get all routing components."""
|
|
68
|
-
return self.get_components_by_type(
|
|
69
|
-
|
|
70
|
-
def get_utility_components(self) -> list[
|
|
68
|
+
return self.get_components_by_type(RoutingComponent)
|
|
69
|
+
|
|
70
|
+
def get_utility_components(self) -> list[UtilityComponent]:
|
|
71
71
|
"""Get all utility components."""
|
|
72
|
-
return self.get_components_by_type(
|
|
72
|
+
return self.get_components_by_type(UtilityComponent)
|
|
73
73
|
|
|
74
|
-
def get_primary_evaluator(self) ->
|
|
74
|
+
def get_primary_evaluator(self) -> EvaluationComponent | None:
|
|
75
75
|
"""Get the primary evaluation component (first one found)."""
|
|
76
76
|
evaluators = self.get_evaluation_components()
|
|
77
77
|
return evaluators[0] if evaluators else None
|
|
78
|
-
|
|
79
|
-
def get_primary_router(self) ->
|
|
78
|
+
|
|
79
|
+
def get_primary_router(self) -> RoutingComponent | None:
|
|
80
80
|
"""Get the primary routing component (first one found)."""
|
|
81
81
|
routers = self.get_routing_components()
|
|
82
82
|
return routers[0] if routers else None
|