flock-core 0.4.543__py3-none-any.whl → 0.5.0__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 +1079 -0
- flock/api/themes.py +71 -0
- flock/artifacts.py +86 -0
- flock/cli.py +147 -0
- flock/components.py +189 -0
- flock/dashboard/__init__.py +30 -0
- flock/dashboard/collector.py +559 -0
- flock/dashboard/events.py +188 -0
- flock/dashboard/graph_builder.py +563 -0
- flock/dashboard/launcher.py +235 -0
- flock/dashboard/models/graph.py +156 -0
- flock/dashboard/service.py +991 -0
- flock/dashboard/static_v2/assets/index-DFRnI_mt.js +111 -0
- flock/dashboard/static_v2/assets/index-fPLNdmp1.css +1 -0
- flock/dashboard/static_v2/index.html +13 -0
- flock/dashboard/websocket.py +246 -0
- flock/engines/__init__.py +6 -0
- flock/engines/dspy_engine.py +932 -0
- flock/examples.py +131 -0
- flock/frontend/README.md +778 -0
- flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
- flock/frontend/index.html +12 -0
- flock/frontend/package-lock.json +4337 -0
- flock/frontend/package.json +48 -0
- flock/frontend/src/App.tsx +139 -0
- flock/frontend/src/__tests__/integration/graph-snapshot.test.tsx +647 -0
- flock/frontend/src/__tests__/integration/indexeddb-persistence.test.tsx +699 -0
- flock/frontend/src/components/common/BuildInfo.tsx +39 -0
- flock/frontend/src/components/common/EmptyState.module.css +115 -0
- flock/frontend/src/components/common/EmptyState.tsx +128 -0
- flock/frontend/src/components/common/ErrorBoundary.module.css +169 -0
- flock/frontend/src/components/common/ErrorBoundary.tsx +118 -0
- flock/frontend/src/components/common/KeyboardShortcutsDialog.css +251 -0
- flock/frontend/src/components/common/KeyboardShortcutsDialog.tsx +151 -0
- flock/frontend/src/components/common/LoadingSpinner.module.css +97 -0
- flock/frontend/src/components/common/LoadingSpinner.tsx +29 -0
- flock/frontend/src/components/controls/PublishControl.css +547 -0
- flock/frontend/src/components/controls/PublishControl.test.tsx +543 -0
- flock/frontend/src/components/controls/PublishControl.tsx +432 -0
- flock/frontend/src/components/details/DetailWindowContainer.tsx +58 -0
- flock/frontend/src/components/details/LiveOutputTab.test.tsx +792 -0
- flock/frontend/src/components/details/LiveOutputTab.tsx +220 -0
- flock/frontend/src/components/details/MessageDetailWindow.tsx +439 -0
- flock/frontend/src/components/details/MessageHistoryTab.tsx +374 -0
- flock/frontend/src/components/details/NodeDetailWindow.test.tsx +501 -0
- flock/frontend/src/components/details/NodeDetailWindow.tsx +218 -0
- flock/frontend/src/components/details/RunStatusTab.tsx +348 -0
- flock/frontend/src/components/details/tabs.test.tsx +1015 -0
- flock/frontend/src/components/filters/ArtifactTypeFilter.tsx +21 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.module.css +102 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.test.tsx +197 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.tsx +121 -0
- flock/frontend/src/components/filters/FilterFlyout.module.css +104 -0
- flock/frontend/src/components/filters/FilterFlyout.tsx +80 -0
- flock/frontend/src/components/filters/FilterPills.module.css +220 -0
- flock/frontend/src/components/filters/FilterPills.test.tsx +189 -0
- flock/frontend/src/components/filters/FilterPills.tsx +143 -0
- flock/frontend/src/components/filters/ProducerFilter.tsx +21 -0
- flock/frontend/src/components/filters/SavedFiltersControl.module.css +60 -0
- flock/frontend/src/components/filters/SavedFiltersControl.test.tsx +158 -0
- flock/frontend/src/components/filters/SavedFiltersControl.tsx +159 -0
- flock/frontend/src/components/filters/TagFilter.tsx +21 -0
- flock/frontend/src/components/filters/TimeRangeFilter.module.css +115 -0
- flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
- flock/frontend/src/components/filters/TimeRangeFilter.tsx +110 -0
- flock/frontend/src/components/filters/VisibilityFilter.tsx +21 -0
- flock/frontend/src/components/graph/AgentNode.test.tsx +77 -0
- flock/frontend/src/components/graph/AgentNode.tsx +324 -0
- flock/frontend/src/components/graph/GraphCanvas.tsx +613 -0
- flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
- flock/frontend/src/components/graph/MessageNode.test.tsx +64 -0
- flock/frontend/src/components/graph/MessageNode.tsx +129 -0
- flock/frontend/src/components/graph/MiniMap.tsx +47 -0
- flock/frontend/src/components/graph/TransformEdge.tsx +123 -0
- flock/frontend/src/components/layout/DashboardLayout.css +420 -0
- flock/frontend/src/components/layout/DashboardLayout.tsx +287 -0
- flock/frontend/src/components/layout/Header.module.css +88 -0
- flock/frontend/src/components/layout/Header.tsx +52 -0
- flock/frontend/src/components/modules/HistoricalArtifactsModule.module.css +288 -0
- flock/frontend/src/components/modules/HistoricalArtifactsModule.tsx +450 -0
- flock/frontend/src/components/modules/HistoricalArtifactsModuleWrapper.tsx +13 -0
- flock/frontend/src/components/modules/JsonAttributeRenderer.tsx +140 -0
- flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
- flock/frontend/src/components/modules/ModuleRegistry.ts +93 -0
- flock/frontend/src/components/modules/ModuleWindow.tsx +223 -0
- flock/frontend/src/components/modules/TraceModuleJaeger.tsx +1971 -0
- flock/frontend/src/components/modules/TraceModuleJaegerWrapper.tsx +13 -0
- flock/frontend/src/components/modules/registerModules.ts +29 -0
- flock/frontend/src/components/settings/AdvancedSettings.tsx +175 -0
- flock/frontend/src/components/settings/AppearanceSettings.tsx +185 -0
- flock/frontend/src/components/settings/GraphSettings.tsx +110 -0
- flock/frontend/src/components/settings/MultiSelect.tsx +235 -0
- flock/frontend/src/components/settings/SettingsPanel.css +327 -0
- flock/frontend/src/components/settings/SettingsPanel.tsx +131 -0
- flock/frontend/src/components/settings/ThemeSelector.tsx +298 -0
- flock/frontend/src/components/settings/TracingSettings.tsx +404 -0
- flock/frontend/src/hooks/useKeyboardShortcuts.ts +148 -0
- flock/frontend/src/hooks/useModulePersistence.test.ts +442 -0
- flock/frontend/src/hooks/useModulePersistence.ts +154 -0
- flock/frontend/src/hooks/useModules.ts +157 -0
- flock/frontend/src/hooks/usePersistence.ts +141 -0
- flock/frontend/src/main.tsx +13 -0
- flock/frontend/src/services/api.ts +337 -0
- flock/frontend/src/services/graphService.test.ts +330 -0
- flock/frontend/src/services/graphService.ts +75 -0
- flock/frontend/src/services/indexeddb.test.ts +793 -0
- flock/frontend/src/services/indexeddb.ts +848 -0
- flock/frontend/src/services/layout.test.ts +437 -0
- flock/frontend/src/services/layout.ts +357 -0
- flock/frontend/src/services/themeApplicator.ts +140 -0
- flock/frontend/src/services/themeService.ts +77 -0
- flock/frontend/src/services/websocket.ts +650 -0
- flock/frontend/src/store/filterStore.test.ts +250 -0
- flock/frontend/src/store/filterStore.ts +272 -0
- flock/frontend/src/store/graphStore.test.ts +570 -0
- flock/frontend/src/store/graphStore.ts +462 -0
- flock/frontend/src/store/moduleStore.test.ts +253 -0
- flock/frontend/src/store/moduleStore.ts +75 -0
- flock/frontend/src/store/settingsStore.ts +188 -0
- flock/frontend/src/store/streamStore.ts +68 -0
- flock/frontend/src/store/uiStore.test.ts +54 -0
- flock/frontend/src/store/uiStore.ts +122 -0
- flock/frontend/src/store/wsStore.ts +34 -0
- flock/frontend/src/styles/index.css +15 -0
- flock/frontend/src/styles/scrollbar.css +47 -0
- flock/frontend/src/styles/variables.css +488 -0
- flock/frontend/src/test/setup.ts +1 -0
- flock/frontend/src/types/filters.ts +47 -0
- flock/frontend/src/types/graph.ts +95 -0
- flock/frontend/src/types/modules.ts +10 -0
- flock/frontend/src/types/theme.ts +55 -0
- flock/frontend/src/utils/artifacts.ts +24 -0
- flock/frontend/src/utils/mockData.ts +98 -0
- flock/frontend/src/utils/performance.ts +16 -0
- flock/frontend/src/vite-env.d.ts +17 -0
- flock/frontend/tsconfig.json +27 -0
- flock/frontend/tsconfig.node.json +11 -0
- flock/frontend/vite.config.ts +25 -0
- flock/frontend/vitest.config.ts +11 -0
- flock/{core/util → helper}/cli_helper.py +9 -5
- flock/{core/logging → logging}/__init__.py +2 -3
- flock/logging/auto_trace.py +159 -0
- 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 -107
- flock/{core/logging → logging}/logging.py +78 -61
- flock/{core/logging → logging}/telemetry.py +66 -26
- flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
- flock/logging/telemetry_exporter/duckdb_exporter.py +216 -0
- flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +13 -10
- flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
- flock/logging/trace_and_logged.py +304 -0
- flock/mcp/__init__.py +91 -0
- flock/{core/mcp/mcp_client.py → mcp/client.py} +131 -158
- flock/{core/mcp/mcp_config.py → mcp/config.py} +86 -132
- flock/mcp/manager.py +286 -0
- flock/mcp/servers/sse/__init__.py +1 -1
- flock/mcp/servers/sse/flock_sse_server.py +16 -58
- flock/mcp/servers/stdio/__init__.py +1 -1
- flock/mcp/servers/stdio/flock_stdio_server.py +13 -53
- flock/mcp/servers/streamable_http/flock_streamable_http_server.py +22 -67
- flock/mcp/servers/websockets/flock_websocket_server.py +12 -45
- flock/{core/mcp/flock_mcp_tool_base.py → mcp/tool.py} +24 -78
- flock/mcp/types/__init__.py +42 -0
- flock/{core/mcp → mcp}/types/callbacks.py +12 -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 +3 -3
- flock/orchestrator.py +970 -0
- flock/registry.py +148 -0
- flock/runtime.py +262 -0
- flock/service.py +277 -0
- flock/store.py +1214 -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/darkmatrix.toml +2 -2
- flock/themes/darkside.toml +1 -1
- flock/themes/deep.toml +2 -2
- 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 +5 -5
- 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/utility/output_utility_component.py +226 -0
- flock/visibility.py +107 -0
- flock_core-0.5.0.dist-info/METADATA +964 -0
- flock_core-0.5.0.dist-info/RECORD +525 -0
- flock_core-0.5.0.dist-info/entry_points.txt +2 -0
- {flock_core-0.4.543.dist-info → flock_core-0.5.0.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/config.py +0 -56
- flock/core/__init__.py +0 -44
- flock/core/api/__init__.py +0 -10
- flock/core/api/custom_endpoint.py +0 -45
- flock/core/api/endpoints.py +0 -262
- flock/core/api/main.py +0 -162
- flock/core/api/models.py +0 -101
- flock/core/api/run_store.py +0 -224
- flock/core/api/runner.py +0 -44
- flock/core/api/service.py +0 -214
- flock/core/config/flock_agent_config.py +0 -11
- flock/core/config/scheduled_agent_config.py +0 -40
- flock/core/context/context.py +0 -214
- flock/core/context/context_manager.py +0 -40
- flock/core/context/context_vars.py +0 -11
- flock/core/evaluation/utils.py +0 -395
- 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 -166
- flock/core/flock.py +0 -1003
- flock/core/flock_agent.py +0 -1258
- flock/core/flock_evaluator.py +0 -60
- flock/core/flock_factory.py +0 -513
- flock/core/flock_module.py +0 -207
- flock/core/flock_registry.py +0 -702
- flock/core/flock_router.py +0 -83
- 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/logging/live_capture.py +0 -137
- flock/core/logging/trace_and_logged.py +0 -59
- flock/core/mcp/__init__.py +0 -1
- flock/core/mcp/flock_mcp_server.py +0 -640
- flock/core/mcp/mcp_client_manager.py +0 -201
- flock/core/mcp/types/__init__.py +0 -1
- flock/core/mixin/dspy_integration.py +0 -445
- flock/core/mixin/prompt_parser.py +0 -125
- flock/core/serialization/__init__.py +0 -13
- flock/core/serialization/callable_registry.py +0 -52
- flock/core/serialization/flock_serializer.py +0 -854
- 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 -409
- flock/core/util/file_path_utils.py +0 -223
- flock/core/util/hydrator.py +0 -309
- flock/core/util/input_resolver.py +0 -141
- flock/core/util/loader.py +0 -59
- flock/core/util/splitter.py +0 -219
- flock/di.py +0 -41
- flock/evaluators/__init__.py +0 -1
- flock/evaluators/declarative/__init__.py +0 -1
- flock/evaluators/declarative/declarative_evaluator.py +0 -217
- flock/evaluators/memory/memory_evaluator.py +0 -90
- flock/evaluators/test/test_case_evaluator.py +0 -38
- flock/evaluators/zep/zep_evaluator.py +0 -59
- flock/modules/__init__.py +0 -1
- flock/modules/assertion/__init__.py +0 -1
- flock/modules/assertion/assertion_module.py +0 -286
- flock/modules/callback/__init__.py +0 -1
- flock/modules/callback/callback_module.py +0 -91
- flock/modules/enterprise_memory/README.md +0 -99
- flock/modules/enterprise_memory/enterprise_memory_module.py +0 -526
- flock/modules/mem0/__init__.py +0 -1
- flock/modules/mem0/mem0_module.py +0 -126
- flock/modules/mem0_async/__init__.py +0 -1
- flock/modules/mem0_async/async_mem0_module.py +0 -126
- flock/modules/memory/__init__.py +0 -1
- flock/modules/memory/memory_module.py +0 -429
- flock/modules/memory/memory_parser.py +0 -125
- flock/modules/memory/memory_storage.py +0 -736
- flock/modules/output/__init__.py +0 -1
- flock/modules/output/output_module.py +0 -196
- flock/modules/performance/__init__.py +0 -1
- flock/modules/performance/metrics_module.py +0 -678
- flock/modules/zep/__init__.py +0 -1
- flock/modules/zep/zep_module.py +0 -192
- flock/platform/docker_tools.py +0 -49
- flock/platform/jaeger_install.py +0 -86
- flock/routers/__init__.py +0 -1
- flock/routers/agent/__init__.py +0 -1
- flock/routers/agent/agent_router.py +0 -236
- flock/routers/agent/handoff_agent.py +0 -58
- flock/routers/conditional/conditional_router.py +0 -486
- flock/routers/default/__init__.py +0 -1
- flock/routers/default/default_router.py +0 -80
- flock/routers/feedback/feedback_router.py +0 -114
- flock/routers/list_generator/list_generator_router.py +0 -166
- flock/routers/llm/__init__.py +0 -1
- flock/routers/llm/llm_router.py +0 -365
- flock/tools/__init__.py +0 -0
- flock/tools/azure_tools.py +0 -781
- flock/tools/code_tools.py +0 -167
- flock/tools/file_tools.py +0 -149
- flock/tools/github_tools.py +0 -157
- flock/tools/markdown_tools.py +0 -205
- flock/tools/system_tools.py +0 -9
- flock/tools/text_tools.py +0 -810
- flock/tools/web_tools.py +0 -92
- flock/tools/zendesk_tools.py +0 -501
- 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 -237
- flock/webapp/app/api/execution.py +0 -503
- flock/webapp/app/api/flock_management.py +0 -125
- flock/webapp/app/api/registry_viewer.py +0 -29
- flock/webapp/app/chat.py +0 -662
- flock/webapp/app/config.py +0 -104
- flock/webapp/app/dependencies.py +0 -117
- flock/webapp/app/main.py +0 -1086
- 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 -345
- flock/webapp/app/services/sharing_models.py +0 -81
- flock/webapp/app/services/sharing_store.py +0 -597
- flock/webapp/app/templates/theme_mapper.html +0 -326
- flock/webapp/app/theme_mapper.py +0 -811
- flock/webapp/app/utils.py +0 -85
- flock/webapp/run.py +0 -219
- 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 -281
- flock/webapp/static/css/sidebar.css +0 -127
- flock/webapp/static/css/two-pane.css +0 -48
- flock/webapp/templates/base.html +0 -389
- 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 -127
- 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/_live_logs.html +0 -13
- 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/_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 -237
- flock/workflow/agent_activities.py +0 -24
- flock/workflow/agent_execution_activity.py +0 -240
- flock/workflow/flock_workflow.py +0 -225
- flock/workflow/temporal_config.py +0 -96
- flock/workflow/temporal_setup.py +0 -60
- flock_core-0.4.543.dist-info/METADATA +0 -676
- flock_core-0.4.543.dist-info/RECORD +0 -572
- flock_core-0.4.543.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.4.543.dist-info → flock_core-0.5.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""DuckDB exporter for OpenTelemetry spans - optimized for analytical queries."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import duckdb
|
|
7
|
+
from opentelemetry.sdk.trace.export import SpanExportResult
|
|
8
|
+
from opentelemetry.trace import Status, StatusCode
|
|
9
|
+
|
|
10
|
+
from flock.logging.telemetry_exporter.base_exporter import TelemetryExporter
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DuckDBSpanExporter(TelemetryExporter):
|
|
14
|
+
"""Export spans to DuckDB for fast analytical queries.
|
|
15
|
+
|
|
16
|
+
DuckDB is a columnar analytical database optimized for OLAP workloads,
|
|
17
|
+
making it 10-100x faster than SQLite for trace analytics like:
|
|
18
|
+
- Aggregations (avg/p95/p99 duration)
|
|
19
|
+
- Time-range queries
|
|
20
|
+
- Service/operation filtering
|
|
21
|
+
- Complex analytical queries
|
|
22
|
+
|
|
23
|
+
The database is a single file with zero configuration required.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, dir: str, db_name: str = "traces.duckdb", ttl_days: int | None = None):
|
|
27
|
+
"""Initialize the DuckDB exporter.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
dir: Directory where the database file will be created
|
|
31
|
+
db_name: Name of the DuckDB file (default: traces.duckdb)
|
|
32
|
+
ttl_days: Delete traces older than this many days (default: None = keep forever)
|
|
33
|
+
"""
|
|
34
|
+
super().__init__()
|
|
35
|
+
self.telemetry_path = Path(dir)
|
|
36
|
+
self.telemetry_path.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
self.db_path = self.telemetry_path / db_name
|
|
38
|
+
self.ttl_days = ttl_days
|
|
39
|
+
|
|
40
|
+
# Initialize database and create schema
|
|
41
|
+
self._init_database()
|
|
42
|
+
|
|
43
|
+
def _init_database(self):
|
|
44
|
+
"""Create the spans table if it doesn't exist."""
|
|
45
|
+
with duckdb.connect(str(self.db_path)) as conn:
|
|
46
|
+
conn.execute("""
|
|
47
|
+
CREATE TABLE IF NOT EXISTS spans (
|
|
48
|
+
trace_id VARCHAR NOT NULL,
|
|
49
|
+
span_id VARCHAR PRIMARY KEY,
|
|
50
|
+
parent_id VARCHAR,
|
|
51
|
+
name VARCHAR NOT NULL,
|
|
52
|
+
service VARCHAR,
|
|
53
|
+
operation VARCHAR,
|
|
54
|
+
kind VARCHAR,
|
|
55
|
+
start_time BIGINT NOT NULL,
|
|
56
|
+
end_time BIGINT NOT NULL,
|
|
57
|
+
duration_ms DOUBLE NOT NULL,
|
|
58
|
+
status_code VARCHAR NOT NULL,
|
|
59
|
+
status_description VARCHAR,
|
|
60
|
+
attributes JSON,
|
|
61
|
+
events JSON,
|
|
62
|
+
links JSON,
|
|
63
|
+
resource JSON,
|
|
64
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
65
|
+
)
|
|
66
|
+
""")
|
|
67
|
+
|
|
68
|
+
# Create indexes for common query patterns
|
|
69
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_trace_id ON spans(trace_id)")
|
|
70
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_service ON spans(service)")
|
|
71
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_start_time ON spans(start_time)")
|
|
72
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_name ON spans(name)")
|
|
73
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_created_at ON spans(created_at)")
|
|
74
|
+
|
|
75
|
+
# Cleanup old traces if TTL is configured
|
|
76
|
+
if self.ttl_days is not None:
|
|
77
|
+
self._cleanup_old_traces()
|
|
78
|
+
|
|
79
|
+
def _cleanup_old_traces(self):
|
|
80
|
+
"""Delete traces older than TTL_DAYS.
|
|
81
|
+
|
|
82
|
+
This runs on exporter initialization to keep the database size manageable.
|
|
83
|
+
"""
|
|
84
|
+
if self.ttl_days is None:
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
with duckdb.connect(str(self.db_path)) as conn:
|
|
89
|
+
# Delete spans older than TTL
|
|
90
|
+
# Note: DuckDB doesn't support ? placeholders inside INTERVAL expressions
|
|
91
|
+
# Safe: ttl_days is guaranteed to be an int, no injection risk
|
|
92
|
+
query = f"""
|
|
93
|
+
DELETE FROM spans
|
|
94
|
+
WHERE created_at < CURRENT_TIMESTAMP - INTERVAL '{self.ttl_days} DAYS'
|
|
95
|
+
""" # nosec B608
|
|
96
|
+
result = conn.execute(query)
|
|
97
|
+
|
|
98
|
+
deleted_count = result.fetchall()[0][0] if result else 0
|
|
99
|
+
|
|
100
|
+
if deleted_count > 0:
|
|
101
|
+
print(
|
|
102
|
+
f"[DuckDB TTL] Deleted {deleted_count} spans older than {self.ttl_days} days"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
except Exception as e:
|
|
106
|
+
print(f"[DuckDB TTL] Error cleaning up old traces: {e}")
|
|
107
|
+
|
|
108
|
+
def _span_to_record(self, span):
|
|
109
|
+
"""Convert a ReadableSpan to a database record."""
|
|
110
|
+
context = span.get_span_context()
|
|
111
|
+
status = span.status or Status(StatusCode.UNSET)
|
|
112
|
+
|
|
113
|
+
# Extract service and operation from span name
|
|
114
|
+
# Format: "ServiceName.operation_name"
|
|
115
|
+
parts = span.name.split(".", 1)
|
|
116
|
+
service = parts[0] if len(parts) > 0 else "unknown"
|
|
117
|
+
operation = parts[1] if len(parts) > 1 else span.name
|
|
118
|
+
|
|
119
|
+
# Calculate duration in milliseconds
|
|
120
|
+
duration_ms = (span.end_time - span.start_time) / 1_000_000
|
|
121
|
+
|
|
122
|
+
# Serialize complex fields to JSON
|
|
123
|
+
attributes_json = json.dumps(dict(span.attributes or {}))
|
|
124
|
+
events_json = json.dumps(
|
|
125
|
+
[
|
|
126
|
+
{
|
|
127
|
+
"name": event.name,
|
|
128
|
+
"timestamp": event.timestamp,
|
|
129
|
+
"attributes": dict(event.attributes or {}),
|
|
130
|
+
}
|
|
131
|
+
for event in span.events
|
|
132
|
+
]
|
|
133
|
+
)
|
|
134
|
+
links_json = json.dumps(
|
|
135
|
+
[
|
|
136
|
+
{
|
|
137
|
+
"context": {
|
|
138
|
+
"trace_id": format(link.context.trace_id, "032x"),
|
|
139
|
+
"span_id": format(link.context.span_id, "016x"),
|
|
140
|
+
},
|
|
141
|
+
"attributes": dict(link.attributes or {}),
|
|
142
|
+
}
|
|
143
|
+
for link in span.links
|
|
144
|
+
]
|
|
145
|
+
)
|
|
146
|
+
resource_json = json.dumps(dict(span.resource.attributes.items()))
|
|
147
|
+
|
|
148
|
+
# Get parent span ID if exists
|
|
149
|
+
parent_id = None
|
|
150
|
+
if span.parent and span.parent.span_id != 0:
|
|
151
|
+
parent_id = format(span.parent.span_id, "016x")
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
"trace_id": format(context.trace_id, "032x"),
|
|
155
|
+
"span_id": format(context.span_id, "016x"),
|
|
156
|
+
"parent_id": parent_id,
|
|
157
|
+
"name": span.name,
|
|
158
|
+
"service": service,
|
|
159
|
+
"operation": operation,
|
|
160
|
+
"kind": span.kind.name if span.kind else None,
|
|
161
|
+
"start_time": span.start_time,
|
|
162
|
+
"end_time": span.end_time,
|
|
163
|
+
"duration_ms": duration_ms,
|
|
164
|
+
"status_code": status.status_code.name,
|
|
165
|
+
"status_description": status.description,
|
|
166
|
+
"attributes": attributes_json,
|
|
167
|
+
"events": events_json,
|
|
168
|
+
"links": links_json,
|
|
169
|
+
"resource": resource_json,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
def export(self, spans):
|
|
173
|
+
"""Export spans to DuckDB."""
|
|
174
|
+
try:
|
|
175
|
+
with duckdb.connect(str(self.db_path)) as conn:
|
|
176
|
+
for span in spans:
|
|
177
|
+
record = self._span_to_record(span)
|
|
178
|
+
|
|
179
|
+
# Insert span record
|
|
180
|
+
conn.execute(
|
|
181
|
+
"""
|
|
182
|
+
INSERT OR REPLACE INTO spans (
|
|
183
|
+
trace_id, span_id, parent_id, name, service, operation,
|
|
184
|
+
kind, start_time, end_time, duration_ms,
|
|
185
|
+
status_code, status_description,
|
|
186
|
+
attributes, events, links, resource
|
|
187
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
188
|
+
""",
|
|
189
|
+
(
|
|
190
|
+
record["trace_id"],
|
|
191
|
+
record["span_id"],
|
|
192
|
+
record["parent_id"],
|
|
193
|
+
record["name"],
|
|
194
|
+
record["service"],
|
|
195
|
+
record["operation"],
|
|
196
|
+
record["kind"],
|
|
197
|
+
record["start_time"],
|
|
198
|
+
record["end_time"],
|
|
199
|
+
record["duration_ms"],
|
|
200
|
+
record["status_code"],
|
|
201
|
+
record["status_description"],
|
|
202
|
+
record["attributes"],
|
|
203
|
+
record["events"],
|
|
204
|
+
record["links"],
|
|
205
|
+
record["resource"],
|
|
206
|
+
),
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
return SpanExportResult.SUCCESS
|
|
210
|
+
except Exception as e:
|
|
211
|
+
print(f"Error exporting spans to DuckDB: {e}")
|
|
212
|
+
return SpanExportResult.FAILURE
|
|
213
|
+
|
|
214
|
+
def shutdown(self) -> None:
|
|
215
|
+
"""Cleanup resources."""
|
|
216
|
+
# DuckDB connections are managed per-transaction, no cleanup needed
|
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
|
|
5
|
+
# with workflow.unsafe.imports_passed_through():
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
5
8
|
from opentelemetry.sdk.trace.export import SpanExportResult
|
|
6
9
|
from opentelemetry.trace import Status, StatusCode
|
|
7
|
-
from temporalio import workflow
|
|
8
10
|
|
|
9
|
-
from
|
|
11
|
+
# from temporalio import workflow
|
|
12
|
+
from flock.logging.telemetry_exporter.base_exporter import (
|
|
10
13
|
TelemetryExporter,
|
|
11
14
|
)
|
|
12
15
|
|
|
13
|
-
with workflow.unsafe.imports_passed_through():
|
|
14
|
-
from pathlib import Path
|
|
15
|
-
|
|
16
16
|
|
|
17
17
|
class FileSpanExporter(TelemetryExporter):
|
|
18
18
|
"""A simple exporter that writes span data as JSON lines into a file."""
|
|
@@ -29,7 +29,7 @@ class FileSpanExporter(TelemetryExporter):
|
|
|
29
29
|
context = span.get_span_context()
|
|
30
30
|
status = span.status or Status(StatusCode.UNSET)
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
result = {
|
|
33
33
|
"name": span.name,
|
|
34
34
|
"context": {
|
|
35
35
|
"trace_id": format(context.trace_id, "032x"),
|
|
@@ -63,12 +63,15 @@ class FileSpanExporter(TelemetryExporter):
|
|
|
63
63
|
}
|
|
64
64
|
for link in span.links
|
|
65
65
|
],
|
|
66
|
-
"resource":
|
|
67
|
-
attr_key: attr_value
|
|
68
|
-
for attr_key, attr_value in span.resource.attributes.items()
|
|
69
|
-
},
|
|
66
|
+
"resource": dict(span.resource.attributes.items()),
|
|
70
67
|
}
|
|
71
68
|
|
|
69
|
+
# Add parent_id if this span has a parent
|
|
70
|
+
if span.parent and span.parent.span_id != 0:
|
|
71
|
+
result["parent_id"] = format(span.parent.span_id, "016x")
|
|
72
|
+
|
|
73
|
+
return result
|
|
74
|
+
|
|
72
75
|
def export(self, spans):
|
|
73
76
|
"""Write spans to a log file."""
|
|
74
77
|
try:
|
|
@@ -7,7 +7,7 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
from opentelemetry.sdk.trace.export import SpanExportResult
|
|
9
9
|
|
|
10
|
-
from flock.
|
|
10
|
+
from flock.logging.telemetry_exporter.base_exporter import (
|
|
11
11
|
TelemetryExporter,
|
|
12
12
|
)
|
|
13
13
|
|
|
@@ -77,7 +77,7 @@ class SqliteTelemetryExporter(TelemetryExporter):
|
|
|
77
77
|
trace_id = format(span.context.trace_id, "032x")
|
|
78
78
|
cursor.execute(
|
|
79
79
|
"""
|
|
80
|
-
INSERT OR REPLACE INTO spans
|
|
80
|
+
INSERT OR REPLACE INTO spans
|
|
81
81
|
(id, name, trace_id, span_id, start_time, end_time, attributes, status)
|
|
82
82
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
83
83
|
""",
|
|
@@ -100,4 +100,3 @@ class SqliteTelemetryExporter(TelemetryExporter):
|
|
|
100
100
|
|
|
101
101
|
def shutdown(self) -> None:
|
|
102
102
|
"""Cleanup resources."""
|
|
103
|
-
pass
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"""A decorator that wraps a function in an OpenTelemetry span and logs its inputs, outputs, and exceptions."""
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import inspect
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
from opentelemetry import trace
|
|
8
|
+
from opentelemetry.trace import Status, StatusCode
|
|
9
|
+
|
|
10
|
+
from flock.logging.logging import get_logger
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
logger = get_logger("tools")
|
|
14
|
+
tracer = trace.get_tracer(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Global trace filter configuration
|
|
18
|
+
class TraceFilterConfig:
|
|
19
|
+
"""Configuration for filtering which operations get traced."""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self.services: set[str] | None = None # Whitelist: only trace these services
|
|
23
|
+
self.ignore_operations: set[str] = set() # Blacklist: never trace these operations
|
|
24
|
+
|
|
25
|
+
def should_trace(self, service: str, operation: str) -> bool:
|
|
26
|
+
"""Check if an operation should be traced based on filters.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
service: Service name (e.g., "Flock", "Agent")
|
|
30
|
+
operation: Full operation name (e.g., "Flock.publish")
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
True if should trace, False otherwise
|
|
34
|
+
"""
|
|
35
|
+
# Check blacklist first (highest priority)
|
|
36
|
+
if operation in self.ignore_operations:
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
# Check whitelist if configured
|
|
40
|
+
if self.services is not None:
|
|
41
|
+
service_lower = service.lower()
|
|
42
|
+
if service_lower not in self.services:
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Global instance
|
|
49
|
+
_trace_filter_config = TraceFilterConfig()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _serialize_value(value, max_depth=10, current_depth=0):
|
|
53
|
+
"""Serialize a value to JSON-compatible format for span attributes.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
value: The value to serialize
|
|
57
|
+
max_depth: Maximum recursion depth to prevent infinite loops
|
|
58
|
+
current_depth: Current recursion depth
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
A JSON-serializable representation of the value
|
|
62
|
+
"""
|
|
63
|
+
if current_depth >= max_depth:
|
|
64
|
+
return f"<max_depth_reached: {type(value).__name__}>"
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
# Handle None
|
|
68
|
+
if value is None:
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
# Handle primitives - these are already JSON-serializable
|
|
72
|
+
if isinstance(value, (str, int, float, bool)):
|
|
73
|
+
return value
|
|
74
|
+
|
|
75
|
+
# Handle lists/tuples
|
|
76
|
+
if isinstance(value, (list, tuple)):
|
|
77
|
+
return [_serialize_value(item, max_depth, current_depth + 1) for item in value]
|
|
78
|
+
|
|
79
|
+
# Handle dicts
|
|
80
|
+
if isinstance(value, dict):
|
|
81
|
+
return {
|
|
82
|
+
str(k): _serialize_value(v, max_depth, current_depth + 1) for k, v in value.items()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# Handle sets
|
|
86
|
+
if isinstance(value, set):
|
|
87
|
+
return [_serialize_value(item, max_depth, current_depth + 1) for item in value]
|
|
88
|
+
|
|
89
|
+
# For custom objects with __dict__, serialize their attributes
|
|
90
|
+
if hasattr(value, "__dict__"):
|
|
91
|
+
class_name = value.__class__.__name__
|
|
92
|
+
try:
|
|
93
|
+
obj_dict = {}
|
|
94
|
+
for k, v in value.__dict__.items():
|
|
95
|
+
# Skip private attributes and methods
|
|
96
|
+
if k.startswith("_"):
|
|
97
|
+
continue
|
|
98
|
+
# Skip methods and callables
|
|
99
|
+
if callable(v):
|
|
100
|
+
continue
|
|
101
|
+
try:
|
|
102
|
+
obj_dict[k] = _serialize_value(v, max_depth, current_depth + 1)
|
|
103
|
+
except Exception:
|
|
104
|
+
obj_dict[k] = f"<error serializing {k}>"
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
"__class__": class_name,
|
|
108
|
+
"__module__": value.__class__.__module__,
|
|
109
|
+
**obj_dict,
|
|
110
|
+
}
|
|
111
|
+
except Exception as e:
|
|
112
|
+
return {"__class__": class_name, "__error__": str(e)}
|
|
113
|
+
|
|
114
|
+
# For objects with a useful string representation
|
|
115
|
+
result = str(value)
|
|
116
|
+
# If the string is too long (> 5000 chars), truncate it
|
|
117
|
+
if len(result) > 5000:
|
|
118
|
+
return result[:5000] + "... (string truncated at 5000 chars)"
|
|
119
|
+
return result
|
|
120
|
+
|
|
121
|
+
except Exception as e:
|
|
122
|
+
# If all else fails, return type information with error
|
|
123
|
+
return {"__type__": type(value).__name__, "__error__": str(e)}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _extract_span_attributes(func, args, kwargs):
|
|
127
|
+
"""Extract useful attributes from function arguments for OTEL spans.
|
|
128
|
+
|
|
129
|
+
Returns a dict of attributes and a display name for the span.
|
|
130
|
+
"""
|
|
131
|
+
attributes = {}
|
|
132
|
+
span_name = func.__name__
|
|
133
|
+
|
|
134
|
+
# Try to get class name if this is a method
|
|
135
|
+
if args and hasattr(args[0], "__class__"):
|
|
136
|
+
obj = args[0]
|
|
137
|
+
class_name = obj.__class__.__name__
|
|
138
|
+
span_name = f"{class_name}.{func.__name__}"
|
|
139
|
+
attributes["class"] = class_name
|
|
140
|
+
|
|
141
|
+
# Extract agent-specific attributes
|
|
142
|
+
if hasattr(obj, "name"):
|
|
143
|
+
attributes["agent.name"] = str(obj.name)
|
|
144
|
+
if hasattr(obj, "description"):
|
|
145
|
+
attributes["agent.description"] = str(obj.description)[:200] # Truncate
|
|
146
|
+
|
|
147
|
+
# Extract context attributes (correlation_id, task_id)
|
|
148
|
+
for arg_name, arg_value in kwargs.items():
|
|
149
|
+
if arg_name == "ctx" and hasattr(arg_value, "correlation_id"):
|
|
150
|
+
if arg_value.correlation_id:
|
|
151
|
+
attributes["correlation_id"] = str(arg_value.correlation_id)
|
|
152
|
+
if hasattr(arg_value, "task_id"):
|
|
153
|
+
attributes["task_id"] = str(arg_value.task_id)
|
|
154
|
+
|
|
155
|
+
# Check positional args for Context
|
|
156
|
+
for arg in args[1:]: # Skip self
|
|
157
|
+
if hasattr(arg, "correlation_id"):
|
|
158
|
+
if arg.correlation_id:
|
|
159
|
+
attributes["correlation_id"] = str(arg.correlation_id)
|
|
160
|
+
if hasattr(arg, "task_id"):
|
|
161
|
+
attributes["task_id"] = str(arg.task_id)
|
|
162
|
+
break
|
|
163
|
+
|
|
164
|
+
# Add function metadata
|
|
165
|
+
attributes["function"] = func.__name__
|
|
166
|
+
attributes["module"] = func.__module__
|
|
167
|
+
|
|
168
|
+
# Capture input arguments (skip 'self' for methods)
|
|
169
|
+
try:
|
|
170
|
+
sig = inspect.signature(func)
|
|
171
|
+
bound_args = sig.bind(*args, **kwargs)
|
|
172
|
+
bound_args.apply_defaults()
|
|
173
|
+
|
|
174
|
+
# Serialize arguments
|
|
175
|
+
for param_name, param_value in bound_args.arguments.items():
|
|
176
|
+
# Skip 'self' and 'cls'
|
|
177
|
+
if param_name in ("self", "cls"):
|
|
178
|
+
continue
|
|
179
|
+
# Serialize the argument value to JSON-compatible format
|
|
180
|
+
serialized = _serialize_value(param_value)
|
|
181
|
+
# Convert to JSON string for OTEL attribute storage
|
|
182
|
+
try:
|
|
183
|
+
attributes[f"input.{param_name}"] = json.dumps(serialized, default=str)
|
|
184
|
+
except Exception:
|
|
185
|
+
# If JSON serialization fails, use string representation
|
|
186
|
+
attributes[f"input.{param_name}"] = str(serialized)
|
|
187
|
+
except Exception as e:
|
|
188
|
+
# If we can't capture inputs, just note that
|
|
189
|
+
attributes["input.error"] = str(e)
|
|
190
|
+
|
|
191
|
+
return attributes, span_name
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def traced_and_logged(func):
|
|
195
|
+
"""A decorator that wraps a function in an OpenTelemetry span.
|
|
196
|
+
|
|
197
|
+
Creates proper parent-child span relationships and extracts relevant
|
|
198
|
+
attributes for observability in Grafana/Jaeger.
|
|
199
|
+
|
|
200
|
+
Automatically extracts:
|
|
201
|
+
- Agent name and description
|
|
202
|
+
- Correlation ID and task ID from Context
|
|
203
|
+
- Class and method names
|
|
204
|
+
- Exception information
|
|
205
|
+
|
|
206
|
+
Supports both synchronous and asynchronous functions.
|
|
207
|
+
"""
|
|
208
|
+
if inspect.iscoroutinefunction(func):
|
|
209
|
+
|
|
210
|
+
@functools.wraps(func)
|
|
211
|
+
async def async_wrapper(*args, **kwargs):
|
|
212
|
+
attributes, span_name = _extract_span_attributes(func, args, kwargs)
|
|
213
|
+
|
|
214
|
+
# Check if we should trace this operation
|
|
215
|
+
service_name = span_name.split(".")[0] if "." in span_name else span_name
|
|
216
|
+
if not _trace_filter_config.should_trace(service_name, span_name):
|
|
217
|
+
# Skip tracing, just call the function
|
|
218
|
+
return await func(*args, **kwargs)
|
|
219
|
+
|
|
220
|
+
with tracer.start_as_current_span(span_name) as span:
|
|
221
|
+
# Set all extracted attributes
|
|
222
|
+
for key, value in attributes.items():
|
|
223
|
+
span.set_attribute(key, value)
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
result = await func(*args, **kwargs)
|
|
227
|
+
|
|
228
|
+
# Capture output value as JSON
|
|
229
|
+
try:
|
|
230
|
+
serialized_result = _serialize_value(result)
|
|
231
|
+
span.set_attribute(
|
|
232
|
+
"output.value", json.dumps(serialized_result, default=str)
|
|
233
|
+
)
|
|
234
|
+
except Exception as e:
|
|
235
|
+
span.set_attribute("output.value", str(result))
|
|
236
|
+
span.set_attribute("output.serialization_error", str(e))
|
|
237
|
+
|
|
238
|
+
# Set result type and metadata
|
|
239
|
+
if result is not None:
|
|
240
|
+
span.set_attribute("output.type", type(result).__name__)
|
|
241
|
+
if hasattr(result, "__len__"):
|
|
242
|
+
try:
|
|
243
|
+
span.set_attribute("output.length", len(result))
|
|
244
|
+
except TypeError:
|
|
245
|
+
pass
|
|
246
|
+
|
|
247
|
+
span.set_status(Status(StatusCode.OK))
|
|
248
|
+
logger.debug(f"{span_name} executed successfully")
|
|
249
|
+
return result
|
|
250
|
+
|
|
251
|
+
except Exception as e:
|
|
252
|
+
span.set_status(Status(StatusCode.ERROR, str(e)))
|
|
253
|
+
span.record_exception(e)
|
|
254
|
+
logger.exception(f"Error in {span_name}", error=str(e))
|
|
255
|
+
raise
|
|
256
|
+
|
|
257
|
+
return async_wrapper
|
|
258
|
+
|
|
259
|
+
@functools.wraps(func)
|
|
260
|
+
def wrapper(*args, **kwargs):
|
|
261
|
+
attributes, span_name = _extract_span_attributes(func, args, kwargs)
|
|
262
|
+
|
|
263
|
+
# Check if we should trace this operation
|
|
264
|
+
service_name = span_name.split(".")[0] if "." in span_name else span_name
|
|
265
|
+
if not _trace_filter_config.should_trace(service_name, span_name):
|
|
266
|
+
# Skip tracing, just call the function
|
|
267
|
+
return func(*args, **kwargs)
|
|
268
|
+
|
|
269
|
+
with tracer.start_as_current_span(span_name) as span:
|
|
270
|
+
# Set all extracted attributes
|
|
271
|
+
for key, value in attributes.items():
|
|
272
|
+
span.set_attribute(key, value)
|
|
273
|
+
|
|
274
|
+
try:
|
|
275
|
+
result = func(*args, **kwargs)
|
|
276
|
+
|
|
277
|
+
# Capture output value as JSON
|
|
278
|
+
try:
|
|
279
|
+
serialized_result = _serialize_value(result)
|
|
280
|
+
span.set_attribute("output.value", json.dumps(serialized_result, default=str))
|
|
281
|
+
except Exception as e:
|
|
282
|
+
span.set_attribute("output.value", str(result))
|
|
283
|
+
span.set_attribute("output.serialization_error", str(e))
|
|
284
|
+
|
|
285
|
+
# Set result type and metadata
|
|
286
|
+
if result is not None:
|
|
287
|
+
span.set_attribute("output.type", type(result).__name__)
|
|
288
|
+
if hasattr(result, "__len__"):
|
|
289
|
+
try:
|
|
290
|
+
span.set_attribute("output.length", len(result))
|
|
291
|
+
except TypeError:
|
|
292
|
+
pass
|
|
293
|
+
|
|
294
|
+
span.set_status(Status(StatusCode.OK))
|
|
295
|
+
logger.debug(f"{span_name} executed successfully")
|
|
296
|
+
return result
|
|
297
|
+
|
|
298
|
+
except Exception as e:
|
|
299
|
+
span.set_status(Status(StatusCode.ERROR, str(e)))
|
|
300
|
+
span.record_exception(e)
|
|
301
|
+
logger.exception(f"Error in {span_name}", error=str(e))
|
|
302
|
+
raise
|
|
303
|
+
|
|
304
|
+
return wrapper
|
flock/mcp/__init__.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""MCP (Model Context Protocol) Integration for Flock-Flow.
|
|
2
|
+
|
|
3
|
+
This package provides integration with MCP servers, enabling agents to
|
|
4
|
+
dynamically discover and use external tools following the Model Context Protocol.
|
|
5
|
+
|
|
6
|
+
Architecture Decisions:
|
|
7
|
+
- AD001: Two-Level Architecture (orchestrator + agent)
|
|
8
|
+
- AD003: Tool Namespacing ({server}__{tool})
|
|
9
|
+
- AD004: Per-(agent_id, run_id) Connection Isolation
|
|
10
|
+
- AD005: Lazy Connection Establishment
|
|
11
|
+
- AD007: Graceful Degradation on MCP Failures
|
|
12
|
+
|
|
13
|
+
Key Components:
|
|
14
|
+
- FlockMCPConfiguration: Server configuration
|
|
15
|
+
- FlockMCPClient: Individual server connection
|
|
16
|
+
- FlockMCPClientManager: Connection pooling and lifecycle
|
|
17
|
+
- FlockMCPTool: MCP tool wrapper compatible with DSPy
|
|
18
|
+
|
|
19
|
+
Example Usage:
|
|
20
|
+
```python
|
|
21
|
+
from flock import Flock
|
|
22
|
+
from flock.mcp import StdioServerParameters
|
|
23
|
+
|
|
24
|
+
# Create orchestrator
|
|
25
|
+
orchestrator = Flock()
|
|
26
|
+
|
|
27
|
+
# Register MCP server
|
|
28
|
+
orchestrator.add_mcp(
|
|
29
|
+
name="filesystem",
|
|
30
|
+
connection_params=StdioServerParameters(
|
|
31
|
+
command="uvx",
|
|
32
|
+
args=["mcp-server-filesystem", "/tmp"]
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Build agent with MCP access
|
|
37
|
+
agent = (
|
|
38
|
+
orchestrator.agent("file_agent")
|
|
39
|
+
.with_mcps(["filesystem"])
|
|
40
|
+
.build()
|
|
41
|
+
)
|
|
42
|
+
```
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from flock.mcp.client import FlockMCPClient
|
|
46
|
+
from flock.mcp.config import (
|
|
47
|
+
FlockMCPCachingConfiguration,
|
|
48
|
+
FlockMCPCallbackConfiguration,
|
|
49
|
+
FlockMCPConfiguration,
|
|
50
|
+
FlockMCPConnectionConfiguration,
|
|
51
|
+
FlockMCPFeatureConfiguration,
|
|
52
|
+
)
|
|
53
|
+
from flock.mcp.manager import FlockMCPClientManager
|
|
54
|
+
from flock.mcp.tool import FlockMCPTool
|
|
55
|
+
from flock.mcp.types import (
|
|
56
|
+
FlockListRootsMCPCallback,
|
|
57
|
+
FlockLoggingMCPCallback,
|
|
58
|
+
FlockMessageHandlerMCPCallback,
|
|
59
|
+
FlockSamplingMCPCallback,
|
|
60
|
+
MCPRoot,
|
|
61
|
+
ServerParameters,
|
|
62
|
+
SseServerParameters,
|
|
63
|
+
StdioServerParameters,
|
|
64
|
+
StreamableHttpServerParameters,
|
|
65
|
+
WebsocketServerParameters,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
__all__ = [
|
|
70
|
+
"FlockListRootsMCPCallback",
|
|
71
|
+
"FlockLoggingMCPCallback",
|
|
72
|
+
"FlockMCPCachingConfiguration",
|
|
73
|
+
"FlockMCPCallbackConfiguration",
|
|
74
|
+
# Client and Manager
|
|
75
|
+
"FlockMCPClient",
|
|
76
|
+
"FlockMCPClientManager",
|
|
77
|
+
# Configuration
|
|
78
|
+
"FlockMCPConfiguration",
|
|
79
|
+
"FlockMCPConnectionConfiguration",
|
|
80
|
+
"FlockMCPFeatureConfiguration",
|
|
81
|
+
"FlockMCPTool",
|
|
82
|
+
"FlockMessageHandlerMCPCallback",
|
|
83
|
+
"FlockSamplingMCPCallback",
|
|
84
|
+
"MCPRoot",
|
|
85
|
+
# Types
|
|
86
|
+
"ServerParameters",
|
|
87
|
+
"SseServerParameters",
|
|
88
|
+
"StdioServerParameters",
|
|
89
|
+
"StreamableHttpServerParameters",
|
|
90
|
+
"WebsocketServerParameters",
|
|
91
|
+
]
|