powermem 0.1.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.
Files changed (123) hide show
  1. powermem/__init__.py +103 -0
  2. powermem/agent/__init__.py +35 -0
  3. powermem/agent/abstract/__init__.py +22 -0
  4. powermem/agent/abstract/collaboration.py +259 -0
  5. powermem/agent/abstract/context.py +187 -0
  6. powermem/agent/abstract/manager.py +232 -0
  7. powermem/agent/abstract/permission.py +217 -0
  8. powermem/agent/abstract/privacy.py +267 -0
  9. powermem/agent/abstract/scope.py +199 -0
  10. powermem/agent/agent.py +791 -0
  11. powermem/agent/components/__init__.py +18 -0
  12. powermem/agent/components/collaboration_coordinator.py +645 -0
  13. powermem/agent/components/permission_controller.py +586 -0
  14. powermem/agent/components/privacy_protector.py +767 -0
  15. powermem/agent/components/scope_controller.py +685 -0
  16. powermem/agent/factories/__init__.py +16 -0
  17. powermem/agent/factories/agent_factory.py +266 -0
  18. powermem/agent/factories/config_factory.py +308 -0
  19. powermem/agent/factories/memory_factory.py +229 -0
  20. powermem/agent/implementations/__init__.py +16 -0
  21. powermem/agent/implementations/hybrid.py +728 -0
  22. powermem/agent/implementations/multi_agent.py +1040 -0
  23. powermem/agent/implementations/multi_user.py +1020 -0
  24. powermem/agent/types.py +53 -0
  25. powermem/agent/wrappers/__init__.py +14 -0
  26. powermem/agent/wrappers/agent_memory_wrapper.py +427 -0
  27. powermem/agent/wrappers/compatibility_wrapper.py +520 -0
  28. powermem/config_loader.py +318 -0
  29. powermem/configs.py +249 -0
  30. powermem/core/__init__.py +19 -0
  31. powermem/core/async_memory.py +1493 -0
  32. powermem/core/audit.py +258 -0
  33. powermem/core/base.py +165 -0
  34. powermem/core/memory.py +1567 -0
  35. powermem/core/setup.py +162 -0
  36. powermem/core/telemetry.py +215 -0
  37. powermem/integrations/__init__.py +17 -0
  38. powermem/integrations/embeddings/__init__.py +13 -0
  39. powermem/integrations/embeddings/aws_bedrock.py +100 -0
  40. powermem/integrations/embeddings/azure_openai.py +55 -0
  41. powermem/integrations/embeddings/base.py +31 -0
  42. powermem/integrations/embeddings/config/base.py +132 -0
  43. powermem/integrations/embeddings/configs.py +31 -0
  44. powermem/integrations/embeddings/factory.py +48 -0
  45. powermem/integrations/embeddings/gemini.py +39 -0
  46. powermem/integrations/embeddings/huggingface.py +41 -0
  47. powermem/integrations/embeddings/langchain.py +35 -0
  48. powermem/integrations/embeddings/lmstudio.py +29 -0
  49. powermem/integrations/embeddings/mock.py +11 -0
  50. powermem/integrations/embeddings/ollama.py +53 -0
  51. powermem/integrations/embeddings/openai.py +49 -0
  52. powermem/integrations/embeddings/qwen.py +102 -0
  53. powermem/integrations/embeddings/together.py +31 -0
  54. powermem/integrations/embeddings/vertexai.py +54 -0
  55. powermem/integrations/llm/__init__.py +18 -0
  56. powermem/integrations/llm/anthropic.py +87 -0
  57. powermem/integrations/llm/base.py +132 -0
  58. powermem/integrations/llm/config/anthropic.py +56 -0
  59. powermem/integrations/llm/config/azure.py +56 -0
  60. powermem/integrations/llm/config/base.py +62 -0
  61. powermem/integrations/llm/config/deepseek.py +56 -0
  62. powermem/integrations/llm/config/ollama.py +56 -0
  63. powermem/integrations/llm/config/openai.py +79 -0
  64. powermem/integrations/llm/config/qwen.py +68 -0
  65. powermem/integrations/llm/config/qwen_asr.py +46 -0
  66. powermem/integrations/llm/config/vllm.py +56 -0
  67. powermem/integrations/llm/configs.py +26 -0
  68. powermem/integrations/llm/deepseek.py +106 -0
  69. powermem/integrations/llm/factory.py +118 -0
  70. powermem/integrations/llm/gemini.py +201 -0
  71. powermem/integrations/llm/langchain.py +65 -0
  72. powermem/integrations/llm/ollama.py +106 -0
  73. powermem/integrations/llm/openai.py +166 -0
  74. powermem/integrations/llm/openai_structured.py +80 -0
  75. powermem/integrations/llm/qwen.py +207 -0
  76. powermem/integrations/llm/qwen_asr.py +171 -0
  77. powermem/integrations/llm/vllm.py +106 -0
  78. powermem/integrations/rerank/__init__.py +20 -0
  79. powermem/integrations/rerank/base.py +43 -0
  80. powermem/integrations/rerank/config/__init__.py +7 -0
  81. powermem/integrations/rerank/config/base.py +27 -0
  82. powermem/integrations/rerank/configs.py +23 -0
  83. powermem/integrations/rerank/factory.py +68 -0
  84. powermem/integrations/rerank/qwen.py +159 -0
  85. powermem/intelligence/__init__.py +17 -0
  86. powermem/intelligence/ebbinghaus_algorithm.py +354 -0
  87. powermem/intelligence/importance_evaluator.py +361 -0
  88. powermem/intelligence/intelligent_memory_manager.py +284 -0
  89. powermem/intelligence/manager.py +148 -0
  90. powermem/intelligence/plugin.py +229 -0
  91. powermem/prompts/__init__.py +29 -0
  92. powermem/prompts/graph/graph_prompts.py +217 -0
  93. powermem/prompts/graph/graph_tools_prompts.py +469 -0
  94. powermem/prompts/importance_evaluation.py +246 -0
  95. powermem/prompts/intelligent_memory_prompts.py +163 -0
  96. powermem/prompts/templates.py +193 -0
  97. powermem/storage/__init__.py +14 -0
  98. powermem/storage/adapter.py +896 -0
  99. powermem/storage/base.py +109 -0
  100. powermem/storage/config/base.py +13 -0
  101. powermem/storage/config/oceanbase.py +58 -0
  102. powermem/storage/config/pgvector.py +52 -0
  103. powermem/storage/config/sqlite.py +27 -0
  104. powermem/storage/configs.py +159 -0
  105. powermem/storage/factory.py +59 -0
  106. powermem/storage/migration_manager.py +438 -0
  107. powermem/storage/oceanbase/__init__.py +8 -0
  108. powermem/storage/oceanbase/constants.py +162 -0
  109. powermem/storage/oceanbase/oceanbase.py +1384 -0
  110. powermem/storage/oceanbase/oceanbase_graph.py +1441 -0
  111. powermem/storage/pgvector/__init__.py +7 -0
  112. powermem/storage/pgvector/pgvector.py +420 -0
  113. powermem/storage/sqlite/__init__.py +0 -0
  114. powermem/storage/sqlite/sqlite.py +218 -0
  115. powermem/storage/sqlite/sqlite_vector_store.py +311 -0
  116. powermem/utils/__init__.py +35 -0
  117. powermem/utils/utils.py +605 -0
  118. powermem/version.py +23 -0
  119. powermem-0.1.0.dist-info/METADATA +187 -0
  120. powermem-0.1.0.dist-info/RECORD +123 -0
  121. powermem-0.1.0.dist-info/WHEEL +5 -0
  122. powermem-0.1.0.dist-info/licenses/LICENSE +206 -0
  123. powermem-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,148 @@
