noesium 0.1.0__py3-none-any.whl → 0.2.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 (59) hide show
  1. noesium/agents/askura_agent/__init__.py +22 -0
  2. noesium/agents/askura_agent/askura_agent.py +480 -0
  3. noesium/agents/askura_agent/conversation.py +164 -0
  4. noesium/agents/askura_agent/extractor.py +175 -0
  5. noesium/agents/askura_agent/memory.py +14 -0
  6. noesium/agents/askura_agent/models.py +239 -0
  7. noesium/agents/askura_agent/prompts.py +202 -0
  8. noesium/agents/askura_agent/reflection.py +234 -0
  9. noesium/agents/askura_agent/summarizer.py +30 -0
  10. noesium/agents/askura_agent/utils.py +6 -0
  11. noesium/agents/deep_research/__init__.py +13 -0
  12. noesium/agents/deep_research/agent.py +398 -0
  13. noesium/agents/deep_research/prompts.py +84 -0
  14. noesium/agents/deep_research/schemas.py +42 -0
  15. noesium/agents/deep_research/state.py +54 -0
  16. noesium/agents/search/__init__.py +5 -0
  17. noesium/agents/search/agent.py +474 -0
  18. noesium/agents/search/state.py +28 -0
  19. noesium/core/__init__.py +1 -1
  20. noesium/core/agent/base.py +10 -2
  21. noesium/core/goalith/decomposer/llm_decomposer.py +1 -1
  22. noesium/core/llm/__init__.py +1 -1
  23. noesium/core/llm/base.py +2 -2
  24. noesium/core/llm/litellm.py +42 -21
  25. noesium/core/llm/llamacpp.py +25 -4
  26. noesium/core/llm/ollama.py +43 -22
  27. noesium/core/llm/openai.py +25 -5
  28. noesium/core/llm/openrouter.py +1 -1
  29. noesium/core/toolify/base.py +9 -2
  30. noesium/core/toolify/config.py +2 -2
  31. noesium/core/toolify/registry.py +21 -5
  32. noesium/core/tracing/opik_tracing.py +7 -7
  33. noesium/core/vector_store/__init__.py +2 -2
  34. noesium/core/vector_store/base.py +1 -1
  35. noesium/core/vector_store/pgvector.py +10 -13
  36. noesium/core/vector_store/weaviate.py +2 -1
  37. noesium/toolkits/__init__.py +1 -0
  38. noesium/toolkits/arxiv_toolkit.py +310 -0
  39. noesium/toolkits/audio_aliyun_toolkit.py +441 -0
  40. noesium/toolkits/audio_toolkit.py +370 -0
  41. noesium/toolkits/bash_toolkit.py +332 -0
  42. noesium/toolkits/document_toolkit.py +454 -0
  43. noesium/toolkits/file_edit_toolkit.py +552 -0
  44. noesium/toolkits/github_toolkit.py +395 -0
  45. noesium/toolkits/gmail_toolkit.py +575 -0
  46. noesium/toolkits/image_toolkit.py +425 -0
  47. noesium/toolkits/memory_toolkit.py +398 -0
  48. noesium/toolkits/python_executor_toolkit.py +334 -0
  49. noesium/toolkits/search_toolkit.py +451 -0
  50. noesium/toolkits/serper_toolkit.py +623 -0
  51. noesium/toolkits/tabular_data_toolkit.py +537 -0
  52. noesium/toolkits/user_interaction_toolkit.py +365 -0
  53. noesium/toolkits/video_toolkit.py +168 -0
  54. noesium/toolkits/wikipedia_toolkit.py +420 -0
  55. {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/METADATA +56 -48
  56. {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/RECORD +59 -23
  57. {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/licenses/LICENSE +1 -1
  58. {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/WHEEL +0 -0
  59. {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,398 @@
1
+ """
2
+ Memory toolkit for persistent text storage and manipulation.
3
+
4
+ Provides tools for storing, retrieving, and editing persistent text content
5
+ with safety features and comprehensive error handling.
6
+ """
7
+
8
+ import os
9
+ from pathlib import Path
10
+ from typing import Callable, Dict, Optional
11
+
12
+ from noesium.core.toolify.base import AsyncBaseToolkit
13
+ from noesium.core.toolify.config import ToolkitConfig
14
+ from noesium.core.toolify.registry import register_toolkit
15
+ from noesium.core.utils.logging import get_logger
16
+
17
+ logger = get_logger(__name__)
18
+
19
+
20
+ @register_toolkit("memory")
21
+ class MemoryToolkit(AsyncBaseToolkit):
22
+ """
23
+ Toolkit for persistent memory storage and manipulation.
24
+
25
+ This toolkit provides capabilities for:
26
+ - Storing and retrieving persistent text content
27
+ - Editing memory content with string replacement
28
+ - Multiple memory slots for different contexts
29
+ - File-based persistence across sessions
30
+ - Safety warnings for overwrite operations
31
+ - Search and pattern matching in memory
32
+
33
+ Features:
34
+ - In-memory and file-based storage options
35
+ - Multiple named memory slots
36
+ - String replacement with occurrence counting
37
+ - Content validation and safety checks
38
+ - Backup and versioning support
39
+ - Search and filtering capabilities
40
+
41
+ Use cases:
42
+ - Maintaining conversation context
43
+ - Storing intermediate results
44
+ - Building knowledge bases
45
+ - Caching computed information
46
+ - Maintaining state across operations
47
+ """
48
+
49
+ def __init__(self, config: ToolkitConfig = None):
50
+ """
51
+ Initialize the memory toolkit.
52
+
53
+ Args:
54
+ config: Toolkit configuration
55
+ """
56
+ super().__init__(config)
57
+
58
+ # Configuration
59
+ self.storage_type = self.config.config.get("storage_type", "memory") # "memory" or "file"
60
+ self.storage_dir = Path(self.config.config.get("storage_dir", "./memory_storage"))
61
+ self.max_memory_size = self.config.config.get("max_memory_size", 1024 * 1024) # 1MB
62
+ self.enable_versioning = self.config.config.get("enable_versioning", False)
63
+
64
+ # In-memory storage
65
+ self.memory_slots: Dict[str, str] = {}
66
+
67
+ # File storage setup
68
+ if self.storage_type == "file":
69
+ self.storage_dir.mkdir(parents=True, exist_ok=True)
70
+ self._load_persistent_memory()
71
+
72
+ self.logger.info(f"Memory toolkit initialized with {self.storage_type} storage")
73
+
74
+ def _get_memory_file_path(self, slot_name: str) -> Path:
75
+ """Get file path for a memory slot."""
76
+ safe_name = "".join(c for c in slot_name if c.isalnum() or c in "._-")
77
+ return self.storage_dir / f"{safe_name}.txt"
78
+
79
+ def _load_persistent_memory(self):
80
+ """Load memory from persistent storage."""
81
+ try:
82
+ for file_path in self.storage_dir.glob("*.txt"):
83
+ slot_name = file_path.stem
84
+ with open(file_path, "r", encoding="utf-8") as f:
85
+ self.memory_slots[slot_name] = f.read()
86
+
87
+ self.logger.info(f"Loaded {len(self.memory_slots)} memory slots from storage")
88
+
89
+ except Exception as e:
90
+ self.logger.warning(f"Failed to load persistent memory: {e}")
91
+
92
+ def _save_memory_slot(self, slot_name: str, content: str):
93
+ """Save a memory slot to persistent storage."""
94
+ if self.storage_type != "file":
95
+ return
96
+
97
+ try:
98
+ file_path = self._get_memory_file_path(slot_name)
99
+
100
+ # Create backup if versioning is enabled
101
+ if self.enable_versioning and file_path.exists():
102
+ backup_path = file_path.with_suffix(f".{int(os.path.getmtime(file_path))}.bak")
103
+ file_path.rename(backup_path)
104
+
105
+ with open(file_path, "w", encoding="utf-8") as f:
106
+ f.write(content)
107
+
108
+ except Exception as e:
109
+ self.logger.error(f"Failed to save memory slot '{slot_name}': {e}")
110
+
111
+ def _validate_content_size(self, content: str) -> bool:
112
+ """Validate that content doesn't exceed size limits."""
113
+ return len(content.encode("utf-8")) <= self.max_memory_size
114
+
115
+ async def read_memory(self, slot_name: str = "default") -> str:
116
+ """
117
+ Read the contents of a memory slot.
118
+
119
+ This tool retrieves the current content stored in the specified memory slot.
120
+ Memory slots allow you to maintain separate contexts or information stores.
121
+
122
+ Args:
123
+ slot_name: Name of the memory slot to read (default: "default")
124
+
125
+ Returns:
126
+ Current content of the memory slot, or empty string if slot doesn't exist
127
+
128
+ Example:
129
+ content = await read_memory("conversation_context")
130
+ print(f"Current context: {content}")
131
+ """
132
+ self.logger.info(f"Reading memory slot: {slot_name}")
133
+
134
+ content = self.memory_slots.get(slot_name, "")
135
+
136
+ if not content:
137
+ return f"Memory slot '{slot_name}' is empty or does not exist."
138
+
139
+ self.logger.info(f"Read {len(content)} characters from slot '{slot_name}'")
140
+ return content
141
+
142
+ async def write_memory(self, content: str, slot_name: str = "default") -> str:
143
+ """
144
+ Write content to a memory slot, replacing any existing content.
145
+
146
+ This tool stores content in the specified memory slot. If the slot already
147
+ contains content, it will be completely replaced. A warning is provided
148
+ when overwriting existing content.
149
+
150
+ Args:
151
+ content: Content to store in memory
152
+ slot_name: Name of the memory slot to write to (default: "default")
153
+
154
+ Returns:
155
+ Success message, including warning if overwriting existing content
156
+
157
+ Example:
158
+ result = await write_memory("Important information to remember", "notes")
159
+ """
160
+ self.logger.info(f"Writing to memory slot: {slot_name}")
161
+
162
+ # Validate content size
163
+ if not self._validate_content_size(content):
164
+ return f"Error: Content too large ({len(content)} chars, max: {self.max_memory_size})"
165
+
166
+ # Check if overwriting existing content
167
+ existing_content = self.memory_slots.get(slot_name, "")
168
+ warning_msg = ""
169
+
170
+ if existing_content:
171
+ warning_msg = (
172
+ f"Warning: Overwriting existing content in slot '{slot_name}'. "
173
+ f"Previous content ({len(existing_content)} chars) was:\n"
174
+ f"{existing_content[:200]}{'...' if len(existing_content) > 200 else ''}\n\n"
175
+ )
176
+
177
+ # Store the content
178
+ self.memory_slots[slot_name] = content
179
+
180
+ # Save to persistent storage if enabled
181
+ self._save_memory_slot(slot_name, content)
182
+
183
+ result_msg = f"Memory slot '{slot_name}' updated successfully with {len(content)} characters."
184
+
185
+ self.logger.info(f"Wrote {len(content)} characters to slot '{slot_name}'")
186
+ return warning_msg + result_msg
187
+
188
+ async def edit_memory(self, old_string: str, new_string: str, slot_name: str = "default") -> str:
189
+ """
190
+ Edit memory content by replacing occurrences of a string.
191
+
192
+ This tool performs string replacement within a memory slot. It provides
193
+ safety checks for multiple occurrences and clear feedback about changes made.
194
+
195
+ Args:
196
+ old_string: String to find and replace
197
+ new_string: String to replace with
198
+ slot_name: Name of the memory slot to edit (default: "default")
199
+
200
+ Returns:
201
+ Result message indicating success, failure, or warnings
202
+
203
+ Example:
204
+ result = await edit_memory("old info", "new info", "notes")
205
+ """
206
+ self.logger.info(f"Editing memory slot: {slot_name}")
207
+
208
+ # Check if slot exists
209
+ if slot_name not in self.memory_slots:
210
+ return f"Error: Memory slot '{slot_name}' does not exist."
211
+
212
+ current_content = self.memory_slots[slot_name]
213
+
214
+ # Check if old_string exists
215
+ if old_string not in current_content:
216
+ return f"Error: String '{old_string}' not found in memory slot '{slot_name}'."
217
+
218
+ # Count occurrences
219
+ occurrence_count = current_content.count(old_string)
220
+
221
+ if occurrence_count > 1:
222
+ return (
223
+ f"Warning: Found {occurrence_count} occurrences of '{old_string}' "
224
+ f"in slot '{slot_name}'. Please use more specific context to avoid "
225
+ "unintended replacements, or use replace_all_in_memory for intentional "
226
+ "multiple replacements."
227
+ )
228
+
229
+ # Perform replacement
230
+ new_content = current_content.replace(old_string, new_string, 1)
231
+
232
+ # Validate new content size
233
+ if not self._validate_content_size(new_content):
234
+ return f"Error: Edited content would be too large"
235
+
236
+ # Update memory
237
+ self.memory_slots[slot_name] = new_content
238
+ self._save_memory_slot(slot_name, new_content)
239
+
240
+ self.logger.info(f"Edited memory slot '{slot_name}': replaced 1 occurrence")
241
+ return f"Successfully replaced 1 occurrence of '{old_string}' with '{new_string}' in slot '{slot_name}'."
242
+
243
+ async def append_to_memory(self, content: str, slot_name: str = "default", separator: str = "\n") -> str:
244
+ """
245
+ Append content to an existing memory slot.
246
+
247
+ Args:
248
+ content: Content to append
249
+ slot_name: Name of the memory slot (default: "default")
250
+ separator: Separator to use between existing and new content
251
+
252
+ Returns:
253
+ Success message
254
+ """
255
+ existing_content = self.memory_slots.get(slot_name, "")
256
+
257
+ if existing_content:
258
+ new_content = existing_content + separator + content
259
+ else:
260
+ new_content = content
261
+
262
+ if not self._validate_content_size(new_content):
263
+ return f"Error: Combined content would be too large"
264
+
265
+ self.memory_slots[slot_name] = new_content
266
+ self._save_memory_slot(slot_name, new_content)
267
+
268
+ self.logger.info(f"Appended {len(content)} characters to slot '{slot_name}'")
269
+ return f"Successfully appended content to memory slot '{slot_name}'."
270
+
271
+ async def clear_memory(self, slot_name: str = "default") -> str:
272
+ """
273
+ Clear the contents of a memory slot.
274
+
275
+ Args:
276
+ slot_name: Name of the memory slot to clear (default: "default")
277
+
278
+ Returns:
279
+ Success message
280
+ """
281
+ if slot_name in self.memory_slots:
282
+ del self.memory_slots[slot_name]
283
+
284
+ # Remove from persistent storage
285
+ if self.storage_type == "file":
286
+ file_path = self._get_memory_file_path(slot_name)
287
+ if file_path.exists():
288
+ file_path.unlink()
289
+
290
+ self.logger.info(f"Cleared memory slot: {slot_name}")
291
+ return f"Memory slot '{slot_name}' has been cleared."
292
+ else:
293
+ return f"Memory slot '{slot_name}' does not exist."
294
+
295
+ async def list_memory_slots(self) -> str:
296
+ """
297
+ List all available memory slots with their sizes.
298
+
299
+ Returns:
300
+ Formatted list of memory slots
301
+ """
302
+ if not self.memory_slots:
303
+ return "No memory slots exist."
304
+
305
+ slot_info = []
306
+ for slot_name, content in self.memory_slots.items():
307
+ size = len(content)
308
+ preview = content[:50].replace("\n", " ") if content else "(empty)"
309
+ if len(content) > 50:
310
+ preview += "..."
311
+
312
+ slot_info.append(f" {slot_name}: {size} chars - {preview}")
313
+
314
+ result = f"Memory slots ({len(self.memory_slots)} total):\n" + "\n".join(slot_info)
315
+ return result
316
+
317
+ async def search_memory(self, query: str, slot_name: Optional[str] = None) -> str:
318
+ """
319
+ Search for text within memory slots.
320
+
321
+ Args:
322
+ query: Text to search for
323
+ slot_name: Specific slot to search (if None, searches all slots)
324
+
325
+ Returns:
326
+ Search results with context
327
+ """
328
+ results = []
329
+
330
+ slots_to_search = {slot_name: self.memory_slots[slot_name]} if slot_name else self.memory_slots
331
+
332
+ for name, content in slots_to_search.items():
333
+ if query.lower() in content.lower():
334
+ # Find all occurrences with context
335
+ lines = content.split("\n")
336
+ for i, line in enumerate(lines):
337
+ if query.lower() in line.lower():
338
+ context_start = max(0, i - 1)
339
+ context_end = min(len(lines), i + 2)
340
+ context_lines = lines[context_start:context_end]
341
+
342
+ results.append(f"Slot '{name}', line {i+1}:")
343
+ results.extend(f" {j+context_start+1}: {line}" for j, line in enumerate(context_lines))
344
+ results.append("")
345
+
346
+ if not results:
347
+ search_scope = f"slot '{slot_name}'" if slot_name else "all memory slots"
348
+ return f"No matches found for '{query}' in {search_scope}."
349
+
350
+ return "\n".join(results)
351
+
352
+ async def get_memory_stats(self) -> str:
353
+ """
354
+ Get statistics about memory usage.
355
+
356
+ Returns:
357
+ Formatted memory statistics
358
+ """
359
+ total_slots = len(self.memory_slots)
360
+ total_chars = sum(len(content) for content in self.memory_slots.values())
361
+ total_bytes = sum(len(content.encode("utf-8")) for content in self.memory_slots.values())
362
+
363
+ if total_slots == 0:
364
+ return "No memory slots exist."
365
+
366
+ avg_size = total_chars // total_slots
367
+ largest_slot = max(self.memory_slots.items(), key=lambda x: len(x[1]))
368
+
369
+ stats = [
370
+ f"Memory Statistics:",
371
+ f" Total slots: {total_slots}",
372
+ f" Total characters: {total_chars:,}",
373
+ f" Total bytes: {total_bytes:,}",
374
+ f" Average slot size: {avg_size:,} characters",
375
+ f" Largest slot: '{largest_slot[0]}' ({len(largest_slot[1]):,} chars)",
376
+ f" Storage type: {self.storage_type}",
377
+ f" Max slot size: {self.max_memory_size:,} bytes",
378
+ ]
379
+
380
+ return "\n".join(stats)
381
+
382
+ async def get_tools_map(self) -> Dict[str, Callable]:
383
+ """
384
+ Get the mapping of tool names to their implementation functions.
385
+
386
+ Returns:
387
+ Dictionary mapping tool names to callable functions
388
+ """
389
+ return {
390
+ "read_memory": self.read_memory,
391
+ "write_memory": self.write_memory,
392
+ "edit_memory": self.edit_memory,
393
+ "append_to_memory": self.append_to_memory,
394
+ "clear_memory": self.clear_memory,
395
+ "list_memory_slots": self.list_memory_slots,
396
+ "search_memory": self.search_memory,
397
+ "get_memory_stats": self.get_memory_stats,
398
+ }