memorisdk 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.
Potentially problematic release.
This version of memorisdk might be problematic. Click here for more details.
- memoriai/__init__.py +140 -0
- memoriai/agents/__init__.py +7 -0
- memoriai/agents/conscious_agent.py +506 -0
- memoriai/agents/memory_agent.py +322 -0
- memoriai/agents/retrieval_agent.py +579 -0
- memoriai/config/__init__.py +14 -0
- memoriai/config/manager.py +281 -0
- memoriai/config/settings.py +287 -0
- memoriai/core/__init__.py +6 -0
- memoriai/core/database.py +966 -0
- memoriai/core/memory.py +1349 -0
- memoriai/database/__init__.py +5 -0
- memoriai/database/connectors/__init__.py +9 -0
- memoriai/database/connectors/mysql_connector.py +159 -0
- memoriai/database/connectors/postgres_connector.py +158 -0
- memoriai/database/connectors/sqlite_connector.py +148 -0
- memoriai/database/queries/__init__.py +15 -0
- memoriai/database/queries/base_queries.py +204 -0
- memoriai/database/queries/chat_queries.py +157 -0
- memoriai/database/queries/entity_queries.py +236 -0
- memoriai/database/queries/memory_queries.py +178 -0
- memoriai/database/templates/__init__.py +0 -0
- memoriai/database/templates/basic_template.py +0 -0
- memoriai/database/templates/schemas/__init__.py +0 -0
- memoriai/integrations/__init__.py +68 -0
- memoriai/integrations/anthropic_integration.py +194 -0
- memoriai/integrations/litellm_integration.py +11 -0
- memoriai/integrations/openai_integration.py +273 -0
- memoriai/scripts/llm_text.py +50 -0
- memoriai/tools/__init__.py +5 -0
- memoriai/tools/memory_tool.py +544 -0
- memoriai/utils/__init__.py +89 -0
- memoriai/utils/exceptions.py +418 -0
- memoriai/utils/helpers.py +433 -0
- memoriai/utils/logging.py +204 -0
- memoriai/utils/pydantic_models.py +258 -0
- memoriai/utils/schemas.py +0 -0
- memoriai/utils/validators.py +339 -0
- memorisdk-1.0.0.dist-info/METADATA +386 -0
- memorisdk-1.0.0.dist-info/RECORD +44 -0
- memorisdk-1.0.0.dist-info/WHEEL +5 -0
- memorisdk-1.0.0.dist-info/entry_points.txt +2 -0
- memorisdk-1.0.0.dist-info/licenses/LICENSE +203 -0
- memorisdk-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration manager for Memoriai
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Dict, Optional, Union
|
|
8
|
+
|
|
9
|
+
from loguru import logger
|
|
10
|
+
|
|
11
|
+
from ..utils.exceptions import ConfigurationError
|
|
12
|
+
from .settings import MemoriSettings
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ConfigManager:
|
|
16
|
+
"""Central configuration manager for Memoriai"""
|
|
17
|
+
|
|
18
|
+
_instance: Optional["ConfigManager"] = None
|
|
19
|
+
_settings: Optional[MemoriSettings] = None
|
|
20
|
+
|
|
21
|
+
def __new__(cls) -> "ConfigManager":
|
|
22
|
+
"""Singleton pattern for configuration manager"""
|
|
23
|
+
if cls._instance is None:
|
|
24
|
+
cls._instance = super().__new__(cls)
|
|
25
|
+
return cls._instance
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
"""Initialize configuration manager"""
|
|
29
|
+
if not hasattr(self, "_initialized"):
|
|
30
|
+
self._initialized = True
|
|
31
|
+
self._config_sources = []
|
|
32
|
+
self._load_default_config()
|
|
33
|
+
|
|
34
|
+
def _load_default_config(self) -> None:
|
|
35
|
+
"""Load default configuration"""
|
|
36
|
+
try:
|
|
37
|
+
self._settings = MemoriSettings()
|
|
38
|
+
self._config_sources.append("defaults")
|
|
39
|
+
logger.debug("Loaded default configuration")
|
|
40
|
+
except Exception as e:
|
|
41
|
+
raise ConfigurationError(f"Failed to load default configuration: {e}")
|
|
42
|
+
|
|
43
|
+
def load_from_env(self) -> None:
|
|
44
|
+
"""Load configuration from environment variables"""
|
|
45
|
+
try:
|
|
46
|
+
self._settings = MemoriSettings.from_env()
|
|
47
|
+
self._config_sources.append("environment")
|
|
48
|
+
logger.info("Configuration loaded from environment variables")
|
|
49
|
+
except Exception as e:
|
|
50
|
+
logger.warning(f"Failed to load configuration from environment: {e}")
|
|
51
|
+
raise ConfigurationError(f"Environment configuration error: {e}")
|
|
52
|
+
|
|
53
|
+
def load_from_file(self, config_path: Union[str, Path]) -> None:
|
|
54
|
+
"""Load configuration from file"""
|
|
55
|
+
try:
|
|
56
|
+
config_path = Path(config_path)
|
|
57
|
+
self._settings = MemoriSettings.from_file(config_path)
|
|
58
|
+
self._config_sources.append(str(config_path))
|
|
59
|
+
logger.info(f"Configuration loaded from file: {config_path}")
|
|
60
|
+
except FileNotFoundError:
|
|
61
|
+
logger.warning(f"Configuration file not found: {config_path}")
|
|
62
|
+
raise ConfigurationError(f"Configuration file not found: {config_path}")
|
|
63
|
+
except Exception as e:
|
|
64
|
+
logger.error(f"Failed to load configuration from file {config_path}: {e}")
|
|
65
|
+
raise ConfigurationError(f"File configuration error: {e}")
|
|
66
|
+
|
|
67
|
+
def auto_load(self) -> None:
|
|
68
|
+
"""Automatically load configuration from multiple sources in priority order"""
|
|
69
|
+
config_locations = [
|
|
70
|
+
# Environment-specific paths
|
|
71
|
+
os.getenv("MEMORI_CONFIG_PATH"),
|
|
72
|
+
"memori.json",
|
|
73
|
+
"memori.yaml",
|
|
74
|
+
"memori.yml",
|
|
75
|
+
"config/memori.json",
|
|
76
|
+
"config/memori.yaml",
|
|
77
|
+
"config/memori.yml",
|
|
78
|
+
Path.home() / ".memori" / "config.json",
|
|
79
|
+
Path.home() / ".memori" / "config.yaml",
|
|
80
|
+
"/etc/memori/config.json",
|
|
81
|
+
"/etc/memori/config.yaml",
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
# Try to load from the first available config file
|
|
85
|
+
for config_path in config_locations:
|
|
86
|
+
if config_path and Path(config_path).exists():
|
|
87
|
+
try:
|
|
88
|
+
self.load_from_file(config_path)
|
|
89
|
+
break
|
|
90
|
+
except ConfigurationError:
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
# Override with environment variables if present
|
|
94
|
+
if any(key.startswith("MEMORI_") for key in os.environ):
|
|
95
|
+
try:
|
|
96
|
+
env_settings = MemoriSettings.from_env()
|
|
97
|
+
if self._settings:
|
|
98
|
+
# Merge environment settings with existing settings
|
|
99
|
+
self._merge_settings(env_settings)
|
|
100
|
+
else:
|
|
101
|
+
self._settings = env_settings
|
|
102
|
+
|
|
103
|
+
if "environment" not in self._config_sources:
|
|
104
|
+
self._config_sources.append("environment")
|
|
105
|
+
|
|
106
|
+
logger.info("Environment variables merged into configuration")
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.warning(f"Failed to merge environment configuration: {e}")
|
|
109
|
+
|
|
110
|
+
def _merge_settings(self, new_settings: MemoriSettings) -> None:
|
|
111
|
+
"""Merge new settings with existing settings"""
|
|
112
|
+
if self._settings is None:
|
|
113
|
+
self._settings = new_settings
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
# Convert to dict, merge, and recreate
|
|
117
|
+
current_dict = self._settings.dict()
|
|
118
|
+
new_dict = new_settings.dict()
|
|
119
|
+
|
|
120
|
+
# Deep merge dictionaries
|
|
121
|
+
merged_dict = self._deep_merge_dicts(current_dict, new_dict)
|
|
122
|
+
self._settings = MemoriSettings(**merged_dict)
|
|
123
|
+
|
|
124
|
+
def _deep_merge_dicts(
|
|
125
|
+
self, base: Dict[str, Any], override: Dict[str, Any]
|
|
126
|
+
) -> Dict[str, Any]:
|
|
127
|
+
"""Deep merge two dictionaries"""
|
|
128
|
+
result = base.copy()
|
|
129
|
+
|
|
130
|
+
for key, value in override.items():
|
|
131
|
+
if (
|
|
132
|
+
key in result
|
|
133
|
+
and isinstance(result[key], dict)
|
|
134
|
+
and isinstance(value, dict)
|
|
135
|
+
):
|
|
136
|
+
result[key] = self._deep_merge_dicts(result[key], value)
|
|
137
|
+
else:
|
|
138
|
+
result[key] = value
|
|
139
|
+
|
|
140
|
+
return result
|
|
141
|
+
|
|
142
|
+
def save_to_file(self, config_path: Union[str, Path], format: str = "json") -> None:
|
|
143
|
+
"""Save current configuration to file"""
|
|
144
|
+
if self._settings is None:
|
|
145
|
+
raise ConfigurationError("No configuration loaded to save")
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
self._settings.to_file(config_path, format)
|
|
149
|
+
logger.info(f"Configuration saved to: {config_path}")
|
|
150
|
+
except Exception as e:
|
|
151
|
+
logger.error(f"Failed to save configuration to {config_path}: {e}")
|
|
152
|
+
raise ConfigurationError(f"Failed to save configuration: {e}")
|
|
153
|
+
|
|
154
|
+
def get_settings(self) -> MemoriSettings:
|
|
155
|
+
"""Get current settings"""
|
|
156
|
+
if self._settings is None:
|
|
157
|
+
raise ConfigurationError("Configuration not loaded")
|
|
158
|
+
return self._settings
|
|
159
|
+
|
|
160
|
+
def update_setting(self, key_path: str, value: Any) -> None:
|
|
161
|
+
"""Update a specific setting using dot notation (e.g., 'database.pool_size')"""
|
|
162
|
+
if self._settings is None:
|
|
163
|
+
raise ConfigurationError("Configuration not loaded")
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
# Convert to dict for easier manipulation
|
|
167
|
+
settings_dict = self._settings.dict()
|
|
168
|
+
|
|
169
|
+
# Navigate to the nested key
|
|
170
|
+
keys = key_path.split(".")
|
|
171
|
+
current = settings_dict
|
|
172
|
+
|
|
173
|
+
for key in keys[:-1]:
|
|
174
|
+
if key not in current:
|
|
175
|
+
current[key] = {}
|
|
176
|
+
current = current[key]
|
|
177
|
+
|
|
178
|
+
# Set the value
|
|
179
|
+
current[keys[-1]] = value
|
|
180
|
+
|
|
181
|
+
# Recreate settings object
|
|
182
|
+
self._settings = MemoriSettings(**settings_dict)
|
|
183
|
+
logger.debug(f"Updated setting {key_path} = {value}")
|
|
184
|
+
|
|
185
|
+
except Exception as e:
|
|
186
|
+
logger.error(f"Failed to update setting {key_path}: {e}")
|
|
187
|
+
raise ConfigurationError(f"Setting update error: {e}")
|
|
188
|
+
|
|
189
|
+
def get_setting(self, key_path: str, default: Any = None) -> Any:
|
|
190
|
+
"""Get a specific setting using dot notation"""
|
|
191
|
+
if self._settings is None:
|
|
192
|
+
raise ConfigurationError("Configuration not loaded")
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
# Navigate through the settings
|
|
196
|
+
current = self._settings.dict()
|
|
197
|
+
keys = key_path.split(".")
|
|
198
|
+
|
|
199
|
+
for key in keys:
|
|
200
|
+
if isinstance(current, dict) and key in current:
|
|
201
|
+
current = current[key]
|
|
202
|
+
else:
|
|
203
|
+
return default
|
|
204
|
+
|
|
205
|
+
return current
|
|
206
|
+
|
|
207
|
+
except Exception:
|
|
208
|
+
return default
|
|
209
|
+
|
|
210
|
+
def validate_configuration(self) -> bool:
|
|
211
|
+
"""Validate current configuration"""
|
|
212
|
+
if self._settings is None:
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
# Pydantic validation happens automatically during object creation
|
|
217
|
+
# Additional custom validation can be added here
|
|
218
|
+
|
|
219
|
+
# Validate database connection if possible
|
|
220
|
+
db_url = self._settings.get_database_url()
|
|
221
|
+
if not db_url:
|
|
222
|
+
logger.error("Database connection string is required")
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
# Validate API keys if conscious ingestion is enabled
|
|
226
|
+
if (
|
|
227
|
+
self._settings.agents.conscious_ingest
|
|
228
|
+
and not self._settings.agents.openai_api_key
|
|
229
|
+
):
|
|
230
|
+
logger.warning("OpenAI API key is required for conscious ingestion")
|
|
231
|
+
|
|
232
|
+
logger.info("Configuration validation passed")
|
|
233
|
+
return True
|
|
234
|
+
|
|
235
|
+
except Exception as e:
|
|
236
|
+
logger.error(f"Configuration validation failed: {e}")
|
|
237
|
+
return False
|
|
238
|
+
|
|
239
|
+
def get_config_info(self) -> Dict[str, Any]:
|
|
240
|
+
"""Get information about current configuration"""
|
|
241
|
+
return {
|
|
242
|
+
"loaded": self._settings is not None,
|
|
243
|
+
"sources": self._config_sources.copy(),
|
|
244
|
+
"version": self._settings.version if self._settings else None,
|
|
245
|
+
"debug_mode": self._settings.debug if self._settings else False,
|
|
246
|
+
"is_production": (
|
|
247
|
+
self._settings.is_production() if self._settings else False
|
|
248
|
+
),
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
def reset_to_defaults(self) -> None:
|
|
252
|
+
"""Reset configuration to defaults"""
|
|
253
|
+
self._settings = MemoriSettings()
|
|
254
|
+
self._config_sources = ["defaults"]
|
|
255
|
+
logger.info("Configuration reset to defaults")
|
|
256
|
+
|
|
257
|
+
def setup_logging(self) -> None:
|
|
258
|
+
"""Setup logging based on current configuration"""
|
|
259
|
+
if self._settings is None:
|
|
260
|
+
raise ConfigurationError("Configuration not loaded")
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
# Import here to avoid circular import
|
|
264
|
+
from ..utils.logging import LoggingManager
|
|
265
|
+
|
|
266
|
+
LoggingManager.setup_logging(
|
|
267
|
+
self._settings.logging, verbose=self._settings.verbose
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
if self._settings.verbose:
|
|
271
|
+
logger.info("Verbose logging enabled through ConfigManager")
|
|
272
|
+
except Exception as e:
|
|
273
|
+
logger.error(f"Failed to setup logging: {e}")
|
|
274
|
+
raise ConfigurationError(f"Logging setup error: {e}")
|
|
275
|
+
|
|
276
|
+
@classmethod
|
|
277
|
+
def get_instance(cls) -> "ConfigManager":
|
|
278
|
+
"""Get singleton instance"""
|
|
279
|
+
if cls._instance is None:
|
|
280
|
+
cls._instance = cls()
|
|
281
|
+
return cls._instance
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pydantic-based configuration settings for Memoriai
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Dict, Optional, Union
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field, validator
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LogLevel(str, Enum):
|
|
13
|
+
"""Logging levels"""
|
|
14
|
+
|
|
15
|
+
DEBUG = "DEBUG"
|
|
16
|
+
INFO = "INFO"
|
|
17
|
+
WARNING = "WARNING"
|
|
18
|
+
ERROR = "ERROR"
|
|
19
|
+
CRITICAL = "CRITICAL"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DatabaseType(str, Enum):
|
|
23
|
+
"""Supported database types"""
|
|
24
|
+
|
|
25
|
+
SQLITE = "sqlite"
|
|
26
|
+
POSTGRESQL = "postgresql"
|
|
27
|
+
MYSQL = "mysql"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class RetentionPolicy(str, Enum):
|
|
31
|
+
"""Memory retention policies"""
|
|
32
|
+
|
|
33
|
+
DAYS_7 = "7_days"
|
|
34
|
+
DAYS_30 = "30_days"
|
|
35
|
+
DAYS_90 = "90_days"
|
|
36
|
+
PERMANENT = "permanent"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DatabaseSettings(BaseModel):
|
|
40
|
+
"""Database configuration settings"""
|
|
41
|
+
|
|
42
|
+
connection_string: str = Field(
|
|
43
|
+
default="sqlite:///memori.db", description="Database connection string"
|
|
44
|
+
)
|
|
45
|
+
database_type: DatabaseType = Field(
|
|
46
|
+
default=DatabaseType.SQLITE, description="Type of database backend"
|
|
47
|
+
)
|
|
48
|
+
template: str = Field(default="basic", description="Database template to use")
|
|
49
|
+
pool_size: int = Field(default=10, ge=1, le=100, description="Connection pool size")
|
|
50
|
+
echo_sql: bool = Field(default=False, description="Echo SQL statements to logs")
|
|
51
|
+
migration_auto: bool = Field(
|
|
52
|
+
default=True, description="Automatically run migrations"
|
|
53
|
+
)
|
|
54
|
+
backup_enabled: bool = Field(default=False, description="Enable automatic backups")
|
|
55
|
+
backup_interval_hours: int = Field(
|
|
56
|
+
default=24, ge=1, description="Backup interval in hours"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
@validator("connection_string")
|
|
60
|
+
def validate_connection_string(cls, v):
|
|
61
|
+
"""Validate database connection string"""
|
|
62
|
+
if not v:
|
|
63
|
+
raise ValueError("Connection string cannot be empty")
|
|
64
|
+
|
|
65
|
+
# Basic validation for supported protocols
|
|
66
|
+
valid_prefixes = ["sqlite://", "sqlite:///", "postgresql://", "mysql://"]
|
|
67
|
+
if not any(v.startswith(prefix) for prefix in valid_prefixes):
|
|
68
|
+
raise ValueError(f"Unsupported database type in connection string: {v}")
|
|
69
|
+
|
|
70
|
+
return v
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class AgentSettings(BaseModel):
|
|
74
|
+
"""AI agent configuration settings"""
|
|
75
|
+
|
|
76
|
+
openai_api_key: Optional[str] = Field(
|
|
77
|
+
default=None, description="OpenAI API key for memory processing"
|
|
78
|
+
)
|
|
79
|
+
default_model: str = Field(
|
|
80
|
+
default="gpt-4o", description="Default model for memory processing"
|
|
81
|
+
)
|
|
82
|
+
fallback_model: str = Field(
|
|
83
|
+
default="gpt-3.5-turbo", description="Fallback model if default fails"
|
|
84
|
+
)
|
|
85
|
+
max_tokens: int = Field(
|
|
86
|
+
default=2000, ge=100, le=8000, description="Maximum tokens per API call"
|
|
87
|
+
)
|
|
88
|
+
temperature: float = Field(
|
|
89
|
+
default=0.1,
|
|
90
|
+
ge=0.0,
|
|
91
|
+
le=2.0,
|
|
92
|
+
description="Model temperature for memory processing",
|
|
93
|
+
)
|
|
94
|
+
timeout_seconds: int = Field(
|
|
95
|
+
default=30, ge=5, le=300, description="API timeout in seconds"
|
|
96
|
+
)
|
|
97
|
+
retry_attempts: int = Field(
|
|
98
|
+
default=3,
|
|
99
|
+
ge=1,
|
|
100
|
+
le=10,
|
|
101
|
+
description="Number of retry attempts for failed API calls",
|
|
102
|
+
)
|
|
103
|
+
conscious_ingest: bool = Field(
|
|
104
|
+
default=True, description="Enable intelligent memory filtering"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
@validator("openai_api_key")
|
|
108
|
+
def validate_api_key(cls, v):
|
|
109
|
+
"""Validate OpenAI API key format"""
|
|
110
|
+
if v and not v.startswith("sk-"):
|
|
111
|
+
raise ValueError("OpenAI API key must start with 'sk-'")
|
|
112
|
+
return v
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class LoggingSettings(BaseModel):
|
|
116
|
+
"""Logging configuration settings"""
|
|
117
|
+
|
|
118
|
+
level: LogLevel = Field(default=LogLevel.INFO, description="Logging level")
|
|
119
|
+
format: str = Field(
|
|
120
|
+
default="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
|
|
121
|
+
description="Log message format",
|
|
122
|
+
)
|
|
123
|
+
log_to_file: bool = Field(default=False, description="Enable logging to file")
|
|
124
|
+
log_file_path: str = Field(default="logs/memori.log", description="Log file path")
|
|
125
|
+
log_rotation: str = Field(default="10 MB", description="Log rotation size")
|
|
126
|
+
log_retention: str = Field(default="30 days", description="Log retention period")
|
|
127
|
+
log_compression: str = Field(default="gz", description="Log compression format")
|
|
128
|
+
structured_logging: bool = Field(
|
|
129
|
+
default=False, description="Enable structured JSON logging"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class MemorySettings(BaseModel):
|
|
134
|
+
"""Memory processing configuration"""
|
|
135
|
+
|
|
136
|
+
namespace: str = Field(
|
|
137
|
+
default="default", description="Default namespace for memory isolation"
|
|
138
|
+
)
|
|
139
|
+
shared_memory: bool = Field(
|
|
140
|
+
default=False, description="Enable shared memory across agents"
|
|
141
|
+
)
|
|
142
|
+
retention_policy: RetentionPolicy = Field(
|
|
143
|
+
default=RetentionPolicy.DAYS_30, description="Default memory retention policy"
|
|
144
|
+
)
|
|
145
|
+
auto_cleanup: bool = Field(
|
|
146
|
+
default=True, description="Enable automatic cleanup of expired memories"
|
|
147
|
+
)
|
|
148
|
+
cleanup_interval_hours: int = Field(
|
|
149
|
+
default=24, ge=1, description="Cleanup interval in hours"
|
|
150
|
+
)
|
|
151
|
+
importance_threshold: float = Field(
|
|
152
|
+
default=0.3,
|
|
153
|
+
ge=0.0,
|
|
154
|
+
le=1.0,
|
|
155
|
+
description="Minimum importance score for memory retention",
|
|
156
|
+
)
|
|
157
|
+
max_short_term_memories: int = Field(
|
|
158
|
+
default=1000, ge=10, description="Maximum number of short-term memories"
|
|
159
|
+
)
|
|
160
|
+
max_long_term_memories: int = Field(
|
|
161
|
+
default=10000, ge=100, description="Maximum number of long-term memories"
|
|
162
|
+
)
|
|
163
|
+
context_injection: bool = Field(
|
|
164
|
+
default=True, description="Enable context injection in conversations"
|
|
165
|
+
)
|
|
166
|
+
context_limit: int = Field(
|
|
167
|
+
default=3,
|
|
168
|
+
ge=1,
|
|
169
|
+
le=10,
|
|
170
|
+
description="Maximum number of context memories to inject",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class IntegrationSettings(BaseModel):
|
|
175
|
+
"""Integration configuration"""
|
|
176
|
+
|
|
177
|
+
litellm_enabled: bool = Field(
|
|
178
|
+
default=True, description="Enable LiteLLM integration"
|
|
179
|
+
)
|
|
180
|
+
openai_wrapper_enabled: bool = Field(
|
|
181
|
+
default=False, description="Enable OpenAI wrapper integration"
|
|
182
|
+
)
|
|
183
|
+
anthropic_wrapper_enabled: bool = Field(
|
|
184
|
+
default=False, description="Enable Anthropic wrapper integration"
|
|
185
|
+
)
|
|
186
|
+
auto_enable_on_import: bool = Field(
|
|
187
|
+
default=False, description="Automatically enable integrations on import"
|
|
188
|
+
)
|
|
189
|
+
callback_timeout: int = Field(
|
|
190
|
+
default=5, ge=1, le=30, description="Callback timeout in seconds"
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class MemoriSettings(BaseModel):
|
|
195
|
+
"""Main Memoriai configuration"""
|
|
196
|
+
|
|
197
|
+
version: str = Field(default="1.0.0", description="Configuration version")
|
|
198
|
+
debug: bool = Field(default=False, description="Enable debug mode")
|
|
199
|
+
verbose: bool = Field(
|
|
200
|
+
default=False, description="Enable verbose logging (loguru only)"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# Component settings
|
|
204
|
+
database: DatabaseSettings = Field(default_factory=DatabaseSettings)
|
|
205
|
+
agents: AgentSettings = Field(default_factory=AgentSettings)
|
|
206
|
+
logging: LoggingSettings = Field(default_factory=LoggingSettings)
|
|
207
|
+
memory: MemorySettings = Field(default_factory=MemorySettings)
|
|
208
|
+
integrations: IntegrationSettings = Field(default_factory=IntegrationSettings)
|
|
209
|
+
|
|
210
|
+
# Custom settings
|
|
211
|
+
custom_settings: Dict[str, Any] = Field(
|
|
212
|
+
default_factory=dict, description="Custom user-defined settings"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
class Config:
|
|
216
|
+
"""Pydantic configuration"""
|
|
217
|
+
|
|
218
|
+
env_prefix = "MEMORI_"
|
|
219
|
+
env_nested_delimiter = "__"
|
|
220
|
+
case_sensitive = False
|
|
221
|
+
|
|
222
|
+
@classmethod
|
|
223
|
+
def from_env(cls) -> "MemoriSettings":
|
|
224
|
+
"""Create settings from environment variables"""
|
|
225
|
+
return cls()
|
|
226
|
+
|
|
227
|
+
@classmethod
|
|
228
|
+
def from_file(cls, config_path: Union[str, Path]) -> "MemoriSettings":
|
|
229
|
+
"""Load settings from JSON/YAML file"""
|
|
230
|
+
import json
|
|
231
|
+
from pathlib import Path
|
|
232
|
+
|
|
233
|
+
config_path = Path(config_path)
|
|
234
|
+
if not config_path.exists():
|
|
235
|
+
raise FileNotFoundError(f"Configuration file not found: {config_path}")
|
|
236
|
+
|
|
237
|
+
with open(config_path) as f:
|
|
238
|
+
if config_path.suffix.lower() == ".json":
|
|
239
|
+
data = json.load(f)
|
|
240
|
+
elif config_path.suffix.lower() in [".yml", ".yaml"]:
|
|
241
|
+
try:
|
|
242
|
+
import yaml
|
|
243
|
+
|
|
244
|
+
data = yaml.safe_load(f)
|
|
245
|
+
except ImportError:
|
|
246
|
+
raise ImportError("PyYAML is required for YAML configuration files")
|
|
247
|
+
else:
|
|
248
|
+
raise ValueError(
|
|
249
|
+
f"Unsupported configuration file format: {config_path.suffix}"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
return cls(**data)
|
|
253
|
+
|
|
254
|
+
def to_file(self, config_path: Union[str, Path], format: str = "json") -> None:
|
|
255
|
+
"""Save settings to file"""
|
|
256
|
+
import json
|
|
257
|
+
from pathlib import Path
|
|
258
|
+
|
|
259
|
+
config_path = Path(config_path)
|
|
260
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
261
|
+
|
|
262
|
+
data = self.dict()
|
|
263
|
+
|
|
264
|
+
with open(config_path, "w") as f:
|
|
265
|
+
if format.lower() == "json":
|
|
266
|
+
json.dump(data, f, indent=2, default=str)
|
|
267
|
+
elif format.lower() in ["yml", "yaml"]:
|
|
268
|
+
try:
|
|
269
|
+
import yaml
|
|
270
|
+
|
|
271
|
+
yaml.safe_dump(data, f, default_flow_style=False)
|
|
272
|
+
except ImportError:
|
|
273
|
+
raise ImportError("PyYAML is required for YAML configuration files")
|
|
274
|
+
else:
|
|
275
|
+
raise ValueError(f"Unsupported format: {format}")
|
|
276
|
+
|
|
277
|
+
def get_database_url(self) -> str:
|
|
278
|
+
"""Get the database connection URL"""
|
|
279
|
+
return self.database.connection_string
|
|
280
|
+
|
|
281
|
+
def is_production(self) -> bool:
|
|
282
|
+
"""Check if running in production mode"""
|
|
283
|
+
return not self.debug and self.logging.level in [
|
|
284
|
+
LogLevel.INFO,
|
|
285
|
+
LogLevel.WARNING,
|
|
286
|
+
LogLevel.ERROR,
|
|
287
|
+
]
|