1
+ """
2
+ Intelligence manager
3
+
4
+ This module provides the main intelligence management interface.
5
+ """
6
+
7
+ import logging
8
+ from typing import Any, Dict, List, Optional
9
+ from datetime import datetime
10
+
11
+ from .intelligent_memory_manager import IntelligentMemoryManager
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class IntelligenceManager:
17
+ """
18
+ Main intelligence manager for memory processing.
19
+ """
20
+
21
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
22
+ """
23
+ Initialize intelligence manager.
24
+
25
+ Args:
26
+ config: Configuration dictionary
27
+ """
28
+ self.config = config or {}
29
+ # Check if intelligent memory is enabled
30
+ intelligent_config = self.config.get("intelligent_memory", {})
31
+ self.enabled = intelligent_config.get("enabled", False) # Default to False for backward compatibility
32
+
33
+ if self.enabled:
34
+ self.intelligent_memory_manager = IntelligentMemoryManager(self.config)
35
+ else:
36
+ self.intelligent_memory_manager = None
37
+
38
+ logger.info(f"IntelligenceManager initialized (enabled: {self.enabled})")
39
+
40
+ def process_metadata(
41
+ self,
42
+ content: str,
43
+ metadata: Optional[Dict[str, Any]] = None,
44
+ context: Optional[Dict[str, Any]] = None
45
+ ) -> Dict[str, Any]:
46
+ """
47
+ Process metadata with intelligence.
48
+
49
+ Args:
50
+ content: Content to analyze
51
+ metadata: Additional metadata
52
+ context: Additional context
53
+
54
+ Returns:
55
+ Enhanced metadata with intelligence analysis
56
+ """
57
+ if not self.enabled or not self.intelligent_memory_manager:
58
+ return metadata or {}
59
+
60
+ return self.intelligent_memory_manager.process_metadata(content, metadata, context)
61
+
62
+ async def process_metadata_async(
63
+ self,
64
+ content: str,
65
+ metadata: Optional[Dict[str, Any]] = None,
66
+ context: Optional[Dict[str, Any]] = None
67
+ ) -> Dict[str, Any]:
68
+ """
69
+ Process metadata with intelligence asynchronously.
70
+
71
+ Args:
72
+ content: Content to analyze
73
+ metadata: Additional metadata
74
+ context: Additional context
75
+
76
+ Returns:
77
+ Enhanced metadata with intelligence analysis
78
+ """
79
+ if not self.enabled or not self.intelligent_memory_manager:
80
+ return metadata or {}
81
+
82
+ return await self.intelligent_memory_manager.process_metadata_async(content, metadata, context)
83
+
84
+ def process_search_results(
85
+ self,
86
+ results: List[Dict[str, Any]],
87
+ query: str
88
+ ) -> List[Dict[str, Any]]:
89
+ """
90
+ Process search results with intelligence.
91
+
92
+ Args:
93
+ results: Search results
94
+ query: Original query
95
+
96
+ Returns:
97
+ Processed and ranked results
98
+ """
99
+ if not self.enabled or not self.intelligent_memory_manager:
100
+ # Return original results if intelligence is disabled
101
+ return results
102
+
103
+ return self.intelligent_memory_manager.process_search_results(results, query)
104
+
105
+ async def process_search_results_async(
106
+ self,
107
+ results: List[Dict[str, Any]],
108
+ query: str
109
+ ) -> List[Dict[str, Any]]:
110
+ """
111
+ Process search results with intelligence asynchronously.
112
+
113
+ Args:
114
+ results: Search results
115
+ query: Original query
116
+
117
+ Returns:
118
+ Processed and ranked results
119
+ """
120
+ if not self.enabled or not self.intelligent_memory_manager:
121
+ # Return original results if intelligence is disabled
122
+ return results
123
+
124
+ return await self.intelligent_memory_manager.process_search_results_async(results, query)
125
+
126
+ def optimize_memories(self) -> Dict[str, Any]:
127
+ """
128
+ Optimize memory storage.
129
+
130
+ Returns:
131
+ Optimization results
132
+ """
133
+ if not self.enabled or not self.intelligent_memory_manager:
134
+ return {"optimized": False, "reason": "intelligence disabled"}
135
+
136
+ return self.intelligent_memory_manager.optimize_memories()
137
+
138
+ def get_memory_stats(self) -> Dict[str, Any]:
139
+ """
140
+ Get memory statistics.
141
+
142
+ Returns:
143
+ Memory statistics
144
+ """
145
+ if not self.enabled or not self.intelligent_memory_manager:
146
+ return {"stats": "intelligence disabled"}
147
+
148
+ return self.intelligent_memory_manager.get_memory_stats()
@@ -0,0 +1,229 @@
1
+ """
2
+ Pluggable intelligent memory plugin interface and default implementation.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import logging
8
+ from datetime import datetime
9
+ from typing import Any, Dict, List, Optional, Tuple
10
+
11
+ from .importance_evaluator import ImportanceEvaluator
12
+ from .ebbinghaus_algorithm import EbbinghausAlgorithm
13
+
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class IntelligentMemoryPlugin:
19
+ """
20
+ Interface for intelligent memory plugins.
21
+ Implementations can annotate new memories and manage lifecycle on access/search.
22
+ """
23
+
24
+ def __init__(self, config: Dict[str, Any]):
25
+ self.config = config or {}
26
+
27
+ @property
28
+ def enabled(self) -> bool: # pragma: no cover - trivial
29
+ return bool(self.config.get("enabled", False))
30
+
31
+ def on_add(self, *, content: str, metadata: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
32
+ """
33
+ Hook invoked before persisting a memory. Return extra fields to merge into record.
34
+ """
35
+ return {}
36
+
37
+ def on_get(self, memory: Dict[str, Any]) -> Tuple[Optional[Dict[str, Any]], bool]:
38
+ """
39
+ Hook invoked on single memory access.
40
+ Returns (updates, delete_flag). If delete_flag is True, caller should delete memory.
41
+ """
42
+ return None, False
43
+
44
+ def on_search(self, results: List[Dict[str, Any]]) -> Tuple[List[Tuple[str, Dict[str, Any]]], List[str]]:
45
+ """
46
+ Hook invoked on batch search results.
47
+ Returns (updates, delete_ids).
48
+ updates: list of (memory_id, update_dict)
49
+ delete_ids: list of memory ids to delete
50
+ """
51
+ return [], []
52
+
53
+
54
+ class EbbinghausIntelligencePlugin(IntelligentMemoryPlugin):
55
+ """
56
+ Default plugin implementing importance evaluation and lifecycle with Ebbinghaus.
57
+ """
58
+
59
+ def __init__(self, config: Dict[str, Any]):
60
+ super().__init__(config)
61
+ self._importance = None
62
+ self._algo = None
63
+ if self.enabled:
64
+ try:
65
+ # Prepare importance config, merging custom_importance_evaluation_prompt if present
66
+ importance_config = self.config.get("importance", {})
67
+ if "custom_importance_evaluation_prompt" in self.config:
68
+ importance_config = importance_config.copy() if isinstance(importance_config, dict) else {}
69
+ importance_config["custom_importance_evaluation_prompt"] = self.config["custom_importance_evaluation_prompt"]
70
+
71
+ self._importance = ImportanceEvaluator(
72
+ importance_config,
73
+ self.config.get("llm", {}),
74
+ )
75
+ # Support both "ebbinghaus" and direct intelligent_memory config
76
+ ebbinghaus_cfg = self.config.get("ebbinghaus", {})
77
+ # If no ebbinghaus config, use the main config as ebbinghaus config
78
+ if not ebbinghaus_cfg:
79
+ ebbinghaus_cfg = self.config
80
+ self._algo = EbbinghausAlgorithm(ebbinghaus_cfg)
81
+ except Exception as e: # pragma: no cover - defensive
82
+ logger.warning(f"Failed to init Ebbinghaus plugin: {e}")
83
+ self.config["enabled"] = False
84
+
85
+ def _classify(self, score: float) -> str:
86
+ if not self._algo:
87
+ return "working"
88
+ if score >= self._algo.long_term_threshold:
89
+ return "long_term"
90
+ if score >= self._algo.short_term_threshold:
91
+ return "short_term"
92
+ return "working"
93
+
94
+ def on_add(self, *, content: str, metadata: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
95
+ if not self.enabled or not self._importance or not self._algo:
96
+ return {}
97
+ try:
98
+ # Evaluate importance
99
+ score = self._importance.evaluate_importance(content, metadata)
100
+
101
+ # Classify memory type
102
+ memory_type = self._classify(score)
103
+
104
+ # Process with Ebbinghaus algorithm to get intelligence metadata
105
+ intelligence_metadata = self._algo.process_memory_metadata(content, score, memory_type)
106
+
107
+ # Return enhanced metadata
108
+ return {
109
+ "importance_score": score,
110
+ "memory_type": memory_type,
111
+ "access_count": 0,
112
+ "intelligence": intelligence_metadata.get('intelligence', {}),
113
+ "memory_management": intelligence_metadata.get('memory_management', {}),
114
+ "processing_applied": True,
115
+ }
116
+ except Exception as e:
117
+ logger.warning(f"Failed to process memory in on_add: {e}")
118
+ return {"access_count": 0}
119
+
120
+ def on_get(self, memory: Dict[str, Any]) -> Tuple[Optional[Dict[str, Any]], bool]:
121
+ if not self.enabled or not self._algo:
122
+ return None, False
123
+ try:
124
+ updates: Dict[str, Any] = {
125
+ "access_count": (memory.get("access_count") or 0) + 1,
126
+ "updated_at": datetime.utcnow(),
127
+ }
128
+
129
+ # Check if memory should be forgotten
130
+ if self._algo.should_forget(memory):
131
+ return None, True
132
+
133
+ # Check if memory should be promoted
134
+ if self._algo.should_promote(memory):
135
+ current = memory.get("memory_type")
136
+ if current == "working":
137
+ updates["memory_type"] = "short_term"
138
+ elif current == "short_term":
139
+ updates["memory_type"] = "long_term"
140
+
141
+ # Check if memory should be archived
142
+ if self._algo.should_archive(memory):
143
+ meta = memory.get("metadata") or {}
144
+ meta["archived"] = True
145
+ updates["metadata"] = meta
146
+
147
+ # Re-process content if memory type changed or if it's been accessed multiple times
148
+ access_count = memory.get("access_count", 0) + 1
149
+ if (updates.get("memory_type") != memory.get("memory_type") or
150
+ access_count % 5 == 0): # Re-process every 5 accesses
151
+
152
+ original_content = memory.get("original_content") or memory.get("content", "")
153
+ importance_score = memory.get("importance_score", 0.5)
154
+ memory_type = updates.get("memory_type") or memory.get("memory_type", "working")
155
+
156
+ # Re-process with updated parameters
157
+ intelligence_metadata = self._algo.process_memory_metadata(original_content, importance_score, memory_type)
158
+ updates.update(intelligence_metadata)
159
+ updates["last_reprocessed_at"] = datetime.utcnow()
160
+
161
+ return updates, False
162
+ except Exception as e:
163
+ logger.warning(f"Failed to process memory in on_get: {e}")
164
+ return None, False
165
+
166
+ def on_search(self, results: List[Dict[str, Any]]) -> Tuple[List[Tuple[str, Dict[str, Any]]], List[str]]:
167
+ if not self.enabled or not self._algo:
168
+ return [], []
169
+ updates: List[Tuple[str, Dict[str, Any]]] = []
170
+ deletes: List[str] = []
171
+
172
+ for item in results:
173
+ try:
174
+ mem_id = item.get("id") or item.get("memory_id")
175
+ if not mem_id:
176
+ continue
177
+
178
+ # Process individual memory
179
+ upd, delete_flag = self.on_get(item)
180
+
181
+ if delete_flag:
182
+ deletes.append(mem_id)
183
+ elif upd:
184
+ # Add search-specific enhancements
185
+ search_updates = self._enhance_for_search(item, upd)
186
+ updates.append((mem_id, search_updates))
187
+
188
+ except Exception as e:
189
+ logger.warning(f"Failed to process memory {mem_id} in on_search: {e}")
190
+ continue
191
+
192
+ return updates, deletes
193
+
194
+ def _enhance_for_search(self, memory: Dict[str, Any], base_updates: Dict[str, Any]) -> Dict[str, Any]:
195
+ """
196
+ Enhance memory for search context.
197
+
198
+ Args:
199
+ memory: Memory data
200
+ base_updates: Base updates from on_get
201
+
202
+ Returns:
203
+ Enhanced updates for search context
204
+ """
205
+ try:
206
+ # Add search-specific metadata
207
+ search_metadata = memory.get("metadata", {})
208
+ search_metadata["last_searched_at"] = datetime.utcnow()
209
+ search_metadata["search_count"] = search_metadata.get("search_count", 0) + 1
210
+
211
+ # Update base updates with search metadata
212
+ enhanced_updates = base_updates.copy()
213
+ enhanced_updates["metadata"] = search_metadata
214
+
215
+ # Add search relevance score if not present
216
+ if "search_relevance_score" not in memory:
217
+ # Simple relevance calculation based on access patterns
218
+ access_count = memory.get("access_count", 0)
219
+ importance_score = memory.get("importance_score", 0.5)
220
+ search_relevance = min(1.0, (access_count * 0.1) + (importance_score * 0.5))
221
+ enhanced_updates["search_relevance_score"] = search_relevance
222
+
223
+ return enhanced_updates
224
+
225
+ except Exception as e:
226
+ logger.warning(f"Failed to enhance memory for search: {e}")
227
+ return base_updates
228
+
229
+
@@ -0,0 +1,29 @@
1
+ """
2
+ Prompt templates for memory operations
3
+
4
+ This module provides prompt templates for different memory operations.
5
+ """
6
+
7
+ from .templates import PromptTemplates
8
+ from .graph.graph_prompts import GraphPrompts
9
+ from .graph.graph_tools_prompts import GraphToolsPrompts
10
+ from .intelligent_memory_prompts import (
11
+ FACT_RETRIEVAL_PROMPT,
12
+ FACT_EXTRACTION_PROMPT,
13
+ DEFAULT_UPDATE_MEMORY_PROMPT,
14
+ MEMORY_UPDATE_PROMPT,
15
+ get_memory_update_prompt,
16
+ parse_messages_for_facts
17
+ )
18
+
19
+ __all__ = [
20
+ "PromptTemplates",
21
+ "GraphPrompts",
22
+ "GraphToolsPrompts",
23
+ "FACT_RETRIEVAL_PROMPT",
24
+ "FACT_EXTRACTION_PROMPT",
25
+ "DEFAULT_UPDATE_MEMORY_PROMPT",
26
+ "MEMORY_UPDATE_PROMPT",
27
+ "get_memory_update_prompt",
28
+ "parse_messages_for_facts",
29
+ ]
@@ -0,0 +1,217 @@
1
+ """
2
+ Graph prompts for memory operations
3
+
4
+ This module provides prompts for graph-based memory operations.
5
+ """
6
+
7
+ import logging
8
+ from typing import Dict, Any, Optional
9
+ from ..templates import PromptTemplates
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ # Graph prompts constants
14
+ UPDATE_GRAPH_PROMPT = """
15
+ You are an AI expert specializing in graph memory management and optimization. Your task is to analyze existing graph memories alongside new information, and update the relationships in the memory list to ensure the most accurate, current, and coherent representation of knowledge.
16
+
17
+ Input:
18
+ 1. Existing Graph Memories: A list of current graph memories, each containing source, target, and relationship information.
19
+ 2. New Graph Memory: Fresh information to be integrated into the existing graph structure.
20
+
21
+ Guidelines:
22
+ 1. Identification: Use the source and target as primary identifiers when matching existing memories with new information.
23
+ 2. Conflict Resolution:
24
+ - If new information contradicts an existing memory:
25
+ a) For matching source and target but differing content, update the relationship of the existing memory.
26
+ b) If the new memory provides more recent or accurate information, update the existing memory accordingly.
27
+ 3. Comprehensive Review: Thoroughly examine each existing graph memory against the new information, updating relationships as necessary. Multiple updates may be required.
28
+ 4. Consistency: Maintain a uniform and clear style across all memories. Each entry should be concise yet comprehensive.
29
+ 5. Semantic Coherence: Ensure that updates maintain or improve the overall semantic structure of the graph.
30
+ 6. Temporal Awareness: If timestamps are available, consider the recency of information when making updates.
31
+ 7. Relationship Refinement: Look for opportunities to refine relationship descriptions for greater precision or clarity.
32
+ 8. Redundancy Elimination: Identify and merge any redundant or highly similar relationships that may result from the update.
33
+
34
+ Memory Format:
35
+ source -- RELATIONSHIP -- destination
36
+
37
+ Task Details:
38
+ ======= Existing Graph Memories:=======
39
+ {existing_memories}
40
+
41
+ ======= New Graph Memory:=======
42
+ {new_memories}
43
+
44
+ Output:
45
+ Provide a list of update instructions, each specifying the source, target, and the new relationship to be set. Only include memories that require updates.
46
+ """
47
+
48
+ EXTRACT_RELATIONS_PROMPT = """
49
+
50
+ You are an advanced algorithm designed to extract structured information from text to construct knowledge graphs. Your goal is to capture comprehensive and accurate information. Follow these key principles:
51
+
52
+ 1. Extract only explicitly stated information from the text.
53
+ 2. Establish relationships among the entities provided.
54
+ 3. Use "USER_ID" as the source entity for any self-references (e.g., "I," "me," "my," etc.) in user messages.
55
+ CUSTOM_PROMPT
56
+
57
+ Relationships:
58
+ - Use consistent, general, and timeless relationship types.
59
+ - Example: Prefer "professor" over "became_professor."
60
+ - Relationships should only be established among the entities explicitly mentioned in the user message.
61
+
62
+ Entity Consistency:
63
+ - Ensure that relationships are coherent and logically align with the context of the message.
64
+ - Maintain consistent naming for entities across the extracted data.
65
+
66
+ Strive to construct a coherent and easily understandable knowledge graph by eshtablishing all the relationships among the entities and adherence to the user's context.
67
+
68
+ Adhere strictly to these guidelines to ensure high-quality knowledge graph extraction."""
69
+
70
+ DELETE_RELATIONS_SYSTEM_PROMPT = """
71
+ You are a graph memory manager specializing in identifying, managing, and optimizing relationships within graph-based memories. Your primary task is to analyze a list of existing relationships and determine which ones should be deleted based on the new information provided.
72
+ Input:
73
+ 1. Existing Graph Memories: A list of current graph memories, each containing source, relationship, and destination information.
74
+ 2. New Text: The new information to be integrated into the existing graph structure.
75
+ 3. Use "USER_ID" as node for any self-references (e.g., "I," "me," "my," etc.) in user messages.
76
+
77
+ Guidelines:
78
+ 1. Identification: Use the new information to evaluate existing relationships in the memory graph.
79
+ 2. Deletion Criteria: Delete a relationship only if it meets at least one of these conditions:
80
+ - Outdated or Inaccurate: The new information is more recent or accurate.
81
+ - Contradictory: The new information conflicts with or negates the existing information.
82
+ 3. DO NOT DELETE if their is a possibility of same type of relationship but different destination nodes.
83
+ 4. Comprehensive Analysis:
84
+ - Thoroughly examine each existing relationship against the new information and delete as necessary.
85
+ - Multiple deletions may be required based on the new information.
86
+ 5. Semantic Integrity:
87
+ - Ensure that deletions maintain or improve the overall semantic structure of the graph.
88
+ - Avoid deleting relationships that are NOT contradictory/outdated to the new information.
89
+ 6. Temporal Awareness: Prioritize recency when timestamps are available.
90
+ 7. Necessity Principle: Only DELETE relationships that must be deleted and are contradictory/outdated to the new information to maintain an accurate and coherent memory graph.
91
+
92
+ Note: DO NOT DELETE if their is a possibility of same type of relationship but different destination nodes.
93
+
94
+ For example:
95
+ Existing Memory: alice -- loves_to_eat -- pizza
96
+ New Information: Alice also loves to eat burger.
97
+
98
+ Do not delete in the above example because there is a possibility that Alice loves to eat both pizza and burger.
99
+
100
+ Memory Format:
101
+ source -- relationship -- destination
102
+
103
+ Provide a list of deletion instructions, each specifying the relationship to be deleted.
104
+ """
105
+
106
+ class GraphPrompts(PromptTemplates):
107
+ """
108
+ Prompts for graph-based memory operations.
109
+ """
110
+
111
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
112
+ """
113
+ Initialize graph prompts.
114
+
115
+ Args:
116
+ config: Configuration dictionary
117
+ """
118
+ super().__init__(config)
119
+ # Store custom prompts from config if provided
120
+ self._custom_update_graph_prompt = None
121
+ self._custom_extract_relations_prompt = None
122
+ self._custom_delete_relations_prompt = None
123
+
124
+ if config:
125
+ # Support both graph_store config structure and direct config
126
+ graph_store_config = config.get("graph_store", {})
127
+ if isinstance(graph_store_config, dict):
128
+ self._custom_update_graph_prompt = graph_store_config.get("custom_update_graph_prompt")
129
+ self._custom_extract_relations_prompt = graph_store_config.get("custom_extract_relations_prompt") or graph_store_config.get("custom_prompt")
130
+ self._custom_delete_relations_prompt = graph_store_config.get("custom_delete_relations_prompt")
131
+ # Also support direct config keys
132
+ if not self._custom_update_graph_prompt:
133
+ self._custom_update_graph_prompt = config.get("custom_update_graph_prompt")
134
+ if not self._custom_extract_relations_prompt:
135
+ self._custom_extract_relations_prompt = config.get("custom_extract_relations_prompt") or config.get("custom_prompt")
136
+ if not self._custom_delete_relations_prompt:
137
+ self._custom_delete_relations_prompt = config.get("custom_delete_relations_prompt")
138
+
139
+ def get_update_graph_prompt(self, existing_memories: str, new_memories: str) -> str:
140
+ """
141
+ Get graph update prompt.
142
+
143
+ Args:
144
+ existing_memories: Existing graph memories
145
+ new_memories: New graph memories to integrate
146
+
147
+ Returns:
148
+ Formatted prompt
149
+ """
150
+ # Use custom prompt if provided, otherwise use default
151
+ prompt_template = self._custom_update_graph_prompt or UPDATE_GRAPH_PROMPT
152
+ try:
153
+ return prompt_template.format(
154
+ existing_memories=existing_memories,
155
+ new_memories=new_memories
156
+ )
157
+ except KeyError:
158
+ # If custom prompt doesn't have format placeholders, return as-is or append
159
+ if "{existing_memories}" not in prompt_template or "{new_memories}" not in prompt_template:
160
+ logger.warning("Custom update graph prompt missing format placeholders, appending data")
161
+ return f"{prompt_template}\n\nExisting Memories:\n{existing_memories}\n\nNew Memories:\n{new_memories}"
162
+ return prompt_template.format(
163
+ existing_memories=existing_memories,
164
+ new_memories=new_memories
165
+ )
166
+
167
+ def get_extract_relations_prompt(self, text: str) -> str:
168
+ """
169
+ Get relations extraction prompt.
170
+
171
+ Args:
172
+ text: Text to extract relations from
173
+
174
+ Returns:
175
+ Formatted prompt
176
+ """
177
+ # Use custom prompt if provided, otherwise use default
178
+ prompt = self._custom_extract_relations_prompt or EXTRACT_RELATIONS_PROMPT
179
+ return prompt.replace("USER_ID", "USER_ID")
180
+
181
+ def get_delete_relations_prompt(self, existing_memories: str, new_text: str, user_id: str = "USER_ID") -> tuple[str, str]:
182
+ """
183
+ Get delete relations prompt.
184
+
185
+ Args:
186
+ existing_memories: Existing graph memories
187
+ new_text: New text information
188
+ user_id: User ID for self-references
189
+
190
+ Returns:
191
+ Tuple of (system_prompt, user_prompt)
192
+ """
193
+ # Use custom prompt if provided, otherwise use default
194
+ system_prompt = self._custom_delete_relations_prompt or DELETE_RELATIONS_SYSTEM_PROMPT
195
+ system_prompt = system_prompt.replace("USER_ID", user_id)
196
+ user_prompt = f"Here are the existing memories: {existing_memories} \n\n New Information: {new_text}"
197
+
198
+ return system_prompt, user_prompt
199
+
200
+ def get_system_prompt(self, prompt_type: str = "extract_relations") -> str:
201
+ """
202
+ Get system prompt for graph operations.
203
+
204
+ Args:
205
+ prompt_type: Type of system prompt (extract_relations, delete_relations)
206
+
207
+ Returns:
208
+ System prompt
209
+ """
210
+ if prompt_type == "extract_relations":
211
+ # Use custom prompt if provided, otherwise use default
212
+ return self._custom_extract_relations_prompt or EXTRACT_RELATIONS_PROMPT
213
+ elif prompt_type == "delete_relations":
214
+ # Use custom prompt if provided, otherwise use default
215
+ return self._custom_delete_relations_prompt or DELETE_RELATIONS_SYSTEM_PROMPT
216
+ else:
217
+ return self._custom_extract_relations_prompt or EXTRACT_RELATIONS_PROMPT