flock-core 0.5.0b27__py3-none-any.whl → 0.5.0b50__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- 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.0b50.dist-info/METADATA +747 -0
- flock_core-0.5.0b50.dist-info/RECORD +398 -0
- flock_core-0.5.0b50.dist-info/entry_points.txt +2 -0
- {flock_core-0.5.0b27.dist-info → flock_core-0.5.0b50.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 -15
- 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 -180
- 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 -551
- 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 -598
- 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.0b27.dist-info/METADATA +0 -274
- flock_core-0.5.0b27.dist-info/RECORD +0 -559
- flock_core-0.5.0b27.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.0b27.dist-info → flock_core-0.5.0b50.dist-info}/WHEEL +0 -0
|
@@ -1,598 +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
|
-
|
|
9
|
-
import aiosqlite
|
|
10
|
-
|
|
11
|
-
from flock.webapp.app.services.sharing_models import (
|
|
12
|
-
FeedbackRecord,
|
|
13
|
-
SharedLinkConfig,
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
# Azure Table Storage imports - will be conditionally imported
|
|
17
|
-
try:
|
|
18
|
-
from azure.core.exceptions import ResourceExistsError, ResourceNotFoundError
|
|
19
|
-
from azure.data.tables.aio import TableServiceClient
|
|
20
|
-
AZURE_AVAILABLE = True
|
|
21
|
-
except ImportError:
|
|
22
|
-
AZURE_AVAILABLE = False
|
|
23
|
-
TableServiceClient = None
|
|
24
|
-
ResourceNotFoundError = None
|
|
25
|
-
ResourceExistsError = None
|
|
26
|
-
|
|
27
|
-
# Get a logger instance
|
|
28
|
-
logger = logging.getLogger(__name__)
|
|
29
|
-
|
|
30
|
-
class SharedLinkStoreInterface(ABC):
|
|
31
|
-
"""Interface for storing and retrieving shared link configurations."""
|
|
32
|
-
|
|
33
|
-
@abstractmethod
|
|
34
|
-
async def initialize(self) -> None:
|
|
35
|
-
"""Initialize the store (e.g., create tables)."""
|
|
36
|
-
pass
|
|
37
|
-
|
|
38
|
-
@abstractmethod
|
|
39
|
-
async def save_config(self, config: SharedLinkConfig) -> SharedLinkConfig:
|
|
40
|
-
"""Saves a shared link configuration."""
|
|
41
|
-
pass
|
|
42
|
-
|
|
43
|
-
@abstractmethod
|
|
44
|
-
async def get_config(self, share_id: str) -> SharedLinkConfig | None:
|
|
45
|
-
"""Retrieves a shared link configuration by its ID."""
|
|
46
|
-
pass
|
|
47
|
-
|
|
48
|
-
@abstractmethod
|
|
49
|
-
async def delete_config(self, share_id: str) -> bool:
|
|
50
|
-
"""Deletes a shared link configuration by its ID. Returns True if deleted, False otherwise."""
|
|
51
|
-
pass
|
|
52
|
-
|
|
53
|
-
# Feedback
|
|
54
|
-
@abstractmethod
|
|
55
|
-
async def save_feedback(self, record: FeedbackRecord):
|
|
56
|
-
"""Persist a feedback record."""
|
|
57
|
-
pass
|
|
58
|
-
|
|
59
|
-
@abstractmethod
|
|
60
|
-
async def get_feedback(self, id: str) -> FeedbackRecord | None:
|
|
61
|
-
"""Get a single feedback record."""
|
|
62
|
-
pass
|
|
63
|
-
|
|
64
|
-
@abstractmethod
|
|
65
|
-
async def get_all_feedback_records_for_agent(self, agent_name: str) -> list[FeedbackRecord]:
|
|
66
|
-
"""Get all feedback records for a given agent."""
|
|
67
|
-
pass
|
|
68
|
-
|
|
69
|
-
class SQLiteSharedLinkStore(SharedLinkStoreInterface):
|
|
70
|
-
"""SQLite implementation for storing and retrieving shared link configurations."""
|
|
71
|
-
|
|
72
|
-
def __init__(self, db_path: str):
|
|
73
|
-
"""Initialize SQLite store with database path."""
|
|
74
|
-
self.db_path = Path(db_path)
|
|
75
|
-
self.db_path.parent.mkdir(parents=True, exist_ok=True) # Ensure directory exists
|
|
76
|
-
logger.info(f"SQLiteSharedLinkStore initialized with db_path: {self.db_path}")
|
|
77
|
-
|
|
78
|
-
async def initialize(self) -> None:
|
|
79
|
-
"""Initializes the database and creates/updates the table if it doesn't exist."""
|
|
80
|
-
try:
|
|
81
|
-
async with aiosqlite.connect(self.db_path) as db:
|
|
82
|
-
# Ensure the table exists with the base schema first
|
|
83
|
-
await db.execute(
|
|
84
|
-
"""
|
|
85
|
-
CREATE TABLE IF NOT EXISTS shared_links (
|
|
86
|
-
share_id TEXT PRIMARY KEY,
|
|
87
|
-
agent_name TEXT NOT NULL,
|
|
88
|
-
flock_definition TEXT NOT NULL,
|
|
89
|
-
created_at TEXT NOT NULL
|
|
90
|
-
/* New columns will be added below if they don't exist */
|
|
91
|
-
)
|
|
92
|
-
"""
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
# Add new columns individually, ignoring errors if they already exist
|
|
96
|
-
new_columns = [
|
|
97
|
-
("share_type", "TEXT DEFAULT 'agent_run' NOT NULL"),
|
|
98
|
-
("chat_message_key", "TEXT"),
|
|
99
|
-
("chat_history_key", "TEXT"),
|
|
100
|
-
("chat_response_key", "TEXT")
|
|
101
|
-
]
|
|
102
|
-
|
|
103
|
-
for column_name, column_type in new_columns:
|
|
104
|
-
try:
|
|
105
|
-
await db.execute(f"ALTER TABLE shared_links ADD COLUMN {column_name} {column_type}")
|
|
106
|
-
logger.info(f"Added column '{column_name}' to shared_links table.")
|
|
107
|
-
except sqlite3.OperationalError as e:
|
|
108
|
-
if "duplicate column name" in str(e).lower():
|
|
109
|
-
logger.debug(f"Column '{column_name}' already exists in shared_links table.")
|
|
110
|
-
else:
|
|
111
|
-
raise # Re-raise if it's a different operational error
|
|
112
|
-
|
|
113
|
-
# Feedback table
|
|
114
|
-
await db.execute(
|
|
115
|
-
"""
|
|
116
|
-
CREATE TABLE IF NOT EXISTS feedback (
|
|
117
|
-
feedback_id TEXT PRIMARY KEY,
|
|
118
|
-
share_id TEXT,
|
|
119
|
-
context_type TEXT NOT NULL,
|
|
120
|
-
reason TEXT NOT NULL,
|
|
121
|
-
expected_response TEXT,
|
|
122
|
-
actual_response TEXT,
|
|
123
|
-
flock_name TEXT,
|
|
124
|
-
agent_name TEXT,
|
|
125
|
-
flock_definition TEXT,
|
|
126
|
-
created_at TEXT NOT NULL,
|
|
127
|
-
FOREIGN KEY(share_id) REFERENCES shared_links(share_id)
|
|
128
|
-
)
|
|
129
|
-
"""
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
await db.commit()
|
|
133
|
-
logger.info(f"Database initialized and shared_links table schema ensured at {self.db_path}")
|
|
134
|
-
except sqlite3.Error as e:
|
|
135
|
-
logger.error(f"SQLite error during initialization: {e}", exc_info=True)
|
|
136
|
-
raise
|
|
137
|
-
|
|
138
|
-
async def save_config(self, config: SharedLinkConfig) -> SharedLinkConfig:
|
|
139
|
-
"""Saves a shared link configuration to the SQLite database."""
|
|
140
|
-
try:
|
|
141
|
-
async with aiosqlite.connect(self.db_path) as db:
|
|
142
|
-
await db.execute(
|
|
143
|
-
"""INSERT INTO shared_links (
|
|
144
|
-
share_id, agent_name, created_at, flock_definition,
|
|
145
|
-
share_type, chat_message_key, chat_history_key, chat_response_key
|
|
146
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
|
|
147
|
-
(
|
|
148
|
-
config.share_id,
|
|
149
|
-
config.agent_name,
|
|
150
|
-
config.created_at.isoformat(),
|
|
151
|
-
config.flock_definition,
|
|
152
|
-
config.share_type,
|
|
153
|
-
config.chat_message_key,
|
|
154
|
-
config.chat_history_key,
|
|
155
|
-
config.chat_response_key,
|
|
156
|
-
),
|
|
157
|
-
)
|
|
158
|
-
await db.commit()
|
|
159
|
-
logger.info(f"Saved shared link config for ID: {config.share_id} with type: {config.share_type}")
|
|
160
|
-
return config
|
|
161
|
-
except sqlite3.Error as e:
|
|
162
|
-
logger.error(f"SQLite error saving config for ID {config.share_id}: {e}", exc_info=True)
|
|
163
|
-
raise
|
|
164
|
-
|
|
165
|
-
async def get_config(self, share_id: str) -> SharedLinkConfig | None:
|
|
166
|
-
"""Retrieves a shared link configuration from SQLite by its ID."""
|
|
167
|
-
try:
|
|
168
|
-
async with aiosqlite.connect(self.db_path) as db:
|
|
169
|
-
async with db.execute(
|
|
170
|
-
"""SELECT
|
|
171
|
-
share_id, agent_name, created_at, flock_definition,
|
|
172
|
-
share_type, chat_message_key, chat_history_key, chat_response_key
|
|
173
|
-
FROM shared_links WHERE share_id = ?""",
|
|
174
|
-
(share_id,)
|
|
175
|
-
) as cursor:
|
|
176
|
-
row = await cursor.fetchone()
|
|
177
|
-
if row:
|
|
178
|
-
logger.debug(f"Retrieved shared link config for ID: {share_id}")
|
|
179
|
-
return SharedLinkConfig(
|
|
180
|
-
share_id=row[0],
|
|
181
|
-
agent_name=row[1],
|
|
182
|
-
created_at=row[2], # SQLite stores as TEXT, Pydantic will parse from ISO format
|
|
183
|
-
flock_definition=row[3],
|
|
184
|
-
share_type=row[4],
|
|
185
|
-
chat_message_key=row[5],
|
|
186
|
-
chat_history_key=row[6],
|
|
187
|
-
chat_response_key=row[7],
|
|
188
|
-
)
|
|
189
|
-
logger.debug(f"No shared link config found for ID: {share_id}")
|
|
190
|
-
return None
|
|
191
|
-
except sqlite3.Error as e:
|
|
192
|
-
logger.error(f"SQLite error retrieving config for ID {share_id}: {e}", exc_info=True)
|
|
193
|
-
return None # Or raise, depending on desired error handling
|
|
194
|
-
|
|
195
|
-
async def delete_config(self, share_id: str) -> bool:
|
|
196
|
-
"""Deletes a shared link configuration from SQLite by its ID."""
|
|
197
|
-
try:
|
|
198
|
-
async with aiosqlite.connect(self.db_path) as db:
|
|
199
|
-
result = await db.execute("DELETE FROM shared_links WHERE share_id = ?", (share_id,))
|
|
200
|
-
await db.commit()
|
|
201
|
-
deleted_count = result.rowcount
|
|
202
|
-
if deleted_count > 0:
|
|
203
|
-
logger.info(f"Deleted shared link config for ID: {share_id}")
|
|
204
|
-
return True
|
|
205
|
-
logger.info(f"Attempted to delete non-existent shared link config for ID: {share_id}")
|
|
206
|
-
return False
|
|
207
|
-
except sqlite3.Error as e:
|
|
208
|
-
logger.error(f"SQLite error deleting config for ID {share_id}: {e}", exc_info=True)
|
|
209
|
-
return False # Or raise
|
|
210
|
-
|
|
211
|
-
# ----------------------- Feedback methods -----------------------
|
|
212
|
-
async def get_feedback(self, id: str) -> FeedbackRecord | None:
|
|
213
|
-
"""Retrieve a single feedback record from SQLite."""
|
|
214
|
-
try:
|
|
215
|
-
async with aiosqlite.connect(self.db_path) as db, db.execute(
|
|
216
|
-
"""SELECT
|
|
217
|
-
feedback_id, share_id, context_type, reason,
|
|
218
|
-
expected_response, actual_response, flock_name, agent_name, flock_definition, created_at
|
|
219
|
-
FROM feedback WHERE feedback_id = ?""",
|
|
220
|
-
(id,)
|
|
221
|
-
) as cursor:
|
|
222
|
-
row = await cursor.fetchone()
|
|
223
|
-
|
|
224
|
-
if row:
|
|
225
|
-
logger.debug(f"Retrieved feedback record for ID: {id}")
|
|
226
|
-
return FeedbackRecord(
|
|
227
|
-
feedback_id=row[0],
|
|
228
|
-
share_id=row[1],
|
|
229
|
-
context_type=row[2],
|
|
230
|
-
reason=row[3],
|
|
231
|
-
expected_response=row[4],
|
|
232
|
-
actual_response=row[5],
|
|
233
|
-
flock_name=row[6],
|
|
234
|
-
agent_name=row[7],
|
|
235
|
-
flock_definition=row[8],
|
|
236
|
-
created_at=row[9], # SQLite stores as TEXT, Pydantic will parse from ISO format
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
logger.debug(f"No feedback record found for ID: {id}")
|
|
240
|
-
return None
|
|
241
|
-
except sqlite3.Error as e:
|
|
242
|
-
logger.error(f"SQLite error retrieving feedback for ID {id}: {e}", exc_info=True)
|
|
243
|
-
return None # Or raise, depending on desired error handling
|
|
244
|
-
|
|
245
|
-
async def get_all_feedback_records_for_agent(self, agent_name: str) -> list[FeedbackRecord]:
|
|
246
|
-
"""Retrieve all feedback records from SQLite."""
|
|
247
|
-
try:
|
|
248
|
-
async with aiosqlite.connect(self.db_path) as db, db.execute(
|
|
249
|
-
"""SELECT
|
|
250
|
-
feedback_id, share_id, context_type, reason,
|
|
251
|
-
expected_response, actual_response, flock_name, agent_name, flock_definition, created_at
|
|
252
|
-
FROM feedback WHERE agent_name = ? ORDER BY created_at DESC""",
|
|
253
|
-
(agent_name,)
|
|
254
|
-
) as cursor:
|
|
255
|
-
rows = await cursor.fetchall()
|
|
256
|
-
|
|
257
|
-
records = []
|
|
258
|
-
for row in rows:
|
|
259
|
-
records.append(FeedbackRecord(
|
|
260
|
-
feedback_id=row[0],
|
|
261
|
-
share_id=row[1],
|
|
262
|
-
context_type=row[2],
|
|
263
|
-
reason=row[3],
|
|
264
|
-
expected_response=row[4],
|
|
265
|
-
actual_response=row[5],
|
|
266
|
-
flock_name=row[6],
|
|
267
|
-
agent_name=row[7],
|
|
268
|
-
flock_definition=row[8],
|
|
269
|
-
created_at=row[9], # SQLite stores as TEXT, Pydantic will parse from ISO format
|
|
270
|
-
))
|
|
271
|
-
|
|
272
|
-
logger.debug(f"Retrieved {len(records)} feedback records")
|
|
273
|
-
return records
|
|
274
|
-
except sqlite3.Error as e:
|
|
275
|
-
logger.error(f"SQLite error retrieving all feedback records: {e}", exc_info=True)
|
|
276
|
-
return [] # Return empty list on error
|
|
277
|
-
|
|
278
|
-
async def save_feedback(self, record: FeedbackRecord) -> FeedbackRecord:
|
|
279
|
-
"""Persist a feedback record to SQLite."""
|
|
280
|
-
try:
|
|
281
|
-
async with aiosqlite.connect(self.db_path) as db:
|
|
282
|
-
await db.execute(
|
|
283
|
-
"""INSERT INTO feedback (
|
|
284
|
-
feedback_id, share_id, context_type, reason,
|
|
285
|
-
expected_response, actual_response, flock_name, agent_name, flock_definition, created_at
|
|
286
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
|
287
|
-
(
|
|
288
|
-
record.feedback_id,
|
|
289
|
-
record.share_id,
|
|
290
|
-
record.context_type,
|
|
291
|
-
record.reason,
|
|
292
|
-
record.expected_response,
|
|
293
|
-
record.actual_response,
|
|
294
|
-
record.flock_name,
|
|
295
|
-
record.agent_name,
|
|
296
|
-
record.flock_definition,
|
|
297
|
-
record.created_at.isoformat(),
|
|
298
|
-
),
|
|
299
|
-
)
|
|
300
|
-
await db.commit()
|
|
301
|
-
logger.info(f"Saved feedback {record.feedback_id} (share={record.share_id})")
|
|
302
|
-
return record
|
|
303
|
-
except sqlite3.Error as e:
|
|
304
|
-
logger.error(f"SQLite error saving feedback {record.feedback_id}: {e}", exc_info=True)
|
|
305
|
-
raise
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
# ---------------------------------------------------------------------------
|
|
309
|
-
# Azure Table + Blob implementation
|
|
310
|
-
# ---------------------------------------------------------------------------
|
|
311
|
-
|
|
312
|
-
try:
|
|
313
|
-
from azure.storage.blob.aio import BlobServiceClient
|
|
314
|
-
AZURE_BLOB_AVAILABLE = True
|
|
315
|
-
except ImportError: # blob SDK not installed
|
|
316
|
-
AZURE_BLOB_AVAILABLE = False
|
|
317
|
-
BlobServiceClient = None
|
|
318
|
-
|
|
319
|
-
class AzureTableSharedLinkStore(SharedLinkStoreInterface):
|
|
320
|
-
"""Store configs in Azure Table; store large flock YAML in Blob Storage."""
|
|
321
|
-
|
|
322
|
-
_TABLE_NAME = "flocksharedlinks"
|
|
323
|
-
_FEEDBACK_TBL_NAME = "flockfeedback"
|
|
324
|
-
_CONTAINER_NAME = "flocksharedlinkdefs" # blobs live here
|
|
325
|
-
_PARTITION_KEY = "shared_links"
|
|
326
|
-
|
|
327
|
-
def __init__(self, connection_string: str):
|
|
328
|
-
if not AZURE_AVAILABLE:
|
|
329
|
-
raise ImportError("pip install azure-data-tables")
|
|
330
|
-
if not AZURE_BLOB_AVAILABLE:
|
|
331
|
-
raise ImportError("pip install azure-storage-blob")
|
|
332
|
-
|
|
333
|
-
self.connection_string = connection_string
|
|
334
|
-
self.table_svc = TableServiceClient.from_connection_string(connection_string)
|
|
335
|
-
self.blob_svc = BlobServiceClient.from_connection_string(connection_string)
|
|
336
|
-
|
|
337
|
-
# ------------------------------------------------------------------ init
|
|
338
|
-
async def initialize(self) -> None:
|
|
339
|
-
# 1. Azure Tables ----------------------------------------------------
|
|
340
|
-
try:
|
|
341
|
-
await self.table_svc.create_table(self._TABLE_NAME)
|
|
342
|
-
logger.info("Created Azure Table '%s'", self._TABLE_NAME)
|
|
343
|
-
except ResourceExistsError:
|
|
344
|
-
logger.debug("Azure Table '%s' already exists", self._TABLE_NAME)
|
|
345
|
-
|
|
346
|
-
try:
|
|
347
|
-
await self.table_svc.create_table(self._FEEDBACK_TBL_NAME)
|
|
348
|
-
logger.info("Created Azure Table '%s'", self._FEEDBACK_TBL_NAME)
|
|
349
|
-
except ResourceExistsError:
|
|
350
|
-
logger.debug("Azure Table '%s' already exists", self._FEEDBACK_TBL_NAME)
|
|
351
|
-
|
|
352
|
-
# 2. Blob container --------------------------------------------------
|
|
353
|
-
try:
|
|
354
|
-
await self.blob_svc.create_container(self._CONTAINER_NAME)
|
|
355
|
-
logger.info("Created Blob container '%s'", self._CONTAINER_NAME)
|
|
356
|
-
except ResourceExistsError:
|
|
357
|
-
logger.debug("Blob container '%s' already exists", self._CONTAINER_NAME)
|
|
358
|
-
|
|
359
|
-
# ------------------------------------------------------------- save_config
|
|
360
|
-
async def save_config(self, config: SharedLinkConfig) -> SharedLinkConfig:
|
|
361
|
-
"""Upload YAML to Blob, then upsert table row containing the blob name."""
|
|
362
|
-
blob_name = f"{config.share_id}.yaml"
|
|
363
|
-
blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
|
|
364
|
-
|
|
365
|
-
# 1. Upload flock_definition (overwrite in case of retry)
|
|
366
|
-
await blob_client.upload_blob(config.flock_definition,
|
|
367
|
-
overwrite=True,
|
|
368
|
-
content_type="text/yaml")
|
|
369
|
-
logger.debug("Uploaded blob '%s' (%d bytes)",
|
|
370
|
-
blob_name, len(config.flock_definition.encode()))
|
|
371
|
-
|
|
372
|
-
# 2. Persist lightweight record in the table
|
|
373
|
-
tbl_client = self.table_svc.get_table_client(self._TABLE_NAME)
|
|
374
|
-
entity = {
|
|
375
|
-
"PartitionKey": self._PARTITION_KEY,
|
|
376
|
-
"RowKey": config.share_id,
|
|
377
|
-
"agent_name": config.agent_name,
|
|
378
|
-
"created_at": config.created_at.isoformat(),
|
|
379
|
-
"share_type": config.share_type,
|
|
380
|
-
"chat_message_key": config.chat_message_key,
|
|
381
|
-
"chat_history_key": config.chat_history_key,
|
|
382
|
-
"chat_response_key": config.chat_response_key,
|
|
383
|
-
# NEW – just a few bytes, well under 64 KiB
|
|
384
|
-
"flock_blob_name": blob_name,
|
|
385
|
-
}
|
|
386
|
-
await tbl_client.upsert_entity(entity)
|
|
387
|
-
logger.info("Saved shared link %s → blob '%s'", config.share_id, blob_name)
|
|
388
|
-
return config
|
|
389
|
-
|
|
390
|
-
# -------------------------------------------------------------- get_config
|
|
391
|
-
async def get_config(self, share_id: str) -> SharedLinkConfig | None:
|
|
392
|
-
tbl_client = self.table_svc.get_table_client(self._TABLE_NAME)
|
|
393
|
-
try:
|
|
394
|
-
entity = await tbl_client.get_entity(self._PARTITION_KEY, share_id)
|
|
395
|
-
except ResourceNotFoundError:
|
|
396
|
-
logger.debug("No config entity for id '%s'", share_id)
|
|
397
|
-
return None
|
|
398
|
-
|
|
399
|
-
blob_name = entity["flock_blob_name"]
|
|
400
|
-
blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
|
|
401
|
-
try:
|
|
402
|
-
blob_bytes = await (await blob_client.download_blob()).readall()
|
|
403
|
-
flock_yaml = blob_bytes.decode()
|
|
404
|
-
except Exception as e:
|
|
405
|
-
logger.error("Cannot download blob '%s' for share_id=%s: %s",
|
|
406
|
-
blob_name, share_id, e, exc_info=True)
|
|
407
|
-
raise
|
|
408
|
-
|
|
409
|
-
return SharedLinkConfig(
|
|
410
|
-
share_id = share_id,
|
|
411
|
-
agent_name = entity["agent_name"],
|
|
412
|
-
created_at = entity["created_at"],
|
|
413
|
-
flock_definition = flock_yaml,
|
|
414
|
-
share_type = entity.get("share_type", "agent_run"),
|
|
415
|
-
chat_message_key = entity.get("chat_message_key"),
|
|
416
|
-
chat_history_key = entity.get("chat_history_key"),
|
|
417
|
-
chat_response_key = entity.get("chat_response_key"),
|
|
418
|
-
)
|
|
419
|
-
|
|
420
|
-
# ----------------------------------------------------------- delete_config
|
|
421
|
-
async def delete_config(self, share_id: str) -> bool:
|
|
422
|
-
tbl_client = self.table_svc.get_table_client(self._TABLE_NAME)
|
|
423
|
-
try:
|
|
424
|
-
entity = await tbl_client.get_entity(self._PARTITION_KEY, share_id)
|
|
425
|
-
except ResourceNotFoundError:
|
|
426
|
-
logger.info("Delete: entity %s not found", share_id)
|
|
427
|
-
return False
|
|
428
|
-
|
|
429
|
-
# 1. Remove blob (ignore missing blob)
|
|
430
|
-
blob_name = entity["flock_blob_name"]
|
|
431
|
-
blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
|
|
432
|
-
try:
|
|
433
|
-
await blob_client.delete_blob(delete_snapshots="include")
|
|
434
|
-
logger.debug("Deleted blob '%s'", blob_name)
|
|
435
|
-
except ResourceNotFoundError:
|
|
436
|
-
logger.warning("Blob '%s' already gone", blob_name)
|
|
437
|
-
|
|
438
|
-
# 2. Remove table row
|
|
439
|
-
await tbl_client.delete_entity(self._PARTITION_KEY, share_id)
|
|
440
|
-
logger.info("Deleted shared link %s and its blob", share_id)
|
|
441
|
-
return True
|
|
442
|
-
|
|
443
|
-
# -------------------------------------------------------- save_feedback --
|
|
444
|
-
async def save_feedback(self, record: FeedbackRecord) -> FeedbackRecord:
|
|
445
|
-
"""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)."""
|
|
446
|
-
tbl_client = self.table_svc.get_table_client(self._FEEDBACK_TBL_NAME)
|
|
447
|
-
|
|
448
|
-
# Core entity fields (avoid dumping the full Pydantic model – too many columns / large value)
|
|
449
|
-
entity: dict[str, Any] = {
|
|
450
|
-
"PartitionKey": "feedback",
|
|
451
|
-
"RowKey": record.feedback_id,
|
|
452
|
-
"share_id": record.share_id,
|
|
453
|
-
"context_type": record.context_type,
|
|
454
|
-
"reason": record.reason,
|
|
455
|
-
"expected_response": record.expected_response,
|
|
456
|
-
"actual_response": record.actual_response,
|
|
457
|
-
"created_at": record.created_at.isoformat(),
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
# additional sanity check
|
|
461
|
-
|
|
462
|
-
if record.flock_name is not None:
|
|
463
|
-
entity["flock_name"] = record.flock_name
|
|
464
|
-
if record.agent_name is not None:
|
|
465
|
-
entity["agent_name"] = record.agent_name
|
|
466
|
-
|
|
467
|
-
# ------------------------------------------------------------------ YAML → Blob
|
|
468
|
-
if record.flock_definition:
|
|
469
|
-
blob_name = f"{record.feedback_id}.yaml"
|
|
470
|
-
blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
|
|
471
|
-
# Overwrite=true so repeated feedback_id uploads (shouldn't happen) won't error
|
|
472
|
-
await blob_client.upload_blob(record.flock_definition,
|
|
473
|
-
overwrite=True,
|
|
474
|
-
content_type="text/yaml")
|
|
475
|
-
entity["flock_blob_name"] = blob_name # lightweight reference only
|
|
476
|
-
|
|
477
|
-
# ------------------------------------------------------------------ Table upsert
|
|
478
|
-
await tbl_client.upsert_entity(entity)
|
|
479
|
-
logger.info("Saved feedback %s%s",
|
|
480
|
-
record.feedback_id,
|
|
481
|
-
f" → blob '{entity['flock_blob_name']}'" if "flock_blob_name" in entity else "")
|
|
482
|
-
return record
|
|
483
|
-
|
|
484
|
-
# -------------------------------------------------------- get_feedback --
|
|
485
|
-
async def get_feedback(self, id: str) -> FeedbackRecord | None:
|
|
486
|
-
"""Retrieve a single feedback record from Azure Table Storage."""
|
|
487
|
-
tbl_client = self.table_svc.get_table_client(self._FEEDBACK_TBL_NAME)
|
|
488
|
-
try:
|
|
489
|
-
entity = await tbl_client.get_entity("feedback", id)
|
|
490
|
-
except ResourceNotFoundError:
|
|
491
|
-
logger.debug("No feedback record found for ID: %s", id)
|
|
492
|
-
return None
|
|
493
|
-
|
|
494
|
-
# Get flock_definition from blob if it exists
|
|
495
|
-
flock_definition = None
|
|
496
|
-
if "flock_blob_name" in entity:
|
|
497
|
-
blob_name = entity["flock_blob_name"]
|
|
498
|
-
blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
|
|
499
|
-
try:
|
|
500
|
-
blob_bytes = await (await blob_client.download_blob()).readall()
|
|
501
|
-
flock_definition = blob_bytes.decode()
|
|
502
|
-
except Exception as e:
|
|
503
|
-
logger.error("Cannot download blob '%s' for feedback_id=%s: %s",
|
|
504
|
-
blob_name, id, e, exc_info=True)
|
|
505
|
-
# Continue without flock_definition rather than failing
|
|
506
|
-
|
|
507
|
-
return FeedbackRecord(
|
|
508
|
-
feedback_id=id,
|
|
509
|
-
share_id=entity.get("share_id"),
|
|
510
|
-
context_type=entity["context_type"],
|
|
511
|
-
reason=entity["reason"],
|
|
512
|
-
expected_response=entity.get("expected_response"),
|
|
513
|
-
actual_response=entity.get("actual_response"),
|
|
514
|
-
flock_name=entity.get("flock_name"),
|
|
515
|
-
agent_name=entity.get("agent_name"),
|
|
516
|
-
flock_definition=flock_definition,
|
|
517
|
-
created_at=entity["created_at"],
|
|
518
|
-
)
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
# ------------------------------------------------ get_all_feedback_records --
|
|
522
|
-
async def get_all_feedback_records_for_agent(self, agent_name: str) -> list[FeedbackRecord]:
|
|
523
|
-
"""Retrieve all feedback records from Azure Table Storage for a specific agent."""
|
|
524
|
-
tbl_client = self.table_svc.get_table_client(self._FEEDBACK_TBL_NAME)
|
|
525
|
-
|
|
526
|
-
# Use Azure Table Storage filtering to only get records for the specified agent
|
|
527
|
-
escaped_agent_name = agent_name.replace("'", "''")
|
|
528
|
-
filter_query = f"agent_name eq '{escaped_agent_name}'"
|
|
529
|
-
|
|
530
|
-
logger.debug(f"Querying feedback records with filter: {filter_query}")
|
|
531
|
-
|
|
532
|
-
records = []
|
|
533
|
-
try:
|
|
534
|
-
async for entity in tbl_client.query_entities(filter_query):
|
|
535
|
-
# Get flock_definition from blob if it exists
|
|
536
|
-
flock_definition = None
|
|
537
|
-
if "flock_blob_name" in entity:
|
|
538
|
-
blob_name = entity["flock_blob_name"]
|
|
539
|
-
blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
|
|
540
|
-
try:
|
|
541
|
-
blob_bytes = await (await blob_client.download_blob()).readall()
|
|
542
|
-
flock_definition = blob_bytes.decode()
|
|
543
|
-
except Exception as e:
|
|
544
|
-
logger.error("Cannot download blob '%s' for feedback_id=%s: %s",
|
|
545
|
-
blob_name, entity["RowKey"], e, exc_info=True)
|
|
546
|
-
# Continue without flock_definition rather than failing
|
|
547
|
-
|
|
548
|
-
records.append(FeedbackRecord(
|
|
549
|
-
feedback_id=entity["RowKey"],
|
|
550
|
-
share_id=entity.get("share_id"),
|
|
551
|
-
context_type=entity["context_type"],
|
|
552
|
-
reason=entity["reason"],
|
|
553
|
-
expected_response=entity.get("expected_response"),
|
|
554
|
-
actual_response=entity.get("actual_response"),
|
|
555
|
-
flock_name=entity.get("flock_name"),
|
|
556
|
-
agent_name=entity.get("agent_name"),
|
|
557
|
-
flock_definition=flock_definition,
|
|
558
|
-
created_at=entity["created_at"],
|
|
559
|
-
))
|
|
560
|
-
|
|
561
|
-
logger.debug("Retrieved %d feedback records for agent %s", len(records), agent_name)
|
|
562
|
-
return records
|
|
563
|
-
|
|
564
|
-
except Exception as e:
|
|
565
|
-
# Log the error.
|
|
566
|
-
logger.error(
|
|
567
|
-
f"Unable to query entries for agent {agent_name}. Exception: {e}"
|
|
568
|
-
)
|
|
569
|
-
return records
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
# ----------------------- Factory Function -----------------------
|
|
573
|
-
|
|
574
|
-
def create_shared_link_store(store_type: str | None = None, connection_string: str | None = None) -> SharedLinkStoreInterface:
|
|
575
|
-
"""Factory function to create the appropriate shared link store based on configuration.
|
|
576
|
-
|
|
577
|
-
Args:
|
|
578
|
-
store_type: Type of store to create ("local" for SQLite, "azure-storage" for Azure Table Storage)
|
|
579
|
-
connection_string: Connection string for the store (file path for SQLite, connection string for Azure)
|
|
580
|
-
|
|
581
|
-
Returns:
|
|
582
|
-
Configured SharedLinkStoreInterface implementation
|
|
583
|
-
"""
|
|
584
|
-
import os
|
|
585
|
-
|
|
586
|
-
# Get values from environment if not provided
|
|
587
|
-
if store_type is None:
|
|
588
|
-
store_type = os.getenv("FLOCK_WEBAPP_STORE", "local").lower()
|
|
589
|
-
|
|
590
|
-
if connection_string is None:
|
|
591
|
-
connection_string = os.getenv("FLOCK_WEBAPP_STORE_CONNECTION", ".flock/shared_links.db")
|
|
592
|
-
|
|
593
|
-
if store_type == "local":
|
|
594
|
-
return SQLiteSharedLinkStore(connection_string)
|
|
595
|
-
elif store_type == "azure-storage":
|
|
596
|
-
return AzureTableSharedLinkStore(connection_string)
|
|
597
|
-
else:
|
|
598
|
-
raise ValueError(f"Unsupported store type: {store_type}. Supported types: 'local', 'azure-storage'")
|