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,762 +0,0 @@
1
- """Shared link and feedback storage implementations supporting SQLite and Azure Table Storage."""
2
-
3
- import logging
4
- import sqlite3
5
- from abc import ABC, abstractmethod
6
- from pathlib import Path
7
- from typing import Any
8
- from datetime import datetime
9
-
10
- import aiosqlite
11
-
12
- from flock.webapp.app.services.sharing_models import (
13
- FeedbackRecord,
14
- SharedLinkConfig,
15
- )
16
-
17
- # Azure Table Storage imports - will be conditionally imported
18
- try:
19
- from azure.core.exceptions import ResourceExistsError, ResourceNotFoundError
20
- from azure.data.tables.aio import TableServiceClient
21
- AZURE_AVAILABLE = True
22
- except ImportError:
23
- AZURE_AVAILABLE = False
24
- TableServiceClient = None
25
- ResourceNotFoundError = None
26
- ResourceExistsError = None
27
-
28
- # Get a logger instance
29
- logger = logging.getLogger(__name__)
30
-
31
- class SharedLinkStoreInterface(ABC):
32
- """Interface for storing and retrieving shared link configurations."""
33
-
34
- @abstractmethod
35
- async def initialize(self) -> None:
36
- """Initialize the store (e.g., create tables)."""
37
- pass
38
-
39
- @abstractmethod
40
- async def save_config(self, config: SharedLinkConfig) -> SharedLinkConfig:
41
- """Saves a shared link configuration."""
42
- pass
43
-
44
- @abstractmethod
45
- async def get_config(self, share_id: str) -> SharedLinkConfig | None:
46
- """Retrieves a shared link configuration by its ID."""
47
- pass
48
-
49
- @abstractmethod
50
- async def delete_config(self, share_id: str) -> bool:
51
- """Deletes a shared link configuration by its ID. Returns True if deleted, False otherwise."""
52
- pass
53
-
54
- # Feedback
55
- @abstractmethod
56
- async def save_feedback(self, record: FeedbackRecord):
57
- """Persist a feedback record."""
58
- pass
59
-
60
- @abstractmethod
61
- async def get_feedback(self, id: str) -> FeedbackRecord | None:
62
- """Get a single feedback record."""
63
- pass
64
-
65
- @abstractmethod
66
- async def get_all_feedback_records_for_agent(self, agent_name: str) -> list[FeedbackRecord]:
67
- """Get all feedback records for a given agent."""
68
- pass
69
-
70
- # Examples
71
- @abstractmethod
72
- async def save_example(self, example: Any):
73
- """Persist an example record."""
74
- pass
75
-
76
- @abstractmethod
77
- async def get_example(self, id: str) -> Any | None:
78
- """Get a single example record."""
79
- pass
80
-
81
- @abstractmethod
82
- async def get_all_examples_for_agent(self, agent_name: str) -> list[Any]:
83
- """Get all example records for a given agent."""
84
- pass
85
-
86
- class SQLiteSharedLinkStore(SharedLinkStoreInterface):
87
- """SQLite implementation for storing and retrieving shared link configurations."""
88
-
89
- def __init__(self, db_path: str):
90
- """Initialize SQLite store with database path."""
91
- self.db_path = Path(db_path)
92
- self.db_path.parent.mkdir(parents=True, exist_ok=True) # Ensure directory exists
93
- logger.info(f"SQLiteSharedLinkStore initialized with db_path: {self.db_path}")
94
-
95
- async def initialize(self) -> None:
96
- """Initializes the database and creates/updates the table if it doesn't exist."""
97
- try:
98
- async with aiosqlite.connect(self.db_path) as db:
99
- # Ensure the table exists with the base schema first
100
- await db.execute(
101
- """
102
- CREATE TABLE IF NOT EXISTS shared_links (
103
- share_id TEXT PRIMARY KEY,
104
- agent_name TEXT NOT NULL,
105
- flock_definition TEXT NOT NULL,
106
- created_at TEXT NOT NULL
107
- /* New columns will be added below if they don't exist */
108
- )
109
- """
110
- )
111
-
112
- # Add new columns individually, ignoring errors if they already exist
113
- new_columns = [
114
- ("share_type", "TEXT DEFAULT 'agent_run' NOT NULL"),
115
- ("chat_message_key", "TEXT"),
116
- ("chat_history_key", "TEXT"),
117
- ("chat_response_key", "TEXT")
118
- ]
119
- for col_name, col_def in new_columns:
120
- try:
121
- await db.execute(f"ALTER TABLE shared_links ADD COLUMN {col_name} {col_def}")
122
- except sqlite3.OperationalError:
123
- pass # Column already exists
124
-
125
- # Create feedback table if it doesn't exist
126
- await db.execute(
127
- """
128
- CREATE TABLE IF NOT EXISTS feedback (
129
- feedback_id TEXT PRIMARY KEY,
130
- share_id TEXT,
131
- context_type TEXT NOT NULL,
132
- reason TEXT NOT NULL,
133
- expected_response TEXT,
134
- actual_response TEXT,
135
- flock_name TEXT,
136
- agent_name TEXT,
137
- flock_definition TEXT,
138
- created_at TEXT NOT NULL
139
- )
140
- """
141
- )
142
-
143
- # Create examples table if it doesn't exist
144
- await db.execute(
145
- """
146
- CREATE TABLE IF NOT EXISTS examples (
147
- example_id TEXT PRIMARY KEY,
148
- agent_name TEXT NOT NULL,
149
- content TEXT NOT NULL,
150
- created_at TEXT NOT NULL,
151
- context_type TEXT NOT NULL DEFAULT 'example'
152
- )
153
- """
154
- )
155
-
156
- await db.commit()
157
- except Exception as e:
158
- logger.error(f"Error initializing database: {e}", exc_info=True)
159
- raise
160
-
161
- async def save_config(self, config: SharedLinkConfig) -> SharedLinkConfig:
162
- """Saves a shared link configuration to the SQLite database."""
163
- try:
164
- async with aiosqlite.connect(self.db_path) as db:
165
- await db.execute(
166
- """INSERT INTO shared_links (
167
- share_id, agent_name, created_at, flock_definition,
168
- share_type, chat_message_key, chat_history_key, chat_response_key
169
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
170
- (
171
- config.share_id,
172
- config.agent_name,
173
- config.created_at.isoformat(),
174
- config.flock_definition,
175
- config.share_type,
176
- config.chat_message_key,
177
- config.chat_history_key,
178
- config.chat_response_key,
179
- ),
180
- )
181
- await db.commit()
182
- logger.info(f"Saved shared link config for ID: {config.share_id} with type: {config.share_type}")
183
- return config
184
- except sqlite3.Error as e:
185
- logger.error(f"SQLite error saving config for ID {config.share_id}: {e}", exc_info=True)
186
- raise
187
-
188
- async def get_config(self, share_id: str) -> SharedLinkConfig | None:
189
- """Retrieves a shared link configuration from SQLite by its ID."""
190
- try:
191
- async with aiosqlite.connect(self.db_path) as db:
192
- async with db.execute(
193
- """SELECT
194
- share_id, agent_name, created_at, flock_definition,
195
- share_type, chat_message_key, chat_history_key, chat_response_key
196
- FROM shared_links WHERE share_id = ?""",
197
- (share_id,)
198
- ) as cursor:
199
- row = await cursor.fetchone()
200
- if row:
201
- logger.debug(f"Retrieved shared link config for ID: {share_id}")
202
- return SharedLinkConfig(
203
- share_id=row[0],
204
- agent_name=row[1],
205
- created_at=row[2], # SQLite stores as TEXT, Pydantic will parse from ISO format
206
- flock_definition=row[3],
207
- share_type=row[4],
208
- chat_message_key=row[5],
209
- chat_history_key=row[6],
210
- chat_response_key=row[7],
211
- )
212
- logger.debug(f"No shared link config found for ID: {share_id}")
213
- return None
214
- except sqlite3.Error as e:
215
- logger.error(f"SQLite error retrieving config for ID {share_id}: {e}", exc_info=True)
216
- return None # Or raise, depending on desired error handling
217
-
218
- async def delete_config(self, share_id: str) -> bool:
219
- """Deletes a shared link configuration from SQLite by its ID."""
220
- try:
221
- async with aiosqlite.connect(self.db_path) as db:
222
- result = await db.execute("DELETE FROM shared_links WHERE share_id = ?", (share_id,))
223
- await db.commit()
224
- deleted_count = result.rowcount
225
- if deleted_count > 0:
226
- logger.info(f"Deleted shared link config for ID: {share_id}")
227
- return True
228
- logger.info(f"Attempted to delete non-existent shared link config for ID: {share_id}")
229
- return False
230
- except sqlite3.Error as e:
231
- logger.error(f"SQLite error deleting config for ID {share_id}: {e}", exc_info=True)
232
- return False # Or raise
233
-
234
- # ----------------------- Feedback methods -----------------------
235
- async def get_feedback(self, id: str) -> FeedbackRecord | None:
236
- """Retrieve a single feedback record from SQLite."""
237
- try:
238
- async with aiosqlite.connect(self.db_path) as db, db.execute(
239
- """SELECT
240
- feedback_id, share_id, context_type, reason,
241
- expected_response, actual_response, flock_name, agent_name, flock_definition, created_at
242
- FROM feedback WHERE feedback_id = ?""",
243
- (id,)
244
- ) as cursor:
245
- row = await cursor.fetchone()
246
-
247
- if row:
248
- logger.debug(f"Retrieved feedback record for ID: {id}")
249
- return FeedbackRecord(
250
- feedback_id=row[0],
251
- share_id=row[1],
252
- context_type=row[2],
253
- reason=row[3],
254
- expected_response=row[4],
255
- actual_response=row[5],
256
- flock_name=row[6],
257
- agent_name=row[7],
258
- flock_definition=row[8],
259
- created_at=row[9], # SQLite stores as TEXT, Pydantic will parse from ISO format
260
- )
261
-
262
- logger.debug(f"No feedback record found for ID: {id}")
263
- return None
264
- except sqlite3.Error as e:
265
- logger.error(f"SQLite error retrieving feedback for ID {id}: {e}", exc_info=True)
266
- return None # Or raise, depending on desired error handling
267
-
268
- async def get_all_feedback_records_for_agent(self, agent_name: str) -> list[FeedbackRecord]:
269
- """Retrieve all feedback records from SQLite."""
270
- try:
271
- async with aiosqlite.connect(self.db_path) as db, db.execute(
272
- """SELECT
273
- feedback_id, share_id, context_type, reason,
274
- expected_response, actual_response, flock_name, agent_name, flock_definition, created_at
275
- FROM feedback WHERE agent_name = ? ORDER BY created_at DESC""",
276
- (agent_name,)
277
- ) as cursor:
278
- rows = await cursor.fetchall()
279
-
280
- records = []
281
- for row in rows:
282
- records.append(FeedbackRecord(
283
- feedback_id=row[0],
284
- share_id=row[1],
285
- context_type=row[2],
286
- reason=row[3],
287
- expected_response=row[4],
288
- actual_response=row[5],
289
- flock_name=row[6],
290
- agent_name=row[7],
291
- flock_definition=row[8],
292
- created_at=row[9], # SQLite stores as TEXT, Pydantic will parse from ISO format
293
- ))
294
-
295
- logger.debug(f"Retrieved {len(records)} feedback records")
296
- return records
297
- except sqlite3.Error as e:
298
- logger.error(f"SQLite error retrieving all feedback records: {e}", exc_info=True)
299
- return [] # Return empty list on error
300
-
301
- async def save_feedback(self, record: FeedbackRecord) -> FeedbackRecord:
302
- """Persist a feedback record to SQLite."""
303
- try:
304
- async with aiosqlite.connect(self.db_path) as db:
305
- await db.execute(
306
- """INSERT INTO feedback (
307
- feedback_id, share_id, context_type, reason,
308
- expected_response, actual_response, flock_name, agent_name, flock_definition, created_at
309
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
310
- (
311
- record.feedback_id,
312
- record.share_id,
313
- record.context_type,
314
- record.reason,
315
- record.expected_response,
316
- record.actual_response,
317
- record.flock_name,
318
- record.agent_name,
319
- record.flock_definition,
320
- record.created_at.isoformat(),
321
- ),
322
- )
323
- await db.commit()
324
- logger.info(f"Saved feedback {record.feedback_id} (share={record.share_id})")
325
- return record
326
- except sqlite3.Error as e:
327
- logger.error(f"SQLite error saving feedback {record.feedback_id}: {e}", exc_info=True)
328
- raise
329
-
330
- async def save_example(self, example: Any) -> Any:
331
- """Persist an example record to SQLite."""
332
- try:
333
- example_dict = example.to_dict()
334
- async with aiosqlite.connect(self.db_path) as db:
335
- await db.execute(
336
- """INSERT INTO examples (
337
- example_id, agent_name, content, created_at, context_type
338
- ) VALUES (?, ?, ?, ?, ?)""",
339
- (
340
- example_dict["example_id"],
341
- example_dict["agent_name"],
342
- example_dict["content"],
343
- example_dict["created_at"],
344
- example_dict["context_type"],
345
- ),
346
- )
347
- await db.commit()
348
- logger.info(f"Saved example {example_dict['example_id']} for agent {example_dict['agent_name']}")
349
- return example
350
- except sqlite3.Error as e:
351
- logger.error(f"SQLite error saving example {example.example_id}: {e}", exc_info=True)
352
- raise
353
-
354
- async def get_example(self, id: str) -> Any | None:
355
- """Get a single example record."""
356
- try:
357
- async with aiosqlite.connect(self.db_path) as db, db.execute(
358
- """SELECT example_id, agent_name, content, created_at FROM examples WHERE example_id = ?""",
359
- (id,)
360
- ) as cursor:
361
- row = await cursor.fetchone()
362
- if row:
363
- from flock.components.utility.example_utility_component import ExampleRecord
364
- return ExampleRecord(
365
- example_id=row[0],
366
- agent_name=row[1],
367
- content=row[2],
368
- created_at=datetime.fromisoformat(row[3])
369
- )
370
- return None
371
- except sqlite3.Error as e:
372
- logger.error(f"SQLite error retrieving example {id}: {e}", exc_info=True)
373
- return None
374
-
375
- async def get_all_examples_for_agent(self, agent_name: str) -> list[Any]:
376
- """Retrieve all example records from SQLite."""
377
- try:
378
- async with aiosqlite.connect(self.db_path) as db, db.execute(
379
- """SELECT example_id, agent_name, content, created_at FROM examples WHERE agent_name = ? ORDER BY created_at DESC""",
380
- (agent_name,)
381
- ) as cursor:
382
- rows = await cursor.fetchall()
383
-
384
- examples = []
385
- for row in rows:
386
- from flock.components.utility.example_utility_component import ExampleRecord
387
- examples.append(ExampleRecord(
388
- example_id=row[0],
389
- agent_name=row[1],
390
- content=row[2],
391
- created_at=datetime.fromisoformat(row[3])
392
- ))
393
-
394
- logger.debug(f"Retrieved {len(examples)} example records")
395
- return examples
396
- except sqlite3.Error as e:
397
- logger.error(f"SQLite error retrieving all example records: {e}", exc_info=True)
398
- return [] # Return empty list on error
399
-
400
-
401
- # ---------------------------------------------------------------------------
402
- # Azure Table + Blob implementation
403
- # ---------------------------------------------------------------------------
404
-
405
- try:
406
- from azure.storage.blob.aio import BlobServiceClient
407
- AZURE_BLOB_AVAILABLE = True
408
- except ImportError: # blob SDK not installed
409
- AZURE_BLOB_AVAILABLE = False
410
- BlobServiceClient = None
411
-
412
- class AzureTableSharedLinkStore(SharedLinkStoreInterface):
413
- """Store configs in Azure Table; store large flock YAML in Blob Storage."""
414
-
415
- _TABLE_NAME = "flocksharedlinks"
416
- _FEEDBACK_TBL_NAME = "flockfeedback"
417
- _CONTAINER_NAME = "flocksharedlinkdefs" # blobs live here
418
- _PARTITION_KEY = "shared_links"
419
-
420
- def __init__(self, connection_string: str):
421
- if not AZURE_AVAILABLE:
422
- raise ImportError("pip install azure-data-tables")
423
- if not AZURE_BLOB_AVAILABLE:
424
- raise ImportError("pip install azure-storage-blob")
425
-
426
- self.connection_string = connection_string
427
- self.table_svc = TableServiceClient.from_connection_string(connection_string)
428
- self.blob_svc = BlobServiceClient.from_connection_string(connection_string)
429
-
430
- # ------------------------------------------------------------------ init
431
- async def initialize(self) -> None:
432
- # 1. Azure Tables ----------------------------------------------------
433
- try:
434
- await self.table_svc.create_table(self._TABLE_NAME)
435
- logger.info("Created Azure Table '%s'", self._TABLE_NAME)
436
- except ResourceExistsError:
437
- logger.debug("Azure Table '%s' already exists", self._TABLE_NAME)
438
-
439
- try:
440
- await self.table_svc.create_table(self._FEEDBACK_TBL_NAME)
441
- logger.info("Created Azure Table '%s'", self._FEEDBACK_TBL_NAME)
442
- except ResourceExistsError:
443
- logger.debug("Azure Table '%s' already exists", self._FEEDBACK_TBL_NAME)
444
-
445
- # 2. Blob container --------------------------------------------------
446
- try:
447
- await self.blob_svc.create_container(self._CONTAINER_NAME)
448
- logger.info("Created Blob container '%s'", self._CONTAINER_NAME)
449
- except ResourceExistsError:
450
- logger.debug("Blob container '%s' already exists", self._CONTAINER_NAME)
451
-
452
- # ------------------------------------------------------------- save_config
453
- async def save_config(self, config: SharedLinkConfig) -> SharedLinkConfig:
454
- """Upload YAML to Blob, then upsert table row containing the blob name."""
455
- blob_name = f"{config.share_id}.yaml"
456
- blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
457
-
458
- # 1. Upload flock_definition (overwrite in case of retry)
459
- await blob_client.upload_blob(config.flock_definition,
460
- overwrite=True,
461
- content_type="text/yaml")
462
- logger.debug("Uploaded blob '%s' (%d bytes)",
463
- blob_name, len(config.flock_definition.encode()))
464
-
465
- # 2. Persist lightweight record in the table
466
- tbl_client = self.table_svc.get_table_client(self._TABLE_NAME)
467
- entity = {
468
- "PartitionKey": self._PARTITION_KEY,
469
- "RowKey": config.share_id,
470
- "agent_name": config.agent_name,
471
- "created_at": config.created_at.isoformat(),
472
- "share_type": config.share_type,
473
- "chat_message_key": config.chat_message_key,
474
- "chat_history_key": config.chat_history_key,
475
- "chat_response_key": config.chat_response_key,
476
- # NEW – just a few bytes, well under 64 KiB
477
- "flock_blob_name": blob_name,
478
- }
479
- await tbl_client.upsert_entity(entity)
480
- logger.info("Saved shared link %s → blob '%s'", config.share_id, blob_name)
481
- return config
482
-
483
- # -------------------------------------------------------------- get_config
484
- async def get_config(self, share_id: str) -> SharedLinkConfig | None:
485
- tbl_client = self.table_svc.get_table_client(self._TABLE_NAME)
486
- try:
487
- entity = await tbl_client.get_entity(self._PARTITION_KEY, share_id)
488
- except ResourceNotFoundError:
489
- logger.debug("No config entity for id '%s'", share_id)
490
- return None
491
-
492
- blob_name = entity["flock_blob_name"]
493
- blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
494
- try:
495
- blob_bytes = await (await blob_client.download_blob()).readall()
496
- flock_yaml = blob_bytes.decode()
497
- except Exception as e:
498
- logger.error("Cannot download blob '%s' for share_id=%s: %s",
499
- blob_name, share_id, e, exc_info=True)
500
- raise
501
-
502
- return SharedLinkConfig(
503
- share_id = share_id,
504
- agent_name = entity["agent_name"],
505
- created_at = entity["created_at"],
506
- flock_definition = flock_yaml,
507
- share_type = entity.get("share_type", "agent_run"),
508
- chat_message_key = entity.get("chat_message_key"),
509
- chat_history_key = entity.get("chat_history_key"),
510
- chat_response_key = entity.get("chat_response_key"),
511
- )
512
-
513
- # ----------------------------------------------------------- delete_config
514
- async def delete_config(self, share_id: str) -> bool:
515
- tbl_client = self.table_svc.get_table_client(self._TABLE_NAME)
516
- try:
517
- entity = await tbl_client.get_entity(self._PARTITION_KEY, share_id)
518
- except ResourceNotFoundError:
519
- logger.info("Delete: entity %s not found", share_id)
520
- return False
521
-
522
- # 1. Remove blob (ignore missing blob)
523
- blob_name = entity["flock_blob_name"]
524
- blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
525
- try:
526
- await blob_client.delete_blob(delete_snapshots="include")
527
- logger.debug("Deleted blob '%s'", blob_name)
528
- except ResourceNotFoundError:
529
- logger.warning("Blob '%s' already gone", blob_name)
530
-
531
- # 2. Remove table row
532
- await tbl_client.delete_entity(self._PARTITION_KEY, share_id)
533
- logger.info("Deleted shared link %s and its blob", share_id)
534
- return True
535
-
536
- # -------------------------------------------------------- save_feedback --
537
- async def save_feedback(self, record: FeedbackRecord) -> FeedbackRecord:
538
- """Persist a feedback record. If a flock_definition is present, upload it as a blob and store only a reference in the table row to avoid oversized entities (64 KiB limit)."""
539
- tbl_client = self.table_svc.get_table_client(self._FEEDBACK_TBL_NAME)
540
-
541
- # Core entity fields (avoid dumping the full Pydantic model – too many columns / large value)
542
- entity: dict[str, Any] = {
543
- "PartitionKey": "feedback",
544
- "RowKey": record.feedback_id,
545
- "share_id": record.share_id,
546
- "context_type": record.context_type,
547
- "reason": record.reason,
548
- "expected_response": record.expected_response,
549
- "actual_response": record.actual_response,
550
- "created_at": record.created_at.isoformat(),
551
- }
552
-
553
- # additional sanity check
554
-
555
- if record.flock_name is not None:
556
- entity["flock_name"] = record.flock_name
557
- if record.agent_name is not None:
558
- entity["agent_name"] = record.agent_name
559
-
560
- # ------------------------------------------------------------------ YAML → Blob
561
- if record.flock_definition:
562
- blob_name = f"{record.feedback_id}.yaml"
563
- blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
564
- # Overwrite=true so repeated feedback_id uploads (shouldn't happen) won't error
565
- await blob_client.upload_blob(record.flock_definition,
566
- overwrite=True,
567
- content_type="text/yaml")
568
- entity["flock_blob_name"] = blob_name # lightweight reference only
569
-
570
- # ------------------------------------------------------------------ Table upsert
571
- await tbl_client.upsert_entity(entity)
572
- logger.info("Saved feedback %s%s",
573
- record.feedback_id,
574
- f" → blob '{entity['flock_blob_name']}'" if "flock_blob_name" in entity else "")
575
- return record
576
-
577
- # -------------------------------------------------------- get_feedback --
578
- async def get_feedback(self, id: str) -> FeedbackRecord | None:
579
- """Retrieve a single feedback record from Azure Table Storage."""
580
- tbl_client = self.table_svc.get_table_client(self._FEEDBACK_TBL_NAME)
581
- try:
582
- entity = await tbl_client.get_entity("feedback", id)
583
- except ResourceNotFoundError:
584
- logger.debug("No feedback record found for ID: %s", id)
585
- return None
586
-
587
- # Get flock_definition from blob if it exists
588
- flock_definition = None
589
- if "flock_blob_name" in entity:
590
- blob_name = entity["flock_blob_name"]
591
- blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
592
- try:
593
- blob_bytes = await (await blob_client.download_blob()).readall()
594
- flock_definition = blob_bytes.decode()
595
- except Exception as e:
596
- logger.error("Cannot download blob '%s' for feedback_id=%s: %s",
597
- blob_name, id, e, exc_info=True)
598
- # Continue without flock_definition rather than failing
599
-
600
- return FeedbackRecord(
601
- feedback_id=id,
602
- share_id=entity.get("share_id"),
603
- context_type=entity["context_type"],
604
- reason=entity["reason"],
605
- expected_response=entity.get("expected_response"),
606
- actual_response=entity.get("actual_response"),
607
- flock_name=entity.get("flock_name"),
608
- agent_name=entity.get("agent_name"),
609
- flock_definition=flock_definition,
610
- created_at=entity["created_at"],
611
- )
612
-
613
-
614
- # ------------------------------------------------ get_all_feedback_records --
615
- async def get_all_feedback_records_for_agent(self, agent_name: str) -> list[FeedbackRecord]:
616
- """Retrieve all feedback records from Azure Table Storage for a specific agent."""
617
- tbl_client = self.table_svc.get_table_client(self._FEEDBACK_TBL_NAME)
618
-
619
- # Use Azure Table Storage filtering to only get records for the specified agent
620
- escaped_agent_name = agent_name.replace("'", "''")
621
- filter_query = f"agent_name eq '{escaped_agent_name}'"
622
-
623
- logger.debug(f"Querying feedback records with filter: {filter_query}")
624
-
625
- records = []
626
- try:
627
- async for entity in tbl_client.query_entities(filter_query):
628
- # Get flock_definition from blob if it exists
629
- flock_definition = None
630
- if "flock_blob_name" in entity:
631
- blob_name = entity["flock_blob_name"]
632
- blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
633
- try:
634
- blob_bytes = await (await blob_client.download_blob()).readall()
635
- flock_definition = blob_bytes.decode()
636
- except Exception as e:
637
- logger.error("Cannot download blob '%s' for feedback_id=%s: %s",
638
- blob_name, entity["RowKey"], e, exc_info=True)
639
- # Continue without flock_definition rather than failing
640
-
641
- records.append(FeedbackRecord(
642
- feedback_id=entity["RowKey"],
643
- share_id=entity.get("share_id"),
644
- context_type=entity["context_type"],
645
- reason=entity["reason"],
646
- expected_response=entity.get("expected_response"),
647
- actual_response=entity.get("actual_response"),
648
- flock_name=entity.get("flock_name"),
649
- agent_name=entity.get("agent_name"),
650
- flock_definition=flock_definition,
651
- created_at=entity["created_at"],
652
- ))
653
-
654
- logger.debug("Retrieved %d feedback records for agent %s", len(records), agent_name)
655
- return records
656
-
657
- except Exception as e:
658
- # Log the error.
659
- logger.error(
660
- f"Unable to query entries for agent {agent_name}. Exception: {e}"
661
- )
662
- return records
663
-
664
- # -------------------------------------------------------- save_example --
665
- async def save_example(self, example: Any) -> Any:
666
- """Persist an example record to Azure Table Storage."""
667
- tbl_client = self.table_svc.get_table_client("flockexamples")
668
-
669
- example_dict = example.to_dict()
670
-
671
- entity = {
672
- "PartitionKey": "examples",
673
- "RowKey": example_dict["example_id"],
674
- "agent_name": example_dict["agent_name"],
675
- "content": example_dict["content"],
676
- "created_at": example_dict["created_at"],
677
- "context_type": example_dict["context_type"]
678
- }
679
-
680
- # ------------------------------------------------------------------ Table upsert
681
- await tbl_client.upsert_entity(entity)
682
- logger.info(f"Saved example {example_dict['example_id']} for agent {example_dict['agent_name']}")
683
- return example
684
-
685
- # -------------------------------------------------------- get_example --
686
- async def get_example(self, id: str) -> Any | None:
687
- """Retrieve a single example record from Azure Table Storage."""
688
- tbl_client = self.table_svc.get_table_client("flockexamples")
689
- try:
690
- entity = await tbl_client.get_entity("examples", id)
691
- except ResourceNotFoundError:
692
- logger.debug("No example record found for ID: %s", id)
693
- return None
694
-
695
- from flock.components.utility.example_utility_component import ExampleRecord
696
- return ExampleRecord(
697
- example_id=id,
698
- agent_name=entity["agent_name"],
699
- content=entity["content"],
700
- created_at=entity["created_at"]
701
- )
702
-
703
- # ------------------------------------------- get_all_examples_for_agent --
704
- async def get_all_examples_for_agent(self, agent_name: str) -> list[Any]:
705
- """Retrieve all example records from Azure Table Storage for a specific agent."""
706
- tbl_client = self.table_svc.get_table_client("flockexamples")
707
-
708
- # Use Azure Table Storage filtering to only get records for the specified agent
709
- escaped_agent_name = agent_name.replace("'", "''")
710
- filter_query = f"agent_name eq '{escaped_agent_name}'"
711
-
712
- logger.debug(f"Querying example records with filter: {filter_query}")
713
-
714
- examples = []
715
- try:
716
- async for entity in tbl_client.query_entities(filter_query):
717
- from flock.components.utility.example_utility_component import ExampleRecord
718
- examples.append(ExampleRecord(
719
- example_id=entity["RowKey"],
720
- agent_name=entity["agent_name"],
721
- content=entity["content"],
722
- created_at=entity["created_at"]
723
- ))
724
-
725
- logger.debug("Retrieved %d example records for agent %s", len(examples), agent_name)
726
- return examples
727
-
728
- except Exception as e:
729
- # Log the error.
730
- logger.error(
731
- f"Unable to query example entries for agent {agent_name}. Exception: {e}"
732
- )
733
- return examples
734
-
735
-
736
- # ----------------------- Factory Function -----------------------
737
-
738
- def create_shared_link_store(store_type: str | None = None, connection_string: str | None = None) -> SharedLinkStoreInterface:
739
- """Factory function to create the appropriate shared link store based on configuration.
740
-
741
- Args:
742
- store_type: Type of store to create ("local" for SQLite, "azure-storage" for Azure Table Storage)
743
- connection_string: Connection string for the store (file path for SQLite, connection string for Azure)
744
-
745
- Returns:
746
- Configured SharedLinkStoreInterface implementation
747
- """
748
- import os
749
-
750
- # Get values from environment if not provided
751
- if store_type is None:
752
- store_type = os.getenv("FLOCK_WEBAPP_STORE", "local").lower()
753
-
754
- if connection_string is None:
755
- connection_string = os.getenv("FLOCK_WEBAPP_STORE_CONNECTION", ".flock/shared_links.db")
756
-
757
- if store_type == "local":
758
- return SQLiteSharedLinkStore(connection_string)
759
- elif store_type == "azure-storage":
760
- return AzureTableSharedLinkStore(connection_string)
761
- else:
762
- raise ValueError(f"Unsupported store type: {store_type}. Supported types: 'local', 'azure-storage'")