flock-core 0.5.0b28__py3-none-any.whl → 0.5.56b0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/__init__.py +12 -217
- flock/agent.py +678 -0
- flock/api/themes.py +71 -0
- flock/artifacts.py +79 -0
- flock/cli.py +75 -0
- flock/components.py +173 -0
- flock/dashboard/__init__.py +28 -0
- flock/dashboard/collector.py +283 -0
- flock/dashboard/events.py +182 -0
- flock/dashboard/launcher.py +230 -0
- flock/dashboard/service.py +537 -0
- flock/dashboard/websocket.py +235 -0
- flock/engines/__init__.py +6 -0
- flock/engines/dspy_engine.py +856 -0
- flock/examples.py +128 -0
- flock/{core/util → helper}/cli_helper.py +4 -3
- flock/{core/logging → logging}/__init__.py +2 -3
- flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
- flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
- flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
- flock/{core/logging → logging}/logging.py +77 -61
- flock/{core/logging → logging}/telemetry.py +20 -26
- flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
- flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
- flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
- flock/{core/logging → logging}/trace_and_logged.py +20 -24
- flock/mcp/__init__.py +91 -0
- flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
- flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
- flock/mcp/manager.py +255 -0
- flock/mcp/servers/sse/__init__.py +1 -1
- flock/mcp/servers/sse/flock_sse_server.py +11 -53
- flock/mcp/servers/stdio/__init__.py +1 -1
- flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
- flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
- flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
- flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
- flock/mcp/types/__init__.py +42 -0
- flock/{core/mcp → mcp}/types/callbacks.py +9 -15
- flock/{core/mcp → mcp}/types/factories.py +7 -6
- flock/{core/mcp → mcp}/types/handlers.py +13 -18
- flock/{core/mcp → mcp}/types/types.py +70 -74
- flock/{core/mcp → mcp}/util/helpers.py +1 -1
- flock/orchestrator.py +645 -0
- flock/registry.py +148 -0
- flock/runtime.py +262 -0
- flock/service.py +140 -0
- flock/store.py +69 -0
- flock/subscription.py +111 -0
- flock/themes/andromeda.toml +1 -1
- flock/themes/apple-system-colors.toml +1 -1
- flock/themes/arcoiris.toml +1 -1
- flock/themes/atomonelight.toml +1 -1
- flock/themes/ayu copy.toml +1 -1
- flock/themes/ayu-light.toml +1 -1
- flock/themes/belafonte-day.toml +1 -1
- flock/themes/belafonte-night.toml +1 -1
- flock/themes/blulocodark.toml +1 -1
- flock/themes/breeze.toml +1 -1
- flock/themes/broadcast.toml +1 -1
- flock/themes/brogrammer.toml +1 -1
- flock/themes/builtin-dark.toml +1 -1
- flock/themes/builtin-pastel-dark.toml +1 -1
- flock/themes/catppuccin-latte.toml +1 -1
- flock/themes/catppuccin-macchiato.toml +1 -1
- flock/themes/catppuccin-mocha.toml +1 -1
- flock/themes/cga.toml +1 -1
- flock/themes/chalk.toml +1 -1
- flock/themes/ciapre.toml +1 -1
- flock/themes/coffee-theme.toml +1 -1
- flock/themes/cyberpunkscarletprotocol.toml +1 -1
- flock/themes/dark+.toml +1 -1
- flock/themes/darkermatrix.toml +1 -1
- flock/themes/darkside.toml +1 -1
- flock/themes/desert.toml +1 -1
- flock/themes/django.toml +1 -1
- flock/themes/djangosmooth.toml +1 -1
- flock/themes/doomone.toml +1 -1
- flock/themes/dotgov.toml +1 -1
- flock/themes/dracula+.toml +1 -1
- flock/themes/duckbones.toml +1 -1
- flock/themes/encom.toml +1 -1
- flock/themes/espresso.toml +1 -1
- flock/themes/everblush.toml +1 -1
- flock/themes/fairyfloss.toml +1 -1
- flock/themes/fideloper.toml +1 -1
- flock/themes/fishtank.toml +1 -1
- flock/themes/flexoki-light.toml +1 -1
- flock/themes/floraverse.toml +1 -1
- flock/themes/framer.toml +1 -1
- flock/themes/galizur.toml +1 -1
- flock/themes/github.toml +1 -1
- flock/themes/grass.toml +1 -1
- flock/themes/grey-green.toml +1 -1
- flock/themes/gruvboxlight.toml +1 -1
- flock/themes/guezwhoz.toml +1 -1
- flock/themes/harper.toml +1 -1
- flock/themes/hax0r-blue.toml +1 -1
- flock/themes/hopscotch.256.toml +1 -1
- flock/themes/ic-green-ppl.toml +1 -1
- flock/themes/iceberg-dark.toml +1 -1
- flock/themes/japanesque.toml +1 -1
- flock/themes/jubi.toml +1 -1
- flock/themes/kibble.toml +1 -1
- flock/themes/kolorit.toml +1 -1
- flock/themes/kurokula.toml +1 -1
- flock/themes/materialdesigncolors.toml +1 -1
- flock/themes/matrix.toml +1 -1
- flock/themes/mellifluous.toml +1 -1
- flock/themes/midnight-in-mojave.toml +1 -1
- flock/themes/monokai-remastered.toml +1 -1
- flock/themes/monokai-soda.toml +1 -1
- flock/themes/neon.toml +1 -1
- flock/themes/neopolitan.toml +1 -1
- flock/themes/nord-light.toml +1 -1
- flock/themes/ocean.toml +1 -1
- flock/themes/onehalfdark.toml +1 -1
- flock/themes/onehalflight.toml +1 -1
- flock/themes/palenighthc.toml +1 -1
- flock/themes/paulmillr.toml +1 -1
- flock/themes/pencildark.toml +1 -1
- flock/themes/pnevma.toml +1 -1
- flock/themes/purple-rain.toml +1 -1
- flock/themes/purplepeter.toml +1 -1
- flock/themes/raycast-dark.toml +1 -1
- flock/themes/red-sands.toml +1 -1
- flock/themes/relaxed.toml +1 -1
- flock/themes/retro.toml +1 -1
- flock/themes/rose-pine.toml +1 -1
- flock/themes/royal.toml +1 -1
- flock/themes/ryuuko.toml +1 -1
- flock/themes/sakura.toml +1 -1
- flock/themes/scarlet-protocol.toml +1 -1
- flock/themes/seoulbones-dark.toml +1 -1
- flock/themes/shades-of-purple.toml +1 -1
- flock/themes/smyck.toml +1 -1
- flock/themes/softserver.toml +1 -1
- flock/themes/solarized-darcula.toml +1 -1
- flock/themes/square.toml +1 -1
- flock/themes/sugarplum.toml +1 -1
- flock/themes/thayer-bright.toml +1 -1
- flock/themes/tokyonight.toml +1 -1
- flock/themes/tomorrow.toml +1 -1
- flock/themes/ubuntu.toml +1 -1
- flock/themes/ultradark.toml +1 -1
- flock/themes/ultraviolent.toml +1 -1
- flock/themes/unikitty.toml +1 -1
- flock/themes/urple.toml +1 -1
- flock/themes/vesper.toml +1 -1
- flock/themes/vimbones.toml +1 -1
- flock/themes/wildcherry.toml +1 -1
- flock/themes/wilmersdorf.toml +1 -1
- flock/themes/wryan.toml +1 -1
- flock/themes/xcodedarkhc.toml +1 -1
- flock/themes/xcodelight.toml +1 -1
- flock/themes/zenbones-light.toml +1 -1
- flock/themes/zenwritten-dark.toml +1 -1
- flock/utilities.py +301 -0
- flock/{components/utility → utility}/output_utility_component.py +68 -53
- flock/visibility.py +107 -0
- flock_core-0.5.56b0.dist-info/METADATA +747 -0
- flock_core-0.5.56b0.dist-info/RECORD +398 -0
- flock_core-0.5.56b0.dist-info/entry_points.txt +2 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/licenses/LICENSE +1 -1
- flock/adapter/__init__.py +0 -14
- flock/adapter/azure_adapter.py +0 -68
- flock/adapter/chroma_adapter.py +0 -73
- flock/adapter/faiss_adapter.py +0 -97
- flock/adapter/pinecone_adapter.py +0 -51
- flock/adapter/vector_base.py +0 -47
- flock/cli/assets/release_notes.md +0 -140
- flock/cli/config.py +0 -8
- flock/cli/constants.py +0 -36
- flock/cli/create_agent.py +0 -1
- flock/cli/create_flock.py +0 -280
- flock/cli/execute_flock.py +0 -620
- flock/cli/load_agent.py +0 -1
- flock/cli/load_examples.py +0 -1
- flock/cli/load_flock.py +0 -192
- flock/cli/load_release_notes.py +0 -20
- flock/cli/loaded_flock_cli.py +0 -254
- flock/cli/manage_agents.py +0 -459
- flock/cli/registry_management.py +0 -889
- flock/cli/runner.py +0 -41
- flock/cli/settings.py +0 -857
- flock/cli/utils.py +0 -135
- flock/cli/view_results.py +0 -29
- flock/cli/yaml_editor.py +0 -396
- flock/components/__init__.py +0 -30
- flock/components/evaluation/__init__.py +0 -9
- flock/components/evaluation/declarative_evaluation_component.py +0 -606
- flock/components/routing/__init__.py +0 -15
- flock/components/routing/conditional_routing_component.py +0 -494
- flock/components/routing/default_routing_component.py +0 -103
- flock/components/routing/llm_routing_component.py +0 -206
- flock/components/utility/__init__.py +0 -22
- flock/components/utility/example_utility_component.py +0 -250
- flock/components/utility/feedback_utility_component.py +0 -206
- flock/components/utility/memory_utility_component.py +0 -550
- flock/components/utility/metrics_utility_component.py +0 -700
- flock/config.py +0 -61
- flock/core/__init__.py +0 -110
- flock/core/agent/__init__.py +0 -16
- flock/core/agent/default_agent.py +0 -216
- flock/core/agent/flock_agent_components.py +0 -104
- flock/core/agent/flock_agent_execution.py +0 -101
- flock/core/agent/flock_agent_integration.py +0 -260
- flock/core/agent/flock_agent_lifecycle.py +0 -186
- flock/core/agent/flock_agent_serialization.py +0 -381
- flock/core/api/__init__.py +0 -10
- flock/core/api/custom_endpoint.py +0 -45
- flock/core/api/endpoints.py +0 -254
- flock/core/api/main.py +0 -162
- flock/core/api/models.py +0 -97
- flock/core/api/run_store.py +0 -224
- flock/core/api/runner.py +0 -44
- flock/core/api/service.py +0 -214
- flock/core/component/__init__.py +0 -15
- flock/core/component/agent_component_base.py +0 -309
- flock/core/component/evaluation_component.py +0 -62
- flock/core/component/routing_component.py +0 -74
- flock/core/component/utility_component.py +0 -69
- flock/core/config/flock_agent_config.py +0 -58
- flock/core/config/scheduled_agent_config.py +0 -40
- flock/core/context/context.py +0 -213
- flock/core/context/context_manager.py +0 -37
- flock/core/context/context_vars.py +0 -10
- flock/core/evaluation/utils.py +0 -396
- flock/core/execution/batch_executor.py +0 -369
- flock/core/execution/evaluation_executor.py +0 -438
- flock/core/execution/local_executor.py +0 -31
- flock/core/execution/opik_executor.py +0 -103
- flock/core/execution/temporal_executor.py +0 -164
- flock/core/flock.py +0 -634
- flock/core/flock_agent.py +0 -336
- flock/core/flock_factory.py +0 -613
- flock/core/flock_scheduler.py +0 -166
- flock/core/flock_server_manager.py +0 -136
- flock/core/interpreter/python_interpreter.py +0 -689
- flock/core/mcp/__init__.py +0 -1
- flock/core/mcp/flock_mcp_server.py +0 -680
- flock/core/mcp/mcp_client_manager.py +0 -201
- flock/core/mcp/types/__init__.py +0 -1
- flock/core/mixin/dspy_integration.py +0 -403
- flock/core/mixin/prompt_parser.py +0 -125
- flock/core/orchestration/__init__.py +0 -15
- flock/core/orchestration/flock_batch_processor.py +0 -94
- flock/core/orchestration/flock_evaluator.py +0 -113
- flock/core/orchestration/flock_execution.py +0 -295
- flock/core/orchestration/flock_initialization.py +0 -149
- flock/core/orchestration/flock_server_manager.py +0 -67
- flock/core/orchestration/flock_web_server.py +0 -117
- flock/core/registry/__init__.py +0 -45
- flock/core/registry/agent_registry.py +0 -69
- flock/core/registry/callable_registry.py +0 -139
- flock/core/registry/component_discovery.py +0 -142
- flock/core/registry/component_registry.py +0 -64
- flock/core/registry/config_mapping.py +0 -64
- flock/core/registry/decorators.py +0 -137
- flock/core/registry/registry_hub.py +0 -205
- flock/core/registry/server_registry.py +0 -57
- flock/core/registry/type_registry.py +0 -86
- flock/core/serialization/__init__.py +0 -13
- flock/core/serialization/callable_registry.py +0 -52
- flock/core/serialization/flock_serializer.py +0 -832
- flock/core/serialization/json_encoder.py +0 -41
- flock/core/serialization/secure_serializer.py +0 -175
- flock/core/serialization/serializable.py +0 -342
- flock/core/serialization/serialization_utils.py +0 -412
- flock/core/util/file_path_utils.py +0 -223
- flock/core/util/hydrator.py +0 -309
- flock/core/util/input_resolver.py +0 -164
- flock/core/util/loader.py +0 -59
- flock/core/util/splitter.py +0 -219
- flock/di.py +0 -27
- flock/platform/docker_tools.py +0 -49
- flock/platform/jaeger_install.py +0 -86
- flock/webapp/__init__.py +0 -1
- flock/webapp/app/__init__.py +0 -0
- flock/webapp/app/api/__init__.py +0 -0
- flock/webapp/app/api/agent_management.py +0 -241
- flock/webapp/app/api/execution.py +0 -709
- flock/webapp/app/api/flock_management.py +0 -129
- flock/webapp/app/api/registry_viewer.py +0 -30
- flock/webapp/app/chat.py +0 -665
- flock/webapp/app/config.py +0 -104
- flock/webapp/app/dependencies.py +0 -117
- flock/webapp/app/main.py +0 -1070
- flock/webapp/app/middleware.py +0 -113
- flock/webapp/app/models_ui.py +0 -7
- flock/webapp/app/services/__init__.py +0 -0
- flock/webapp/app/services/feedback_file_service.py +0 -363
- flock/webapp/app/services/flock_service.py +0 -337
- flock/webapp/app/services/sharing_models.py +0 -81
- flock/webapp/app/services/sharing_store.py +0 -762
- flock/webapp/app/templates/theme_mapper.html +0 -326
- flock/webapp/app/theme_mapper.py +0 -812
- flock/webapp/app/utils.py +0 -85
- flock/webapp/run.py +0 -215
- flock/webapp/static/css/chat.css +0 -301
- flock/webapp/static/css/components.css +0 -167
- flock/webapp/static/css/header.css +0 -39
- flock/webapp/static/css/layout.css +0 -46
- flock/webapp/static/css/sidebar.css +0 -127
- flock/webapp/static/css/two-pane.css +0 -48
- flock/webapp/templates/base.html +0 -200
- flock/webapp/templates/chat.html +0 -152
- flock/webapp/templates/chat_settings.html +0 -19
- flock/webapp/templates/flock_editor.html +0 -16
- flock/webapp/templates/index.html +0 -12
- flock/webapp/templates/partials/_agent_detail_form.html +0 -93
- flock/webapp/templates/partials/_agent_list.html +0 -18
- flock/webapp/templates/partials/_agent_manager_view.html +0 -51
- flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
- flock/webapp/templates/partials/_chat_container.html +0 -15
- flock/webapp/templates/partials/_chat_messages.html +0 -57
- flock/webapp/templates/partials/_chat_settings_form.html +0 -85
- flock/webapp/templates/partials/_create_flock_form.html +0 -50
- flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
- flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
- flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
- flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
- flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
- flock/webapp/templates/partials/_env_vars_table.html +0 -23
- flock/webapp/templates/partials/_execution_form.html +0 -118
- flock/webapp/templates/partials/_execution_view_container.html +0 -28
- flock/webapp/templates/partials/_flock_file_list.html +0 -23
- flock/webapp/templates/partials/_flock_properties_form.html +0 -52
- flock/webapp/templates/partials/_flock_upload_form.html +0 -16
- flock/webapp/templates/partials/_header_flock_status.html +0 -5
- flock/webapp/templates/partials/_load_manager_view.html +0 -49
- flock/webapp/templates/partials/_registry_table.html +0 -25
- flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
- flock/webapp/templates/partials/_results_display.html +0 -78
- flock/webapp/templates/partials/_settings_env_content.html +0 -9
- flock/webapp/templates/partials/_settings_theme_content.html +0 -14
- flock/webapp/templates/partials/_settings_view.html +0 -36
- flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
- flock/webapp/templates/partials/_share_link_snippet.html +0 -35
- flock/webapp/templates/partials/_sidebar.html +0 -74
- flock/webapp/templates/partials/_streaming_results_container.html +0 -195
- flock/webapp/templates/partials/_structured_data_view.html +0 -40
- flock/webapp/templates/partials/_theme_preview.html +0 -36
- flock/webapp/templates/registry_viewer.html +0 -84
- flock/webapp/templates/shared_run_page.html +0 -140
- flock/workflow/__init__.py +0 -0
- flock/workflow/activities.py +0 -196
- flock/workflow/agent_activities.py +0 -24
- flock/workflow/agent_execution_activity.py +0 -202
- flock/workflow/flock_workflow.py +0 -214
- flock/workflow/temporal_config.py +0 -96
- flock/workflow/temporal_setup.py +0 -68
- flock_core-0.5.0b28.dist-info/METADATA +0 -274
- flock_core-0.5.0b28.dist-info/RECORD +0 -561
- flock_core-0.5.0b28.dist-info/entry_points.txt +0 -2
- /flock/{core/logging → logging}/formatters/themes.py +0 -0
- /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
- /flock/{core/mcp → mcp}/util/__init__.py +0 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/WHEEL +0 -0
|
@@ -1,550 +0,0 @@
|
|
|
1
|
-
# src/flock/components/utility/memory_utility_component.py
|
|
2
|
-
"""Enterprise-grade memory utility component for Flock using unified component architecture.
|
|
3
|
-
|
|
4
|
-
This component persists:
|
|
5
|
-
• vector embeddings in a Chroma collection (or any collection that
|
|
6
|
-
implements the same API)
|
|
7
|
-
• a concept graph in Neo4j/Memgraph (Cypher-compatible)
|
|
8
|
-
|
|
9
|
-
It follows the same life-cycle callbacks as the standard MemoryModule but
|
|
10
|
-
is designed for large-scale, concurrent deployments.
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
from __future__ import annotations
|
|
14
|
-
|
|
15
|
-
import asyncio
|
|
16
|
-
import json
|
|
17
|
-
import time
|
|
18
|
-
import uuid
|
|
19
|
-
from pathlib import Path
|
|
20
|
-
from typing import TYPE_CHECKING, Any, Literal
|
|
21
|
-
|
|
22
|
-
from neo4j import AsyncGraphDatabase
|
|
23
|
-
from opentelemetry import trace
|
|
24
|
-
from pydantic import Field
|
|
25
|
-
from sentence_transformers import SentenceTransformer
|
|
26
|
-
|
|
27
|
-
from flock.adapter.azure_adapter import AzureSearchAdapter
|
|
28
|
-
from flock.adapter.chroma_adapter import ChromaAdapter
|
|
29
|
-
from flock.adapter.faiss_adapter import FAISSAdapter
|
|
30
|
-
from flock.adapter.pinecone_adapter import PineconeAdapter
|
|
31
|
-
from flock.adapter.vector_base import VectorAdapter
|
|
32
|
-
from flock.core.component.agent_component_base import AgentComponentConfig
|
|
33
|
-
from flock.core.component.utility_component import UtilityComponent
|
|
34
|
-
from flock.core.context.context import FlockContext
|
|
35
|
-
from flock.core.logging.logging import get_logger
|
|
36
|
-
from flock.core.registry import flock_component
|
|
37
|
-
|
|
38
|
-
# Conditional import for MetricsUtilityComponent to avoid circular imports
|
|
39
|
-
if TYPE_CHECKING:
|
|
40
|
-
from flock.components.utility.metrics_utility_component import (
|
|
41
|
-
MetricsUtilityComponent,
|
|
42
|
-
)
|
|
43
|
-
from flock.core.flock_agent import FlockAgent
|
|
44
|
-
|
|
45
|
-
logger = get_logger("components.utility.memory")
|
|
46
|
-
tracer = trace.get_tracer(__name__)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class MemoryUtilityConfig(AgentComponentConfig):
|
|
50
|
-
"""Configuration for MemoryUtilityComponent."""
|
|
51
|
-
|
|
52
|
-
# ---------------------
|
|
53
|
-
# Vector store settings
|
|
54
|
-
# ---------------------
|
|
55
|
-
|
|
56
|
-
vector_backend: Literal["chroma", "pinecone", "azure", "faiss"] = Field(
|
|
57
|
-
default="chroma",
|
|
58
|
-
description="Which vector backend to use (chroma | pinecone | azure)",
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
# --- Chroma ---
|
|
62
|
-
chroma_path: str | None = Field(
|
|
63
|
-
default="./vector_store",
|
|
64
|
-
description="Disk path for Chroma persistent storage (if running embedded).",
|
|
65
|
-
)
|
|
66
|
-
chroma_collection: str = Field(
|
|
67
|
-
default="flock_memories", description="Chroma collection name"
|
|
68
|
-
)
|
|
69
|
-
chroma_host: str | None = Field(
|
|
70
|
-
default=None,
|
|
71
|
-
description="If provided, connect to a remote Chroma HTTP server at this host",
|
|
72
|
-
)
|
|
73
|
-
chroma_port: int = Field(
|
|
74
|
-
default=8000, description="Remote Chroma HTTP port"
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
# --- Pinecone ---
|
|
78
|
-
pinecone_api_key: str | None = Field(
|
|
79
|
-
default=None, description="Pinecone API key"
|
|
80
|
-
)
|
|
81
|
-
pinecone_env: str | None = Field(
|
|
82
|
-
default=None, description="Pinecone environment"
|
|
83
|
-
)
|
|
84
|
-
pinecone_index: str | None = Field(
|
|
85
|
-
default=None, description="Pinecone index name"
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
# --- Azure Cognitive Search ---
|
|
89
|
-
azure_search_endpoint: str | None = Field(
|
|
90
|
-
default=None,
|
|
91
|
-
description="Azure search endpoint (https://<service>.search.windows.net)",
|
|
92
|
-
)
|
|
93
|
-
azure_search_key: str | None = Field(
|
|
94
|
-
default=None, description="Azure search admin/key"
|
|
95
|
-
)
|
|
96
|
-
azure_search_index_name: str | None = Field(
|
|
97
|
-
default=None, description="Azure search index name"
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
# Graph DB (Neo4j / Memgraph) settings
|
|
101
|
-
cypher_uri: str = Field(
|
|
102
|
-
default="bolt://localhost:7687", description="Bolt URI for the graph DB"
|
|
103
|
-
)
|
|
104
|
-
cypher_username: str = Field(default="neo4j", description="Username for DB")
|
|
105
|
-
cypher_password: str = Field(
|
|
106
|
-
default="password", description="Password for DB"
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
similarity_threshold: float = Field(
|
|
110
|
-
default=0.5, description="Cosine-similarity threshold for retrieval"
|
|
111
|
-
)
|
|
112
|
-
max_results: int = Field(
|
|
113
|
-
default=10, description="Maximum retrieved memories"
|
|
114
|
-
)
|
|
115
|
-
number_of_concepts_to_extract: int = Field(
|
|
116
|
-
default=3, description="Number of concepts extracted per chunk"
|
|
117
|
-
)
|
|
118
|
-
save_interval: int = Field(
|
|
119
|
-
default=10,
|
|
120
|
-
description="Persist to disk after this many new chunks (0 disables auto-save)",
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
export_graph_image: bool = Field(
|
|
124
|
-
default=False,
|
|
125
|
-
description="If true, exports a PNG image of the concept graph each time it is updated.",
|
|
126
|
-
)
|
|
127
|
-
graph_image_dir: str = Field(
|
|
128
|
-
default="./concept_graphs",
|
|
129
|
-
description="Directory where graph images will be stored when export_graph_image is true.",
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
class MemoryStore:
|
|
134
|
-
"""Persistence layer that wraps vector store + Cypher graph."""
|
|
135
|
-
|
|
136
|
-
def __init__(
|
|
137
|
-
self,
|
|
138
|
-
cfg: MemoryUtilityConfig,
|
|
139
|
-
metrics_component: MetricsUtilityComponent | None = None,
|
|
140
|
-
):
|
|
141
|
-
self.cfg = cfg
|
|
142
|
-
# Metrics component (DI-resolved or fallback)
|
|
143
|
-
self._metrics = metrics_component
|
|
144
|
-
# Lazy initialise expensive resources
|
|
145
|
-
self._embedding_model: SentenceTransformer | None = None
|
|
146
|
-
self._adapter: VectorAdapter | None = None
|
|
147
|
-
self._driver = None # Neo4j driver
|
|
148
|
-
self._pending_writes: list[tuple[str, dict[str, Any]]] = []
|
|
149
|
-
self._write_lock = asyncio.Lock()
|
|
150
|
-
self._concept_cache: set[str] | None = None # names of known concepts
|
|
151
|
-
|
|
152
|
-
def _ensure_embedding_model(self) -> SentenceTransformer:
|
|
153
|
-
if self._embedding_model is None:
|
|
154
|
-
logger.debug("Loading embedding model 'all-MiniLM-L6-v2'")
|
|
155
|
-
with tracer.start_as_current_span(
|
|
156
|
-
"memory.load_embedding_model"
|
|
157
|
-
) as span:
|
|
158
|
-
try:
|
|
159
|
-
self._embedding_model = SentenceTransformer(
|
|
160
|
-
"all-MiniLM-L6-v2"
|
|
161
|
-
)
|
|
162
|
-
span.set_attribute("model", "all-MiniLM-L6-v2")
|
|
163
|
-
except Exception as e:
|
|
164
|
-
span.record_exception(e)
|
|
165
|
-
raise
|
|
166
|
-
return self._embedding_model
|
|
167
|
-
|
|
168
|
-
def _ensure_adapter(self) -> VectorAdapter:
|
|
169
|
-
if self._adapter is not None:
|
|
170
|
-
return self._adapter
|
|
171
|
-
|
|
172
|
-
backend = self.cfg.vector_backend
|
|
173
|
-
|
|
174
|
-
if backend == "chroma":
|
|
175
|
-
self._adapter = ChromaAdapter(
|
|
176
|
-
collection=self.cfg.chroma_collection,
|
|
177
|
-
host=self.cfg.chroma_host,
|
|
178
|
-
port=self.cfg.chroma_port,
|
|
179
|
-
path=self.cfg.chroma_path,
|
|
180
|
-
)
|
|
181
|
-
elif backend == "pinecone":
|
|
182
|
-
self._adapter = PineconeAdapter(
|
|
183
|
-
api_key=self.cfg.pinecone_api_key,
|
|
184
|
-
environment=self.cfg.pinecone_env,
|
|
185
|
-
index=self.cfg.pinecone_index,
|
|
186
|
-
)
|
|
187
|
-
elif backend == "azure":
|
|
188
|
-
self._adapter = AzureSearchAdapter(
|
|
189
|
-
endpoint=self.cfg.azure_search_endpoint,
|
|
190
|
-
key=self.cfg.azure_search_key,
|
|
191
|
-
index_name=self.cfg.azure_search_index_name,
|
|
192
|
-
)
|
|
193
|
-
elif backend == "faiss":
|
|
194
|
-
self._adapter = FAISSAdapter(index_path="./faiss.index")
|
|
195
|
-
else:
|
|
196
|
-
raise ValueError(f"Unsupported vector backend: {backend}")
|
|
197
|
-
|
|
198
|
-
return self._adapter
|
|
199
|
-
|
|
200
|
-
def _ensure_graph_driver(self):
|
|
201
|
-
if self._driver is None:
|
|
202
|
-
self._driver = AsyncGraphDatabase.driver(
|
|
203
|
-
self.cfg.cypher_uri,
|
|
204
|
-
auth=(self.cfg.cypher_username, self.cfg.cypher_password),
|
|
205
|
-
encrypted=False,
|
|
206
|
-
)
|
|
207
|
-
return self._driver
|
|
208
|
-
|
|
209
|
-
async def add_entry(
|
|
210
|
-
self,
|
|
211
|
-
content: str,
|
|
212
|
-
concepts: set[str],
|
|
213
|
-
metadata: dict[str, Any] | None = None,
|
|
214
|
-
) -> str:
|
|
215
|
-
"""Store a chunk in both vector store and graph DB and return its id."""
|
|
216
|
-
with tracer.start_as_current_span("memory.add_entry") as span:
|
|
217
|
-
entry_id = str(uuid.uuid4())
|
|
218
|
-
span.set_attribute("entry_id", entry_id)
|
|
219
|
-
|
|
220
|
-
# Embed
|
|
221
|
-
embedding = self._ensure_embedding_model().encode(content).tolist()
|
|
222
|
-
span.set_attribute("embedding_length", len(embedding))
|
|
223
|
-
|
|
224
|
-
# Vector store write
|
|
225
|
-
adapter = self._ensure_adapter()
|
|
226
|
-
span.set_attribute("vector_backend", self.cfg.vector_backend)
|
|
227
|
-
|
|
228
|
-
start_t = time.perf_counter()
|
|
229
|
-
try:
|
|
230
|
-
adapter.add(
|
|
231
|
-
id=entry_id,
|
|
232
|
-
content=content,
|
|
233
|
-
embedding=embedding,
|
|
234
|
-
metadata=metadata,
|
|
235
|
-
)
|
|
236
|
-
except Exception as e:
|
|
237
|
-
span.record_exception(e)
|
|
238
|
-
raise
|
|
239
|
-
finally:
|
|
240
|
-
elapsed = (time.perf_counter() - start_t) * 1000 # ms
|
|
241
|
-
if self._metrics:
|
|
242
|
-
self._metrics.record(
|
|
243
|
-
"memory_add_latency_ms",
|
|
244
|
-
elapsed,
|
|
245
|
-
{"backend": self.cfg.vector_backend},
|
|
246
|
-
)
|
|
247
|
-
|
|
248
|
-
# Schedule graph writes (batched)
|
|
249
|
-
async with self._write_lock:
|
|
250
|
-
self._pending_writes.append((entry_id, {"concepts": concepts}))
|
|
251
|
-
if (
|
|
252
|
-
self.cfg.save_interval
|
|
253
|
-
and len(self._pending_writes) >= self.cfg.save_interval
|
|
254
|
-
):
|
|
255
|
-
await self._flush_pending_graph_writes()
|
|
256
|
-
return entry_id
|
|
257
|
-
|
|
258
|
-
async def search(
|
|
259
|
-
self, query_text: str, threshold: float, k: int
|
|
260
|
-
) -> list[dict[str, Any]]:
|
|
261
|
-
"""Vector similarity search followed by graph enrichment."""
|
|
262
|
-
with tracer.start_as_current_span("memory.search") as span:
|
|
263
|
-
span.set_attribute("vector_backend", self.cfg.vector_backend)
|
|
264
|
-
embedding = (
|
|
265
|
-
self._ensure_embedding_model().encode(query_text).tolist()
|
|
266
|
-
)
|
|
267
|
-
span.set_attribute("embedding_length", len(embedding))
|
|
268
|
-
adapter = self._ensure_adapter()
|
|
269
|
-
backend = self.cfg.vector_backend
|
|
270
|
-
results: list[dict[str, Any]] = []
|
|
271
|
-
|
|
272
|
-
search_start = time.perf_counter()
|
|
273
|
-
vector_hits = adapter.query(embedding=embedding, k=k)
|
|
274
|
-
search_elapsed = (time.perf_counter() - search_start) * 1000
|
|
275
|
-
if self._metrics:
|
|
276
|
-
self._metrics.record(
|
|
277
|
-
"memory_search_hits", len(vector_hits), {"backend": backend}
|
|
278
|
-
)
|
|
279
|
-
for hit in vector_hits:
|
|
280
|
-
if hit.score < threshold:
|
|
281
|
-
continue
|
|
282
|
-
results.append(
|
|
283
|
-
{
|
|
284
|
-
"id": hit.id,
|
|
285
|
-
"content": hit.content,
|
|
286
|
-
"metadata": hit.metadata,
|
|
287
|
-
"score": hit.score,
|
|
288
|
-
}
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
span.set_attribute("results_count", len(results))
|
|
292
|
-
if self._metrics:
|
|
293
|
-
self._metrics.record(
|
|
294
|
-
"memory_search_latency_ms",
|
|
295
|
-
search_elapsed,
|
|
296
|
-
{"backend": backend},
|
|
297
|
-
)
|
|
298
|
-
return results
|
|
299
|
-
|
|
300
|
-
async def _flush_pending_graph_writes(self):
|
|
301
|
-
"""Commit queued node/edge creations to the Cypher store."""
|
|
302
|
-
if not self._pending_writes:
|
|
303
|
-
return
|
|
304
|
-
driver = self._ensure_graph_driver()
|
|
305
|
-
async with driver.session() as session:
|
|
306
|
-
tx_commands: list[str] = []
|
|
307
|
-
params: dict[str, Any] = {}
|
|
308
|
-
# Build Cypher in one transaction
|
|
309
|
-
for idx, (entry_id, extra) in enumerate(self._pending_writes):
|
|
310
|
-
concept_param = f"concepts_{idx}"
|
|
311
|
-
tx_commands.append(
|
|
312
|
-
f"MERGE (e:Memory {{id: '{entry_id}'}}) "
|
|
313
|
-
f"SET e.created = datetime() "
|
|
314
|
-
)
|
|
315
|
-
if extra.get("concepts"):
|
|
316
|
-
tx_commands.append(
|
|
317
|
-
f"WITH e UNWIND ${concept_param} AS c "
|
|
318
|
-
"MERGE (co:Concept {name: c}) "
|
|
319
|
-
"MERGE (e)-[:MENTIONS]->(co)"
|
|
320
|
-
)
|
|
321
|
-
params[concept_param] = list(extra["concepts"])
|
|
322
|
-
cypher = "\n".join(tx_commands)
|
|
323
|
-
await session.run(cypher, params)
|
|
324
|
-
# Export graph image if requested
|
|
325
|
-
if self.cfg.export_graph_image:
|
|
326
|
-
await self._export_graph_image(session)
|
|
327
|
-
self._pending_writes.clear()
|
|
328
|
-
|
|
329
|
-
async def _export_graph_image(self, session):
|
|
330
|
-
"""Generate and save a PNG of the concept graph."""
|
|
331
|
-
try:
|
|
332
|
-
import matplotlib
|
|
333
|
-
|
|
334
|
-
matplotlib.use("Agg")
|
|
335
|
-
import matplotlib.pyplot as plt
|
|
336
|
-
import networkx as nx
|
|
337
|
-
|
|
338
|
-
records = await session.run(
|
|
339
|
-
"MATCH (c1:Concept)<-[:MENTIONS]-(:Memory)-[:MENTIONS]->(c2:Concept) "
|
|
340
|
-
"RETURN DISTINCT c1.name AS source, c2.name AS target"
|
|
341
|
-
)
|
|
342
|
-
edges = [
|
|
343
|
-
(r["source"], r["target"])
|
|
344
|
-
for r in await records.values("source", "target")
|
|
345
|
-
]
|
|
346
|
-
if not edges:
|
|
347
|
-
return
|
|
348
|
-
|
|
349
|
-
G = nx.Graph()
|
|
350
|
-
G.add_edges_from(edges)
|
|
351
|
-
|
|
352
|
-
pos = nx.spring_layout(G, k=0.4)
|
|
353
|
-
plt.figure(figsize=(12, 9), dpi=100)
|
|
354
|
-
nx.draw_networkx_nodes(
|
|
355
|
-
G, pos, node_color="#8fa8d6", node_size=500, edgecolors="white"
|
|
356
|
-
)
|
|
357
|
-
nx.draw_networkx_edges(G, pos, alpha=0.5, width=1.5)
|
|
358
|
-
nx.draw_networkx_labels(G, pos, font_size=8)
|
|
359
|
-
plt.axis("off")
|
|
360
|
-
|
|
361
|
-
img_dir = Path(self.cfg.graph_image_dir)
|
|
362
|
-
img_dir.mkdir(parents=True, exist_ok=True)
|
|
363
|
-
filename = img_dir / f"concept_graph_{uuid.uuid4().hex[:8]}.png"
|
|
364
|
-
plt.savefig(filename, bbox_inches="tight", facecolor="white")
|
|
365
|
-
plt.close()
|
|
366
|
-
logger.info("Concept graph image exported to %s", filename)
|
|
367
|
-
except Exception as e:
|
|
368
|
-
logger.warning("Failed to export concept graph image: %s", e)
|
|
369
|
-
|
|
370
|
-
async def _deduplicate_concepts(self, new_concepts: set[str]) -> set[str]:
|
|
371
|
-
"""Return a set of concept names that merges with existing ones to avoid duplicates.
|
|
372
|
-
|
|
373
|
-
Strategy: case-insensitive equality first, then fuzzy match via difflib with cutoff 0.85.
|
|
374
|
-
"""
|
|
375
|
-
await self._ensure_concept_cache()
|
|
376
|
-
assert self._concept_cache is not None
|
|
377
|
-
|
|
378
|
-
from difflib import get_close_matches
|
|
379
|
-
|
|
380
|
-
unified: set[str] = set()
|
|
381
|
-
for concept in new_concepts:
|
|
382
|
-
# Exact (case-insensitive) match
|
|
383
|
-
lower = concept.lower()
|
|
384
|
-
exact = next(
|
|
385
|
-
(c for c in self._concept_cache if c.lower() == lower), None
|
|
386
|
-
)
|
|
387
|
-
if exact:
|
|
388
|
-
unified.add(exact)
|
|
389
|
-
continue
|
|
390
|
-
|
|
391
|
-
# Fuzzy match (>=0.85 similarity)
|
|
392
|
-
close = get_close_matches(
|
|
393
|
-
concept, list(self._concept_cache), n=1, cutoff=0.85
|
|
394
|
-
)
|
|
395
|
-
if close:
|
|
396
|
-
unified.add(close[0])
|
|
397
|
-
continue
|
|
398
|
-
|
|
399
|
-
# No match – treat as new
|
|
400
|
-
unified.add(concept)
|
|
401
|
-
self._concept_cache.add(concept)
|
|
402
|
-
return unified
|
|
403
|
-
|
|
404
|
-
async def _ensure_concept_cache(self):
|
|
405
|
-
if self._concept_cache is not None:
|
|
406
|
-
return
|
|
407
|
-
driver = self._ensure_graph_driver()
|
|
408
|
-
async with driver.session() as session:
|
|
409
|
-
records = await session.run(
|
|
410
|
-
"MATCH (c:Concept) RETURN c.name AS name"
|
|
411
|
-
)
|
|
412
|
-
self._concept_cache = {
|
|
413
|
-
r["name"] for r in await records.values("name")
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
async def close(self):
|
|
417
|
-
if self._pending_writes:
|
|
418
|
-
await self._flush_pending_graph_writes()
|
|
419
|
-
if self._driver:
|
|
420
|
-
await self._driver.close()
|
|
421
|
-
if self._adapter and hasattr(self._adapter, "close"):
|
|
422
|
-
self._adapter.close()
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
@flock_component(config_class=MemoryUtilityConfig)
|
|
426
|
-
class MemoryUtilityComponent(UtilityComponent):
|
|
427
|
-
"""Enterprise-ready memory utility component using real datastores."""
|
|
428
|
-
|
|
429
|
-
config: MemoryUtilityConfig = Field(
|
|
430
|
-
default_factory=MemoryUtilityConfig,
|
|
431
|
-
description="Memory configuration",
|
|
432
|
-
)
|
|
433
|
-
|
|
434
|
-
def __init__(
|
|
435
|
-
self,
|
|
436
|
-
name: str = "memory",
|
|
437
|
-
config: MemoryUtilityConfig | None = None,
|
|
438
|
-
**data,
|
|
439
|
-
):
|
|
440
|
-
if config is None:
|
|
441
|
-
config = MemoryUtilityConfig()
|
|
442
|
-
super().__init__(name=name, config=config, **data)
|
|
443
|
-
|
|
444
|
-
self._store: MemoryStore | None = None
|
|
445
|
-
self._metrics_component: MetricsUtilityComponent | None = None
|
|
446
|
-
|
|
447
|
-
def _get_metrics_component(self) -> MetricsUtilityComponent | None:
|
|
448
|
-
"""Try to get the metrics component from the singleton if available."""
|
|
449
|
-
try:
|
|
450
|
-
from flock.components.utility.metrics_utility_component import (
|
|
451
|
-
MetricsUtilityComponent,
|
|
452
|
-
)
|
|
453
|
-
|
|
454
|
-
return MetricsUtilityComponent._INSTANCE
|
|
455
|
-
except ImportError:
|
|
456
|
-
return None
|
|
457
|
-
|
|
458
|
-
async def on_initialize(
|
|
459
|
-
self,
|
|
460
|
-
agent: FlockAgent,
|
|
461
|
-
inputs: dict[str, Any],
|
|
462
|
-
context: FlockContext | None = None,
|
|
463
|
-
) -> None:
|
|
464
|
-
"""Initialize the memory store."""
|
|
465
|
-
self._metrics_component = self._get_metrics_component()
|
|
466
|
-
self._store = MemoryStore(self.config, self._metrics_component)
|
|
467
|
-
logger.info(
|
|
468
|
-
"MemoryUtilityComponent initialised for agent: %s", agent.name
|
|
469
|
-
)
|
|
470
|
-
|
|
471
|
-
async def on_pre_evaluate(
|
|
472
|
-
self,
|
|
473
|
-
agent: FlockAgent,
|
|
474
|
-
inputs: dict[str, Any],
|
|
475
|
-
context: FlockContext | None = None,
|
|
476
|
-
) -> dict[str, Any]:
|
|
477
|
-
"""Retrieve relevant memories and inject into inputs."""
|
|
478
|
-
if not self._store:
|
|
479
|
-
return inputs
|
|
480
|
-
try:
|
|
481
|
-
query_str = json.dumps(inputs)
|
|
482
|
-
matches = await self._store.search(
|
|
483
|
-
query_str,
|
|
484
|
-
threshold=self.config.similarity_threshold,
|
|
485
|
-
k=self.config.max_results,
|
|
486
|
-
)
|
|
487
|
-
if matches:
|
|
488
|
-
inputs = {**inputs, "context": matches}
|
|
489
|
-
# Advertise new input key to DSPy signature if needed
|
|
490
|
-
if (
|
|
491
|
-
isinstance(agent.input, str)
|
|
492
|
-
and "context:" not in agent.input
|
|
493
|
-
):
|
|
494
|
-
agent.input += ", context: list | retrieved memories"
|
|
495
|
-
except Exception as e:
|
|
496
|
-
logger.warning(
|
|
497
|
-
"Memory retrieval failed for agent %s: %s", agent.name, e
|
|
498
|
-
)
|
|
499
|
-
return inputs
|
|
500
|
-
|
|
501
|
-
async def on_post_evaluate(
|
|
502
|
-
self,
|
|
503
|
-
agent: FlockAgent,
|
|
504
|
-
inputs: dict[str, Any],
|
|
505
|
-
context: FlockContext | None = None,
|
|
506
|
-
result: dict[str, Any] | None = None,
|
|
507
|
-
) -> dict[str, Any] | None:
|
|
508
|
-
"""Store new memories from inputs and results."""
|
|
509
|
-
if not self._store:
|
|
510
|
-
return result
|
|
511
|
-
try:
|
|
512
|
-
full_text = json.dumps(inputs) + (
|
|
513
|
-
json.dumps(result) if result else ""
|
|
514
|
-
)
|
|
515
|
-
concepts = await self._extract_concepts(agent, full_text)
|
|
516
|
-
if self._store:
|
|
517
|
-
concepts = await self._store._deduplicate_concepts(concepts)
|
|
518
|
-
await self._store.add_entry(full_text, concepts)
|
|
519
|
-
except Exception as e:
|
|
520
|
-
logger.warning(
|
|
521
|
-
"Memory store failed for agent %s: %s", agent.name, e
|
|
522
|
-
)
|
|
523
|
-
return result
|
|
524
|
-
|
|
525
|
-
async def on_terminate(
|
|
526
|
-
self,
|
|
527
|
-
agent: FlockAgent,
|
|
528
|
-
inputs: dict[str, Any],
|
|
529
|
-
context: FlockContext | None = None,
|
|
530
|
-
result: dict[str, Any] | None = None,
|
|
531
|
-
) -> None:
|
|
532
|
-
"""Clean up memory store resources."""
|
|
533
|
-
if self._store:
|
|
534
|
-
await self._store.close()
|
|
535
|
-
|
|
536
|
-
async def _extract_concepts(
|
|
537
|
-
self, agent: FlockAgent, text: str
|
|
538
|
-
) -> set[str]:
|
|
539
|
-
"""Use the LLM to extract concept tokens."""
|
|
540
|
-
concept_signature = agent.create_dspy_signature_class(
|
|
541
|
-
f"{agent.name}_concept_extractor_memory",
|
|
542
|
-
"Extract key concepts from text",
|
|
543
|
-
"text: str | Input text -> concepts: list[str] | key concepts lower case",
|
|
544
|
-
)
|
|
545
|
-
import dspy
|
|
546
|
-
lm = dspy.LM(model=agent.model, cache=True, temperature=0.0, max_tokens=8192)
|
|
547
|
-
predictor = agent._select_task(concept_signature, "Predict")
|
|
548
|
-
with dspy.settings.context(lm=lm):
|
|
549
|
-
res = predictor(text=text)
|
|
550
|
-
return set(getattr(res, "concepts", []))
|