noesium 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 (86) hide show
  1. noesium/core/__init__.py +4 -0
  2. noesium/core/agent/__init__.py +14 -0
  3. noesium/core/agent/base.py +227 -0
  4. noesium/core/consts.py +6 -0
  5. noesium/core/goalith/conflict/conflict.py +104 -0
  6. noesium/core/goalith/conflict/detector.py +53 -0
  7. noesium/core/goalith/decomposer/__init__.py +6 -0
  8. noesium/core/goalith/decomposer/base.py +46 -0
  9. noesium/core/goalith/decomposer/callable_decomposer.py +65 -0
  10. noesium/core/goalith/decomposer/llm_decomposer.py +326 -0
  11. noesium/core/goalith/decomposer/prompts.py +140 -0
  12. noesium/core/goalith/decomposer/simple_decomposer.py +61 -0
  13. noesium/core/goalith/errors.py +22 -0
  14. noesium/core/goalith/goalgraph/graph.py +526 -0
  15. noesium/core/goalith/goalgraph/node.py +179 -0
  16. noesium/core/goalith/replanner/base.py +31 -0
  17. noesium/core/goalith/replanner/replanner.py +36 -0
  18. noesium/core/goalith/service.py +26 -0
  19. noesium/core/llm/__init__.py +154 -0
  20. noesium/core/llm/base.py +152 -0
  21. noesium/core/llm/litellm.py +528 -0
  22. noesium/core/llm/llamacpp.py +487 -0
  23. noesium/core/llm/message.py +184 -0
  24. noesium/core/llm/ollama.py +459 -0
  25. noesium/core/llm/openai.py +520 -0
  26. noesium/core/llm/openrouter.py +89 -0
  27. noesium/core/llm/prompt.py +551 -0
  28. noesium/core/memory/__init__.py +11 -0
  29. noesium/core/memory/base.py +464 -0
  30. noesium/core/memory/memu/__init__.py +24 -0
  31. noesium/core/memory/memu/config/__init__.py +26 -0
  32. noesium/core/memory/memu/config/activity/config.py +46 -0
  33. noesium/core/memory/memu/config/event/config.py +46 -0
  34. noesium/core/memory/memu/config/markdown_config.py +241 -0
  35. noesium/core/memory/memu/config/profile/config.py +48 -0
  36. noesium/core/memory/memu/llm_adapter.py +129 -0
  37. noesium/core/memory/memu/memory/__init__.py +31 -0
  38. noesium/core/memory/memu/memory/actions/__init__.py +40 -0
  39. noesium/core/memory/memu/memory/actions/add_activity_memory.py +299 -0
  40. noesium/core/memory/memu/memory/actions/base_action.py +342 -0
  41. noesium/core/memory/memu/memory/actions/cluster_memories.py +262 -0
  42. noesium/core/memory/memu/memory/actions/generate_suggestions.py +198 -0
  43. noesium/core/memory/memu/memory/actions/get_available_categories.py +66 -0
  44. noesium/core/memory/memu/memory/actions/link_related_memories.py +515 -0
  45. noesium/core/memory/memu/memory/actions/run_theory_of_mind.py +254 -0
  46. noesium/core/memory/memu/memory/actions/update_memory_with_suggestions.py +514 -0
  47. noesium/core/memory/memu/memory/embeddings.py +130 -0
  48. noesium/core/memory/memu/memory/file_manager.py +306 -0
  49. noesium/core/memory/memu/memory/memory_agent.py +578 -0
  50. noesium/core/memory/memu/memory/recall_agent.py +376 -0
  51. noesium/core/memory/memu/memory_store.py +628 -0
  52. noesium/core/memory/models.py +149 -0
  53. noesium/core/msgbus/__init__.py +12 -0
  54. noesium/core/msgbus/base.py +395 -0
  55. noesium/core/orchestrix/__init__.py +0 -0
  56. noesium/core/py.typed +0 -0
  57. noesium/core/routing/__init__.py +20 -0
  58. noesium/core/routing/base.py +66 -0
  59. noesium/core/routing/router.py +241 -0
  60. noesium/core/routing/strategies/__init__.py +9 -0
  61. noesium/core/routing/strategies/dynamic_complexity.py +361 -0
  62. noesium/core/routing/strategies/self_assessment.py +147 -0
  63. noesium/core/routing/types.py +38 -0
  64. noesium/core/toolify/__init__.py +39 -0
  65. noesium/core/toolify/base.py +360 -0
  66. noesium/core/toolify/config.py +138 -0
  67. noesium/core/toolify/mcp_integration.py +275 -0
  68. noesium/core/toolify/registry.py +214 -0
  69. noesium/core/toolify/toolkits/__init__.py +1 -0
  70. noesium/core/tracing/__init__.py +37 -0
  71. noesium/core/tracing/langgraph_hooks.py +308 -0
  72. noesium/core/tracing/opik_tracing.py +144 -0
  73. noesium/core/tracing/token_tracker.py +166 -0
  74. noesium/core/utils/__init__.py +10 -0
  75. noesium/core/utils/logging.py +172 -0
  76. noesium/core/utils/statistics.py +12 -0
  77. noesium/core/utils/typing.py +17 -0
  78. noesium/core/vector_store/__init__.py +79 -0
  79. noesium/core/vector_store/base.py +94 -0
  80. noesium/core/vector_store/pgvector.py +304 -0
  81. noesium/core/vector_store/weaviate.py +383 -0
  82. noesium-0.1.0.dist-info/METADATA +525 -0
  83. noesium-0.1.0.dist-info/RECORD +86 -0
  84. noesium-0.1.0.dist-info/WHEEL +5 -0
  85. noesium-0.1.0.dist-info/licenses/LICENSE +21 -0
  86. noesium-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,262 @@
