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.

Files changed (44) hide show
  1. memoriai/__init__.py +140 -0
  2. memoriai/agents/__init__.py +7 -0
  3. memoriai/agents/conscious_agent.py +506 -0
  4. memoriai/agents/memory_agent.py +322 -0
  5. memoriai/agents/retrieval_agent.py +579 -0
  6. memoriai/config/__init__.py +14 -0
  7. memoriai/config/manager.py +281 -0
  8. memoriai/config/settings.py +287 -0
  9. memoriai/core/__init__.py +6 -0
  10. memoriai/core/database.py +966 -0
  11. memoriai/core/memory.py +1349 -0
  12. memoriai/database/__init__.py +5 -0
  13. memoriai/database/connectors/__init__.py +9 -0
  14. memoriai/database/connectors/mysql_connector.py +159 -0
  15. memoriai/database/connectors/postgres_connector.py +158 -0
  16. memoriai/database/connectors/sqlite_connector.py +148 -0
  17. memoriai/database/queries/__init__.py +15 -0
  18. memoriai/database/queries/base_queries.py +204 -0
  19. memoriai/database/queries/chat_queries.py +157 -0
  20. memoriai/database/queries/entity_queries.py +236 -0
  21. memoriai/database/queries/memory_queries.py +178 -0
  22. memoriai/database/templates/__init__.py +0 -0
  23. memoriai/database/templates/basic_template.py +0 -0
  24. memoriai/database/templates/schemas/__init__.py +0 -0
  25. memoriai/integrations/__init__.py +68 -0
  26. memoriai/integrations/anthropic_integration.py +194 -0
  27. memoriai/integrations/litellm_integration.py +11 -0
  28. memoriai/integrations/openai_integration.py +273 -0
  29. memoriai/scripts/llm_text.py +50 -0
  30. memoriai/tools/__init__.py +5 -0
  31. memoriai/tools/memory_tool.py +544 -0
  32. memoriai/utils/__init__.py +89 -0
  33. memoriai/utils/exceptions.py +418 -0
  34. memoriai/utils/helpers.py +433 -0
  35. memoriai/utils/logging.py +204 -0
  36. memoriai/utils/pydantic_models.py +258 -0
  37. memoriai/utils/schemas.py +0 -0
  38. memoriai/utils/validators.py +339 -0
  39. memorisdk-1.0.0.dist-info/METADATA +386 -0
  40. memorisdk-1.0.0.dist-info/RECORD +44 -0
  41. memorisdk-1.0.0.dist-info/WHEEL +5 -0
  42. memorisdk-1.0.0.dist-info/entry_points.txt +2 -0
  43. memorisdk-1.0.0.dist-info/licenses/LICENSE +203 -0
  44. 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
+ ]
@@ -0,0 +1,6 @@
1
+ """Core memory functionality for Memoriai v1.0"""
2
+
3
+ from .database import DatabaseManager
4
+ from .memory import Memori
5
+
6
+ __all__ = ["Memori", "DatabaseManager"]