flock-core 0.4.527__py3-none-any.whl → 0.5.0b0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/cli/execute_flock.py +1 -1
- flock/cli/manage_agents.py +6 -6
- flock/components/__init__.py +30 -0
- flock/components/evaluation/__init__.py +9 -0
- flock/components/evaluation/declarative_evaluation_component.py +222 -0
- flock/components/routing/__init__.py +15 -0
- flock/{routers/conditional/conditional_router.py → components/routing/conditional_routing_component.py} +61 -53
- flock/components/routing/default_routing_component.py +103 -0
- flock/components/routing/llm_routing_component.py +206 -0
- flock/components/utility/__init__.py +15 -0
- flock/{modules/enterprise_memory/enterprise_memory_module.py → components/utility/memory_utility_component.py} +195 -173
- flock/{modules/performance/metrics_module.py → components/utility/metrics_utility_component.py} +110 -95
- flock/{modules/output/output_module.py → components/utility/output_utility_component.py} +47 -45
- flock/core/__init__.py +26 -18
- flock/core/agent/__init__.py +16 -0
- flock/core/agent/flock_agent_components.py +104 -0
- flock/core/agent/flock_agent_execution.py +101 -0
- flock/core/agent/flock_agent_integration.py +206 -0
- flock/core/agent/flock_agent_lifecycle.py +177 -0
- flock/core/agent/flock_agent_serialization.py +381 -0
- flock/core/api/endpoints.py +2 -2
- flock/core/api/service.py +2 -2
- flock/core/component/__init__.py +15 -0
- flock/core/{flock_module.py → component/agent_component_base.py} +136 -34
- flock/core/component/evaluation_component.py +56 -0
- flock/core/component/routing_component.py +74 -0
- flock/core/component/utility_component.py +69 -0
- flock/core/config/flock_agent_config.py +49 -2
- flock/core/evaluation/utils.py +3 -2
- flock/core/execution/batch_executor.py +1 -1
- flock/core/execution/evaluation_executor.py +2 -2
- flock/core/execution/opik_executor.py +1 -1
- flock/core/flock.py +147 -493
- flock/core/flock_agent.py +195 -1032
- flock/core/flock_factory.py +114 -90
- flock/core/flock_scheduler.py +1 -1
- flock/core/flock_server_manager.py +8 -8
- flock/core/logging/logging.py +1 -0
- flock/core/mcp/flock_mcp_server.py +53 -48
- flock/core/mcp/{flock_mcp_tool_base.py → flock_mcp_tool.py} +2 -2
- flock/core/mcp/mcp_client.py +9 -9
- flock/core/mcp/mcp_client_manager.py +9 -9
- flock/core/mcp/mcp_config.py +24 -24
- flock/core/mixin/dspy_integration.py +5 -5
- flock/core/orchestration/__init__.py +18 -0
- flock/core/orchestration/flock_batch_processor.py +94 -0
- flock/core/orchestration/flock_evaluator.py +113 -0
- flock/core/orchestration/flock_execution.py +288 -0
- flock/core/orchestration/flock_initialization.py +125 -0
- flock/core/orchestration/flock_server_manager.py +67 -0
- flock/core/orchestration/flock_web_server.py +117 -0
- flock/core/registry/__init__.py +45 -0
- flock/core/registry/agent_registry.py +69 -0
- flock/core/registry/callable_registry.py +139 -0
- flock/core/registry/component_discovery.py +142 -0
- flock/core/registry/component_registry.py +64 -0
- flock/core/registry/config_mapping.py +64 -0
- flock/core/registry/decorators.py +137 -0
- flock/core/registry/registry_hub.py +205 -0
- flock/core/registry/server_registry.py +57 -0
- flock/core/registry/type_registry.py +86 -0
- flock/core/serialization/flock_serializer.py +36 -32
- flock/core/serialization/serialization_utils.py +28 -25
- flock/core/util/hydrator.py +1 -1
- flock/core/util/input_resolver.py +29 -2
- flock/mcp/servers/sse/flock_sse_server.py +10 -10
- flock/mcp/servers/stdio/flock_stdio_server.py +10 -10
- flock/mcp/servers/streamable_http/flock_streamable_http_server.py +10 -10
- flock/mcp/servers/websockets/flock_websocket_server.py +10 -10
- flock/platform/docker_tools.py +3 -3
- flock/webapp/app/chat.py +1 -1
- flock/webapp/app/main.py +9 -5
- flock/webapp/app/services/flock_service.py +1 -1
- flock/webapp/app/services/sharing_store.py +1 -0
- flock/workflow/activities.py +67 -92
- flock/workflow/agent_execution_activity.py +6 -6
- flock/workflow/flock_workflow.py +1 -1
- flock_core-0.5.0b0.dist-info/METADATA +272 -0
- {flock_core-0.4.527.dist-info → flock_core-0.5.0b0.dist-info}/RECORD +82 -95
- flock/core/flock_evaluator.py +0 -60
- flock/core/flock_registry.py +0 -702
- flock/core/flock_router.py +0 -83
- flock/evaluators/__init__.py +0 -1
- flock/evaluators/declarative/__init__.py +0 -1
- flock/evaluators/declarative/declarative_evaluator.py +0 -217
- flock/evaluators/memory/memory_evaluator.py +0 -90
- flock/evaluators/test/test_case_evaluator.py +0 -38
- flock/evaluators/zep/zep_evaluator.py +0 -59
- flock/modules/__init__.py +0 -1
- flock/modules/assertion/__init__.py +0 -1
- flock/modules/assertion/assertion_module.py +0 -286
- flock/modules/callback/__init__.py +0 -1
- flock/modules/callback/callback_module.py +0 -91
- flock/modules/enterprise_memory/README.md +0 -99
- flock/modules/mem0/__init__.py +0 -1
- flock/modules/mem0/mem0_module.py +0 -126
- flock/modules/mem0_async/__init__.py +0 -1
- flock/modules/mem0_async/async_mem0_module.py +0 -126
- flock/modules/memory/__init__.py +0 -1
- flock/modules/memory/memory_module.py +0 -429
- flock/modules/memory/memory_parser.py +0 -125
- flock/modules/memory/memory_storage.py +0 -736
- flock/modules/output/__init__.py +0 -1
- flock/modules/performance/__init__.py +0 -1
- flock/modules/zep/__init__.py +0 -1
- flock/modules/zep/zep_module.py +0 -192
- flock/routers/__init__.py +0 -1
- flock/routers/agent/__init__.py +0 -1
- flock/routers/agent/agent_router.py +0 -236
- flock/routers/agent/handoff_agent.py +0 -58
- flock/routers/default/__init__.py +0 -1
- flock/routers/default/default_router.py +0 -80
- flock/routers/feedback/feedback_router.py +0 -114
- flock/routers/list_generator/list_generator_router.py +0 -166
- flock/routers/llm/__init__.py +0 -1
- flock/routers/llm/llm_router.py +0 -365
- flock/tools/__init__.py +0 -0
- flock/tools/azure_tools.py +0 -781
- flock/tools/code_tools.py +0 -167
- flock/tools/file_tools.py +0 -149
- flock/tools/github_tools.py +0 -157
- flock/tools/markdown_tools.py +0 -205
- flock/tools/system_tools.py +0 -9
- flock/tools/text_tools.py +0 -810
- flock/tools/web_tools.py +0 -90
- flock/tools/zendesk_tools.py +0 -147
- flock_core-0.4.527.dist-info/METADATA +0 -674
- {flock_core-0.4.527.dist-info → flock_core-0.5.0b0.dist-info}/WHEEL +0 -0
- {flock_core-0.4.527.dist-info → flock_core-0.5.0b0.dist-info}/entry_points.txt +0 -0
- {flock_core-0.4.527.dist-info → flock_core-0.5.0b0.dist-info}/licenses/LICENSE +0 -0
flock/cli/execute_flock.py
CHANGED
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,11 +165,11 @@ 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
|
-
basic_info.add_row("Write to File", str(agent.write_to_file))
|
|
172
|
-
basic_info.add_row("Wait for input", str(agent.wait_for_input))
|
|
171
|
+
basic_info.add_row("Write to File", str(agent.config.write_to_file))
|
|
172
|
+
basic_info.add_row("Wait for input", str(agent.config.wait_for_input))
|
|
173
173
|
|
|
174
174
|
console.print(Panel(basic_info, title="Basic Information"))
|
|
175
175
|
|
|
@@ -180,7 +180,7 @@ def _view_agent_details(agent: FlockAgent):
|
|
|
180
180
|
console.print(Panel(evaluator_info, title="Evaluator"))
|
|
181
181
|
|
|
182
182
|
# Router info
|
|
183
|
-
router_info = f"Type: {type(agent.
|
|
183
|
+
router_info = f"Type: {type(agent.router).__name__ if agent.router else 'None'}"
|
|
184
184
|
console.print(Panel(router_info, title="Router"))
|
|
185
185
|
|
|
186
186
|
# Tools
|
|
@@ -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))
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# src/flock/components/__init__.py
|
|
2
|
+
"""Unified component implementations for Flock agents."""
|
|
3
|
+
|
|
4
|
+
# Evaluation components
|
|
5
|
+
from .evaluation.declarative_evaluation_component import (
|
|
6
|
+
DeclarativeEvaluationComponent,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
# Routing components
|
|
10
|
+
from .routing.conditional_routing_component import ConditionalRoutingComponent
|
|
11
|
+
from .routing.default_routing_component import DefaultRoutingComponent
|
|
12
|
+
from .routing.llm_routing_component import LLMRoutingComponent
|
|
13
|
+
|
|
14
|
+
# Utility components
|
|
15
|
+
from .utility.memory_utility_component import MemoryUtilityComponent
|
|
16
|
+
from .utility.metrics_utility_component import MetricsUtilityComponent
|
|
17
|
+
from .utility.output_utility_component import OutputUtilityComponent
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
# Routing
|
|
21
|
+
"ConditionalRoutingComponent",
|
|
22
|
+
# Evaluation
|
|
23
|
+
"DeclarativeEvaluationComponent",
|
|
24
|
+
"DefaultRoutingComponent",
|
|
25
|
+
"LLMRoutingComponent",
|
|
26
|
+
# Utility
|
|
27
|
+
"MemoryUtilityComponent",
|
|
28
|
+
"MetricsUtilityComponent",
|
|
29
|
+
"OutputUtilityComponent",
|
|
30
|
+
]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# src/flock/components/evaluation/__init__.py
|
|
2
|
+
"""Evaluation components for the unified component system."""
|
|
3
|
+
|
|
4
|
+
from .declarative_evaluation_component import DeclarativeEvaluationComponent, DeclarativeEvaluationConfig
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"DeclarativeEvaluationComponent",
|
|
8
|
+
"DeclarativeEvaluationConfig",
|
|
9
|
+
]
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# src/flock/components/evaluation/declarative_evaluation_component.py
|
|
2
|
+
"""DeclarativeEvaluationComponent - DSPy-based evaluation using the unified component system."""
|
|
3
|
+
|
|
4
|
+
from collections.abc import Generator
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from temporalio import workflow
|
|
8
|
+
|
|
9
|
+
with workflow.unsafe.imports_passed_through():
|
|
10
|
+
import dspy
|
|
11
|
+
|
|
12
|
+
from pydantic import Field, PrivateAttr
|
|
13
|
+
|
|
14
|
+
from flock.core.component.agent_component_base import AgentComponentConfig
|
|
15
|
+
from flock.core.component.evaluation_component import EvaluationComponent
|
|
16
|
+
from flock.core.context.context import FlockContext
|
|
17
|
+
from flock.core.logging.logging import get_logger
|
|
18
|
+
from flock.core.mixin.dspy_integration import DSPyIntegrationMixin
|
|
19
|
+
from flock.core.mixin.prompt_parser import PromptParserMixin
|
|
20
|
+
from flock.core.registry import flock_component
|
|
21
|
+
|
|
22
|
+
logger = get_logger("components.evaluation.declarative")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DeclarativeEvaluationConfig(AgentComponentConfig):
|
|
26
|
+
"""Configuration for the DeclarativeEvaluationComponent."""
|
|
27
|
+
|
|
28
|
+
override_evaluator_type: str | None = None
|
|
29
|
+
model: str | None = "openai/gpt-4o"
|
|
30
|
+
use_cache: bool = True
|
|
31
|
+
temperature: float = 0.0
|
|
32
|
+
max_tokens: int = 4096
|
|
33
|
+
max_retries: int = 3
|
|
34
|
+
max_tool_calls: int = 10
|
|
35
|
+
stream: bool = Field(
|
|
36
|
+
default=False,
|
|
37
|
+
description="Enable streaming output from the underlying DSPy program.",
|
|
38
|
+
)
|
|
39
|
+
include_thought_process: bool = Field(
|
|
40
|
+
default=False,
|
|
41
|
+
description="Include the thought process in the output.",
|
|
42
|
+
)
|
|
43
|
+
include_reasoning: bool = Field(
|
|
44
|
+
default=False,
|
|
45
|
+
description="Include the reasoning in the output.",
|
|
46
|
+
)
|
|
47
|
+
kwargs: dict[str, Any] = Field(default_factory=dict)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@flock_component(config_class=DeclarativeEvaluationConfig)
|
|
51
|
+
class DeclarativeEvaluationComponent(
|
|
52
|
+
EvaluationComponent, DSPyIntegrationMixin, PromptParserMixin
|
|
53
|
+
):
|
|
54
|
+
"""Evaluation component that uses DSPy for generation.
|
|
55
|
+
|
|
56
|
+
This component provides the core intelligence for agents using DSPy's
|
|
57
|
+
declarative programming model. It handles LLM interactions, tool usage,
|
|
58
|
+
and prompt management through DSPy's framework.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
config: DeclarativeEvaluationConfig = Field(
|
|
62
|
+
default_factory=DeclarativeEvaluationConfig,
|
|
63
|
+
description="Evaluation configuration",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
_cost: float = PrivateAttr(default=0.0)
|
|
67
|
+
_lm_history: list = PrivateAttr(default_factory=list)
|
|
68
|
+
|
|
69
|
+
def __init__(self, **data):
|
|
70
|
+
super().__init__(**data)
|
|
71
|
+
|
|
72
|
+
async def evaluate_core(
|
|
73
|
+
self,
|
|
74
|
+
agent: Any,
|
|
75
|
+
inputs: dict[str, Any],
|
|
76
|
+
context: FlockContext | None = None,
|
|
77
|
+
tools: list[Any] | None = None,
|
|
78
|
+
mcp_tools: list[Any] | None = None,
|
|
79
|
+
) -> dict[str, Any]:
|
|
80
|
+
"""Core evaluation logic using DSPy - migrated from DeclarativeEvaluator."""
|
|
81
|
+
logger.debug(f"Starting declarative evaluation for component '{self.name}'")
|
|
82
|
+
|
|
83
|
+
# Setup DSPy context with LM (directly from original implementation)
|
|
84
|
+
with dspy.context(
|
|
85
|
+
lm=dspy.LM(
|
|
86
|
+
model=self.config.model or agent.model,
|
|
87
|
+
cache=self.config.use_cache,
|
|
88
|
+
temperature=self.config.temperature,
|
|
89
|
+
max_tokens=self.config.max_tokens,
|
|
90
|
+
num_retries=self.config.max_retries,
|
|
91
|
+
)
|
|
92
|
+
):
|
|
93
|
+
try:
|
|
94
|
+
from rich.console import Console
|
|
95
|
+
console = Console()
|
|
96
|
+
|
|
97
|
+
# Create DSPy signature from agent definition
|
|
98
|
+
_dspy_signature = self.create_dspy_signature_class(
|
|
99
|
+
agent.name,
|
|
100
|
+
agent.description,
|
|
101
|
+
f"{agent.input} -> {agent.output}",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Get output field names for streaming
|
|
105
|
+
output_field_names = list(_dspy_signature.output_fields.keys())
|
|
106
|
+
if not output_field_names:
|
|
107
|
+
logger.warning(
|
|
108
|
+
f"DSPy signature for agent '{agent.name}' has no defined output fields. Streaming might not produce text."
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Select appropriate DSPy task
|
|
112
|
+
agent_task = self._select_task(
|
|
113
|
+
_dspy_signature,
|
|
114
|
+
override_evaluator_type=self.config.override_evaluator_type,
|
|
115
|
+
tools=tools or [],
|
|
116
|
+
max_tool_calls=self.config.max_tool_calls,
|
|
117
|
+
mcp_tools=mcp_tools or [],
|
|
118
|
+
kwargs=self.config.kwargs,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
except Exception as setup_error:
|
|
122
|
+
logger.error(
|
|
123
|
+
f"Error setting up DSPy task for agent '{agent.name}': {setup_error}",
|
|
124
|
+
exc_info=True,
|
|
125
|
+
)
|
|
126
|
+
raise RuntimeError(
|
|
127
|
+
f"DSPy task setup failed: {setup_error}"
|
|
128
|
+
) from setup_error
|
|
129
|
+
|
|
130
|
+
# Execute with streaming or non-streaming
|
|
131
|
+
if self.config.stream:
|
|
132
|
+
return await self._execute_streaming(agent_task, inputs, agent, console)
|
|
133
|
+
else:
|
|
134
|
+
return await self._execute_standard(agent_task, inputs, agent)
|
|
135
|
+
|
|
136
|
+
async def _execute_streaming(self, agent_task, inputs: dict[str, Any], agent: Any, console) -> dict[str, Any]:
|
|
137
|
+
"""Execute DSPy program in streaming mode (from original implementation)."""
|
|
138
|
+
logger.info(f"Evaluating agent '{agent.name}' with async streaming.")
|
|
139
|
+
|
|
140
|
+
if not callable(agent_task):
|
|
141
|
+
logger.error("agent_task is not callable, cannot stream.")
|
|
142
|
+
raise TypeError("DSPy task could not be created or is not callable.")
|
|
143
|
+
|
|
144
|
+
streaming_task = dspy.streamify(agent_task, is_async_program=True)
|
|
145
|
+
stream_generator: Generator = streaming_task(**inputs)
|
|
146
|
+
delta_content = ""
|
|
147
|
+
|
|
148
|
+
console.print("\n")
|
|
149
|
+
async for chunk in stream_generator:
|
|
150
|
+
if (
|
|
151
|
+
hasattr(chunk, "choices")
|
|
152
|
+
and chunk.choices
|
|
153
|
+
and hasattr(chunk.choices[0], "delta")
|
|
154
|
+
and chunk.choices[0].delta
|
|
155
|
+
and hasattr(chunk.choices[0].delta, "content")
|
|
156
|
+
):
|
|
157
|
+
delta_content = chunk.choices[0].delta.content
|
|
158
|
+
|
|
159
|
+
if delta_content:
|
|
160
|
+
console.print(delta_content, end="")
|
|
161
|
+
|
|
162
|
+
result_dict, cost, lm_history = self._process_result(chunk, inputs)
|
|
163
|
+
self._cost = cost
|
|
164
|
+
self._lm_history = lm_history
|
|
165
|
+
|
|
166
|
+
console.print("\n")
|
|
167
|
+
result_dict = self.filter_reasoning(
|
|
168
|
+
result_dict, self.config.include_reasoning
|
|
169
|
+
)
|
|
170
|
+
return self.filter_thought_process(
|
|
171
|
+
result_dict, self.config.include_thought_process
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
async def _execute_standard(self, agent_task, inputs: dict[str, Any], agent: Any) -> dict[str, Any]:
|
|
175
|
+
"""Execute DSPy program in standard mode (from original implementation)."""
|
|
176
|
+
logger.info(f"Evaluating agent '{agent.name}' without streaming.")
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
# Ensure the call is awaited if the underlying task is async
|
|
180
|
+
result_obj = await agent_task.acall(**inputs)
|
|
181
|
+
result_dict, cost, lm_history = self._process_result(result_obj, inputs)
|
|
182
|
+
self._cost = cost
|
|
183
|
+
self._lm_history = lm_history
|
|
184
|
+
result_dict = self.filter_reasoning(
|
|
185
|
+
result_dict, self.config.include_reasoning
|
|
186
|
+
)
|
|
187
|
+
return self.filter_thought_process(
|
|
188
|
+
result_dict, self.config.include_thought_process
|
|
189
|
+
)
|
|
190
|
+
except Exception as e:
|
|
191
|
+
logger.error(
|
|
192
|
+
f"Error during non-streaming evaluation for agent '{agent.name}': {e}",
|
|
193
|
+
exc_info=True,
|
|
194
|
+
)
|
|
195
|
+
raise RuntimeError(f"Evaluation failed: {e}") from e
|
|
196
|
+
|
|
197
|
+
def filter_thought_process(
|
|
198
|
+
self, result_dict: dict[str, Any], include_thought_process: bool
|
|
199
|
+
) -> dict[str, Any]:
|
|
200
|
+
"""Filter out thought process from the result dictionary (from original implementation)."""
|
|
201
|
+
if include_thought_process:
|
|
202
|
+
return result_dict
|
|
203
|
+
else:
|
|
204
|
+
return {
|
|
205
|
+
k: v
|
|
206
|
+
for k, v in result_dict.items()
|
|
207
|
+
if not (k.startswith("reasoning") or k.startswith("trajectory"))
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
def filter_reasoning(
|
|
211
|
+
self, result_dict: dict[str, Any], include_reasoning: bool
|
|
212
|
+
) -> dict[str, Any]:
|
|
213
|
+
"""Filter out reasoning from the result dictionary."""
|
|
214
|
+
if include_reasoning:
|
|
215
|
+
return result_dict
|
|
216
|
+
else:
|
|
217
|
+
return {
|
|
218
|
+
k: v
|
|
219
|
+
for k, v in result_dict.items()
|
|
220
|
+
if not (k.startswith("reasoning"))
|
|
221
|
+
}
|
|
222
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# src/flock/components/routing/__init__.py
|
|
2
|
+
"""Routing components for the Flock framework."""
|
|
3
|
+
|
|
4
|
+
from .conditional_routing_component import ConditionalRoutingComponent, ConditionalRoutingConfig
|
|
5
|
+
from .default_routing_component import DefaultRoutingComponent, DefaultRoutingConfig
|
|
6
|
+
from .llm_routing_component import LLMRoutingComponent, LLMRoutingConfig
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"ConditionalRoutingComponent",
|
|
10
|
+
"ConditionalRoutingConfig",
|
|
11
|
+
"DefaultRoutingComponent",
|
|
12
|
+
"DefaultRoutingConfig",
|
|
13
|
+
"LLMRoutingComponent",
|
|
14
|
+
"LLMRoutingConfig",
|
|
15
|
+
]
|
|
@@ -1,26 +1,28 @@
|
|
|
1
|
-
# src/flock/
|
|
1
|
+
# src/flock/components/routing/conditional_routing_component.py
|
|
2
|
+
"""Conditional routing component implementation for the unified component architecture."""
|
|
2
3
|
|
|
3
4
|
import re
|
|
4
5
|
from collections.abc import Callable
|
|
5
|
-
from typing import Any, Literal
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
6
7
|
|
|
7
8
|
from pydantic import Field, model_validator
|
|
8
9
|
|
|
10
|
+
from flock.core.component.agent_component_base import AgentComponentConfig
|
|
11
|
+
from flock.core.component.routing_component import RoutingComponent
|
|
9
12
|
from flock.core.context.context import FlockContext
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
from flock.core.flock_router import (
|
|
13
|
-
FlockRouter,
|
|
14
|
-
FlockRouterConfig,
|
|
15
|
-
HandOffRequest,
|
|
16
|
-
)
|
|
13
|
+
|
|
14
|
+
# HandOffRequest removed - using agent.next_agent directly
|
|
17
15
|
from flock.core.logging.logging import get_logger
|
|
16
|
+
from flock.core.registry import flock_component, get_registry
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from flock.core.flock_agent import FlockAgent
|
|
18
20
|
|
|
19
|
-
logger = get_logger("
|
|
21
|
+
logger = get_logger("components.routing.conditional")
|
|
20
22
|
|
|
21
23
|
|
|
22
|
-
class
|
|
23
|
-
"""Configuration for the
|
|
24
|
+
class ConditionalRoutingConfig(AgentComponentConfig):
|
|
25
|
+
"""Configuration for the ConditionalRoutingComponent."""
|
|
24
26
|
|
|
25
27
|
condition_context_key: str = Field(
|
|
26
28
|
default="flock.condition",
|
|
@@ -115,10 +117,6 @@ class ConditionalRouterConfig(FlockRouterConfig):
|
|
|
115
117
|
default="flock.assertion_feedback", # Useful if paired with AssertionCheckerModule
|
|
116
118
|
description="Optional context key containing feedback message to potentially include when retrying.",
|
|
117
119
|
)
|
|
118
|
-
feedback_on_failure: str | None = Field(
|
|
119
|
-
default=None,
|
|
120
|
-
description="Default feedback message to use when condition evaluation fails.",
|
|
121
|
-
)
|
|
122
120
|
retry_count_context_key_prefix: str = Field(
|
|
123
121
|
default="flock.conditional_retry_count_",
|
|
124
122
|
description="Internal prefix for context key storing retry attempts per agent.",
|
|
@@ -126,7 +124,7 @@ class ConditionalRouterConfig(FlockRouterConfig):
|
|
|
126
124
|
|
|
127
125
|
# --- Validator to ensure only one condition type is set ---
|
|
128
126
|
@model_validator(mode="after")
|
|
129
|
-
def check_exclusive_condition(self) -> "
|
|
127
|
+
def check_exclusive_condition(self) -> "ConditionalRoutingConfig":
|
|
130
128
|
conditions_set = [
|
|
131
129
|
self.condition_callable is not None,
|
|
132
130
|
self.expected_string is not None
|
|
@@ -141,27 +139,37 @@ class ConditionalRouterConfig(FlockRouterConfig):
|
|
|
141
139
|
]
|
|
142
140
|
if sum(conditions_set) > 1:
|
|
143
141
|
raise ValueError(
|
|
144
|
-
"Only one type of condition (callable, string/length, number, list size, type, boolean, exists) can be configured per
|
|
142
|
+
"Only one type of condition (callable, string/length, number, list size, type, boolean, exists) can be configured per ConditionalRoutingComponent."
|
|
145
143
|
)
|
|
146
144
|
if sum(conditions_set) == 0:
|
|
147
145
|
raise ValueError(
|
|
148
|
-
"At least one condition type must be configured for
|
|
146
|
+
"At least one condition type must be configured for ConditionalRoutingComponent."
|
|
149
147
|
)
|
|
150
148
|
return self
|
|
151
149
|
|
|
152
150
|
|
|
153
|
-
@flock_component(config_class=
|
|
154
|
-
class
|
|
151
|
+
@flock_component(config_class=ConditionalRoutingConfig)
|
|
152
|
+
class ConditionalRoutingComponent(RoutingComponent):
|
|
155
153
|
"""Routes workflow based on evaluating a condition against a value in the FlockContext.
|
|
154
|
+
|
|
156
155
|
Supports various built-in checks (string, number, list, type, bool, existence)
|
|
157
156
|
or a custom callable. Can optionally retry the current agent on failure.
|
|
158
157
|
"""
|
|
159
158
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
default_factory=ConditionalRouterConfig
|
|
159
|
+
config: ConditionalRoutingConfig = Field(
|
|
160
|
+
default_factory=ConditionalRoutingConfig
|
|
163
161
|
)
|
|
164
162
|
|
|
163
|
+
def __init__(
|
|
164
|
+
self,
|
|
165
|
+
name: str = "conditional_router",
|
|
166
|
+
config: ConditionalRoutingConfig | None = None,
|
|
167
|
+
**data,
|
|
168
|
+
):
|
|
169
|
+
if config is None:
|
|
170
|
+
config = ConditionalRoutingConfig()
|
|
171
|
+
super().__init__(name=name, config=config, **data)
|
|
172
|
+
|
|
165
173
|
def _evaluate_condition(self, value: Any) -> tuple[bool, str | None]:
|
|
166
174
|
"""Evaluates the condition based on the router's configuration.
|
|
167
175
|
|
|
@@ -172,7 +180,7 @@ class ConditionalRouter(FlockRouter):
|
|
|
172
180
|
"""
|
|
173
181
|
cfg = self.config
|
|
174
182
|
condition_passed = False
|
|
175
|
-
feedback =
|
|
183
|
+
feedback = None # Default feedback
|
|
176
184
|
condition_type = "unknown"
|
|
177
185
|
|
|
178
186
|
try:
|
|
@@ -377,12 +385,17 @@ class ConditionalRouter(FlockRouter):
|
|
|
377
385
|
feedback,
|
|
378
386
|
) # Treat evaluation errors as condition failure
|
|
379
387
|
|
|
380
|
-
async def
|
|
388
|
+
async def determine_next_step(
|
|
381
389
|
self,
|
|
382
|
-
|
|
390
|
+
agent: "FlockAgent",
|
|
383
391
|
result: dict[str, Any],
|
|
384
|
-
context: FlockContext,
|
|
385
|
-
) ->
|
|
392
|
+
context: FlockContext | None = None,
|
|
393
|
+
) -> None:
|
|
394
|
+
"""Determine next step based on evaluating a condition against context value."""
|
|
395
|
+
if not context:
|
|
396
|
+
logger.warning("No context provided for conditional routing")
|
|
397
|
+
return
|
|
398
|
+
|
|
386
399
|
cfg = self.config
|
|
387
400
|
condition_value = context.get_variable(cfg.condition_context_key, None)
|
|
388
401
|
feedback_value = context.get_variable(cfg.feedback_context_key, None)
|
|
@@ -399,17 +412,17 @@ class ConditionalRouter(FlockRouter):
|
|
|
399
412
|
if condition_passed:
|
|
400
413
|
# --- Success Path ---
|
|
401
414
|
logger.info(
|
|
402
|
-
f"Condition PASSED for agent '{
|
|
415
|
+
f"Condition PASSED for agent '{agent.name}'. Routing to success path."
|
|
403
416
|
)
|
|
404
417
|
# Reset retry count if applicable
|
|
405
418
|
if cfg.retry_on_failure:
|
|
406
419
|
retry_key = (
|
|
407
|
-
f"{cfg.retry_count_context_key_prefix}{
|
|
420
|
+
f"{cfg.retry_count_context_key_prefix}{agent.name}"
|
|
408
421
|
)
|
|
409
422
|
if retry_key in context.state:
|
|
410
423
|
del context.state[retry_key]
|
|
411
424
|
logger.debug(
|
|
412
|
-
f"Reset retry count for agent '{
|
|
425
|
+
f"Reset retry count for agent '{agent.name}'."
|
|
413
426
|
)
|
|
414
427
|
|
|
415
428
|
# Clear feedback from context on success
|
|
@@ -422,20 +435,21 @@ class ConditionalRouter(FlockRouter):
|
|
|
422
435
|
f"Cleared feedback key '{cfg.feedback_context_key}' on success."
|
|
423
436
|
)
|
|
424
437
|
|
|
425
|
-
next_agent = cfg.success_agent or
|
|
438
|
+
next_agent = cfg.success_agent or None # Stop chain if None
|
|
426
439
|
logger.debug(f"Success route target: '{next_agent}'")
|
|
427
|
-
|
|
440
|
+
|
|
441
|
+
agent.next_agent = next_agent # Set directly on agent
|
|
428
442
|
|
|
429
443
|
else:
|
|
430
444
|
# --- Failure Path ---
|
|
431
445
|
logger.warning(
|
|
432
|
-
f"Condition FAILED for agent '{
|
|
446
|
+
f"Condition FAILED for agent '{agent.name}'. Reason: {feedback_msg}"
|
|
433
447
|
)
|
|
434
448
|
|
|
435
449
|
if cfg.retry_on_failure:
|
|
436
450
|
# --- Retry Logic ---
|
|
437
451
|
retry_key = (
|
|
438
|
-
f"{cfg.retry_count_context_key_prefix}{
|
|
452
|
+
f"{cfg.retry_count_context_key_prefix}{agent.name}"
|
|
439
453
|
)
|
|
440
454
|
retry_count = context.get_variable(retry_key, 0)
|
|
441
455
|
|
|
@@ -443,44 +457,38 @@ class ConditionalRouter(FlockRouter):
|
|
|
443
457
|
next_retry_count = retry_count + 1
|
|
444
458
|
context.set_variable(retry_key, next_retry_count)
|
|
445
459
|
logger.info(
|
|
446
|
-
f"Routing back to agent '{
|
|
460
|
+
f"Routing back to agent '{agent.name}' for retry #{next_retry_count}/{cfg.max_retries}."
|
|
447
461
|
)
|
|
448
462
|
|
|
449
463
|
# Add specific feedback to context if retry is enabled
|
|
450
464
|
if cfg.feedback_context_key:
|
|
451
465
|
context.set_variable(
|
|
452
466
|
cfg.feedback_context_key,
|
|
453
|
-
feedback_msg or
|
|
467
|
+
feedback_msg or "Condition failed",
|
|
454
468
|
)
|
|
455
469
|
logger.debug(
|
|
456
|
-
f"Set feedback key '{cfg.feedback_context_key}': {feedback_msg or
|
|
470
|
+
f"Set feedback key '{cfg.feedback_context_key}': {feedback_msg or 'Condition failed'}"
|
|
457
471
|
)
|
|
458
472
|
|
|
459
|
-
|
|
460
|
-
next_agent=current_agent.name, # Route back to self
|
|
461
|
-
output_to_input_merge_strategy="add", # Make feedback available
|
|
462
|
-
)
|
|
473
|
+
agent.next_agent = agent.name # Route back to self
|
|
463
474
|
else:
|
|
464
475
|
# --- Max Retries Exceeded ---
|
|
465
476
|
logger.error(
|
|
466
|
-
f"Max retries ({cfg.max_retries}) exceeded for agent '{
|
|
477
|
+
f"Max retries ({cfg.max_retries}) exceeded for agent '{agent.name}'."
|
|
467
478
|
)
|
|
468
479
|
if retry_key in context.state:
|
|
469
480
|
del context.state[retry_key] # Reset count
|
|
470
|
-
|
|
471
|
-
# if cfg.feedback_context_key in context.state: del context.state[cfg.feedback_context_key]
|
|
472
|
-
next_agent = cfg.failure_agent or ""
|
|
481
|
+
next_agent = cfg.failure_agent or None
|
|
473
482
|
logger.debug(
|
|
474
483
|
f"Failure route target (after retries): '{next_agent}'"
|
|
475
484
|
)
|
|
476
|
-
|
|
485
|
+
|
|
486
|
+
agent.next_agent = next_agent
|
|
477
487
|
else:
|
|
478
488
|
# --- No Retry Logic ---
|
|
479
489
|
next_agent = (
|
|
480
|
-
cfg.failure_agent or
|
|
490
|
+
cfg.failure_agent or None
|
|
481
491
|
) # Use failure agent or stop
|
|
482
492
|
logger.debug(f"Failure route target (no retry): '{next_agent}'")
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
# context.set_variable(cfg.feedback_context_key, feedback_msg or cfg.feedback_on_failure)
|
|
486
|
-
return HandOffRequest(next_agent=next_agent)
|
|
493
|
+
|
|
494
|
+
agent.next_agent = next_agent
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# src/flock/components/routing/default_routing_component.py
|
|
2
|
+
"""Default routing component implementation for the unified component architecture."""
|
|
3
|
+
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
|
|
9
|
+
from flock.core.component.agent_component_base import AgentComponentConfig
|
|
10
|
+
from flock.core.component.routing_component import RoutingComponent
|
|
11
|
+
from flock.core.context.context import FlockContext
|
|
12
|
+
from flock.core.logging.logging import get_logger
|
|
13
|
+
from flock.core.registry import flock_component
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from flock.core.flock_agent import FlockAgent
|
|
17
|
+
|
|
18
|
+
logger = get_logger("components.routing.default")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DefaultRoutingConfig(AgentComponentConfig):
|
|
22
|
+
"""Configuration for the default routing component."""
|
|
23
|
+
|
|
24
|
+
next_agent: str | Callable[..., str] = Field(
|
|
25
|
+
default="", description="Next agent to hand off to"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@flock_component(config_class=DefaultRoutingConfig)
|
|
30
|
+
class DefaultRoutingComponent(RoutingComponent):
|
|
31
|
+
"""Default routing component implementation.
|
|
32
|
+
|
|
33
|
+
This router simply uses the configured hand_off property to determine the next agent.
|
|
34
|
+
It does not perform any dynamic routing based on agent results.
|
|
35
|
+
|
|
36
|
+
Configuration can be:
|
|
37
|
+
- A string: Simple agent name to route to
|
|
38
|
+
- A callable: Function that takes (context, result) and returns agent name
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
config: DefaultRoutingConfig = Field(
|
|
42
|
+
default_factory=DefaultRoutingConfig,
|
|
43
|
+
description="Default routing configuration",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
name: str = "default_router",
|
|
49
|
+
config: DefaultRoutingConfig | None = None,
|
|
50
|
+
**data,
|
|
51
|
+
):
|
|
52
|
+
"""Initialize the DefaultRoutingComponent.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
name: The name of the routing component
|
|
56
|
+
config: The routing configuration
|
|
57
|
+
"""
|
|
58
|
+
if config is None:
|
|
59
|
+
config = DefaultRoutingConfig()
|
|
60
|
+
super().__init__(name=name, config=config, **data)
|
|
61
|
+
|
|
62
|
+
async def determine_next_step(
|
|
63
|
+
self,
|
|
64
|
+
agent: "FlockAgent",
|
|
65
|
+
result: dict[str, Any],
|
|
66
|
+
context: FlockContext | None = None,
|
|
67
|
+
) -> str | None:
|
|
68
|
+
"""Determine the next agent to hand off to based on configuration.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
agent: The agent that just completed execution
|
|
72
|
+
result: The output from the current agent
|
|
73
|
+
context: The global execution context
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
String agent name to route to, or None to end workflow
|
|
77
|
+
"""
|
|
78
|
+
handoff = self.config.next_agent
|
|
79
|
+
|
|
80
|
+
# If empty string, end the workflow
|
|
81
|
+
if handoff == "":
|
|
82
|
+
logger.debug("No handoff configured, ending workflow")
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
# If callable, invoke it with context and result
|
|
86
|
+
if callable(handoff):
|
|
87
|
+
logger.debug("Invoking handoff callable")
|
|
88
|
+
try:
|
|
89
|
+
handoff = handoff(context, result)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.error("Error invoking handoff callable: %s", e)
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
# Validate it's a string
|
|
95
|
+
if not isinstance(handoff, str):
|
|
96
|
+
logger.error(
|
|
97
|
+
"Invalid handoff type: %s. Expected str or callable returning str",
|
|
98
|
+
type(handoff),
|
|
99
|
+
)
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
logger.debug("Routing to agent: %s", handoff)
|
|
103
|
+
return handoff
|