1
+ import itertools
2
+ from typing import Any, Dict, List
3
+
4
+ from .base_action import BaseAction
5
+
6
+
7
+ class ClusterMemoriesAction(BaseAction):
8
+ """
9
+ Cluster memories into different categories.
10
+ """
11
+
12
+ @property
13
+ def action_name(self) -> str:
14
+ return "cluster_memories"
15
+
16
+ def get_schema(self) -> Dict[str, Any]:
17
+ """Return OpenAI-compatible function schema"""
18
+ return {
19
+ "name": self.action_name,
20
+ "description": "Cluster memories into different categories",
21
+ "parameters": {
22
+ "type": "object",
23
+ "properties": {
24
+ "character_name": {
25
+ "type": "string",
26
+ "description": "Name of the character",
27
+ },
28
+ "conversation_content": {
29
+ "type": "string",
30
+ "description": "The full conversation content to provide context for clustering",
31
+ },
32
+ "new_memory_items": {
33
+ "type": "array",
34
+ "items": {
35
+ "type": "object",
36
+ "properties": {
37
+ "memory_id": {"type": "string"},
38
+ "content": {"type": "string"},
39
+ "mentioned_at": {"type": "string"},
40
+ },
41
+ "required": ["memory_id", "content", "mentioned_at"],
42
+ },
43
+ "description": "List of new memory items from the conversation",
44
+ },
45
+ },
46
+ "required": ["character_name", "conversation_content", "new_memory_items"],
47
+ },
48
+ }
49
+
50
+ def execute(
51
+ self,
52
+ character_name: str,
53
+ conversation_content: str,
54
+ new_memory_items: List[Dict[str, str]],
55
+ new_theory_of_mind_items: List[Dict[str, str]] = [],
56
+ # available_categories: List[str]
57
+ session_date: str = None,
58
+ ) -> Dict[str, Any]:
59
+ """
60
+ Cluster memories into different categories
61
+ """
62
+
63
+ if session_date:
64
+ for item in new_memory_items:
65
+ if not item.get("mentioned_at", None):
66
+ item["mentioned_at"] = session_date
67
+
68
+ existing_clusters = self.memory_types["cluster"].keys()
69
+ # existing_clusters = [cluster.replace("_", " ") for cluster in existing_clusters]
70
+
71
+ updated_clusters = {}
72
+ if existing_clusters:
73
+ updated_clusters = self._merge_existing_clusters(
74
+ character_name,
75
+ conversation_content,
76
+ existing_clusters,
77
+ new_memory_items,
78
+ new_theory_of_mind_items,
79
+ )
80
+
81
+ new_clusters = self._detect_new_clusters(
82
+ character_name,
83
+ conversation_content,
84
+ existing_clusters,
85
+ new_memory_items,
86
+ new_theory_of_mind_items,
87
+ )
88
+
89
+ return self._add_metadata(
90
+ {
91
+ "success": True,
92
+ "character_name": character_name,
93
+ "updated_clusters": sorted(updated_clusters.keys()),
94
+ "new_clusters": sorted(new_clusters.keys()),
95
+ "message": f"Analyzed {len(new_memory_items)} new memory items. Updated {len(updated_clusters)} existing clusters and detected {len(new_clusters)} new clusters",
96
+ }
97
+ )
98
+
99
+ def _format_memory_item(self, memory_item: Dict[str, str]) -> str:
100
+ """Format memory items into a string"""
101
+ return f"[{memory_item['memory_id']}][mentioned at {memory_item['mentioned_at']}] {memory_item['content']} [{memory_item['memory_id']}]"
102
+
103
+ def _merge_existing_clusters(
104
+ self,
105
+ character_name: str,
106
+ conversation_content: str,
107
+ existing_clusters: List[str],
108
+ new_memory_items: List[Dict[str, str]],
109
+ new_theory_of_mind_items: List[Dict[str, str]],
110
+ count_threshold: int = 3,
111
+ ) -> List[str]:
112
+ """
113
+ Merge existing clusters with new memory items and theory of mind items
114
+ """
115
+
116
+ all_items = {item["memory_id"]: item for item in itertools.chain(new_memory_items, new_theory_of_mind_items)}
117
+
118
+ memory_items_text = "\n".join(
119
+ [f"Memory ID: {item['memory_id']}\nContent: {item['content']}" for item in all_items.values()]
120
+ )
121
+
122
+ # Create cluster list string outside f-string to avoid backslash issue
123
+ clusters_list = "\n".join(f"- {cluster}" for cluster in existing_clusters)
124
+
125
+ system_message = f"""You are an expert in analyzing and categorizing memories items.
126
+
127
+ You are given a list of existing clusters, a list of memory items, and the full conversation context that generated these memories.
128
+ Your task is to analyze if each of the memory items is related to any of the existing clusters.
129
+
130
+ **CONVERSATION CONTEXT:**
131
+ {conversation_content}
132
+
133
+ **EXISTING CLUSTERS:**
134
+ {clusters_list}
135
+
136
+ **MEMORY ITEMS:**
137
+ {memory_items_text}
138
+
139
+ **INSTRUCTIONS:**
140
+ 1. Use the conversation context to better understand the background and relationships of the memory items.
141
+ 2. It is possible that a memory item is related to multiple clusters.
142
+ Example: "We went to hiking in Blue Ridge Mountains this summer" is related to both "hiking" and "summer events" clusters, if both these two clusters are in the Existing Clusters.
143
+ 3. If it possible that some memory items are not related to any of the existing clusters, you don't need to force them into any cluster.
144
+ 4. DO NOT output memory items that are not related to any of the existing clusters.
145
+ 5. Consider the conversation context when determining relationships - topics discussed together might belong to the same cluster.
146
+
147
+ **OUTPUT FORMAT:**
148
+ - [Memory ID]: [Cluster names that the memory item is related to, separated by comma]
149
+ - [Memory ID]: [Cluster names that the memory item is related to, separated by comma]
150
+ - ...
151
+ """
152
+
153
+ response = self.llm_client.simple_chat(system_message)
154
+
155
+ if not response.strip():
156
+ return self._add_metadata({"success": False, "error": "LLM returned empty response"})
157
+
158
+ updated_clusters = {}
159
+
160
+ for line in response.split("\n"):
161
+ if not line.startswith("- "):
162
+ continue
163
+
164
+ memory_id, clusters = line[2:].split(": ", 1)
165
+ memory_id = memory_id.strip()
166
+ if memory_id not in all_items:
167
+ continue
168
+
169
+ for cluster in clusters.split(","):
170
+ if cluster not in existing_clusters:
171
+ continue
172
+
173
+ self.storage_manager.append_memory_file(cluster, self._format_memory_item(all_items[memory_id]))
174
+
175
+ if cluster not in updated_clusters:
176
+ updated_clusters[cluster] = []
177
+ updated_clusters[cluster].append(memory_id)
178
+
179
+ return updated_clusters
180
+
181
+ def _detect_new_clusters(
182
+ self,
183
+ character_name: str,
184
+ conversation_content: str,
185
+ existing_clusters: List[str],
186
+ new_memory_items: List[Dict[str, str]],
187
+ new_theory_of_mind_items: List[Dict[str, str]],
188
+ count_threshold: int = 3,
189
+ ) -> List[str]:
190
+ """
191
+ Detect new clusters from new memory items and theory of mind items
192
+ """
193
+
194
+ all_items = {item["memory_id"]: item for item in itertools.chain(new_memory_items, new_theory_of_mind_items)}
195
+
196
+ memory_items_text = "\n".join(
197
+ [f"Memory ID: {item['memory_id']}\nContent: {item['content']}" for item in all_items.values()]
198
+ )
199
+
200
+ # Create cluster list string outside f-string to avoid backslash issue
201
+ existing_clusters_list = "\n".join(f"- {cluster}" for cluster in existing_clusters)
202
+
203
+ system_message = f"""You are an expert in discovering some important or repeating events in one's memory records.
204
+
205
+ You are given a conversation context, a list of memory items extracted from this conversation, and existing clusters.
206
+ Your task is to discover NEW events/themes that are either:
207
+ - Important (e.g., marriage, job promotion, etc.), or
208
+ - Repeating, periodical, or routine (e.g., going to gym, attending specific events, etc.).
209
+
210
+ **CONVERSATION CONTEXT:**
211
+ {conversation_content}
212
+
213
+ **EXISTING CLUSTERS (DO NOT recreate these):**
214
+ {existing_clusters_list}
215
+
216
+ **MEMORY ITEMS:**
217
+ {memory_items_text}
218
+
219
+ **INSTRUCTIONS:**
220
+ 1. Use the conversation context to better understand the significance and relationships of events.
221
+ 2. Only create NEW clusters - do not recreate existing clusters listed above.
222
+ 3. You should create a Event Name for each NEW event you discover.
223
+ 4. The Event Name should be short and clear. A single word is the best (e.g., "marriage", "hiking"). Never let the name be longer than 3 words.
224
+ 5. The Event Name should contains only alphabets or space. DO NOT use any other characters including hyphen, underscore, etc.
225
+ 6. An event can be considered repeating, periodical, or routine, if they are mentioned at least twice in the memory items OR if the conversation context suggests it's a recurring theme.
226
+ 7. If an event is considered important enough (e.g., proposal), you should record it no matter how many times it is mentioned.
227
+ 8. For event content that are close (e.g., hiking and backpacking), you can merge them into a single event, and accumulate the count.
228
+ 9. Consider the conversation flow - events discussed together might indicate related themes or patterns.
229
+
230
+ **OUTPUT FORMAT:**
231
+ - [Event Name]: [Memory ID of ALL memory items related to this event, separated by comma]
232
+ - [Event Name]: [Memory ID of ALL memory items related to this event, separated by comma]
233
+ - ...
234
+ """
235
+
236
+ response = self.llm_client.simple_chat(system_message)
237
+
238
+ if not response.strip():
239
+ return self._add_metadata({"success": False, "error": "LLM returned empty response"})
240
+
241
+ new_clusters = {}
242
+
243
+ for line in response.split("\n"):
244
+ if not line.startswith("- "):
245
+ continue
246
+
247
+ cluster, memory_ids = line[2:].split(": ", 1)
248
+ cluster = cluster.strip().lower()
249
+
250
+ if cluster not in self.memory_types["cluster"]:
251
+ new_clusters[cluster] = []
252
+ self.storage_manager.create_cluster_category(cluster)
253
+
254
+ for memory_id in memory_ids.split(","):
255
+ memory_id = memory_id.strip()
256
+ if memory_id not in all_items:
257
+ continue
258
+ self.storage_manager.append_memory_file(cluster, self._format_memory_item(all_items[memory_id]))
259
+
260
+ new_clusters[cluster].append(memory_id)
261
+
262
+ return new_clusters
@@ -0,0 +1,198 @@
1
+ """
2
+ Generate Memory Suggestions Action
3
+
4
+ Analyzes new memory items and suggests what should be added to different memory categories.
5
+ """
6
+
7
+ from typing import Any, Dict, Optional
8
+
9
+ from .base_action import BaseAction
10
+
11
+
12
+ class GenerateMemorySuggestionsAction(BaseAction):
13
+ """
14
+ Generate suggestions for what memory content should be added to different categories
15
+ based on new memory items from conversations.
16
+ """
17
+
18
+ @property
19
+ def action_name(self) -> str:
20
+ return "generate_memory_suggestions"
21
+
22
+ def get_schema(self) -> Dict[str, Any]:
23
+ """Return OpenAI-compatible function schema"""
24
+ return {
25
+ "name": self.action_name,
26
+ "description": "Analyze new memory items and generate suggestions for what should be added to different memory categories",
27
+ "parameters": {
28
+ "type": "object",
29
+ "properties": {
30
+ "character_name": {
31
+ "type": "string",
32
+ "description": "Name of the character",
33
+ },
34
+ "new_memory_items": {
35
+ "type": "array",
36
+ "items": {
37
+ "type": "object",
38
+ "properties": {
39
+ "memory_id": {"type": "string"},
40
+ "content": {"type": "string"},
41
+ "mentioned_at": {"type": "string"},
42
+ },
43
+ "required": ["memory_id", "content"],
44
+ },
45
+ "description": "List of new memory items from the conversation",
46
+ },
47
+ },
48
+ "required": ["character_name", "new_memory_items"],
49
+ },
50
+ }
51
+
52
+ def execute(
53
+ self,
54
+ character_name: str,
55
+ new_memory_items: list[dict[str, str]],
56
+ available_categories: Optional[list[str]] = None,
57
+ ) -> Dict[str, Any]:
58
+ """
59
+ Generate memory suggestions for different categories
60
+
61
+ Args:
62
+ character_name: Name of the character
63
+ new_memory_items: List of new memory items with memory_id and content
64
+ available_categories: List of available memory categories
65
+
66
+ Returns:
67
+ Dict containing suggestions for each category
68
+ """
69
+ try:
70
+ if not new_memory_items:
71
+ return self._add_metadata({"success": False, "error": "No memory items provided"})
72
+
73
+ if available_categories is None:
74
+ available_categories = self._get_available_categories(character_name)
75
+ if not available_categories:
76
+ return self._add_metadata({"success": False, "error": "No available categories found"})
77
+
78
+ # Convert memory items to text for analysis
79
+ memory_items_text = "\n".join(
80
+ [
81
+ # f"Memory ID: {item['memory_id']}\nContent: {item['content']}"
82
+ f"- {item['content']}"
83
+ for item in new_memory_items
84
+ ]
85
+ )
86
+
87
+ # Create enhanced prompt for LLM to analyze and generate suggestions
88
+ suggestions_prompt = f"""You are an expert in analyzing the provided memory items for {character_name} and suggesting the memory items that should be added to each memory category.
89
+
90
+ New Memory Items:
91
+ {memory_items_text}
92
+
93
+ Available Categories: {', '.join(available_categories)}
94
+
95
+ **CRITICAL REQUIREMENT: Suggestions must be SELF-CONTAINED MEMORY ITEMS**
96
+
97
+ **SELF-CONTAINED MEMORY REQUIREMENTS:**
98
+ - EVERY activity item must be complete and standalone
99
+ - ALWAYS include the full subject (do not use "she/he/they/it")
100
+ - NEVER use pronouns that depend on context (no "she", "he", "they", "it")
101
+ - Include specific names, places, dates, and full context in each item
102
+ - Each activity should be understandable without reading other items
103
+ - Include all relevant details, emotions, and outcomes in the activity description
104
+
105
+ **CATEGORY-SPECIFIC REQUIREMENTS:**
106
+
107
+ For each category, analyze the new memory items and suggest what specific information should be extracted and added to that category:
108
+
109
+ - **activity**: Detailed description of the conversation, including the time, place, and people involved
110
+ - **profile**: ONLY basic personal information (age, location, occupation, education, family status, demographics) - EXCLUDE events, activities, things they did
111
+ - **event**: Specific events, dates, milestones, appointments, meetings, activities with time references
112
+ - **Other categories**: Relevant information for each specific category
113
+
114
+ **CRITICAL DISTINCTION - Profile vs Activity/Event:**
115
+ - Profile (GOOD): "Alice lives in San Francisco", "Alice is 28 years old", "Alice works at TechFlow Solutions"
116
+ - Profile (BAD): "Alice went hiking" (this is activity), "Alice attended workshop" (this is event)
117
+ - Activity/Event (GOOD): "Alice went hiking in Blue Ridge Mountains", "Alice attended photography workshop"
118
+
119
+ **SUGGESTION REQUIREMENTS:**
120
+ - Specify that memory items should include "{character_name}" as the subject
121
+ - Mention specific names, places, titles, and dates that should be included
122
+ - Ensure suggestions lead to complete, self-contained memory items
123
+ - Avoid suggesting content that would result in pronouns or incomplete sentences
124
+ - For profile: Focus ONLY on stable, factual, demographic information
125
+ - If one input memory item involves information belongs to multiple categories, you should reasonable seperete the information and provide suggestions to all involved categories
126
+ - **IMPORTANT** If the input memory item use modal adverbs (perhaps, probably, likely, etc.) to indicate an uncertain inference, keep the modal adverbs as-is in your suggestions
127
+
128
+ **OUTPUT INSTRUCTIONS:**
129
+ - **IMPORTANT** NEVER suggest categories that are not in the Available Categories
130
+ - Only output categories where there are suggestions for new memory items
131
+
132
+ **OUTPUT FORMAT:**
133
+
134
+ **Category: [category_name]**
135
+ - Suggestion: [What specific self-contained content should be added to this category, ensuring full subjects and complete context]
136
+ - Suggestion: [What specific self-contained content should be added to this category, ensuring full subjects and complete context]
137
+
138
+ **Category: [category_name]**
139
+ - Suggestion: [What specific self-contained content should be added to this category, ensuring full subjects and complete context]
140
+
141
+ ... other categories ...
142
+ """
143
+
144
+ # Call LLM to generate suggestions
145
+ response = self.llm_client.simple_chat(suggestions_prompt)
146
+
147
+ if not response.strip():
148
+ return self._add_metadata({"success": False, "error": "LLM returned empty suggestions"})
149
+
150
+ # Parse text response
151
+ suggestions = self._parse_suggestions_from_text(response.strip(), available_categories, new_memory_items)
152
+
153
+ return self._add_metadata(
154
+ {
155
+ "success": True,
156
+ "character_name": character_name,
157
+ "suggestions": suggestions,
158
+ "categories_analyzed": available_categories,
159
+ "message": f"Generated self-contained suggestions for {len(suggestions)} categories based on {len(new_memory_items)} memory items",
160
+ }
161
+ )
162
+
163
+ except Exception as e:
164
+ return self._handle_error(e)
165
+
166
+ def _get_available_categories(self, character_name: str) -> list[str]:
167
+ """Get available categories for a character"""
168
+ return [category for category in self.basic_memory_types.keys() if category != "activity"]
169
+
170
+ def _parse_suggestions_from_text(
171
+ self,
172
+ response_text: str,
173
+ available_categories: list[str],
174
+ new_memory_items: list[dict[str, str]],
175
+ ) -> Dict[str, Dict[str, Any]]:
176
+ """Parse suggestions from text format response"""
177
+ suggestions = {}
178
+
179
+ try:
180
+ lines = response_text.split("\n")
181
+ current_category = None
182
+
183
+ for line in lines:
184
+ line = line.strip()
185
+
186
+ if line.startswith("**Category:") and line.endswith("**"):
187
+ category_name = line.replace("**Category:", "").replace("**", "").strip()
188
+ if category_name in available_categories:
189
+ current_category = category_name
190
+ suggestions[current_category] = ""
191
+ elif current_category and line.startswith("- Suggestion:"):
192
+ suggestion_text = line.replace("- Suggestion:", "").strip()
193
+ suggestions[current_category] += f"{suggestion_text}\n"
194
+
195
+ suggestions = {k: v for k, v in suggestions.items() if v.strip()}
196
+ except Exception:
197
+ raise
198
+ return suggestions
@@ -0,0 +1,66 @@
1
+ """
2
+ Get Available Categories Action
3
+
4
+ Gets all available memory categories and their descriptions, excluding activity category.
5
+ """
6
+
7
+ import logging
8
+ from typing import Any, Dict
9
+
10
+ from .base_action import BaseAction
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class GetAvailableCategoriesAction(BaseAction):
16
+ """Action to get all available memory categories from config, excluding activity"""
17
+
18
+ @property
19
+ def action_name(self) -> str:
20
+ return "get_available_categories"
21
+
22
+ def get_schema(self) -> Dict[str, Any]:
23
+ """Return OpenAI-compatible function schema"""
24
+ return {
25
+ "name": "get_available_categories",
26
+ "description": "Get all available memory categories and their descriptions (excluding activity category)",
27
+ "parameters": {"type": "object", "properties": {}, "required": []},
28
+ }
29
+
30
+ def execute(self) -> Dict[str, Any]:
31
+ """
32
+ Execute get available categories operation
33
+
34
+ Returns:
35
+ Dict containing category information (excluding activity category)
36
+ """
37
+ try:
38
+ categories = {}
39
+
40
+ for category, filename in self.basic_memory_types.items():
41
+ # Skip activity category as it's handled separately by add_activity_memory
42
+ if category == "activity":
43
+ continue
44
+
45
+ description = self.config_manager.get_file_description(category)
46
+
47
+ categories[category] = {
48
+ "filename": filename,
49
+ "description": description,
50
+ "config_source": self.config_manager.get_folder_path(category),
51
+ }
52
+
53
+ return self._add_metadata(
54
+ {
55
+ "success": True,
56
+ "categories": categories,
57
+ "total_categories": len(categories),
58
+ "processing_order": [cat for cat in self.processing_order if cat != "activity"],
59
+ "embeddings_enabled": self.embeddings_enabled,
60
+ "excluded_categories": ["activity"],
61
+ "message": f"Found {len(categories)} memory categories from config (excluding activity)",
62
+ }
63
+ )
64
+
65
+ except Exception as e:
66
+ return self._handle_error(e)