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.

Files changed (359) 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.56b0.dist-info/METADATA +747 -0
  162. flock_core-0.5.56b0.dist-info/RECORD +398 -0
  163. flock_core-0.5.56b0.dist-info/entry_points.txt +2 -0
  164. {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.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 -22
  197. flock/components/utility/example_utility_component.py +0 -250
  198. flock/components/utility/feedback_utility_component.py +0 -206
  199. flock/components/utility/memory_utility_component.py +0 -550
  200. flock/components/utility/metrics_utility_component.py +0 -700
  201. flock/config.py +0 -61
  202. flock/core/__init__.py +0 -110
  203. flock/core/agent/__init__.py +0 -16
  204. flock/core/agent/default_agent.py +0 -216
  205. flock/core/agent/flock_agent_components.py +0 -104
  206. flock/core/agent/flock_agent_execution.py +0 -101
  207. flock/core/agent/flock_agent_integration.py +0 -260
  208. flock/core/agent/flock_agent_lifecycle.py +0 -186
  209. flock/core/agent/flock_agent_serialization.py +0 -381
  210. flock/core/api/__init__.py +0 -10
  211. flock/core/api/custom_endpoint.py +0 -45
  212. flock/core/api/endpoints.py +0 -254
  213. flock/core/api/main.py +0 -162
  214. flock/core/api/models.py +0 -97
  215. flock/core/api/run_store.py +0 -224
  216. flock/core/api/runner.py +0 -44
  217. flock/core/api/service.py +0 -214
  218. flock/core/component/__init__.py +0 -15
  219. flock/core/component/agent_component_base.py +0 -309
  220. flock/core/component/evaluation_component.py +0 -62
  221. flock/core/component/routing_component.py +0 -74
  222. flock/core/component/utility_component.py +0 -69
  223. flock/core/config/flock_agent_config.py +0 -58
  224. flock/core/config/scheduled_agent_config.py +0 -40
  225. flock/core/context/context.py +0 -213
  226. flock/core/context/context_manager.py +0 -37
  227. flock/core/context/context_vars.py +0 -10
  228. flock/core/evaluation/utils.py +0 -396
  229. flock/core/execution/batch_executor.py +0 -369
  230. flock/core/execution/evaluation_executor.py +0 -438
  231. flock/core/execution/local_executor.py +0 -31
  232. flock/core/execution/opik_executor.py +0 -103
  233. flock/core/execution/temporal_executor.py +0 -164
  234. flock/core/flock.py +0 -634
  235. flock/core/flock_agent.py +0 -336
  236. flock/core/flock_factory.py +0 -613
  237. flock/core/flock_scheduler.py +0 -166
  238. flock/core/flock_server_manager.py +0 -136
  239. flock/core/interpreter/python_interpreter.py +0 -689
  240. flock/core/mcp/__init__.py +0 -1
  241. flock/core/mcp/flock_mcp_server.py +0 -680
  242. flock/core/mcp/mcp_client_manager.py +0 -201
  243. flock/core/mcp/types/__init__.py +0 -1
  244. flock/core/mixin/dspy_integration.py +0 -403
  245. flock/core/mixin/prompt_parser.py +0 -125
  246. flock/core/orchestration/__init__.py +0 -15
  247. flock/core/orchestration/flock_batch_processor.py +0 -94
  248. flock/core/orchestration/flock_evaluator.py +0 -113
  249. flock/core/orchestration/flock_execution.py +0 -295
  250. flock/core/orchestration/flock_initialization.py +0 -149
  251. flock/core/orchestration/flock_server_manager.py +0 -67
  252. flock/core/orchestration/flock_web_server.py +0 -117
  253. flock/core/registry/__init__.py +0 -45
  254. flock/core/registry/agent_registry.py +0 -69
  255. flock/core/registry/callable_registry.py +0 -139
  256. flock/core/registry/component_discovery.py +0 -142
  257. flock/core/registry/component_registry.py +0 -64
  258. flock/core/registry/config_mapping.py +0 -64
  259. flock/core/registry/decorators.py +0 -137
  260. flock/core/registry/registry_hub.py +0 -205
  261. flock/core/registry/server_registry.py +0 -57
  262. flock/core/registry/type_registry.py +0 -86
  263. flock/core/serialization/__init__.py +0 -13
  264. flock/core/serialization/callable_registry.py +0 -52
  265. flock/core/serialization/flock_serializer.py +0 -832
  266. flock/core/serialization/json_encoder.py +0 -41
  267. flock/core/serialization/secure_serializer.py +0 -175
  268. flock/core/serialization/serializable.py +0 -342
  269. flock/core/serialization/serialization_utils.py +0 -412
  270. flock/core/util/file_path_utils.py +0 -223
  271. flock/core/util/hydrator.py +0 -309
  272. flock/core/util/input_resolver.py +0 -164
  273. flock/core/util/loader.py +0 -59
  274. flock/core/util/splitter.py +0 -219
  275. flock/di.py +0 -27
  276. flock/platform/docker_tools.py +0 -49
  277. flock/platform/jaeger_install.py +0 -86
  278. flock/webapp/__init__.py +0 -1
  279. flock/webapp/app/__init__.py +0 -0
  280. flock/webapp/app/api/__init__.py +0 -0
  281. flock/webapp/app/api/agent_management.py +0 -241
  282. flock/webapp/app/api/execution.py +0 -709
  283. flock/webapp/app/api/flock_management.py +0 -129
  284. flock/webapp/app/api/registry_viewer.py +0 -30
  285. flock/webapp/app/chat.py +0 -665
  286. flock/webapp/app/config.py +0 -104
  287. flock/webapp/app/dependencies.py +0 -117
  288. flock/webapp/app/main.py +0 -1070
  289. flock/webapp/app/middleware.py +0 -113
  290. flock/webapp/app/models_ui.py +0 -7
  291. flock/webapp/app/services/__init__.py +0 -0
  292. flock/webapp/app/services/feedback_file_service.py +0 -363
  293. flock/webapp/app/services/flock_service.py +0 -337
  294. flock/webapp/app/services/sharing_models.py +0 -81
  295. flock/webapp/app/services/sharing_store.py +0 -762
  296. flock/webapp/app/templates/theme_mapper.html +0 -326
  297. flock/webapp/app/theme_mapper.py +0 -812
  298. flock/webapp/app/utils.py +0 -85
  299. flock/webapp/run.py +0 -215
  300. flock/webapp/static/css/chat.css +0 -301
  301. flock/webapp/static/css/components.css +0 -167
  302. flock/webapp/static/css/header.css +0 -39
  303. flock/webapp/static/css/layout.css +0 -46
  304. flock/webapp/static/css/sidebar.css +0 -127
  305. flock/webapp/static/css/two-pane.css +0 -48
  306. flock/webapp/templates/base.html +0 -200
  307. flock/webapp/templates/chat.html +0 -152
  308. flock/webapp/templates/chat_settings.html +0 -19
  309. flock/webapp/templates/flock_editor.html +0 -16
  310. flock/webapp/templates/index.html +0 -12
  311. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  312. flock/webapp/templates/partials/_agent_list.html +0 -18
  313. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  314. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  315. flock/webapp/templates/partials/_chat_container.html +0 -15
  316. flock/webapp/templates/partials/_chat_messages.html +0 -57
  317. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  318. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  319. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  320. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  321. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  322. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  323. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  324. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  325. flock/webapp/templates/partials/_execution_form.html +0 -118
  326. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  327. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  328. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  329. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  330. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  331. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  332. flock/webapp/templates/partials/_registry_table.html +0 -25
  333. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  334. flock/webapp/templates/partials/_results_display.html +0 -78
  335. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  336. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  337. flock/webapp/templates/partials/_settings_view.html +0 -36
  338. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  339. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  340. flock/webapp/templates/partials/_sidebar.html +0 -74
  341. flock/webapp/templates/partials/_streaming_results_container.html +0 -195
  342. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  343. flock/webapp/templates/partials/_theme_preview.html +0 -36
  344. flock/webapp/templates/registry_viewer.html +0 -84
  345. flock/webapp/templates/shared_run_page.html +0 -140
  346. flock/workflow/__init__.py +0 -0
  347. flock/workflow/activities.py +0 -196
  348. flock/workflow/agent_activities.py +0 -24
  349. flock/workflow/agent_execution_activity.py +0 -202
  350. flock/workflow/flock_workflow.py +0 -214
  351. flock/workflow/temporal_config.py +0 -96
  352. flock/workflow/temporal_setup.py +0 -68
  353. flock_core-0.5.0b28.dist-info/METADATA +0 -274
  354. flock_core-0.5.0b28.dist-info/RECORD +0 -561
  355. flock_core-0.5.0b28.dist-info/entry_points.txt +0 -2
  356. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  357. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  358. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  359. {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