genxai-framework 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.
- cli/__init__.py +3 -0
- cli/commands/__init__.py +6 -0
- cli/commands/approval.py +85 -0
- cli/commands/audit.py +127 -0
- cli/commands/metrics.py +25 -0
- cli/commands/tool.py +389 -0
- cli/main.py +32 -0
- genxai/__init__.py +81 -0
- genxai/api/__init__.py +5 -0
- genxai/api/app.py +21 -0
- genxai/config/__init__.py +5 -0
- genxai/config/settings.py +37 -0
- genxai/connectors/__init__.py +19 -0
- genxai/connectors/base.py +122 -0
- genxai/connectors/kafka.py +92 -0
- genxai/connectors/postgres_cdc.py +95 -0
- genxai/connectors/registry.py +44 -0
- genxai/connectors/sqs.py +94 -0
- genxai/connectors/webhook.py +73 -0
- genxai/core/__init__.py +37 -0
- genxai/core/agent/__init__.py +32 -0
- genxai/core/agent/base.py +206 -0
- genxai/core/agent/config_io.py +59 -0
- genxai/core/agent/registry.py +98 -0
- genxai/core/agent/runtime.py +970 -0
- genxai/core/communication/__init__.py +6 -0
- genxai/core/communication/collaboration.py +44 -0
- genxai/core/communication/message_bus.py +192 -0
- genxai/core/communication/protocols.py +35 -0
- genxai/core/execution/__init__.py +22 -0
- genxai/core/execution/metadata.py +181 -0
- genxai/core/execution/queue.py +201 -0
- genxai/core/graph/__init__.py +30 -0
- genxai/core/graph/checkpoints.py +77 -0
- genxai/core/graph/edges.py +131 -0
- genxai/core/graph/engine.py +813 -0
- genxai/core/graph/executor.py +516 -0
- genxai/core/graph/nodes.py +161 -0
- genxai/core/graph/trigger_runner.py +40 -0
- genxai/core/memory/__init__.py +19 -0
- genxai/core/memory/base.py +72 -0
- genxai/core/memory/embedding.py +327 -0
- genxai/core/memory/episodic.py +448 -0
- genxai/core/memory/long_term.py +467 -0
- genxai/core/memory/manager.py +543 -0
- genxai/core/memory/persistence.py +297 -0
- genxai/core/memory/procedural.py +461 -0
- genxai/core/memory/semantic.py +526 -0
- genxai/core/memory/shared.py +62 -0
- genxai/core/memory/short_term.py +303 -0
- genxai/core/memory/vector_store.py +508 -0
- genxai/core/memory/working.py +211 -0
- genxai/core/state/__init__.py +6 -0
- genxai/core/state/manager.py +293 -0
- genxai/core/state/schema.py +115 -0
- genxai/llm/__init__.py +14 -0
- genxai/llm/base.py +150 -0
- genxai/llm/factory.py +329 -0
- genxai/llm/providers/__init__.py +1 -0
- genxai/llm/providers/anthropic.py +249 -0
- genxai/llm/providers/cohere.py +274 -0
- genxai/llm/providers/google.py +334 -0
- genxai/llm/providers/ollama.py +147 -0
- genxai/llm/providers/openai.py +257 -0
- genxai/llm/routing.py +83 -0
- genxai/observability/__init__.py +6 -0
- genxai/observability/logging.py +327 -0
- genxai/observability/metrics.py +494 -0
- genxai/observability/tracing.py +372 -0
- genxai/performance/__init__.py +39 -0
- genxai/performance/cache.py +256 -0
- genxai/performance/pooling.py +289 -0
- genxai/security/audit.py +304 -0
- genxai/security/auth.py +315 -0
- genxai/security/cost_control.py +528 -0
- genxai/security/default_policies.py +44 -0
- genxai/security/jwt.py +142 -0
- genxai/security/oauth.py +226 -0
- genxai/security/pii.py +366 -0
- genxai/security/policy_engine.py +82 -0
- genxai/security/rate_limit.py +341 -0
- genxai/security/rbac.py +247 -0
- genxai/security/validation.py +218 -0
- genxai/tools/__init__.py +21 -0
- genxai/tools/base.py +383 -0
- genxai/tools/builtin/__init__.py +131 -0
- genxai/tools/builtin/communication/__init__.py +15 -0
- genxai/tools/builtin/communication/email_sender.py +159 -0
- genxai/tools/builtin/communication/notification_manager.py +167 -0
- genxai/tools/builtin/communication/slack_notifier.py +118 -0
- genxai/tools/builtin/communication/sms_sender.py +118 -0
- genxai/tools/builtin/communication/webhook_caller.py +136 -0
- genxai/tools/builtin/computation/__init__.py +15 -0
- genxai/tools/builtin/computation/calculator.py +101 -0
- genxai/tools/builtin/computation/code_executor.py +183 -0
- genxai/tools/builtin/computation/data_validator.py +259 -0
- genxai/tools/builtin/computation/hash_generator.py +129 -0
- genxai/tools/builtin/computation/regex_matcher.py +201 -0
- genxai/tools/builtin/data/__init__.py +15 -0
- genxai/tools/builtin/data/csv_processor.py +213 -0
- genxai/tools/builtin/data/data_transformer.py +299 -0
- genxai/tools/builtin/data/json_processor.py +233 -0
- genxai/tools/builtin/data/text_analyzer.py +288 -0
- genxai/tools/builtin/data/xml_processor.py +175 -0
- genxai/tools/builtin/database/__init__.py +15 -0
- genxai/tools/builtin/database/database_inspector.py +157 -0
- genxai/tools/builtin/database/mongodb_query.py +196 -0
- genxai/tools/builtin/database/redis_cache.py +167 -0
- genxai/tools/builtin/database/sql_query.py +145 -0
- genxai/tools/builtin/database/vector_search.py +163 -0
- genxai/tools/builtin/file/__init__.py +17 -0
- genxai/tools/builtin/file/directory_scanner.py +214 -0
- genxai/tools/builtin/file/file_compressor.py +237 -0
- genxai/tools/builtin/file/file_reader.py +102 -0
- genxai/tools/builtin/file/file_writer.py +122 -0
- genxai/tools/builtin/file/image_processor.py +186 -0
- genxai/tools/builtin/file/pdf_parser.py +144 -0
- genxai/tools/builtin/test/__init__.py +15 -0
- genxai/tools/builtin/test/async_simulator.py +62 -0
- genxai/tools/builtin/test/data_transformer.py +99 -0
- genxai/tools/builtin/test/error_generator.py +82 -0
- genxai/tools/builtin/test/simple_math.py +94 -0
- genxai/tools/builtin/test/string_processor.py +72 -0
- genxai/tools/builtin/web/__init__.py +15 -0
- genxai/tools/builtin/web/api_caller.py +161 -0
- genxai/tools/builtin/web/html_parser.py +330 -0
- genxai/tools/builtin/web/http_client.py +187 -0
- genxai/tools/builtin/web/url_validator.py +162 -0
- genxai/tools/builtin/web/web_scraper.py +170 -0
- genxai/tools/custom/my_test_tool_2.py +9 -0
- genxai/tools/dynamic.py +105 -0
- genxai/tools/mcp_server.py +167 -0
- genxai/tools/persistence/__init__.py +6 -0
- genxai/tools/persistence/models.py +55 -0
- genxai/tools/persistence/service.py +322 -0
- genxai/tools/registry.py +227 -0
- genxai/tools/security/__init__.py +11 -0
- genxai/tools/security/limits.py +214 -0
- genxai/tools/security/policy.py +20 -0
- genxai/tools/security/sandbox.py +248 -0
- genxai/tools/templates.py +435 -0
- genxai/triggers/__init__.py +19 -0
- genxai/triggers/base.py +104 -0
- genxai/triggers/file_watcher.py +75 -0
- genxai/triggers/queue.py +68 -0
- genxai/triggers/registry.py +82 -0
- genxai/triggers/schedule.py +66 -0
- genxai/triggers/webhook.py +68 -0
- genxai/utils/__init__.py +1 -0
- genxai/utils/tokens.py +295 -0
- genxai_framework-0.1.0.dist-info/METADATA +495 -0
- genxai_framework-0.1.0.dist-info/RECORD +156 -0
- genxai_framework-0.1.0.dist-info/WHEEL +5 -0
- genxai_framework-0.1.0.dist-info/entry_points.txt +2 -0
- genxai_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
- genxai_framework-0.1.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"""Short-term memory implementation with LRU eviction."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from collections import OrderedDict
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
from genxai.core.memory.base import Memory, MemoryType, MemoryConfig
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ShortTermMemory:
|
|
14
|
+
"""Short-term memory with limited capacity and LRU eviction.
|
|
15
|
+
|
|
16
|
+
This memory type stores recent interactions and automatically evicts
|
|
17
|
+
the least recently used items when capacity is reached.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, config: Optional[MemoryConfig] = None, capacity: Optional[int] = None) -> None:
|
|
21
|
+
"""Initialize short-term memory.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
config: Memory configuration (uses defaults if not provided)
|
|
25
|
+
capacity: Override capacity (for backward compatibility)
|
|
26
|
+
"""
|
|
27
|
+
self.config = config or MemoryConfig()
|
|
28
|
+
self.capacity = capacity if capacity is not None else self.config.short_term_capacity
|
|
29
|
+
|
|
30
|
+
# Use OrderedDict for LRU behavior
|
|
31
|
+
self._memories: OrderedDict[str, Memory] = OrderedDict()
|
|
32
|
+
self._access_count = 0
|
|
33
|
+
|
|
34
|
+
logger.info(f"Initialized short-term memory with capacity: {self.capacity}")
|
|
35
|
+
|
|
36
|
+
def store(self, memory: Memory) -> None:
|
|
37
|
+
"""Store a memory item.
|
|
38
|
+
|
|
39
|
+
If capacity is reached, evicts the least recently used item.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
memory: Memory to store
|
|
43
|
+
"""
|
|
44
|
+
# If memory already exists, remove it (will be re-added at end)
|
|
45
|
+
if memory.id in self._memories:
|
|
46
|
+
del self._memories[memory.id]
|
|
47
|
+
|
|
48
|
+
# If at capacity, remove oldest (least recently used)
|
|
49
|
+
if len(self._memories) >= self.capacity:
|
|
50
|
+
oldest_id = next(iter(self._memories))
|
|
51
|
+
evicted = self._memories.pop(oldest_id)
|
|
52
|
+
logger.debug(f"Evicted memory {oldest_id} (importance: {evicted.importance})")
|
|
53
|
+
|
|
54
|
+
# Add new memory at end (most recently used)
|
|
55
|
+
self._memories[memory.id] = memory
|
|
56
|
+
logger.debug(f"Stored memory {memory.id} in short-term memory")
|
|
57
|
+
|
|
58
|
+
def retrieve(self, memory_id: str) -> Optional[Memory]:
|
|
59
|
+
"""Retrieve a memory by ID.
|
|
60
|
+
|
|
61
|
+
Accessing a memory moves it to the end (most recently used).
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
memory_id: ID of memory to retrieve
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Memory if found, None otherwise
|
|
68
|
+
"""
|
|
69
|
+
if memory_id not in self._memories:
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
# Move to end (mark as recently used)
|
|
73
|
+
memory = self._memories.pop(memory_id)
|
|
74
|
+
self._memories[memory_id] = memory
|
|
75
|
+
|
|
76
|
+
# Update access tracking
|
|
77
|
+
memory.access_count += 1
|
|
78
|
+
memory.last_accessed = datetime.now()
|
|
79
|
+
self._access_count += 1
|
|
80
|
+
|
|
81
|
+
logger.debug(f"Retrieved memory {memory_id} (access count: {memory.access_count})")
|
|
82
|
+
return memory
|
|
83
|
+
|
|
84
|
+
def retrieve_recent(self, limit: int = 10) -> List[Memory]:
|
|
85
|
+
"""Retrieve the most recent memories.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
limit: Maximum number of memories to retrieve
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
List of recent memories (most recent first)
|
|
92
|
+
"""
|
|
93
|
+
# Get last N items (most recent)
|
|
94
|
+
recent_items = list(self._memories.values())[-limit:]
|
|
95
|
+
|
|
96
|
+
# Reverse to get most recent first
|
|
97
|
+
recent_items.reverse()
|
|
98
|
+
|
|
99
|
+
logger.debug(f"Retrieved {len(recent_items)} recent memories")
|
|
100
|
+
return recent_items
|
|
101
|
+
|
|
102
|
+
def retrieve_by_importance(self, threshold: float = 0.5, limit: int = 10) -> List[Memory]:
|
|
103
|
+
"""Retrieve memories above an importance threshold.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
threshold: Minimum importance score (0.0 to 1.0)
|
|
107
|
+
limit: Maximum number of memories to retrieve
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
List of important memories (sorted by importance, descending)
|
|
111
|
+
"""
|
|
112
|
+
# Filter by importance
|
|
113
|
+
important = [m for m in self._memories.values() if m.importance >= threshold]
|
|
114
|
+
|
|
115
|
+
# Sort by importance (descending)
|
|
116
|
+
important.sort(key=lambda m: m.importance, reverse=True)
|
|
117
|
+
|
|
118
|
+
# Limit results
|
|
119
|
+
result = important[:limit]
|
|
120
|
+
|
|
121
|
+
logger.debug(
|
|
122
|
+
f"Retrieved {len(result)} memories with importance >= {threshold}"
|
|
123
|
+
)
|
|
124
|
+
return result
|
|
125
|
+
|
|
126
|
+
def search(self, query: str, limit: int = 5) -> List[Memory]:
|
|
127
|
+
"""Search memories by content.
|
|
128
|
+
|
|
129
|
+
Simple text-based search. For semantic search, use long-term memory.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
query: Search query
|
|
133
|
+
limit: Maximum number of results
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
List of matching memories
|
|
137
|
+
"""
|
|
138
|
+
query_lower = query.lower()
|
|
139
|
+
matches = []
|
|
140
|
+
|
|
141
|
+
for memory in self._memories.values():
|
|
142
|
+
# Convert content to string for searching
|
|
143
|
+
content_str = str(memory.content).lower()
|
|
144
|
+
|
|
145
|
+
if query_lower in content_str:
|
|
146
|
+
matches.append(memory)
|
|
147
|
+
|
|
148
|
+
# Sort by recency (most recent first)
|
|
149
|
+
matches.reverse()
|
|
150
|
+
|
|
151
|
+
# Limit results
|
|
152
|
+
result = matches[:limit]
|
|
153
|
+
|
|
154
|
+
logger.debug(f"Found {len(result)} memories matching '{query}'")
|
|
155
|
+
return result
|
|
156
|
+
|
|
157
|
+
def delete(self, memory_id: str) -> bool:
|
|
158
|
+
"""Delete a memory by ID.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
memory_id: ID of memory to delete
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
True if deleted, False if not found
|
|
165
|
+
"""
|
|
166
|
+
if memory_id in self._memories:
|
|
167
|
+
del self._memories[memory_id]
|
|
168
|
+
logger.debug(f"Deleted memory {memory_id}")
|
|
169
|
+
return True
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
def clear(self) -> None:
|
|
173
|
+
"""Clear all memories."""
|
|
174
|
+
count = len(self._memories)
|
|
175
|
+
self._memories.clear()
|
|
176
|
+
logger.info(f"Cleared {count} memories from short-term memory")
|
|
177
|
+
|
|
178
|
+
def get_size(self) -> int:
|
|
179
|
+
"""Get current number of stored memories.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Number of memories
|
|
183
|
+
"""
|
|
184
|
+
return len(self._memories)
|
|
185
|
+
|
|
186
|
+
def get_capacity(self) -> int:
|
|
187
|
+
"""Get maximum capacity.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Maximum number of memories
|
|
191
|
+
"""
|
|
192
|
+
return self.capacity
|
|
193
|
+
|
|
194
|
+
def is_full(self) -> bool:
|
|
195
|
+
"""Check if memory is at capacity.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
True if at capacity
|
|
199
|
+
"""
|
|
200
|
+
return len(self._memories) >= self.capacity
|
|
201
|
+
|
|
202
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
203
|
+
"""Get memory statistics.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Statistics dictionary
|
|
207
|
+
"""
|
|
208
|
+
if not self._memories:
|
|
209
|
+
return {
|
|
210
|
+
"size": 0,
|
|
211
|
+
"capacity": self.capacity,
|
|
212
|
+
"utilization": 0.0,
|
|
213
|
+
"total_accesses": self._access_count,
|
|
214
|
+
"avg_importance": 0.0,
|
|
215
|
+
"avg_access_count": 0.0,
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
memories = list(self._memories.values())
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
"size": len(memories),
|
|
222
|
+
"capacity": self.capacity,
|
|
223
|
+
"utilization": len(memories) / self.capacity,
|
|
224
|
+
"total_accesses": self._access_count,
|
|
225
|
+
"avg_importance": sum(m.importance for m in memories) / len(memories),
|
|
226
|
+
"avg_access_count": sum(m.access_count for m in memories) / len(memories),
|
|
227
|
+
"oldest_memory": memories[0].timestamp.isoformat() if memories else None,
|
|
228
|
+
"newest_memory": memories[-1].timestamp.isoformat() if memories else None,
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
def __len__(self) -> int:
|
|
232
|
+
"""Get number of stored memories."""
|
|
233
|
+
return len(self._memories)
|
|
234
|
+
|
|
235
|
+
def __contains__(self, memory_id: str) -> bool:
|
|
236
|
+
"""Check if memory exists."""
|
|
237
|
+
return memory_id in self._memories
|
|
238
|
+
|
|
239
|
+
async def add(self, content: Any, metadata: Optional[Dict[str, Any]] = None) -> str:
|
|
240
|
+
"""Add content to short-term memory.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
content: Content to store
|
|
244
|
+
metadata: Optional metadata
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
Memory ID
|
|
248
|
+
"""
|
|
249
|
+
import uuid
|
|
250
|
+
from genxai.core.memory.base import Memory, MemoryType
|
|
251
|
+
from datetime import datetime
|
|
252
|
+
|
|
253
|
+
memory = Memory(
|
|
254
|
+
id=str(uuid.uuid4()),
|
|
255
|
+
content=content,
|
|
256
|
+
type=MemoryType.SHORT_TERM,
|
|
257
|
+
importance=0.5,
|
|
258
|
+
timestamp=datetime.now(),
|
|
259
|
+
metadata=metadata or {},
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
self.store(memory)
|
|
263
|
+
return memory.id
|
|
264
|
+
|
|
265
|
+
async def get_context(self, max_tokens: int = 4000) -> str:
|
|
266
|
+
"""Get formatted context string for LLM.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
max_tokens: Maximum tokens to include
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
Formatted context string
|
|
273
|
+
"""
|
|
274
|
+
recent = self.retrieve_recent(limit=10)
|
|
275
|
+
|
|
276
|
+
if not recent:
|
|
277
|
+
return ""
|
|
278
|
+
|
|
279
|
+
context_parts = ["Recent context:"]
|
|
280
|
+
for memory in recent:
|
|
281
|
+
# Keep this robust: if content is a dict, stringify it so unit tests
|
|
282
|
+
# can find values like "Hello" in the context.
|
|
283
|
+
if isinstance(memory.content, dict):
|
|
284
|
+
context_parts.append(f"- {memory.content}")
|
|
285
|
+
else:
|
|
286
|
+
context_parts.append(f"- {memory.content}")
|
|
287
|
+
|
|
288
|
+
return "\n".join(context_parts)
|
|
289
|
+
|
|
290
|
+
async def clear_async(self) -> None:
|
|
291
|
+
"""Clear all memories (async version)."""
|
|
292
|
+
count = len(self._memories)
|
|
293
|
+
self._memories.clear()
|
|
294
|
+
logger.info(f"Cleared {count} memories from short-term memory")
|
|
295
|
+
|
|
296
|
+
@property
|
|
297
|
+
def memories(self) -> List[Memory]:
|
|
298
|
+
"""Get all memories as a list."""
|
|
299
|
+
return list(self._memories.values())
|
|
300
|
+
|
|
301
|
+
def __repr__(self) -> str:
|
|
302
|
+
"""String representation."""
|
|
303
|
+
return f"ShortTermMemory(size={len(self._memories)}, capacity={self.capacity})"
|