flock-core 0.5.0b28__py3-none-any.whl → 0.5.56b0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/__init__.py +12 -217
- flock/agent.py +678 -0
- flock/api/themes.py +71 -0
- flock/artifacts.py +79 -0
- flock/cli.py +75 -0
- flock/components.py +173 -0
- flock/dashboard/__init__.py +28 -0
- flock/dashboard/collector.py +283 -0
- flock/dashboard/events.py +182 -0
- flock/dashboard/launcher.py +230 -0
- flock/dashboard/service.py +537 -0
- flock/dashboard/websocket.py +235 -0
- flock/engines/__init__.py +6 -0
- flock/engines/dspy_engine.py +856 -0
- flock/examples.py +128 -0
- flock/{core/util → helper}/cli_helper.py +4 -3
- flock/{core/logging → logging}/__init__.py +2 -3
- flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
- flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
- flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
- flock/{core/logging → logging}/logging.py +77 -61
- flock/{core/logging → logging}/telemetry.py +20 -26
- flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
- flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
- flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
- flock/{core/logging → logging}/trace_and_logged.py +20 -24
- flock/mcp/__init__.py +91 -0
- flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
- flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
- flock/mcp/manager.py +255 -0
- flock/mcp/servers/sse/__init__.py +1 -1
- flock/mcp/servers/sse/flock_sse_server.py +11 -53
- flock/mcp/servers/stdio/__init__.py +1 -1
- flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
- flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
- flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
- flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
- flock/mcp/types/__init__.py +42 -0
- flock/{core/mcp → mcp}/types/callbacks.py +9 -15
- flock/{core/mcp → mcp}/types/factories.py +7 -6
- flock/{core/mcp → mcp}/types/handlers.py +13 -18
- flock/{core/mcp → mcp}/types/types.py +70 -74
- flock/{core/mcp → mcp}/util/helpers.py +1 -1
- flock/orchestrator.py +645 -0
- flock/registry.py +148 -0
- flock/runtime.py +262 -0
- flock/service.py +140 -0
- flock/store.py +69 -0
- flock/subscription.py +111 -0
- flock/themes/andromeda.toml +1 -1
- flock/themes/apple-system-colors.toml +1 -1
- flock/themes/arcoiris.toml +1 -1
- flock/themes/atomonelight.toml +1 -1
- flock/themes/ayu copy.toml +1 -1
- flock/themes/ayu-light.toml +1 -1
- flock/themes/belafonte-day.toml +1 -1
- flock/themes/belafonte-night.toml +1 -1
- flock/themes/blulocodark.toml +1 -1
- flock/themes/breeze.toml +1 -1
- flock/themes/broadcast.toml +1 -1
- flock/themes/brogrammer.toml +1 -1
- flock/themes/builtin-dark.toml +1 -1
- flock/themes/builtin-pastel-dark.toml +1 -1
- flock/themes/catppuccin-latte.toml +1 -1
- flock/themes/catppuccin-macchiato.toml +1 -1
- flock/themes/catppuccin-mocha.toml +1 -1
- flock/themes/cga.toml +1 -1
- flock/themes/chalk.toml +1 -1
- flock/themes/ciapre.toml +1 -1
- flock/themes/coffee-theme.toml +1 -1
- flock/themes/cyberpunkscarletprotocol.toml +1 -1
- flock/themes/dark+.toml +1 -1
- flock/themes/darkermatrix.toml +1 -1
- flock/themes/darkside.toml +1 -1
- flock/themes/desert.toml +1 -1
- flock/themes/django.toml +1 -1
- flock/themes/djangosmooth.toml +1 -1
- flock/themes/doomone.toml +1 -1
- flock/themes/dotgov.toml +1 -1
- flock/themes/dracula+.toml +1 -1
- flock/themes/duckbones.toml +1 -1
- flock/themes/encom.toml +1 -1
- flock/themes/espresso.toml +1 -1
- flock/themes/everblush.toml +1 -1
- flock/themes/fairyfloss.toml +1 -1
- flock/themes/fideloper.toml +1 -1
- flock/themes/fishtank.toml +1 -1
- flock/themes/flexoki-light.toml +1 -1
- flock/themes/floraverse.toml +1 -1
- flock/themes/framer.toml +1 -1
- flock/themes/galizur.toml +1 -1
- flock/themes/github.toml +1 -1
- flock/themes/grass.toml +1 -1
- flock/themes/grey-green.toml +1 -1
- flock/themes/gruvboxlight.toml +1 -1
- flock/themes/guezwhoz.toml +1 -1
- flock/themes/harper.toml +1 -1
- flock/themes/hax0r-blue.toml +1 -1
- flock/themes/hopscotch.256.toml +1 -1
- flock/themes/ic-green-ppl.toml +1 -1
- flock/themes/iceberg-dark.toml +1 -1
- flock/themes/japanesque.toml +1 -1
- flock/themes/jubi.toml +1 -1
- flock/themes/kibble.toml +1 -1
- flock/themes/kolorit.toml +1 -1
- flock/themes/kurokula.toml +1 -1
- flock/themes/materialdesigncolors.toml +1 -1
- flock/themes/matrix.toml +1 -1
- flock/themes/mellifluous.toml +1 -1
- flock/themes/midnight-in-mojave.toml +1 -1
- flock/themes/monokai-remastered.toml +1 -1
- flock/themes/monokai-soda.toml +1 -1
- flock/themes/neon.toml +1 -1
- flock/themes/neopolitan.toml +1 -1
- flock/themes/nord-light.toml +1 -1
- flock/themes/ocean.toml +1 -1
- flock/themes/onehalfdark.toml +1 -1
- flock/themes/onehalflight.toml +1 -1
- flock/themes/palenighthc.toml +1 -1
- flock/themes/paulmillr.toml +1 -1
- flock/themes/pencildark.toml +1 -1
- flock/themes/pnevma.toml +1 -1
- flock/themes/purple-rain.toml +1 -1
- flock/themes/purplepeter.toml +1 -1
- flock/themes/raycast-dark.toml +1 -1
- flock/themes/red-sands.toml +1 -1
- flock/themes/relaxed.toml +1 -1
- flock/themes/retro.toml +1 -1
- flock/themes/rose-pine.toml +1 -1
- flock/themes/royal.toml +1 -1
- flock/themes/ryuuko.toml +1 -1
- flock/themes/sakura.toml +1 -1
- flock/themes/scarlet-protocol.toml +1 -1
- flock/themes/seoulbones-dark.toml +1 -1
- flock/themes/shades-of-purple.toml +1 -1
- flock/themes/smyck.toml +1 -1
- flock/themes/softserver.toml +1 -1
- flock/themes/solarized-darcula.toml +1 -1
- flock/themes/square.toml +1 -1
- flock/themes/sugarplum.toml +1 -1
- flock/themes/thayer-bright.toml +1 -1
- flock/themes/tokyonight.toml +1 -1
- flock/themes/tomorrow.toml +1 -1
- flock/themes/ubuntu.toml +1 -1
- flock/themes/ultradark.toml +1 -1
- flock/themes/ultraviolent.toml +1 -1
- flock/themes/unikitty.toml +1 -1
- flock/themes/urple.toml +1 -1
- flock/themes/vesper.toml +1 -1
- flock/themes/vimbones.toml +1 -1
- flock/themes/wildcherry.toml +1 -1
- flock/themes/wilmersdorf.toml +1 -1
- flock/themes/wryan.toml +1 -1
- flock/themes/xcodedarkhc.toml +1 -1
- flock/themes/xcodelight.toml +1 -1
- flock/themes/zenbones-light.toml +1 -1
- flock/themes/zenwritten-dark.toml +1 -1
- flock/utilities.py +301 -0
- flock/{components/utility → utility}/output_utility_component.py +68 -53
- flock/visibility.py +107 -0
- flock_core-0.5.56b0.dist-info/METADATA +747 -0
- flock_core-0.5.56b0.dist-info/RECORD +398 -0
- flock_core-0.5.56b0.dist-info/entry_points.txt +2 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/licenses/LICENSE +1 -1
- flock/adapter/__init__.py +0 -14
- flock/adapter/azure_adapter.py +0 -68
- flock/adapter/chroma_adapter.py +0 -73
- flock/adapter/faiss_adapter.py +0 -97
- flock/adapter/pinecone_adapter.py +0 -51
- flock/adapter/vector_base.py +0 -47
- flock/cli/assets/release_notes.md +0 -140
- flock/cli/config.py +0 -8
- flock/cli/constants.py +0 -36
- flock/cli/create_agent.py +0 -1
- flock/cli/create_flock.py +0 -280
- flock/cli/execute_flock.py +0 -620
- flock/cli/load_agent.py +0 -1
- flock/cli/load_examples.py +0 -1
- flock/cli/load_flock.py +0 -192
- flock/cli/load_release_notes.py +0 -20
- flock/cli/loaded_flock_cli.py +0 -254
- flock/cli/manage_agents.py +0 -459
- flock/cli/registry_management.py +0 -889
- flock/cli/runner.py +0 -41
- flock/cli/settings.py +0 -857
- flock/cli/utils.py +0 -135
- flock/cli/view_results.py +0 -29
- flock/cli/yaml_editor.py +0 -396
- flock/components/__init__.py +0 -30
- flock/components/evaluation/__init__.py +0 -9
- flock/components/evaluation/declarative_evaluation_component.py +0 -606
- flock/components/routing/__init__.py +0 -15
- flock/components/routing/conditional_routing_component.py +0 -494
- flock/components/routing/default_routing_component.py +0 -103
- flock/components/routing/llm_routing_component.py +0 -206
- flock/components/utility/__init__.py +0 -22
- flock/components/utility/example_utility_component.py +0 -250
- flock/components/utility/feedback_utility_component.py +0 -206
- flock/components/utility/memory_utility_component.py +0 -550
- flock/components/utility/metrics_utility_component.py +0 -700
- flock/config.py +0 -61
- flock/core/__init__.py +0 -110
- flock/core/agent/__init__.py +0 -16
- flock/core/agent/default_agent.py +0 -216
- flock/core/agent/flock_agent_components.py +0 -104
- flock/core/agent/flock_agent_execution.py +0 -101
- flock/core/agent/flock_agent_integration.py +0 -260
- flock/core/agent/flock_agent_lifecycle.py +0 -186
- flock/core/agent/flock_agent_serialization.py +0 -381
- flock/core/api/__init__.py +0 -10
- flock/core/api/custom_endpoint.py +0 -45
- flock/core/api/endpoints.py +0 -254
- flock/core/api/main.py +0 -162
- flock/core/api/models.py +0 -97
- flock/core/api/run_store.py +0 -224
- flock/core/api/runner.py +0 -44
- flock/core/api/service.py +0 -214
- flock/core/component/__init__.py +0 -15
- flock/core/component/agent_component_base.py +0 -309
- flock/core/component/evaluation_component.py +0 -62
- flock/core/component/routing_component.py +0 -74
- flock/core/component/utility_component.py +0 -69
- flock/core/config/flock_agent_config.py +0 -58
- flock/core/config/scheduled_agent_config.py +0 -40
- flock/core/context/context.py +0 -213
- flock/core/context/context_manager.py +0 -37
- flock/core/context/context_vars.py +0 -10
- flock/core/evaluation/utils.py +0 -396
- flock/core/execution/batch_executor.py +0 -369
- flock/core/execution/evaluation_executor.py +0 -438
- flock/core/execution/local_executor.py +0 -31
- flock/core/execution/opik_executor.py +0 -103
- flock/core/execution/temporal_executor.py +0 -164
- flock/core/flock.py +0 -634
- flock/core/flock_agent.py +0 -336
- flock/core/flock_factory.py +0 -613
- flock/core/flock_scheduler.py +0 -166
- flock/core/flock_server_manager.py +0 -136
- flock/core/interpreter/python_interpreter.py +0 -689
- flock/core/mcp/__init__.py +0 -1
- flock/core/mcp/flock_mcp_server.py +0 -680
- flock/core/mcp/mcp_client_manager.py +0 -201
- flock/core/mcp/types/__init__.py +0 -1
- flock/core/mixin/dspy_integration.py +0 -403
- flock/core/mixin/prompt_parser.py +0 -125
- flock/core/orchestration/__init__.py +0 -15
- flock/core/orchestration/flock_batch_processor.py +0 -94
- flock/core/orchestration/flock_evaluator.py +0 -113
- flock/core/orchestration/flock_execution.py +0 -295
- flock/core/orchestration/flock_initialization.py +0 -149
- flock/core/orchestration/flock_server_manager.py +0 -67
- flock/core/orchestration/flock_web_server.py +0 -117
- flock/core/registry/__init__.py +0 -45
- flock/core/registry/agent_registry.py +0 -69
- flock/core/registry/callable_registry.py +0 -139
- flock/core/registry/component_discovery.py +0 -142
- flock/core/registry/component_registry.py +0 -64
- flock/core/registry/config_mapping.py +0 -64
- flock/core/registry/decorators.py +0 -137
- flock/core/registry/registry_hub.py +0 -205
- flock/core/registry/server_registry.py +0 -57
- flock/core/registry/type_registry.py +0 -86
- flock/core/serialization/__init__.py +0 -13
- flock/core/serialization/callable_registry.py +0 -52
- flock/core/serialization/flock_serializer.py +0 -832
- flock/core/serialization/json_encoder.py +0 -41
- flock/core/serialization/secure_serializer.py +0 -175
- flock/core/serialization/serializable.py +0 -342
- flock/core/serialization/serialization_utils.py +0 -412
- flock/core/util/file_path_utils.py +0 -223
- flock/core/util/hydrator.py +0 -309
- flock/core/util/input_resolver.py +0 -164
- flock/core/util/loader.py +0 -59
- flock/core/util/splitter.py +0 -219
- flock/di.py +0 -27
- flock/platform/docker_tools.py +0 -49
- flock/platform/jaeger_install.py +0 -86
- flock/webapp/__init__.py +0 -1
- flock/webapp/app/__init__.py +0 -0
- flock/webapp/app/api/__init__.py +0 -0
- flock/webapp/app/api/agent_management.py +0 -241
- flock/webapp/app/api/execution.py +0 -709
- flock/webapp/app/api/flock_management.py +0 -129
- flock/webapp/app/api/registry_viewer.py +0 -30
- flock/webapp/app/chat.py +0 -665
- flock/webapp/app/config.py +0 -104
- flock/webapp/app/dependencies.py +0 -117
- flock/webapp/app/main.py +0 -1070
- flock/webapp/app/middleware.py +0 -113
- flock/webapp/app/models_ui.py +0 -7
- flock/webapp/app/services/__init__.py +0 -0
- flock/webapp/app/services/feedback_file_service.py +0 -363
- flock/webapp/app/services/flock_service.py +0 -337
- flock/webapp/app/services/sharing_models.py +0 -81
- flock/webapp/app/services/sharing_store.py +0 -762
- flock/webapp/app/templates/theme_mapper.html +0 -326
- flock/webapp/app/theme_mapper.py +0 -812
- flock/webapp/app/utils.py +0 -85
- flock/webapp/run.py +0 -215
- flock/webapp/static/css/chat.css +0 -301
- flock/webapp/static/css/components.css +0 -167
- flock/webapp/static/css/header.css +0 -39
- flock/webapp/static/css/layout.css +0 -46
- flock/webapp/static/css/sidebar.css +0 -127
- flock/webapp/static/css/two-pane.css +0 -48
- flock/webapp/templates/base.html +0 -200
- flock/webapp/templates/chat.html +0 -152
- flock/webapp/templates/chat_settings.html +0 -19
- flock/webapp/templates/flock_editor.html +0 -16
- flock/webapp/templates/index.html +0 -12
- flock/webapp/templates/partials/_agent_detail_form.html +0 -93
- flock/webapp/templates/partials/_agent_list.html +0 -18
- flock/webapp/templates/partials/_agent_manager_view.html +0 -51
- flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
- flock/webapp/templates/partials/_chat_container.html +0 -15
- flock/webapp/templates/partials/_chat_messages.html +0 -57
- flock/webapp/templates/partials/_chat_settings_form.html +0 -85
- flock/webapp/templates/partials/_create_flock_form.html +0 -50
- flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
- flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
- flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
- flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
- flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
- flock/webapp/templates/partials/_env_vars_table.html +0 -23
- flock/webapp/templates/partials/_execution_form.html +0 -118
- flock/webapp/templates/partials/_execution_view_container.html +0 -28
- flock/webapp/templates/partials/_flock_file_list.html +0 -23
- flock/webapp/templates/partials/_flock_properties_form.html +0 -52
- flock/webapp/templates/partials/_flock_upload_form.html +0 -16
- flock/webapp/templates/partials/_header_flock_status.html +0 -5
- flock/webapp/templates/partials/_load_manager_view.html +0 -49
- flock/webapp/templates/partials/_registry_table.html +0 -25
- flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
- flock/webapp/templates/partials/_results_display.html +0 -78
- flock/webapp/templates/partials/_settings_env_content.html +0 -9
- flock/webapp/templates/partials/_settings_theme_content.html +0 -14
- flock/webapp/templates/partials/_settings_view.html +0 -36
- flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
- flock/webapp/templates/partials/_share_link_snippet.html +0 -35
- flock/webapp/templates/partials/_sidebar.html +0 -74
- flock/webapp/templates/partials/_streaming_results_container.html +0 -195
- flock/webapp/templates/partials/_structured_data_view.html +0 -40
- flock/webapp/templates/partials/_theme_preview.html +0 -36
- flock/webapp/templates/registry_viewer.html +0 -84
- flock/webapp/templates/shared_run_page.html +0 -140
- flock/workflow/__init__.py +0 -0
- flock/workflow/activities.py +0 -196
- flock/workflow/agent_activities.py +0 -24
- flock/workflow/agent_execution_activity.py +0 -202
- flock/workflow/flock_workflow.py +0 -214
- flock/workflow/temporal_config.py +0 -96
- flock/workflow/temporal_setup.py +0 -68
- flock_core-0.5.0b28.dist-info/METADATA +0 -274
- flock_core-0.5.0b28.dist-info/RECORD +0 -561
- flock_core-0.5.0b28.dist-info/entry_points.txt +0 -2
- /flock/{core/logging → logging}/formatters/themes.py +0 -0
- /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
- /flock/{core/mcp → mcp}/util/__init__.py +0 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/WHEEL +0 -0
|
@@ -1,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'")
|