flock-core 0.5.0b28__py3-none-any.whl → 0.5.56b0__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/__init__.py +12 -217
- flock/agent.py +678 -0
- flock/api/themes.py +71 -0
- flock/artifacts.py +79 -0
- flock/cli.py +75 -0
- flock/components.py +173 -0
- flock/dashboard/__init__.py +28 -0
- flock/dashboard/collector.py +283 -0
- flock/dashboard/events.py +182 -0
- flock/dashboard/launcher.py +230 -0
- flock/dashboard/service.py +537 -0
- flock/dashboard/websocket.py +235 -0
- flock/engines/__init__.py +6 -0
- flock/engines/dspy_engine.py +856 -0
- flock/examples.py +128 -0
- flock/{core/util → helper}/cli_helper.py +4 -3
- flock/{core/logging → logging}/__init__.py +2 -3
- flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
- flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
- flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
- flock/{core/logging → logging}/logging.py +77 -61
- flock/{core/logging → logging}/telemetry.py +20 -26
- flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
- flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
- flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
- flock/{core/logging → logging}/trace_and_logged.py +20 -24
- flock/mcp/__init__.py +91 -0
- flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
- flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
- flock/mcp/manager.py +255 -0
- flock/mcp/servers/sse/__init__.py +1 -1
- flock/mcp/servers/sse/flock_sse_server.py +11 -53
- flock/mcp/servers/stdio/__init__.py +1 -1
- flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
- flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
- flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
- flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
- flock/mcp/types/__init__.py +42 -0
- flock/{core/mcp → mcp}/types/callbacks.py +9 -15
- flock/{core/mcp → mcp}/types/factories.py +7 -6
- flock/{core/mcp → mcp}/types/handlers.py +13 -18
- flock/{core/mcp → mcp}/types/types.py +70 -74
- flock/{core/mcp → mcp}/util/helpers.py +1 -1
- flock/orchestrator.py +645 -0
- flock/registry.py +148 -0
- flock/runtime.py +262 -0
- flock/service.py +140 -0
- flock/store.py +69 -0
- flock/subscription.py +111 -0
- flock/themes/andromeda.toml +1 -1
- flock/themes/apple-system-colors.toml +1 -1
- flock/themes/arcoiris.toml +1 -1
- flock/themes/atomonelight.toml +1 -1
- flock/themes/ayu copy.toml +1 -1
- flock/themes/ayu-light.toml +1 -1
- flock/themes/belafonte-day.toml +1 -1
- flock/themes/belafonte-night.toml +1 -1
- flock/themes/blulocodark.toml +1 -1
- flock/themes/breeze.toml +1 -1
- flock/themes/broadcast.toml +1 -1
- flock/themes/brogrammer.toml +1 -1
- flock/themes/builtin-dark.toml +1 -1
- flock/themes/builtin-pastel-dark.toml +1 -1
- flock/themes/catppuccin-latte.toml +1 -1
- flock/themes/catppuccin-macchiato.toml +1 -1
- flock/themes/catppuccin-mocha.toml +1 -1
- flock/themes/cga.toml +1 -1
- flock/themes/chalk.toml +1 -1
- flock/themes/ciapre.toml +1 -1
- flock/themes/coffee-theme.toml +1 -1
- flock/themes/cyberpunkscarletprotocol.toml +1 -1
- flock/themes/dark+.toml +1 -1
- flock/themes/darkermatrix.toml +1 -1
- flock/themes/darkside.toml +1 -1
- flock/themes/desert.toml +1 -1
- flock/themes/django.toml +1 -1
- flock/themes/djangosmooth.toml +1 -1
- flock/themes/doomone.toml +1 -1
- flock/themes/dotgov.toml +1 -1
- flock/themes/dracula+.toml +1 -1
- flock/themes/duckbones.toml +1 -1
- flock/themes/encom.toml +1 -1
- flock/themes/espresso.toml +1 -1
- flock/themes/everblush.toml +1 -1
- flock/themes/fairyfloss.toml +1 -1
- flock/themes/fideloper.toml +1 -1
- flock/themes/fishtank.toml +1 -1
- flock/themes/flexoki-light.toml +1 -1
- flock/themes/floraverse.toml +1 -1
- flock/themes/framer.toml +1 -1
- flock/themes/galizur.toml +1 -1
- flock/themes/github.toml +1 -1
- flock/themes/grass.toml +1 -1
- flock/themes/grey-green.toml +1 -1
- flock/themes/gruvboxlight.toml +1 -1
- flock/themes/guezwhoz.toml +1 -1
- flock/themes/harper.toml +1 -1
- flock/themes/hax0r-blue.toml +1 -1
- flock/themes/hopscotch.256.toml +1 -1
- flock/themes/ic-green-ppl.toml +1 -1
- flock/themes/iceberg-dark.toml +1 -1
- flock/themes/japanesque.toml +1 -1
- flock/themes/jubi.toml +1 -1
- flock/themes/kibble.toml +1 -1
- flock/themes/kolorit.toml +1 -1
- flock/themes/kurokula.toml +1 -1
- flock/themes/materialdesigncolors.toml +1 -1
- flock/themes/matrix.toml +1 -1
- flock/themes/mellifluous.toml +1 -1
- flock/themes/midnight-in-mojave.toml +1 -1
- flock/themes/monokai-remastered.toml +1 -1
- flock/themes/monokai-soda.toml +1 -1
- flock/themes/neon.toml +1 -1
- flock/themes/neopolitan.toml +1 -1
- flock/themes/nord-light.toml +1 -1
- flock/themes/ocean.toml +1 -1
- flock/themes/onehalfdark.toml +1 -1
- flock/themes/onehalflight.toml +1 -1
- flock/themes/palenighthc.toml +1 -1
- flock/themes/paulmillr.toml +1 -1
- flock/themes/pencildark.toml +1 -1
- flock/themes/pnevma.toml +1 -1
- flock/themes/purple-rain.toml +1 -1
- flock/themes/purplepeter.toml +1 -1
- flock/themes/raycast-dark.toml +1 -1
- flock/themes/red-sands.toml +1 -1
- flock/themes/relaxed.toml +1 -1
- flock/themes/retro.toml +1 -1
- flock/themes/rose-pine.toml +1 -1
- flock/themes/royal.toml +1 -1
- flock/themes/ryuuko.toml +1 -1
- flock/themes/sakura.toml +1 -1
- flock/themes/scarlet-protocol.toml +1 -1
- flock/themes/seoulbones-dark.toml +1 -1
- flock/themes/shades-of-purple.toml +1 -1
- flock/themes/smyck.toml +1 -1
- flock/themes/softserver.toml +1 -1
- flock/themes/solarized-darcula.toml +1 -1
- flock/themes/square.toml +1 -1
- flock/themes/sugarplum.toml +1 -1
- flock/themes/thayer-bright.toml +1 -1
- flock/themes/tokyonight.toml +1 -1
- flock/themes/tomorrow.toml +1 -1
- flock/themes/ubuntu.toml +1 -1
- flock/themes/ultradark.toml +1 -1
- flock/themes/ultraviolent.toml +1 -1
- flock/themes/unikitty.toml +1 -1
- flock/themes/urple.toml +1 -1
- flock/themes/vesper.toml +1 -1
- flock/themes/vimbones.toml +1 -1
- flock/themes/wildcherry.toml +1 -1
- flock/themes/wilmersdorf.toml +1 -1
- flock/themes/wryan.toml +1 -1
- flock/themes/xcodedarkhc.toml +1 -1
- flock/themes/xcodelight.toml +1 -1
- flock/themes/zenbones-light.toml +1 -1
- flock/themes/zenwritten-dark.toml +1 -1
- flock/utilities.py +301 -0
- flock/{components/utility → utility}/output_utility_component.py +68 -53
- flock/visibility.py +107 -0
- flock_core-0.5.56b0.dist-info/METADATA +747 -0
- flock_core-0.5.56b0.dist-info/RECORD +398 -0
- flock_core-0.5.56b0.dist-info/entry_points.txt +2 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/licenses/LICENSE +1 -1
- flock/adapter/__init__.py +0 -14
- flock/adapter/azure_adapter.py +0 -68
- flock/adapter/chroma_adapter.py +0 -73
- flock/adapter/faiss_adapter.py +0 -97
- flock/adapter/pinecone_adapter.py +0 -51
- flock/adapter/vector_base.py +0 -47
- flock/cli/assets/release_notes.md +0 -140
- flock/cli/config.py +0 -8
- flock/cli/constants.py +0 -36
- flock/cli/create_agent.py +0 -1
- flock/cli/create_flock.py +0 -280
- flock/cli/execute_flock.py +0 -620
- flock/cli/load_agent.py +0 -1
- flock/cli/load_examples.py +0 -1
- flock/cli/load_flock.py +0 -192
- flock/cli/load_release_notes.py +0 -20
- flock/cli/loaded_flock_cli.py +0 -254
- flock/cli/manage_agents.py +0 -459
- flock/cli/registry_management.py +0 -889
- flock/cli/runner.py +0 -41
- flock/cli/settings.py +0 -857
- flock/cli/utils.py +0 -135
- flock/cli/view_results.py +0 -29
- flock/cli/yaml_editor.py +0 -396
- flock/components/__init__.py +0 -30
- flock/components/evaluation/__init__.py +0 -9
- flock/components/evaluation/declarative_evaluation_component.py +0 -606
- flock/components/routing/__init__.py +0 -15
- flock/components/routing/conditional_routing_component.py +0 -494
- flock/components/routing/default_routing_component.py +0 -103
- flock/components/routing/llm_routing_component.py +0 -206
- flock/components/utility/__init__.py +0 -22
- flock/components/utility/example_utility_component.py +0 -250
- flock/components/utility/feedback_utility_component.py +0 -206
- flock/components/utility/memory_utility_component.py +0 -550
- flock/components/utility/metrics_utility_component.py +0 -700
- flock/config.py +0 -61
- flock/core/__init__.py +0 -110
- flock/core/agent/__init__.py +0 -16
- flock/core/agent/default_agent.py +0 -216
- flock/core/agent/flock_agent_components.py +0 -104
- flock/core/agent/flock_agent_execution.py +0 -101
- flock/core/agent/flock_agent_integration.py +0 -260
- flock/core/agent/flock_agent_lifecycle.py +0 -186
- flock/core/agent/flock_agent_serialization.py +0 -381
- flock/core/api/__init__.py +0 -10
- flock/core/api/custom_endpoint.py +0 -45
- flock/core/api/endpoints.py +0 -254
- flock/core/api/main.py +0 -162
- flock/core/api/models.py +0 -97
- flock/core/api/run_store.py +0 -224
- flock/core/api/runner.py +0 -44
- flock/core/api/service.py +0 -214
- flock/core/component/__init__.py +0 -15
- flock/core/component/agent_component_base.py +0 -309
- flock/core/component/evaluation_component.py +0 -62
- flock/core/component/routing_component.py +0 -74
- flock/core/component/utility_component.py +0 -69
- flock/core/config/flock_agent_config.py +0 -58
- flock/core/config/scheduled_agent_config.py +0 -40
- flock/core/context/context.py +0 -213
- flock/core/context/context_manager.py +0 -37
- flock/core/context/context_vars.py +0 -10
- flock/core/evaluation/utils.py +0 -396
- flock/core/execution/batch_executor.py +0 -369
- flock/core/execution/evaluation_executor.py +0 -438
- flock/core/execution/local_executor.py +0 -31
- flock/core/execution/opik_executor.py +0 -103
- flock/core/execution/temporal_executor.py +0 -164
- flock/core/flock.py +0 -634
- flock/core/flock_agent.py +0 -336
- flock/core/flock_factory.py +0 -613
- flock/core/flock_scheduler.py +0 -166
- flock/core/flock_server_manager.py +0 -136
- flock/core/interpreter/python_interpreter.py +0 -689
- flock/core/mcp/__init__.py +0 -1
- flock/core/mcp/flock_mcp_server.py +0 -680
- flock/core/mcp/mcp_client_manager.py +0 -201
- flock/core/mcp/types/__init__.py +0 -1
- flock/core/mixin/dspy_integration.py +0 -403
- flock/core/mixin/prompt_parser.py +0 -125
- flock/core/orchestration/__init__.py +0 -15
- flock/core/orchestration/flock_batch_processor.py +0 -94
- flock/core/orchestration/flock_evaluator.py +0 -113
- flock/core/orchestration/flock_execution.py +0 -295
- flock/core/orchestration/flock_initialization.py +0 -149
- flock/core/orchestration/flock_server_manager.py +0 -67
- flock/core/orchestration/flock_web_server.py +0 -117
- flock/core/registry/__init__.py +0 -45
- flock/core/registry/agent_registry.py +0 -69
- flock/core/registry/callable_registry.py +0 -139
- flock/core/registry/component_discovery.py +0 -142
- flock/core/registry/component_registry.py +0 -64
- flock/core/registry/config_mapping.py +0 -64
- flock/core/registry/decorators.py +0 -137
- flock/core/registry/registry_hub.py +0 -205
- flock/core/registry/server_registry.py +0 -57
- flock/core/registry/type_registry.py +0 -86
- flock/core/serialization/__init__.py +0 -13
- flock/core/serialization/callable_registry.py +0 -52
- flock/core/serialization/flock_serializer.py +0 -832
- flock/core/serialization/json_encoder.py +0 -41
- flock/core/serialization/secure_serializer.py +0 -175
- flock/core/serialization/serializable.py +0 -342
- flock/core/serialization/serialization_utils.py +0 -412
- flock/core/util/file_path_utils.py +0 -223
- flock/core/util/hydrator.py +0 -309
- flock/core/util/input_resolver.py +0 -164
- flock/core/util/loader.py +0 -59
- flock/core/util/splitter.py +0 -219
- flock/di.py +0 -27
- flock/platform/docker_tools.py +0 -49
- flock/platform/jaeger_install.py +0 -86
- flock/webapp/__init__.py +0 -1
- flock/webapp/app/__init__.py +0 -0
- flock/webapp/app/api/__init__.py +0 -0
- flock/webapp/app/api/agent_management.py +0 -241
- flock/webapp/app/api/execution.py +0 -709
- flock/webapp/app/api/flock_management.py +0 -129
- flock/webapp/app/api/registry_viewer.py +0 -30
- flock/webapp/app/chat.py +0 -665
- flock/webapp/app/config.py +0 -104
- flock/webapp/app/dependencies.py +0 -117
- flock/webapp/app/main.py +0 -1070
- flock/webapp/app/middleware.py +0 -113
- flock/webapp/app/models_ui.py +0 -7
- flock/webapp/app/services/__init__.py +0 -0
- flock/webapp/app/services/feedback_file_service.py +0 -363
- flock/webapp/app/services/flock_service.py +0 -337
- flock/webapp/app/services/sharing_models.py +0 -81
- flock/webapp/app/services/sharing_store.py +0 -762
- flock/webapp/app/templates/theme_mapper.html +0 -326
- flock/webapp/app/theme_mapper.py +0 -812
- flock/webapp/app/utils.py +0 -85
- flock/webapp/run.py +0 -215
- flock/webapp/static/css/chat.css +0 -301
- flock/webapp/static/css/components.css +0 -167
- flock/webapp/static/css/header.css +0 -39
- flock/webapp/static/css/layout.css +0 -46
- flock/webapp/static/css/sidebar.css +0 -127
- flock/webapp/static/css/two-pane.css +0 -48
- flock/webapp/templates/base.html +0 -200
- flock/webapp/templates/chat.html +0 -152
- flock/webapp/templates/chat_settings.html +0 -19
- flock/webapp/templates/flock_editor.html +0 -16
- flock/webapp/templates/index.html +0 -12
- flock/webapp/templates/partials/_agent_detail_form.html +0 -93
- flock/webapp/templates/partials/_agent_list.html +0 -18
- flock/webapp/templates/partials/_agent_manager_view.html +0 -51
- flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
- flock/webapp/templates/partials/_chat_container.html +0 -15
- flock/webapp/templates/partials/_chat_messages.html +0 -57
- flock/webapp/templates/partials/_chat_settings_form.html +0 -85
- flock/webapp/templates/partials/_create_flock_form.html +0 -50
- flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
- flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
- flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
- flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
- flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
- flock/webapp/templates/partials/_env_vars_table.html +0 -23
- flock/webapp/templates/partials/_execution_form.html +0 -118
- flock/webapp/templates/partials/_execution_view_container.html +0 -28
- flock/webapp/templates/partials/_flock_file_list.html +0 -23
- flock/webapp/templates/partials/_flock_properties_form.html +0 -52
- flock/webapp/templates/partials/_flock_upload_form.html +0 -16
- flock/webapp/templates/partials/_header_flock_status.html +0 -5
- flock/webapp/templates/partials/_load_manager_view.html +0 -49
- flock/webapp/templates/partials/_registry_table.html +0 -25
- flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
- flock/webapp/templates/partials/_results_display.html +0 -78
- flock/webapp/templates/partials/_settings_env_content.html +0 -9
- flock/webapp/templates/partials/_settings_theme_content.html +0 -14
- flock/webapp/templates/partials/_settings_view.html +0 -36
- flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
- flock/webapp/templates/partials/_share_link_snippet.html +0 -35
- flock/webapp/templates/partials/_sidebar.html +0 -74
- flock/webapp/templates/partials/_streaming_results_container.html +0 -195
- flock/webapp/templates/partials/_structured_data_view.html +0 -40
- flock/webapp/templates/partials/_theme_preview.html +0 -36
- flock/webapp/templates/registry_viewer.html +0 -84
- flock/webapp/templates/shared_run_page.html +0 -140
- flock/workflow/__init__.py +0 -0
- flock/workflow/activities.py +0 -196
- flock/workflow/agent_activities.py +0 -24
- flock/workflow/agent_execution_activity.py +0 -202
- flock/workflow/flock_workflow.py +0 -214
- flock/workflow/temporal_config.py +0 -96
- flock/workflow/temporal_setup.py +0 -68
- flock_core-0.5.0b28.dist-info/METADATA +0 -274
- flock_core-0.5.0b28.dist-info/RECORD +0 -561
- flock_core-0.5.0b28.dist-info/entry_points.txt +0 -2
- /flock/{core/logging → logging}/formatters/themes.py +0 -0
- /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
- /flock/{core/mcp → mcp}/util/__init__.py +0 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/WHEEL +0 -0
|
@@ -1,494 +0,0 @@
|
|
|
1
|
-
# src/flock/components/routing/conditional_routing_component.py
|
|
2
|
-
"""Conditional routing component implementation for the unified component architecture."""
|
|
3
|
-
|
|
4
|
-
import re
|
|
5
|
-
from collections.abc import Callable
|
|
6
|
-
from typing import TYPE_CHECKING, Any, Literal
|
|
7
|
-
|
|
8
|
-
from pydantic import Field, model_validator
|
|
9
|
-
|
|
10
|
-
from flock.core.component.agent_component_base import AgentComponentConfig
|
|
11
|
-
from flock.core.component.routing_component import RoutingComponent
|
|
12
|
-
from flock.core.context.context import FlockContext
|
|
13
|
-
|
|
14
|
-
# HandOffRequest removed - using agent.next_agent directly
|
|
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
|
|
20
|
-
|
|
21
|
-
logger = get_logger("components.routing.conditional")
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class ConditionalRoutingConfig(AgentComponentConfig):
|
|
25
|
-
"""Configuration for the ConditionalRoutingComponent."""
|
|
26
|
-
|
|
27
|
-
condition_context_key: str = Field(
|
|
28
|
-
default="flock.condition",
|
|
29
|
-
description="Context key containing the value to evaluate the condition against.",
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
# --- Define ONE type of condition check ---
|
|
33
|
-
condition_callable: (
|
|
34
|
-
str | Callable[[Any], tuple[bool, str | None]] | None
|
|
35
|
-
) = Field(
|
|
36
|
-
default=None,
|
|
37
|
-
description="A callable (or registered name) that takes the context value and returns a tuple containing: (bool: True if condition passed, False otherwise, Optional[str]: Feedback message if condition failed).",
|
|
38
|
-
)
|
|
39
|
-
# String Checks
|
|
40
|
-
expected_string: str | None = Field(
|
|
41
|
-
default=None, description="String value to compare against."
|
|
42
|
-
)
|
|
43
|
-
string_mode: Literal[
|
|
44
|
-
"equals",
|
|
45
|
-
"contains",
|
|
46
|
-
"regex",
|
|
47
|
-
"startswith",
|
|
48
|
-
"endswith",
|
|
49
|
-
"not_equals",
|
|
50
|
-
"not_contains",
|
|
51
|
-
] = Field(default="equals", description="How to compare strings.")
|
|
52
|
-
ignore_case: bool = Field(
|
|
53
|
-
default=True, description="Ignore case during string comparison."
|
|
54
|
-
)
|
|
55
|
-
# Length Checks (String or List)
|
|
56
|
-
min_length: int | None = Field(
|
|
57
|
-
default=None,
|
|
58
|
-
description="Minimum length for strings or items for lists.",
|
|
59
|
-
)
|
|
60
|
-
max_length: int | None = Field(
|
|
61
|
-
default=None,
|
|
62
|
-
description="Maximum length for strings or items for lists.",
|
|
63
|
-
)
|
|
64
|
-
# Number Checks
|
|
65
|
-
expected_number: int | float | None = Field(
|
|
66
|
-
default=None, description="Number to compare against."
|
|
67
|
-
)
|
|
68
|
-
number_mode: Literal["<", "<=", "==", "!=", ">=", ">"] = Field(
|
|
69
|
-
default="==", description="How to compare numbers."
|
|
70
|
-
)
|
|
71
|
-
# List Checks
|
|
72
|
-
min_items: int | None = Field(
|
|
73
|
-
default=None, description="Minimum number of items in a list."
|
|
74
|
-
)
|
|
75
|
-
max_items: int | None = Field(
|
|
76
|
-
default=None, description="Maximum number of items in a list."
|
|
77
|
-
)
|
|
78
|
-
# Type Check
|
|
79
|
-
expected_type_name: str | None = Field(
|
|
80
|
-
default=None,
|
|
81
|
-
description="Registered name of the expected Python type (e.g., 'str', 'list', 'MyCustomType').",
|
|
82
|
-
)
|
|
83
|
-
# Boolean Check
|
|
84
|
-
expected_bool: bool | None = Field(
|
|
85
|
-
default=None, description="Expected boolean value (True or False)."
|
|
86
|
-
)
|
|
87
|
-
# Existence Check
|
|
88
|
-
check_exists: bool | None = Field(
|
|
89
|
-
default=None,
|
|
90
|
-
description="If True, succeeds if key exists; if False, succeeds if key *doesn't* exist. Ignores value.",
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
# --- Routing Targets ---
|
|
94
|
-
success_agent: str | None = Field(
|
|
95
|
-
default=None,
|
|
96
|
-
description="Agent name to route to if the condition evaluates to True.",
|
|
97
|
-
)
|
|
98
|
-
failure_agent: str | None = Field(
|
|
99
|
-
default=None,
|
|
100
|
-
description="Agent name to route to if the condition evaluates to False (after retries, if enabled).",
|
|
101
|
-
)
|
|
102
|
-
retry_agent: str | None = Field(
|
|
103
|
-
default=None,
|
|
104
|
-
description="Agent name to route to if the condition evaluates to False (during retries, if enabled).",
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
# --- Optional Retry Logic (for Failure Path) ---
|
|
108
|
-
retry_on_failure: bool = Field(
|
|
109
|
-
default=False,
|
|
110
|
-
description="If True, route back to the retry_agent on failure before going to failure_agent.",
|
|
111
|
-
)
|
|
112
|
-
max_retries: int = Field(
|
|
113
|
-
default=1,
|
|
114
|
-
description="Maximum number of times to retry the current agent on failure.",
|
|
115
|
-
)
|
|
116
|
-
feedback_context_key: str | None = Field(
|
|
117
|
-
default="flock.assertion_feedback", # Useful if paired with AssertionCheckerModule
|
|
118
|
-
description="Optional context key containing feedback message to potentially include when retrying.",
|
|
119
|
-
)
|
|
120
|
-
retry_count_context_key_prefix: str = Field(
|
|
121
|
-
default="flock.conditional_retry_count_",
|
|
122
|
-
description="Internal prefix for context key storing retry attempts per agent.",
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
# --- Validator to ensure only one condition type is set ---
|
|
126
|
-
@model_validator(mode="after")
|
|
127
|
-
def check_exclusive_condition(self) -> "ConditionalRoutingConfig":
|
|
128
|
-
conditions_set = [
|
|
129
|
-
self.condition_callable is not None,
|
|
130
|
-
self.expected_string is not None
|
|
131
|
-
or self.min_length is not None
|
|
132
|
-
or self.max_length is not None, # String/Length group
|
|
133
|
-
self.expected_number is not None, # Number group
|
|
134
|
-
self.min_items is not None
|
|
135
|
-
or self.max_items is not None, # List size group
|
|
136
|
-
self.expected_type_name is not None, # Type group
|
|
137
|
-
self.expected_bool is not None, # Bool group
|
|
138
|
-
self.check_exists is not None, # Existence group
|
|
139
|
-
]
|
|
140
|
-
if sum(conditions_set) > 1:
|
|
141
|
-
raise ValueError(
|
|
142
|
-
"Only one type of condition (callable, string/length, number, list size, type, boolean, exists) can be configured per ConditionalRoutingComponent."
|
|
143
|
-
)
|
|
144
|
-
if sum(conditions_set) == 0:
|
|
145
|
-
raise ValueError(
|
|
146
|
-
"At least one condition type must be configured for ConditionalRoutingComponent."
|
|
147
|
-
)
|
|
148
|
-
return self
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
@flock_component(config_class=ConditionalRoutingConfig)
|
|
152
|
-
class ConditionalRoutingComponent(RoutingComponent):
|
|
153
|
-
"""Routes workflow based on evaluating a condition against a value in the FlockContext.
|
|
154
|
-
|
|
155
|
-
Supports various built-in checks (string, number, list, type, bool, existence)
|
|
156
|
-
or a custom callable. Can optionally retry the current agent on failure.
|
|
157
|
-
"""
|
|
158
|
-
|
|
159
|
-
config: ConditionalRoutingConfig = Field(
|
|
160
|
-
default_factory=ConditionalRoutingConfig
|
|
161
|
-
)
|
|
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
|
-
|
|
173
|
-
def _evaluate_condition(self, value: Any) -> tuple[bool, str | None]:
|
|
174
|
-
"""Evaluates the condition based on the router's configuration.
|
|
175
|
-
|
|
176
|
-
Returns:
|
|
177
|
-
Tuple[bool, Optional[str]]: A tuple containing:
|
|
178
|
-
- bool: True if the condition passed, False otherwise.
|
|
179
|
-
- Optional[str]: A feedback message if the condition failed, otherwise None.
|
|
180
|
-
"""
|
|
181
|
-
cfg = self.config
|
|
182
|
-
condition_passed = False
|
|
183
|
-
feedback = None # Default feedback
|
|
184
|
-
condition_type = "unknown"
|
|
185
|
-
|
|
186
|
-
try:
|
|
187
|
-
# 0. Check Existence first (simplest)
|
|
188
|
-
if cfg.check_exists is not None:
|
|
189
|
-
condition_type = "existence"
|
|
190
|
-
value_exists = value is not None
|
|
191
|
-
condition_passed = (
|
|
192
|
-
value_exists if cfg.check_exists else not value_exists
|
|
193
|
-
)
|
|
194
|
-
if not condition_passed:
|
|
195
|
-
feedback = f"Existence check failed: Expected key '{cfg.condition_context_key}' to {'exist' if cfg.check_exists else 'not exist or be None'}, but it was {'found' if value_exists else 'missing/None'}."
|
|
196
|
-
|
|
197
|
-
# 1. Custom Callable
|
|
198
|
-
elif cfg.condition_callable:
|
|
199
|
-
condition_type = "callable"
|
|
200
|
-
callable_func = cfg.condition_callable
|
|
201
|
-
if isinstance(callable_func, str): # Lookup registered callable
|
|
202
|
-
registry = get_registry()
|
|
203
|
-
try:
|
|
204
|
-
callable_func = registry.get_callable(callable_func)
|
|
205
|
-
except KeyError:
|
|
206
|
-
feedback = f"Condition callable '{cfg.condition_callable}' not found in registry."
|
|
207
|
-
logger.error(feedback)
|
|
208
|
-
return False, feedback # Treat as failure
|
|
209
|
-
|
|
210
|
-
if callable(callable_func):
|
|
211
|
-
eval_result = callable_func(value)
|
|
212
|
-
if (
|
|
213
|
-
isinstance(eval_result, tuple)
|
|
214
|
-
and len(eval_result) == 2
|
|
215
|
-
and isinstance(eval_result[0], bool)
|
|
216
|
-
):
|
|
217
|
-
condition_passed, custom_feedback = eval_result
|
|
218
|
-
if not condition_passed and isinstance(
|
|
219
|
-
custom_feedback, str
|
|
220
|
-
):
|
|
221
|
-
feedback = custom_feedback
|
|
222
|
-
elif isinstance(eval_result, bool):
|
|
223
|
-
condition_passed = eval_result
|
|
224
|
-
if not condition_passed:
|
|
225
|
-
feedback = f"Callable condition '{getattr(callable_func, '__name__', 'anonymous')}' returned False."
|
|
226
|
-
else:
|
|
227
|
-
feedback = f"Condition callable '{getattr(callable_func, '__name__', 'anonymous')}' returned unexpected type: {type(eval_result)}."
|
|
228
|
-
logger.warning(feedback)
|
|
229
|
-
return False, feedback # Treat as failure
|
|
230
|
-
else:
|
|
231
|
-
feedback = f"Configured condition_callable '{cfg.condition_callable}' is not callable."
|
|
232
|
-
logger.error(feedback)
|
|
233
|
-
return False, feedback
|
|
234
|
-
|
|
235
|
-
# 2. String / Length Checks
|
|
236
|
-
elif (
|
|
237
|
-
cfg.expected_string is not None
|
|
238
|
-
or cfg.min_length is not None
|
|
239
|
-
or cfg.max_length is not None
|
|
240
|
-
):
|
|
241
|
-
condition_type = "string/length"
|
|
242
|
-
if not isinstance(value, str):
|
|
243
|
-
feedback = f"Cannot perform string/length check on non-string value: {type(value)}."
|
|
244
|
-
logger.warning(feedback)
|
|
245
|
-
return False, feedback
|
|
246
|
-
s_value = value
|
|
247
|
-
val_len = len(s_value)
|
|
248
|
-
length_passed = True
|
|
249
|
-
length_feedback = []
|
|
250
|
-
if cfg.min_length is not None and val_len < cfg.min_length:
|
|
251
|
-
length_passed = False
|
|
252
|
-
length_feedback.append(
|
|
253
|
-
f"length {val_len} is less than minimum {cfg.min_length}"
|
|
254
|
-
)
|
|
255
|
-
if cfg.max_length is not None and val_len > cfg.max_length:
|
|
256
|
-
length_passed = False
|
|
257
|
-
length_feedback.append(
|
|
258
|
-
f"length {val_len} is greater than maximum {cfg.max_length}"
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
content_passed = True
|
|
262
|
-
content_feedback = ""
|
|
263
|
-
if cfg.expected_string is not None:
|
|
264
|
-
expected = cfg.expected_string
|
|
265
|
-
s1 = s_value if not cfg.ignore_case else s_value.lower()
|
|
266
|
-
s2 = expected if not cfg.ignore_case else expected.lower()
|
|
267
|
-
mode = cfg.string_mode
|
|
268
|
-
if mode == "equals":
|
|
269
|
-
content_passed = s1 == s2
|
|
270
|
-
elif mode == "contains":
|
|
271
|
-
content_passed = s2 in s1
|
|
272
|
-
elif mode == "startswith":
|
|
273
|
-
content_passed = s1.startswith(s2)
|
|
274
|
-
elif mode == "endswith":
|
|
275
|
-
content_passed = s1.endswith(s2)
|
|
276
|
-
elif mode == "not_equals":
|
|
277
|
-
content_passed = s1 != s2
|
|
278
|
-
elif mode == "not_contains":
|
|
279
|
-
content_passed = s2 not in s1
|
|
280
|
-
elif mode == "regex":
|
|
281
|
-
content_passed = bool(re.search(expected, value))
|
|
282
|
-
else:
|
|
283
|
-
content_passed = False
|
|
284
|
-
if not content_passed:
|
|
285
|
-
content_feedback = f"String content check '{mode}' failed against expected '{expected}' (ignore_case={cfg.ignore_case})."
|
|
286
|
-
|
|
287
|
-
condition_passed = length_passed and content_passed
|
|
288
|
-
if not condition_passed:
|
|
289
|
-
feedback_parts = length_feedback + (
|
|
290
|
-
[content_feedback] if content_feedback else []
|
|
291
|
-
)
|
|
292
|
-
feedback = (
|
|
293
|
-
"; ".join(feedback_parts)
|
|
294
|
-
if feedback_parts
|
|
295
|
-
else "String/length condition failed."
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
# 3. Number Check
|
|
299
|
-
elif cfg.expected_number is not None:
|
|
300
|
-
condition_type = "number"
|
|
301
|
-
if not isinstance(value, (int, float)):
|
|
302
|
-
feedback = f"Cannot perform number check on non-numeric value: {type(value)}."
|
|
303
|
-
logger.warning(feedback)
|
|
304
|
-
return False, feedback
|
|
305
|
-
num_value = value
|
|
306
|
-
expected = cfg.expected_number
|
|
307
|
-
mode = cfg.number_mode
|
|
308
|
-
op_map = {
|
|
309
|
-
"<": lambda a, b: a < b,
|
|
310
|
-
"<=": lambda a, b: a <= b,
|
|
311
|
-
"==": lambda a, b: a == b,
|
|
312
|
-
"!=": lambda a, b: a != b,
|
|
313
|
-
">=": lambda a, b: a >= b,
|
|
314
|
-
">": lambda a, b: a > b,
|
|
315
|
-
}
|
|
316
|
-
if mode in op_map:
|
|
317
|
-
condition_passed = op_map[mode](num_value, expected)
|
|
318
|
-
if not condition_passed:
|
|
319
|
-
feedback = f"Number check failed: {num_value} {mode} {expected} is false."
|
|
320
|
-
else:
|
|
321
|
-
condition_passed = False
|
|
322
|
-
feedback = f"Invalid number comparison mode: {mode}"
|
|
323
|
-
|
|
324
|
-
# 4. List Size Check
|
|
325
|
-
elif cfg.min_items is not None or cfg.max_items is not None:
|
|
326
|
-
condition_type = "list size"
|
|
327
|
-
if not isinstance(value, list):
|
|
328
|
-
feedback = f"Cannot perform list size check on non-list value: {type(value)}."
|
|
329
|
-
logger.warning(feedback)
|
|
330
|
-
return False, feedback
|
|
331
|
-
list_len = len(value)
|
|
332
|
-
size_passed = True
|
|
333
|
-
size_feedback = []
|
|
334
|
-
if cfg.min_items is not None and list_len < cfg.min_items:
|
|
335
|
-
size_passed = False
|
|
336
|
-
size_feedback.append(
|
|
337
|
-
f"list size {list_len} is less than minimum {cfg.min_items}"
|
|
338
|
-
)
|
|
339
|
-
if cfg.max_items is not None and list_len > cfg.max_items:
|
|
340
|
-
size_passed = False
|
|
341
|
-
size_feedback.append(
|
|
342
|
-
f"list size {list_len} is greater than maximum {cfg.max_items}"
|
|
343
|
-
)
|
|
344
|
-
condition_passed = size_passed
|
|
345
|
-
if not condition_passed:
|
|
346
|
-
feedback = "; ".join(size_feedback)
|
|
347
|
-
|
|
348
|
-
# 5. Type Check
|
|
349
|
-
elif cfg.expected_type_name is not None:
|
|
350
|
-
condition_type = "type"
|
|
351
|
-
registry = get_registry()
|
|
352
|
-
try:
|
|
353
|
-
expected_type = registry.get_type(cfg.expected_type_name)
|
|
354
|
-
condition_passed = isinstance(value, expected_type)
|
|
355
|
-
if not condition_passed:
|
|
356
|
-
feedback = f"Type check failed: Value type '{type(value).__name__}' is not instance of expected '{cfg.expected_type_name}'."
|
|
357
|
-
except KeyError:
|
|
358
|
-
feedback = f"Expected type '{cfg.expected_type_name}' not found in registry."
|
|
359
|
-
logger.error(feedback)
|
|
360
|
-
return False, feedback
|
|
361
|
-
|
|
362
|
-
# 6. Boolean Check
|
|
363
|
-
elif cfg.expected_bool is not None:
|
|
364
|
-
condition_type = "boolean"
|
|
365
|
-
if not isinstance(value, bool):
|
|
366
|
-
feedback = f"Cannot perform boolean check on non-bool value: {type(value)}."
|
|
367
|
-
logger.warning(feedback)
|
|
368
|
-
return False, feedback
|
|
369
|
-
condition_passed = value == cfg.expected_bool
|
|
370
|
-
if not condition_passed:
|
|
371
|
-
feedback = f"Boolean check failed: Value '{value}' is not expected '{cfg.expected_bool}'."
|
|
372
|
-
|
|
373
|
-
logger.debug(
|
|
374
|
-
f"Condition check '{condition_type}' result: {condition_passed}"
|
|
375
|
-
)
|
|
376
|
-
return condition_passed, feedback if not condition_passed else None
|
|
377
|
-
|
|
378
|
-
except Exception as e:
|
|
379
|
-
feedback = (
|
|
380
|
-
f"Error evaluating condition type '{condition_type}': {e}"
|
|
381
|
-
)
|
|
382
|
-
logger.error(feedback, exc_info=True)
|
|
383
|
-
return (
|
|
384
|
-
False,
|
|
385
|
-
feedback,
|
|
386
|
-
) # Treat evaluation errors as condition failure
|
|
387
|
-
|
|
388
|
-
async def determine_next_step(
|
|
389
|
-
self,
|
|
390
|
-
agent: "FlockAgent",
|
|
391
|
-
result: dict[str, Any],
|
|
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
|
-
|
|
399
|
-
cfg = self.config
|
|
400
|
-
condition_value = context.get_variable(cfg.condition_context_key, None)
|
|
401
|
-
feedback_value = context.get_variable(cfg.feedback_context_key, None)
|
|
402
|
-
|
|
403
|
-
logger.debug(
|
|
404
|
-
f"Routing based on condition key '{cfg.condition_context_key}', value: {str(condition_value)[:100]}..."
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
# Evaluate the condition and get feedback on failure
|
|
408
|
-
condition_passed, feedback_msg = self._evaluate_condition(
|
|
409
|
-
condition_value
|
|
410
|
-
)
|
|
411
|
-
|
|
412
|
-
if condition_passed:
|
|
413
|
-
# --- Success Path ---
|
|
414
|
-
logger.info(
|
|
415
|
-
f"Condition PASSED for agent '{agent.name}'. Routing to success path."
|
|
416
|
-
)
|
|
417
|
-
# Reset retry count if applicable
|
|
418
|
-
if cfg.retry_on_failure:
|
|
419
|
-
retry_key = (
|
|
420
|
-
f"{cfg.retry_count_context_key_prefix}{agent.name}"
|
|
421
|
-
)
|
|
422
|
-
if retry_key in context.state:
|
|
423
|
-
del context.state[retry_key]
|
|
424
|
-
logger.debug(
|
|
425
|
-
f"Reset retry count for agent '{agent.name}'."
|
|
426
|
-
)
|
|
427
|
-
|
|
428
|
-
# Clear feedback from context on success
|
|
429
|
-
if (
|
|
430
|
-
cfg.feedback_context_key
|
|
431
|
-
and cfg.feedback_context_key in context.state
|
|
432
|
-
):
|
|
433
|
-
del context.state[cfg.feedback_context_key]
|
|
434
|
-
logger.debug(
|
|
435
|
-
f"Cleared feedback key '{cfg.feedback_context_key}' on success."
|
|
436
|
-
)
|
|
437
|
-
|
|
438
|
-
next_agent = cfg.success_agent or None # Stop chain if None
|
|
439
|
-
logger.debug(f"Success route target: '{next_agent}'")
|
|
440
|
-
|
|
441
|
-
agent.next_agent = next_agent # Set directly on agent
|
|
442
|
-
|
|
443
|
-
else:
|
|
444
|
-
# --- Failure Path ---
|
|
445
|
-
logger.warning(
|
|
446
|
-
f"Condition FAILED for agent '{agent.name}'. Reason: {feedback_msg}"
|
|
447
|
-
)
|
|
448
|
-
|
|
449
|
-
if cfg.retry_on_failure:
|
|
450
|
-
# --- Retry Logic ---
|
|
451
|
-
retry_key = (
|
|
452
|
-
f"{cfg.retry_count_context_key_prefix}{agent.name}"
|
|
453
|
-
)
|
|
454
|
-
retry_count = context.get_variable(retry_key, 0)
|
|
455
|
-
|
|
456
|
-
if retry_count < cfg.max_retries:
|
|
457
|
-
next_retry_count = retry_count + 1
|
|
458
|
-
context.set_variable(retry_key, next_retry_count)
|
|
459
|
-
logger.info(
|
|
460
|
-
f"Routing back to agent '{agent.name}' for retry #{next_retry_count}/{cfg.max_retries}."
|
|
461
|
-
)
|
|
462
|
-
|
|
463
|
-
# Add specific feedback to context if retry is enabled
|
|
464
|
-
if cfg.feedback_context_key:
|
|
465
|
-
context.set_variable(
|
|
466
|
-
cfg.feedback_context_key,
|
|
467
|
-
feedback_msg or "Condition failed",
|
|
468
|
-
)
|
|
469
|
-
logger.debug(
|
|
470
|
-
f"Set feedback key '{cfg.feedback_context_key}': {feedback_msg or 'Condition failed'}"
|
|
471
|
-
)
|
|
472
|
-
|
|
473
|
-
agent.next_agent = agent.name # Route back to self
|
|
474
|
-
else:
|
|
475
|
-
# --- Max Retries Exceeded ---
|
|
476
|
-
logger.error(
|
|
477
|
-
f"Max retries ({cfg.max_retries}) exceeded for agent '{agent.name}'."
|
|
478
|
-
)
|
|
479
|
-
if retry_key in context.state:
|
|
480
|
-
del context.state[retry_key] # Reset count
|
|
481
|
-
next_agent = cfg.failure_agent or None
|
|
482
|
-
logger.debug(
|
|
483
|
-
f"Failure route target (after retries): '{next_agent}'"
|
|
484
|
-
)
|
|
485
|
-
|
|
486
|
-
agent.next_agent = next_agent
|
|
487
|
-
else:
|
|
488
|
-
# --- No Retry Logic ---
|
|
489
|
-
next_agent = (
|
|
490
|
-
cfg.failure_agent or None
|
|
491
|
-
) # Use failure agent or stop
|
|
492
|
-
logger.debug(f"Failure route target (no retry): '{next_agent}'")
|
|
493
|
-
|
|
494
|
-
agent.next_agent = next_agent
|
|
@@ -1,103 +0,0 @@
|
|
|
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
|