stellar-memory 1.0.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.
- stellar_memory/__init__.py +164 -0
- stellar_memory/adapters/__init__.py +6 -0
- stellar_memory/adapters/langchain.py +67 -0
- stellar_memory/adapters/openai_plugin.py +125 -0
- stellar_memory/adaptive_decay.py +57 -0
- stellar_memory/benchmark.py +229 -0
- stellar_memory/cli.py +460 -0
- stellar_memory/config.py +341 -0
- stellar_memory/connectors/__init__.py +46 -0
- stellar_memory/connectors/api_connector.py +64 -0
- stellar_memory/connectors/file_connector.py +88 -0
- stellar_memory/connectors/web_connector.py +66 -0
- stellar_memory/consolidator.py +72 -0
- stellar_memory/dashboard/__init__.py +18 -0
- stellar_memory/dashboard/app.py +260 -0
- stellar_memory/decay_manager.py +73 -0
- stellar_memory/embedder.py +79 -0
- stellar_memory/emotion.py +111 -0
- stellar_memory/event_bus.py +57 -0
- stellar_memory/event_logger.py +68 -0
- stellar_memory/graph_analyzer.py +225 -0
- stellar_memory/importance_evaluator.py +148 -0
- stellar_memory/llm_adapter.py +95 -0
- stellar_memory/mcp_server.py +376 -0
- stellar_memory/memory_function.py +79 -0
- stellar_memory/memory_graph.py +62 -0
- stellar_memory/metacognition.py +153 -0
- stellar_memory/models.py +395 -0
- stellar_memory/multimodal.py +167 -0
- stellar_memory/namespace.py +47 -0
- stellar_memory/orbit_manager.py +138 -0
- stellar_memory/persistent_graph.py +118 -0
- stellar_memory/providers/__init__.py +140 -0
- stellar_memory/providers/ollama_provider.py +64 -0
- stellar_memory/providers/openai_provider.py +71 -0
- stellar_memory/reasoning.py +293 -0
- stellar_memory/scheduler.py +63 -0
- stellar_memory/security/__init__.py +9 -0
- stellar_memory/security/access_control.py +59 -0
- stellar_memory/security/audit.py +77 -0
- stellar_memory/security/encryption.py +84 -0
- stellar_memory/self_learning.py +341 -0
- stellar_memory/serializer.py +78 -0
- stellar_memory/server.py +605 -0
- stellar_memory/session.py +44 -0
- stellar_memory/stellar.py +946 -0
- stellar_memory/storage/__init__.py +94 -0
- stellar_memory/storage/in_memory.py +61 -0
- stellar_memory/storage/postgres_storage.py +300 -0
- stellar_memory/storage/redis_cache.py +138 -0
- stellar_memory/storage/sqlite_storage.py +192 -0
- stellar_memory/stream.py +113 -0
- stellar_memory/summarizer.py +49 -0
- stellar_memory/sync/__init__.py +7 -0
- stellar_memory/sync/sync_manager.py +104 -0
- stellar_memory/sync/ws_client.py +91 -0
- stellar_memory/sync/ws_server.py +90 -0
- stellar_memory/utils.py +28 -0
- stellar_memory/vector_index.py +233 -0
- stellar_memory/weight_tuner.py +163 -0
- stellar_memory-1.0.0.dist-info/LICENSE +21 -0
- stellar_memory-1.0.0.dist-info/METADATA +241 -0
- stellar_memory-1.0.0.dist-info/RECORD +66 -0
- stellar_memory-1.0.0.dist-info/WHEEL +5 -0
- stellar_memory-1.0.0.dist-info/entry_points.txt +2 -0
- stellar_memory-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Stellar Memory - A celestial-structure-based AI memory management system.
|
|
2
|
+
|
|
3
|
+
Give any AI human-like memory, built on a celestial structure.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from stellar_memory.stellar import StellarMemory
|
|
7
|
+
from stellar_memory.config import (
|
|
8
|
+
StellarConfig, MemoryFunctionConfig, ZoneConfig,
|
|
9
|
+
EmbedderConfig, LLMConfig, TunerConfig,
|
|
10
|
+
ConsolidationConfig, SessionConfig,
|
|
11
|
+
EventConfig, NamespaceConfig, GraphConfig,
|
|
12
|
+
DecayConfig, EventLoggerConfig, RecallConfig,
|
|
13
|
+
VectorIndexConfig, SummarizationConfig,
|
|
14
|
+
AdaptiveDecayConfig, GraphAnalyticsConfig,
|
|
15
|
+
StorageConfig, SyncConfig, SecurityConfig,
|
|
16
|
+
ConnectorConfig, DashboardConfig,
|
|
17
|
+
EmotionConfig, ServerConfig,
|
|
18
|
+
MetacognitionConfig, SelfLearningConfig,
|
|
19
|
+
MultimodalConfig, ReasoningConfig, BenchmarkConfig,
|
|
20
|
+
)
|
|
21
|
+
from stellar_memory.models import (
|
|
22
|
+
MemoryItem, ScoreBreakdown, ReorbitResult, MemoryStats,
|
|
23
|
+
EvaluationResult, FeedbackRecord,
|
|
24
|
+
ConsolidationResult, SessionInfo, MemorySnapshot,
|
|
25
|
+
MemoryEdge, DecayResult, HealthStatus, SummarizationResult,
|
|
26
|
+
ChangeEvent, AccessRole, IngestResult, ZoneDistribution,
|
|
27
|
+
EmotionVector, TimelineEntry,
|
|
28
|
+
IntrospectionResult, ConfidentRecall, RecallLog,
|
|
29
|
+
OptimizationReport, ReasoningResult, Contradiction,
|
|
30
|
+
BenchmarkReport,
|
|
31
|
+
)
|
|
32
|
+
from stellar_memory.event_bus import EventBus
|
|
33
|
+
from stellar_memory.memory_graph import MemoryGraph
|
|
34
|
+
from stellar_memory.persistent_graph import PersistentMemoryGraph
|
|
35
|
+
from stellar_memory.namespace import NamespaceManager
|
|
36
|
+
from stellar_memory.decay_manager import DecayManager
|
|
37
|
+
from stellar_memory.event_logger import EventLogger
|
|
38
|
+
from stellar_memory.llm_adapter import MemoryMiddleware, AnthropicAdapter
|
|
39
|
+
from stellar_memory.vector_index import VectorIndex, BruteForceIndex, BallTreeIndex
|
|
40
|
+
from stellar_memory.summarizer import MemorySummarizer
|
|
41
|
+
from stellar_memory.adaptive_decay import AdaptiveDecayManager
|
|
42
|
+
from stellar_memory.graph_analyzer import GraphAnalyzer, GraphStats, CentralityResult
|
|
43
|
+
from stellar_memory.providers import ProviderRegistry
|
|
44
|
+
from stellar_memory.storage import StorageBackend
|
|
45
|
+
from stellar_memory.security.encryption import EncryptionManager
|
|
46
|
+
from stellar_memory.security.access_control import AccessControl
|
|
47
|
+
from stellar_memory.security.audit import SecurityAudit
|
|
48
|
+
from stellar_memory.sync import MemorySyncManager
|
|
49
|
+
from stellar_memory.emotion import EmotionAnalyzer
|
|
50
|
+
from stellar_memory.stream import MemoryStream
|
|
51
|
+
from stellar_memory.metacognition import Introspector, ConfidenceScorer
|
|
52
|
+
from stellar_memory.multimodal import (
|
|
53
|
+
ContentTypeHandler, TextHandler, CodeHandler, JsonHandler,
|
|
54
|
+
get_handler, detect_content_type,
|
|
55
|
+
)
|
|
56
|
+
from stellar_memory.self_learning import PatternCollector, WeightOptimizer
|
|
57
|
+
from stellar_memory.reasoning import MemoryReasoner, ContradictionDetector
|
|
58
|
+
from stellar_memory.benchmark import StandardDataset, MemoryBenchmark
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
from importlib.metadata import version as _pkg_version
|
|
62
|
+
__version__ = _pkg_version("stellar-memory")
|
|
63
|
+
except Exception:
|
|
64
|
+
__version__ = "1.0.0"
|
|
65
|
+
|
|
66
|
+
__all__ = [
|
|
67
|
+
"StellarMemory",
|
|
68
|
+
"StellarConfig",
|
|
69
|
+
"MemoryFunctionConfig",
|
|
70
|
+
"ZoneConfig",
|
|
71
|
+
"EmbedderConfig",
|
|
72
|
+
"LLMConfig",
|
|
73
|
+
"TunerConfig",
|
|
74
|
+
"ConsolidationConfig",
|
|
75
|
+
"SessionConfig",
|
|
76
|
+
"EventConfig",
|
|
77
|
+
"NamespaceConfig",
|
|
78
|
+
"GraphConfig",
|
|
79
|
+
"DecayConfig",
|
|
80
|
+
"EventLoggerConfig",
|
|
81
|
+
"RecallConfig",
|
|
82
|
+
"VectorIndexConfig",
|
|
83
|
+
"SummarizationConfig",
|
|
84
|
+
"AdaptiveDecayConfig",
|
|
85
|
+
"GraphAnalyticsConfig",
|
|
86
|
+
"MemoryItem",
|
|
87
|
+
"ScoreBreakdown",
|
|
88
|
+
"ReorbitResult",
|
|
89
|
+
"MemoryStats",
|
|
90
|
+
"EvaluationResult",
|
|
91
|
+
"FeedbackRecord",
|
|
92
|
+
"ConsolidationResult",
|
|
93
|
+
"SessionInfo",
|
|
94
|
+
"MemorySnapshot",
|
|
95
|
+
"MemoryEdge",
|
|
96
|
+
"DecayResult",
|
|
97
|
+
"HealthStatus",
|
|
98
|
+
"SummarizationResult",
|
|
99
|
+
"EventBus",
|
|
100
|
+
"MemoryGraph",
|
|
101
|
+
"PersistentMemoryGraph",
|
|
102
|
+
"NamespaceManager",
|
|
103
|
+
"DecayManager",
|
|
104
|
+
"EventLogger",
|
|
105
|
+
"MemoryMiddleware",
|
|
106
|
+
"AnthropicAdapter",
|
|
107
|
+
"VectorIndex",
|
|
108
|
+
"BruteForceIndex",
|
|
109
|
+
"BallTreeIndex",
|
|
110
|
+
"MemorySummarizer",
|
|
111
|
+
"AdaptiveDecayManager",
|
|
112
|
+
"GraphAnalyzer",
|
|
113
|
+
"GraphStats",
|
|
114
|
+
"CentralityResult",
|
|
115
|
+
"ProviderRegistry",
|
|
116
|
+
"StorageConfig",
|
|
117
|
+
"SyncConfig",
|
|
118
|
+
"SecurityConfig",
|
|
119
|
+
"ConnectorConfig",
|
|
120
|
+
"DashboardConfig",
|
|
121
|
+
"ChangeEvent",
|
|
122
|
+
"AccessRole",
|
|
123
|
+
"IngestResult",
|
|
124
|
+
"ZoneDistribution",
|
|
125
|
+
"StorageBackend",
|
|
126
|
+
"EncryptionManager",
|
|
127
|
+
"AccessControl",
|
|
128
|
+
"SecurityAudit",
|
|
129
|
+
"MemorySyncManager",
|
|
130
|
+
# P7
|
|
131
|
+
"EmotionConfig",
|
|
132
|
+
"ServerConfig",
|
|
133
|
+
"EmotionVector",
|
|
134
|
+
"TimelineEntry",
|
|
135
|
+
"EmotionAnalyzer",
|
|
136
|
+
"MemoryStream",
|
|
137
|
+
# P9
|
|
138
|
+
"MetacognitionConfig",
|
|
139
|
+
"SelfLearningConfig",
|
|
140
|
+
"MultimodalConfig",
|
|
141
|
+
"ReasoningConfig",
|
|
142
|
+
"BenchmarkConfig",
|
|
143
|
+
"IntrospectionResult",
|
|
144
|
+
"ConfidentRecall",
|
|
145
|
+
"RecallLog",
|
|
146
|
+
"OptimizationReport",
|
|
147
|
+
"ReasoningResult",
|
|
148
|
+
"Contradiction",
|
|
149
|
+
"BenchmarkReport",
|
|
150
|
+
"Introspector",
|
|
151
|
+
"ConfidenceScorer",
|
|
152
|
+
"ContentTypeHandler",
|
|
153
|
+
"TextHandler",
|
|
154
|
+
"CodeHandler",
|
|
155
|
+
"JsonHandler",
|
|
156
|
+
"get_handler",
|
|
157
|
+
"detect_content_type",
|
|
158
|
+
"PatternCollector",
|
|
159
|
+
"WeightOptimizer",
|
|
160
|
+
"MemoryReasoner",
|
|
161
|
+
"ContradictionDetector",
|
|
162
|
+
"StandardDataset",
|
|
163
|
+
"MemoryBenchmark",
|
|
164
|
+
]
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""Stellar Memory adapters for AI frameworks."""
|
|
2
|
+
|
|
3
|
+
from stellar_memory.adapters.langchain import StellarLangChainMemory
|
|
4
|
+
from stellar_memory.adapters.openai_plugin import OpenAIMemoryPlugin, STELLAR_TOOLS
|
|
5
|
+
|
|
6
|
+
__all__ = ["StellarLangChainMemory", "OpenAIMemoryPlugin", "STELLAR_TOOLS"]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""LangChain Memory interface adapter for Stellar Memory."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from stellar_memory.stellar import StellarMemory
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class StellarLangChainMemory:
|
|
12
|
+
"""LangChain BaseMemory compatible adapter.
|
|
13
|
+
|
|
14
|
+
Usage::
|
|
15
|
+
|
|
16
|
+
from stellar_memory import StellarMemory
|
|
17
|
+
from stellar_memory.adapters.langchain import StellarLangChainMemory
|
|
18
|
+
|
|
19
|
+
memory = StellarMemory()
|
|
20
|
+
lc_memory = StellarLangChainMemory(memory)
|
|
21
|
+
# Use with LangChain ConversationChain
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
memory_key: str = "history"
|
|
25
|
+
input_key: str = "input"
|
|
26
|
+
output_key: str = "output"
|
|
27
|
+
|
|
28
|
+
def __init__(self, stellar_memory: StellarMemory,
|
|
29
|
+
recall_limit: int = 5,
|
|
30
|
+
memory_key: str = "history"):
|
|
31
|
+
self._memory = stellar_memory
|
|
32
|
+
self._recall_limit = recall_limit
|
|
33
|
+
self.memory_key = memory_key
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def memory_variables(self) -> list[str]:
|
|
37
|
+
return [self.memory_key]
|
|
38
|
+
|
|
39
|
+
def load_memory_variables(self, inputs: dict[str, Any]) -> dict[str, str]:
|
|
40
|
+
"""Recall relevant memories based on input."""
|
|
41
|
+
query = inputs.get(self.input_key, "")
|
|
42
|
+
if not query:
|
|
43
|
+
return {self.memory_key: ""}
|
|
44
|
+
|
|
45
|
+
results = self._memory.recall(str(query), limit=self._recall_limit)
|
|
46
|
+
if not results:
|
|
47
|
+
return {self.memory_key: ""}
|
|
48
|
+
|
|
49
|
+
context = "\n".join(f"- {item.content}" for item in results)
|
|
50
|
+
return {self.memory_key: context}
|
|
51
|
+
|
|
52
|
+
def save_context(self, inputs: dict[str, Any],
|
|
53
|
+
outputs: dict[str, str]) -> None:
|
|
54
|
+
"""Store the conversation exchange as a memory."""
|
|
55
|
+
user_input = inputs.get(self.input_key, "")
|
|
56
|
+
assistant_output = outputs.get(self.output_key, "")
|
|
57
|
+
if user_input or assistant_output:
|
|
58
|
+
content = f"User: {user_input}\nAssistant: {assistant_output}"
|
|
59
|
+
self._memory.store(
|
|
60
|
+
content=content,
|
|
61
|
+
auto_evaluate=True,
|
|
62
|
+
metadata={"source": "langchain"},
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def clear(self) -> None:
|
|
66
|
+
"""No-op for persistent memory."""
|
|
67
|
+
pass
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""OpenAI function calling schema for Stellar Memory."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from stellar_memory.stellar import StellarMemory
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
STELLAR_TOOLS = [
|
|
13
|
+
{
|
|
14
|
+
"type": "function",
|
|
15
|
+
"function": {
|
|
16
|
+
"name": "memory_store",
|
|
17
|
+
"description": "Store a new memory for future recall",
|
|
18
|
+
"parameters": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"content": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Memory content to store",
|
|
24
|
+
},
|
|
25
|
+
"importance": {
|
|
26
|
+
"type": "number",
|
|
27
|
+
"minimum": 0,
|
|
28
|
+
"maximum": 1,
|
|
29
|
+
"description": "Importance score 0.0-1.0",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
"required": ["content"],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"type": "function",
|
|
38
|
+
"function": {
|
|
39
|
+
"name": "memory_recall",
|
|
40
|
+
"description": "Search memories by query",
|
|
41
|
+
"parameters": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"query": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": "Search query",
|
|
47
|
+
},
|
|
48
|
+
"limit": {
|
|
49
|
+
"type": "integer",
|
|
50
|
+
"minimum": 1,
|
|
51
|
+
"maximum": 20,
|
|
52
|
+
"description": "Max results",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
"required": ["query"],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"type": "function",
|
|
61
|
+
"function": {
|
|
62
|
+
"name": "memory_forget",
|
|
63
|
+
"description": "Delete a specific memory by ID",
|
|
64
|
+
"parameters": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"properties": {
|
|
67
|
+
"memory_id": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"description": "Memory UUID",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
"required": ["memory_id"],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class OpenAIMemoryPlugin:
|
|
80
|
+
"""Plugin that handles OpenAI function call dispatch.
|
|
81
|
+
|
|
82
|
+
Usage::
|
|
83
|
+
|
|
84
|
+
from stellar_memory import StellarMemory
|
|
85
|
+
from stellar_memory.adapters.openai_plugin import OpenAIMemoryPlugin, STELLAR_TOOLS
|
|
86
|
+
|
|
87
|
+
memory = StellarMemory()
|
|
88
|
+
plugin = OpenAIMemoryPlugin(memory)
|
|
89
|
+
result = plugin.handle_call("memory_store", '{"content": "hello"}')
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __init__(self, stellar_memory: StellarMemory):
|
|
93
|
+
self._memory = stellar_memory
|
|
94
|
+
|
|
95
|
+
def get_tools(self) -> list[dict]:
|
|
96
|
+
"""Return OpenAI tools schema."""
|
|
97
|
+
return STELLAR_TOOLS
|
|
98
|
+
|
|
99
|
+
def handle_call(self, function_name: str, arguments: str) -> str:
|
|
100
|
+
"""Dispatch function call and return JSON result."""
|
|
101
|
+
args = json.loads(arguments)
|
|
102
|
+
|
|
103
|
+
if function_name == "memory_store":
|
|
104
|
+
item = self._memory.store(
|
|
105
|
+
content=args["content"],
|
|
106
|
+
importance=args.get("importance", 0.5),
|
|
107
|
+
auto_evaluate=True,
|
|
108
|
+
)
|
|
109
|
+
return json.dumps({"id": item.id, "zone": item.zone})
|
|
110
|
+
|
|
111
|
+
elif function_name == "memory_recall":
|
|
112
|
+
results = self._memory.recall(
|
|
113
|
+
args["query"], limit=args.get("limit", 5)
|
|
114
|
+
)
|
|
115
|
+
return json.dumps([{
|
|
116
|
+
"id": item.id, "content": item.content,
|
|
117
|
+
"zone": item.zone, "importance": item.arbitrary_importance,
|
|
118
|
+
} for item in results])
|
|
119
|
+
|
|
120
|
+
elif function_name == "memory_forget":
|
|
121
|
+
removed = self._memory.forget(args["memory_id"])
|
|
122
|
+
return json.dumps({"removed": removed})
|
|
123
|
+
|
|
124
|
+
else:
|
|
125
|
+
return json.dumps({"error": f"Unknown function: {function_name}"})
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Adaptive decay - importance-based differential forgetting rates."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
|
|
7
|
+
from stellar_memory.config import DecayConfig
|
|
8
|
+
from stellar_memory.models import MemoryItem
|
|
9
|
+
|
|
10
|
+
SECONDS_PER_DAY = 86400
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AdaptiveDecayManager:
|
|
14
|
+
"""Manages importance-based differential decay rates."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, decay_config: DecayConfig):
|
|
17
|
+
self._config = decay_config
|
|
18
|
+
self._adaptive = decay_config.adaptive
|
|
19
|
+
|
|
20
|
+
def effective_decay_days(self, item: MemoryItem) -> float:
|
|
21
|
+
"""Calculate effective decay days based on importance.
|
|
22
|
+
|
|
23
|
+
Formula: base_days * (0.5 + importance * weight)
|
|
24
|
+
- importance=0.9, weight=1.0 → 30 * 1.4 = 42 days
|
|
25
|
+
- importance=0.3, weight=1.0 → 30 * 0.8 = 24 days
|
|
26
|
+
- importance=0.0, weight=1.0 → 30 * 0.5 = 15 days
|
|
27
|
+
"""
|
|
28
|
+
base = self._config.decay_days
|
|
29
|
+
importance = item.arbitrary_importance
|
|
30
|
+
weight = self._adaptive.importance_weight
|
|
31
|
+
factor = 0.5 + importance * weight
|
|
32
|
+
|
|
33
|
+
if self._adaptive.zone_factor and item.zone > 0:
|
|
34
|
+
zone_multiplier = max(0.5, 2.0 - item.zone * 0.5)
|
|
35
|
+
factor *= zone_multiplier
|
|
36
|
+
|
|
37
|
+
return base * factor
|
|
38
|
+
|
|
39
|
+
def effective_forget_days(self, item: MemoryItem) -> float:
|
|
40
|
+
"""Calculate effective auto-forget days."""
|
|
41
|
+
base = self._config.auto_forget_days
|
|
42
|
+
importance = item.arbitrary_importance
|
|
43
|
+
return base * (0.5 + importance * self._adaptive.importance_weight)
|
|
44
|
+
|
|
45
|
+
def apply_curve(self, elapsed_days: float, threshold_days: float) -> float:
|
|
46
|
+
"""Apply decay curve. Returns 0.0 (no decay) to 1.0 (full decay)."""
|
|
47
|
+
if elapsed_days < threshold_days:
|
|
48
|
+
return 0.0
|
|
49
|
+
ratio = elapsed_days / threshold_days
|
|
50
|
+
|
|
51
|
+
if self._adaptive.decay_curve == "exponential":
|
|
52
|
+
return min(1.0, 1.0 - math.exp(-(ratio - 1.0)))
|
|
53
|
+
elif self._adaptive.decay_curve == "sigmoid":
|
|
54
|
+
x = (ratio - 1.0) * 5
|
|
55
|
+
return 1.0 / (1.0 + math.exp(-x))
|
|
56
|
+
else: # linear
|
|
57
|
+
return min(1.0, ratio - 1.0)
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""P9-F5: Memory Benchmark - quantitative performance measurement."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import random
|
|
6
|
+
import time
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
|
|
9
|
+
from stellar_memory.models import BenchmarkReport
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Standard dataset categories with template content
|
|
13
|
+
_CATEGORIES = ["daily", "work", "tech", "emotion", "code"]
|
|
14
|
+
|
|
15
|
+
_TEMPLATES = {
|
|
16
|
+
"daily": [
|
|
17
|
+
"I had coffee with {name} at the cafe this morning",
|
|
18
|
+
"The weather today was {adj} and {adj2}",
|
|
19
|
+
"Went grocery shopping and bought {item} and {item2}",
|
|
20
|
+
"Watched a movie called {title} last night",
|
|
21
|
+
"Took a walk in the park near {place}",
|
|
22
|
+
],
|
|
23
|
+
"work": [
|
|
24
|
+
"Meeting with {name} about the {topic} project",
|
|
25
|
+
"Deadline for {topic} report is next {day}",
|
|
26
|
+
"New team member {name} joined the {topic} team",
|
|
27
|
+
"Completed the {topic} feature implementation",
|
|
28
|
+
"Code review feedback on {topic} module was positive",
|
|
29
|
+
],
|
|
30
|
+
"tech": [
|
|
31
|
+
"Learned about {tech} framework for building {topic}",
|
|
32
|
+
"{tech} version {ver} was released with new features",
|
|
33
|
+
"The {tech} documentation recommends using {pattern}",
|
|
34
|
+
"Benchmark shows {tech} is {num}x faster than alternatives",
|
|
35
|
+
"Migrated from {tech} to {tech2} for better performance",
|
|
36
|
+
],
|
|
37
|
+
"emotion": [
|
|
38
|
+
"Felt really happy when {name} surprised me with {item}",
|
|
39
|
+
"Frustrated with the {topic} bug that took hours to fix",
|
|
40
|
+
"Excited about the upcoming {topic} conference",
|
|
41
|
+
"Anxious about the {topic} presentation tomorrow",
|
|
42
|
+
"Relieved that the {topic} deployment went smoothly",
|
|
43
|
+
],
|
|
44
|
+
"code": [
|
|
45
|
+
"def calculate_{func}(data): return sum(data) / len(data)",
|
|
46
|
+
"class {cls}Manager: def __init__(self): self.items = []",
|
|
47
|
+
"async def fetch_{func}(url): return await client.get(url)",
|
|
48
|
+
"SELECT * FROM {table} WHERE status = 'active' ORDER BY created_at",
|
|
49
|
+
"const {func} = ({param}) => {{ return {param}.filter(x => x > 0) }}",
|
|
50
|
+
],
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
_NAMES = ["Alice", "Bob", "Carol", "David", "Eve", "Frank", "Grace", "Henry"]
|
|
54
|
+
_TOPICS = ["memory", "search", "auth", "deploy", "cache", "sync", "graph", "api"]
|
|
55
|
+
_TECHS = ["React", "Python", "Rust", "Go", "Docker", "Redis", "PostgreSQL", "FastAPI"]
|
|
56
|
+
_ITEMS = ["milk", "bread", "coffee", "laptop", "book", "headphones", "cake", "flowers"]
|
|
57
|
+
_ADJS = ["sunny", "rainy", "cold", "warm", "windy", "cloudy", "beautiful", "foggy"]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class StandardDataset:
|
|
61
|
+
"""Reproducible benchmark dataset generator."""
|
|
62
|
+
|
|
63
|
+
SIZES = {
|
|
64
|
+
"small": (100, 20),
|
|
65
|
+
"standard": (1000, 100),
|
|
66
|
+
"large": (10000, 500),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
def __init__(self, name: str = "standard", seed: int = 42):
|
|
70
|
+
self._name = name
|
|
71
|
+
self._seed = seed
|
|
72
|
+
self._rng = random.Random(seed)
|
|
73
|
+
count, query_count = self.SIZES.get(name, (1000, 100))
|
|
74
|
+
self._memory_count = count
|
|
75
|
+
self._query_count = query_count
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def name(self) -> str:
|
|
79
|
+
return self._name
|
|
80
|
+
|
|
81
|
+
def generate_memories(self) -> list[dict]:
|
|
82
|
+
"""Generate reproducible memory items."""
|
|
83
|
+
memories = []
|
|
84
|
+
for i in range(self._memory_count):
|
|
85
|
+
cat = _CATEGORIES[i % len(_CATEGORIES)]
|
|
86
|
+
template = self._rng.choice(_TEMPLATES[cat])
|
|
87
|
+
content = self._fill_template(template)
|
|
88
|
+
importance = self._rng.uniform(0.1, 0.9)
|
|
89
|
+
tags = [cat]
|
|
90
|
+
if cat == "code":
|
|
91
|
+
tags.append("code")
|
|
92
|
+
memories.append({
|
|
93
|
+
"content": content,
|
|
94
|
+
"importance": round(importance, 2),
|
|
95
|
+
"tags": tags,
|
|
96
|
+
"category": cat,
|
|
97
|
+
"id_hint": f"bench_{i}",
|
|
98
|
+
})
|
|
99
|
+
return memories
|
|
100
|
+
|
|
101
|
+
def generate_queries(self) -> list[dict]:
|
|
102
|
+
"""Generate queries with expected category matches."""
|
|
103
|
+
queries = []
|
|
104
|
+
for i in range(self._query_count):
|
|
105
|
+
cat = _CATEGORIES[i % len(_CATEGORIES)]
|
|
106
|
+
keywords = {
|
|
107
|
+
"daily": ["coffee", "weather", "morning", "walk", "movie"],
|
|
108
|
+
"work": ["meeting", "deadline", "team", "project", "review"],
|
|
109
|
+
"tech": ["framework", "version", "documentation", "benchmark"],
|
|
110
|
+
"emotion": ["happy", "frustrated", "excited", "anxious", "relieved"],
|
|
111
|
+
"code": ["function", "class", "async", "SELECT", "const"],
|
|
112
|
+
}
|
|
113
|
+
query = self._rng.choice(keywords.get(cat, ["memory"]))
|
|
114
|
+
queries.append({
|
|
115
|
+
"query": query,
|
|
116
|
+
"expected_category": cat,
|
|
117
|
+
})
|
|
118
|
+
return queries
|
|
119
|
+
|
|
120
|
+
def _fill_template(self, template: str) -> str:
|
|
121
|
+
replacements = {
|
|
122
|
+
"{name}": self._rng.choice(_NAMES),
|
|
123
|
+
"{topic}": self._rng.choice(_TOPICS),
|
|
124
|
+
"{tech}": self._rng.choice(_TECHS),
|
|
125
|
+
"{tech2}": self._rng.choice(_TECHS),
|
|
126
|
+
"{item}": self._rng.choice(_ITEMS),
|
|
127
|
+
"{item2}": self._rng.choice(_ITEMS),
|
|
128
|
+
"{adj}": self._rng.choice(_ADJS),
|
|
129
|
+
"{adj2}": self._rng.choice(_ADJS),
|
|
130
|
+
"{place}": self._rng.choice(["river", "hill", "garden", "lake"]),
|
|
131
|
+
"{title}": self._rng.choice(["Inception", "Matrix", "Arrival", "Dune"]),
|
|
132
|
+
"{day}": self._rng.choice(["Monday", "Friday", "Wednesday"]),
|
|
133
|
+
"{ver}": f"{self._rng.randint(1,5)}.{self._rng.randint(0,9)}",
|
|
134
|
+
"{pattern}": self._rng.choice(["hooks", "middleware", "plugins"]),
|
|
135
|
+
"{num}": str(self._rng.randint(2, 10)),
|
|
136
|
+
"{func}": self._rng.choice(["average", "total", "count", "max"]),
|
|
137
|
+
"{cls}": self._rng.choice(["Data", "Task", "User", "Event"]),
|
|
138
|
+
"{table}": self._rng.choice(["users", "orders", "events", "logs"]),
|
|
139
|
+
"{param}": self._rng.choice(["items", "data", "values", "records"]),
|
|
140
|
+
}
|
|
141
|
+
result = template
|
|
142
|
+
for key, val in replacements.items():
|
|
143
|
+
result = result.replace(key, val, 1)
|
|
144
|
+
return result
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class MemoryBenchmark:
|
|
148
|
+
"""Comprehensive memory system benchmark."""
|
|
149
|
+
|
|
150
|
+
def __init__(self, stellar):
|
|
151
|
+
self._stellar = stellar
|
|
152
|
+
|
|
153
|
+
def run(self, queries: int = 100, dataset: str = "standard",
|
|
154
|
+
seed: int = 42) -> BenchmarkReport:
|
|
155
|
+
ds = StandardDataset(dataset, seed)
|
|
156
|
+
memories = ds.generate_memories()
|
|
157
|
+
query_list = ds.generate_queries()[:queries]
|
|
158
|
+
|
|
159
|
+
# Measure store latency
|
|
160
|
+
store_times = []
|
|
161
|
+
stored_ids = []
|
|
162
|
+
for mem in memories:
|
|
163
|
+
t0 = time.perf_counter()
|
|
164
|
+
mid = self._stellar.store(
|
|
165
|
+
content=mem["content"],
|
|
166
|
+
importance=mem["importance"],
|
|
167
|
+
metadata={"category": mem["category"], "bench_id": mem["id_hint"]},
|
|
168
|
+
)
|
|
169
|
+
t1 = time.perf_counter()
|
|
170
|
+
store_times.append((t1 - t0) * 1000)
|
|
171
|
+
stored_ids.append(mid.id if hasattr(mid, 'id') else mid)
|
|
172
|
+
|
|
173
|
+
# Measure recall latency + accuracy
|
|
174
|
+
recall_times = []
|
|
175
|
+
hits_at_5 = 0
|
|
176
|
+
hits_at_10 = 0
|
|
177
|
+
precision_hits_5 = 0
|
|
178
|
+
for q in query_list:
|
|
179
|
+
t0 = time.perf_counter()
|
|
180
|
+
results = self._stellar.recall(q["query"], limit=10)
|
|
181
|
+
t1 = time.perf_counter()
|
|
182
|
+
recall_times.append((t1 - t0) * 1000)
|
|
183
|
+
|
|
184
|
+
# Check if any result matches expected category
|
|
185
|
+
top5 = results[:5]
|
|
186
|
+
top10 = results[:10]
|
|
187
|
+
cat = q["expected_category"]
|
|
188
|
+
if any(r.metadata.get("category") == cat for r in top5):
|
|
189
|
+
hits_at_5 += 1
|
|
190
|
+
if any(r.metadata.get("category") == cat for r in top10):
|
|
191
|
+
hits_at_10 += 1
|
|
192
|
+
precision_hits_5 += sum(
|
|
193
|
+
1 for r in top5 if r.metadata.get("category") == cat
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Measure reorbit latency
|
|
197
|
+
t0 = time.perf_counter()
|
|
198
|
+
self._stellar.reorbit()
|
|
199
|
+
t1 = time.perf_counter()
|
|
200
|
+
reorbit_ms = (t1 - t0) * 1000
|
|
201
|
+
|
|
202
|
+
# Stats
|
|
203
|
+
stats = self._stellar.stats()
|
|
204
|
+
import os
|
|
205
|
+
db_size = 0.0
|
|
206
|
+
if hasattr(self._stellar, 'config') and self._stellar.config.db_path != ":memory:":
|
|
207
|
+
try:
|
|
208
|
+
db_size = os.path.getsize(self._stellar.config.db_path) / (1024 * 1024)
|
|
209
|
+
except OSError:
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
import sys
|
|
213
|
+
mem_usage = sys.getsizeof(self._stellar) / (1024 * 1024)
|
|
214
|
+
|
|
215
|
+
total_q = len(query_list) or 1
|
|
216
|
+
return BenchmarkReport(
|
|
217
|
+
recall_at_5=hits_at_5 / total_q,
|
|
218
|
+
recall_at_10=hits_at_10 / total_q,
|
|
219
|
+
precision_at_5=(precision_hits_5 / (total_q * 5)) if total_q else 0.0,
|
|
220
|
+
avg_store_latency_ms=sum(store_times) / len(store_times) if store_times else 0.0,
|
|
221
|
+
avg_recall_latency_ms=sum(recall_times) / len(recall_times) if recall_times else 0.0,
|
|
222
|
+
avg_reorbit_latency_ms=reorbit_ms,
|
|
223
|
+
memory_usage_mb=round(mem_usage, 2),
|
|
224
|
+
db_size_mb=round(db_size, 2),
|
|
225
|
+
total_memories=stats.total_memories,
|
|
226
|
+
zone_distribution=dict(stats.zone_counts),
|
|
227
|
+
dataset_name=dataset,
|
|
228
|
+
queries_run=total_q,
|
|
229
|
+
)
|