flock-core 0.5.0b27__py3-none-any.whl → 0.5.0b50__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.

Files changed (357) hide show
  1. flock/__init__.py +12 -217
  2. flock/agent.py +678 -0
  3. flock/api/themes.py +71 -0
  4. flock/artifacts.py +79 -0
  5. flock/cli.py +75 -0
  6. flock/components.py +173 -0
  7. flock/dashboard/__init__.py +28 -0
  8. flock/dashboard/collector.py +283 -0
  9. flock/dashboard/events.py +182 -0
  10. flock/dashboard/launcher.py +230 -0
  11. flock/dashboard/service.py +537 -0
  12. flock/dashboard/websocket.py +235 -0
  13. flock/engines/__init__.py +6 -0
  14. flock/engines/dspy_engine.py +856 -0
  15. flock/examples.py +128 -0
  16. flock/{core/util → helper}/cli_helper.py +4 -3
  17. flock/{core/logging → logging}/__init__.py +2 -3
  18. flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
  19. flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
  20. flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
  21. flock/{core/logging → logging}/logging.py +77 -61
  22. flock/{core/logging → logging}/telemetry.py +20 -26
  23. flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
  24. flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
  25. flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
  26. flock/{core/logging → logging}/trace_and_logged.py +20 -24
  27. flock/mcp/__init__.py +91 -0
  28. flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
  29. flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
  30. flock/mcp/manager.py +255 -0
  31. flock/mcp/servers/sse/__init__.py +1 -1
  32. flock/mcp/servers/sse/flock_sse_server.py +11 -53
  33. flock/mcp/servers/stdio/__init__.py +1 -1
  34. flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
  35. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
  36. flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
  37. flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
  38. flock/mcp/types/__init__.py +42 -0
  39. flock/{core/mcp → mcp}/types/callbacks.py +9 -15
  40. flock/{core/mcp → mcp}/types/factories.py +7 -6
  41. flock/{core/mcp → mcp}/types/handlers.py +13 -18
  42. flock/{core/mcp → mcp}/types/types.py +70 -74
  43. flock/{core/mcp → mcp}/util/helpers.py +1 -1
  44. flock/orchestrator.py +645 -0
  45. flock/registry.py +148 -0
  46. flock/runtime.py +262 -0
  47. flock/service.py +140 -0
  48. flock/store.py +69 -0
  49. flock/subscription.py +111 -0
  50. flock/themes/andromeda.toml +1 -1
  51. flock/themes/apple-system-colors.toml +1 -1
  52. flock/themes/arcoiris.toml +1 -1
  53. flock/themes/atomonelight.toml +1 -1
  54. flock/themes/ayu copy.toml +1 -1
  55. flock/themes/ayu-light.toml +1 -1
  56. flock/themes/belafonte-day.toml +1 -1
  57. flock/themes/belafonte-night.toml +1 -1
  58. flock/themes/blulocodark.toml +1 -1
  59. flock/themes/breeze.toml +1 -1
  60. flock/themes/broadcast.toml +1 -1
  61. flock/themes/brogrammer.toml +1 -1
  62. flock/themes/builtin-dark.toml +1 -1
  63. flock/themes/builtin-pastel-dark.toml +1 -1
  64. flock/themes/catppuccin-latte.toml +1 -1
  65. flock/themes/catppuccin-macchiato.toml +1 -1
  66. flock/themes/catppuccin-mocha.toml +1 -1
  67. flock/themes/cga.toml +1 -1
  68. flock/themes/chalk.toml +1 -1
  69. flock/themes/ciapre.toml +1 -1
  70. flock/themes/coffee-theme.toml +1 -1
  71. flock/themes/cyberpunkscarletprotocol.toml +1 -1
  72. flock/themes/dark+.toml +1 -1
  73. flock/themes/darkermatrix.toml +1 -1
  74. flock/themes/darkside.toml +1 -1
  75. flock/themes/desert.toml +1 -1
  76. flock/themes/django.toml +1 -1
  77. flock/themes/djangosmooth.toml +1 -1
  78. flock/themes/doomone.toml +1 -1
  79. flock/themes/dotgov.toml +1 -1
  80. flock/themes/dracula+.toml +1 -1
  81. flock/themes/duckbones.toml +1 -1
  82. flock/themes/encom.toml +1 -1
  83. flock/themes/espresso.toml +1 -1
  84. flock/themes/everblush.toml +1 -1
  85. flock/themes/fairyfloss.toml +1 -1
  86. flock/themes/fideloper.toml +1 -1
  87. flock/themes/fishtank.toml +1 -1
  88. flock/themes/flexoki-light.toml +1 -1
  89. flock/themes/floraverse.toml +1 -1
  90. flock/themes/framer.toml +1 -1
  91. flock/themes/galizur.toml +1 -1
  92. flock/themes/github.toml +1 -1
  93. flock/themes/grass.toml +1 -1
  94. flock/themes/grey-green.toml +1 -1
  95. flock/themes/gruvboxlight.toml +1 -1
  96. flock/themes/guezwhoz.toml +1 -1
  97. flock/themes/harper.toml +1 -1
  98. flock/themes/hax0r-blue.toml +1 -1
  99. flock/themes/hopscotch.256.toml +1 -1
  100. flock/themes/ic-green-ppl.toml +1 -1
  101. flock/themes/iceberg-dark.toml +1 -1
  102. flock/themes/japanesque.toml +1 -1
  103. flock/themes/jubi.toml +1 -1
  104. flock/themes/kibble.toml +1 -1
  105. flock/themes/kolorit.toml +1 -1
  106. flock/themes/kurokula.toml +1 -1
  107. flock/themes/materialdesigncolors.toml +1 -1
  108. flock/themes/matrix.toml +1 -1
  109. flock/themes/mellifluous.toml +1 -1
  110. flock/themes/midnight-in-mojave.toml +1 -1
  111. flock/themes/monokai-remastered.toml +1 -1
  112. flock/themes/monokai-soda.toml +1 -1
  113. flock/themes/neon.toml +1 -1
  114. flock/themes/neopolitan.toml +1 -1
  115. flock/themes/nord-light.toml +1 -1
  116. flock/themes/ocean.toml +1 -1
  117. flock/themes/onehalfdark.toml +1 -1
  118. flock/themes/onehalflight.toml +1 -1
  119. flock/themes/palenighthc.toml +1 -1
  120. flock/themes/paulmillr.toml +1 -1
  121. flock/themes/pencildark.toml +1 -1
  122. flock/themes/pnevma.toml +1 -1
  123. flock/themes/purple-rain.toml +1 -1
  124. flock/themes/purplepeter.toml +1 -1
  125. flock/themes/raycast-dark.toml +1 -1
  126. flock/themes/red-sands.toml +1 -1
  127. flock/themes/relaxed.toml +1 -1
  128. flock/themes/retro.toml +1 -1
  129. flock/themes/rose-pine.toml +1 -1
  130. flock/themes/royal.toml +1 -1
  131. flock/themes/ryuuko.toml +1 -1
  132. flock/themes/sakura.toml +1 -1
  133. flock/themes/scarlet-protocol.toml +1 -1
  134. flock/themes/seoulbones-dark.toml +1 -1
  135. flock/themes/shades-of-purple.toml +1 -1
  136. flock/themes/smyck.toml +1 -1
  137. flock/themes/softserver.toml +1 -1
  138. flock/themes/solarized-darcula.toml +1 -1
  139. flock/themes/square.toml +1 -1
  140. flock/themes/sugarplum.toml +1 -1
  141. flock/themes/thayer-bright.toml +1 -1
  142. flock/themes/tokyonight.toml +1 -1
  143. flock/themes/tomorrow.toml +1 -1
  144. flock/themes/ubuntu.toml +1 -1
  145. flock/themes/ultradark.toml +1 -1
  146. flock/themes/ultraviolent.toml +1 -1
  147. flock/themes/unikitty.toml +1 -1
  148. flock/themes/urple.toml +1 -1
  149. flock/themes/vesper.toml +1 -1
  150. flock/themes/vimbones.toml +1 -1
  151. flock/themes/wildcherry.toml +1 -1
  152. flock/themes/wilmersdorf.toml +1 -1
  153. flock/themes/wryan.toml +1 -1
  154. flock/themes/xcodedarkhc.toml +1 -1
  155. flock/themes/xcodelight.toml +1 -1
  156. flock/themes/zenbones-light.toml +1 -1
  157. flock/themes/zenwritten-dark.toml +1 -1
  158. flock/utilities.py +301 -0
  159. flock/{components/utility → utility}/output_utility_component.py +68 -53
  160. flock/visibility.py +107 -0
  161. flock_core-0.5.0b50.dist-info/METADATA +747 -0
  162. flock_core-0.5.0b50.dist-info/RECORD +398 -0
  163. flock_core-0.5.0b50.dist-info/entry_points.txt +2 -0
  164. {flock_core-0.5.0b27.dist-info → flock_core-0.5.0b50.dist-info}/licenses/LICENSE +1 -1
  165. flock/adapter/__init__.py +0 -14
  166. flock/adapter/azure_adapter.py +0 -68
  167. flock/adapter/chroma_adapter.py +0 -73
  168. flock/adapter/faiss_adapter.py +0 -97
  169. flock/adapter/pinecone_adapter.py +0 -51
  170. flock/adapter/vector_base.py +0 -47
  171. flock/cli/assets/release_notes.md +0 -140
  172. flock/cli/config.py +0 -8
  173. flock/cli/constants.py +0 -36
  174. flock/cli/create_agent.py +0 -1
  175. flock/cli/create_flock.py +0 -280
  176. flock/cli/execute_flock.py +0 -620
  177. flock/cli/load_agent.py +0 -1
  178. flock/cli/load_examples.py +0 -1
  179. flock/cli/load_flock.py +0 -192
  180. flock/cli/load_release_notes.py +0 -20
  181. flock/cli/loaded_flock_cli.py +0 -254
  182. flock/cli/manage_agents.py +0 -459
  183. flock/cli/registry_management.py +0 -889
  184. flock/cli/runner.py +0 -41
  185. flock/cli/settings.py +0 -857
  186. flock/cli/utils.py +0 -135
  187. flock/cli/view_results.py +0 -29
  188. flock/cli/yaml_editor.py +0 -396
  189. flock/components/__init__.py +0 -30
  190. flock/components/evaluation/__init__.py +0 -9
  191. flock/components/evaluation/declarative_evaluation_component.py +0 -606
  192. flock/components/routing/__init__.py +0 -15
  193. flock/components/routing/conditional_routing_component.py +0 -494
  194. flock/components/routing/default_routing_component.py +0 -103
  195. flock/components/routing/llm_routing_component.py +0 -206
  196. flock/components/utility/__init__.py +0 -15
  197. flock/components/utility/memory_utility_component.py +0 -550
  198. flock/components/utility/metrics_utility_component.py +0 -700
  199. flock/config.py +0 -61
  200. flock/core/__init__.py +0 -110
  201. flock/core/agent/__init__.py +0 -16
  202. flock/core/agent/default_agent.py +0 -180
  203. flock/core/agent/flock_agent_components.py +0 -104
  204. flock/core/agent/flock_agent_execution.py +0 -101
  205. flock/core/agent/flock_agent_integration.py +0 -260
  206. flock/core/agent/flock_agent_lifecycle.py +0 -186
  207. flock/core/agent/flock_agent_serialization.py +0 -381
  208. flock/core/api/__init__.py +0 -10
  209. flock/core/api/custom_endpoint.py +0 -45
  210. flock/core/api/endpoints.py +0 -254
  211. flock/core/api/main.py +0 -162
  212. flock/core/api/models.py +0 -97
  213. flock/core/api/run_store.py +0 -224
  214. flock/core/api/runner.py +0 -44
  215. flock/core/api/service.py +0 -214
  216. flock/core/component/__init__.py +0 -15
  217. flock/core/component/agent_component_base.py +0 -309
  218. flock/core/component/evaluation_component.py +0 -62
  219. flock/core/component/routing_component.py +0 -74
  220. flock/core/component/utility_component.py +0 -69
  221. flock/core/config/flock_agent_config.py +0 -58
  222. flock/core/config/scheduled_agent_config.py +0 -40
  223. flock/core/context/context.py +0 -213
  224. flock/core/context/context_manager.py +0 -37
  225. flock/core/context/context_vars.py +0 -10
  226. flock/core/evaluation/utils.py +0 -396
  227. flock/core/execution/batch_executor.py +0 -369
  228. flock/core/execution/evaluation_executor.py +0 -438
  229. flock/core/execution/local_executor.py +0 -31
  230. flock/core/execution/opik_executor.py +0 -103
  231. flock/core/execution/temporal_executor.py +0 -164
  232. flock/core/flock.py +0 -634
  233. flock/core/flock_agent.py +0 -336
  234. flock/core/flock_factory.py +0 -551
  235. flock/core/flock_scheduler.py +0 -166
  236. flock/core/flock_server_manager.py +0 -136
  237. flock/core/interpreter/python_interpreter.py +0 -689
  238. flock/core/mcp/__init__.py +0 -1
  239. flock/core/mcp/flock_mcp_server.py +0 -680
  240. flock/core/mcp/mcp_client_manager.py +0 -201
  241. flock/core/mcp/types/__init__.py +0 -1
  242. flock/core/mixin/dspy_integration.py +0 -403
  243. flock/core/mixin/prompt_parser.py +0 -125
  244. flock/core/orchestration/__init__.py +0 -15
  245. flock/core/orchestration/flock_batch_processor.py +0 -94
  246. flock/core/orchestration/flock_evaluator.py +0 -113
  247. flock/core/orchestration/flock_execution.py +0 -295
  248. flock/core/orchestration/flock_initialization.py +0 -149
  249. flock/core/orchestration/flock_server_manager.py +0 -67
  250. flock/core/orchestration/flock_web_server.py +0 -117
  251. flock/core/registry/__init__.py +0 -45
  252. flock/core/registry/agent_registry.py +0 -69
  253. flock/core/registry/callable_registry.py +0 -139
  254. flock/core/registry/component_discovery.py +0 -142
  255. flock/core/registry/component_registry.py +0 -64
  256. flock/core/registry/config_mapping.py +0 -64
  257. flock/core/registry/decorators.py +0 -137
  258. flock/core/registry/registry_hub.py +0 -205
  259. flock/core/registry/server_registry.py +0 -57
  260. flock/core/registry/type_registry.py +0 -86
  261. flock/core/serialization/__init__.py +0 -13
  262. flock/core/serialization/callable_registry.py +0 -52
  263. flock/core/serialization/flock_serializer.py +0 -832
  264. flock/core/serialization/json_encoder.py +0 -41
  265. flock/core/serialization/secure_serializer.py +0 -175
  266. flock/core/serialization/serializable.py +0 -342
  267. flock/core/serialization/serialization_utils.py +0 -412
  268. flock/core/util/file_path_utils.py +0 -223
  269. flock/core/util/hydrator.py +0 -309
  270. flock/core/util/input_resolver.py +0 -164
  271. flock/core/util/loader.py +0 -59
  272. flock/core/util/splitter.py +0 -219
  273. flock/di.py +0 -27
  274. flock/platform/docker_tools.py +0 -49
  275. flock/platform/jaeger_install.py +0 -86
  276. flock/webapp/__init__.py +0 -1
  277. flock/webapp/app/__init__.py +0 -0
  278. flock/webapp/app/api/__init__.py +0 -0
  279. flock/webapp/app/api/agent_management.py +0 -241
  280. flock/webapp/app/api/execution.py +0 -709
  281. flock/webapp/app/api/flock_management.py +0 -129
  282. flock/webapp/app/api/registry_viewer.py +0 -30
  283. flock/webapp/app/chat.py +0 -665
  284. flock/webapp/app/config.py +0 -104
  285. flock/webapp/app/dependencies.py +0 -117
  286. flock/webapp/app/main.py +0 -1070
  287. flock/webapp/app/middleware.py +0 -113
  288. flock/webapp/app/models_ui.py +0 -7
  289. flock/webapp/app/services/__init__.py +0 -0
  290. flock/webapp/app/services/feedback_file_service.py +0 -363
  291. flock/webapp/app/services/flock_service.py +0 -337
  292. flock/webapp/app/services/sharing_models.py +0 -81
  293. flock/webapp/app/services/sharing_store.py +0 -598
  294. flock/webapp/app/templates/theme_mapper.html +0 -326
  295. flock/webapp/app/theme_mapper.py +0 -812
  296. flock/webapp/app/utils.py +0 -85
  297. flock/webapp/run.py +0 -215
  298. flock/webapp/static/css/chat.css +0 -301
  299. flock/webapp/static/css/components.css +0 -167
  300. flock/webapp/static/css/header.css +0 -39
  301. flock/webapp/static/css/layout.css +0 -46
  302. flock/webapp/static/css/sidebar.css +0 -127
  303. flock/webapp/static/css/two-pane.css +0 -48
  304. flock/webapp/templates/base.html +0 -200
  305. flock/webapp/templates/chat.html +0 -152
  306. flock/webapp/templates/chat_settings.html +0 -19
  307. flock/webapp/templates/flock_editor.html +0 -16
  308. flock/webapp/templates/index.html +0 -12
  309. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  310. flock/webapp/templates/partials/_agent_list.html +0 -18
  311. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  312. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  313. flock/webapp/templates/partials/_chat_container.html +0 -15
  314. flock/webapp/templates/partials/_chat_messages.html +0 -57
  315. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  316. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  317. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  318. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  319. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  320. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  321. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  322. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  323. flock/webapp/templates/partials/_execution_form.html +0 -118
  324. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  325. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  326. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  327. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  328. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  329. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  330. flock/webapp/templates/partials/_registry_table.html +0 -25
  331. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  332. flock/webapp/templates/partials/_results_display.html +0 -78
  333. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  334. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  335. flock/webapp/templates/partials/_settings_view.html +0 -36
  336. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  337. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  338. flock/webapp/templates/partials/_sidebar.html +0 -74
  339. flock/webapp/templates/partials/_streaming_results_container.html +0 -195
  340. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  341. flock/webapp/templates/partials/_theme_preview.html +0 -36
  342. flock/webapp/templates/registry_viewer.html +0 -84
  343. flock/webapp/templates/shared_run_page.html +0 -140
  344. flock/workflow/__init__.py +0 -0
  345. flock/workflow/activities.py +0 -196
  346. flock/workflow/agent_activities.py +0 -24
  347. flock/workflow/agent_execution_activity.py +0 -202
  348. flock/workflow/flock_workflow.py +0 -214
  349. flock/workflow/temporal_config.py +0 -96
  350. flock/workflow/temporal_setup.py +0 -68
  351. flock_core-0.5.0b27.dist-info/METADATA +0 -274
  352. flock_core-0.5.0b27.dist-info/RECORD +0 -559
  353. flock_core-0.5.0b27.dist-info/entry_points.txt +0 -2
  354. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  355. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  356. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  357. {flock_core-0.5.0b27.dist-info → flock_core-0.5.0b50.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