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,461 @@
|
|
|
1
|
+
"""Procedural memory implementation for storing learned skills and procedures."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
import logging
|
|
6
|
+
import uuid
|
|
7
|
+
|
|
8
|
+
from genxai.core.memory.persistence import JsonMemoryStore, MemoryPersistenceConfig
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Procedure:
|
|
14
|
+
"""Represents a learned procedure or skill."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
id: str,
|
|
19
|
+
name: str,
|
|
20
|
+
description: str,
|
|
21
|
+
steps: List[Dict[str, Any]],
|
|
22
|
+
preconditions: Optional[List[str]] = None,
|
|
23
|
+
postconditions: Optional[List[str]] = None,
|
|
24
|
+
success_count: int = 0,
|
|
25
|
+
failure_count: int = 0,
|
|
26
|
+
avg_duration: float = 0.0,
|
|
27
|
+
timestamp: Optional[datetime] = None,
|
|
28
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Initialize procedure.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
id: Unique procedure ID
|
|
34
|
+
name: Procedure name
|
|
35
|
+
description: Description of what the procedure does
|
|
36
|
+
steps: List of steps to execute
|
|
37
|
+
preconditions: Conditions that must be true before execution
|
|
38
|
+
postconditions: Expected conditions after execution
|
|
39
|
+
success_count: Number of successful executions
|
|
40
|
+
failure_count: Number of failed executions
|
|
41
|
+
avg_duration: Average execution duration
|
|
42
|
+
timestamp: When procedure was learned
|
|
43
|
+
metadata: Additional metadata
|
|
44
|
+
"""
|
|
45
|
+
self.id = id
|
|
46
|
+
self.name = name
|
|
47
|
+
self.description = description
|
|
48
|
+
self.steps = steps
|
|
49
|
+
self.preconditions = preconditions or []
|
|
50
|
+
self.postconditions = postconditions or []
|
|
51
|
+
self.success_count = success_count
|
|
52
|
+
self.failure_count = failure_count
|
|
53
|
+
self.avg_duration = avg_duration
|
|
54
|
+
self.timestamp = timestamp or datetime.now()
|
|
55
|
+
self.last_used = timestamp or datetime.now()
|
|
56
|
+
self.metadata = metadata or {}
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def success_rate(self) -> float:
|
|
60
|
+
"""Calculate success rate."""
|
|
61
|
+
total = self.success_count + self.failure_count
|
|
62
|
+
if total == 0:
|
|
63
|
+
return 0.0
|
|
64
|
+
return self.success_count / total
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def total_executions(self) -> int:
|
|
68
|
+
"""Get total number of executions."""
|
|
69
|
+
return self.success_count + self.failure_count
|
|
70
|
+
|
|
71
|
+
def record_execution(
|
|
72
|
+
self,
|
|
73
|
+
success: bool,
|
|
74
|
+
duration: float,
|
|
75
|
+
) -> None:
|
|
76
|
+
"""Record an execution of this procedure.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
success: Whether execution was successful
|
|
80
|
+
duration: Execution duration in seconds
|
|
81
|
+
"""
|
|
82
|
+
if success:
|
|
83
|
+
self.success_count += 1
|
|
84
|
+
else:
|
|
85
|
+
self.failure_count += 1
|
|
86
|
+
|
|
87
|
+
# Update average duration
|
|
88
|
+
total = self.total_executions
|
|
89
|
+
self.avg_duration = (
|
|
90
|
+
(self.avg_duration * (total - 1) + duration) / total
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
self.last_used = datetime.now()
|
|
94
|
+
|
|
95
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
96
|
+
"""Convert to dictionary."""
|
|
97
|
+
return {
|
|
98
|
+
"id": self.id,
|
|
99
|
+
"name": self.name,
|
|
100
|
+
"description": self.description,
|
|
101
|
+
"steps": self.steps,
|
|
102
|
+
"preconditions": self.preconditions,
|
|
103
|
+
"postconditions": self.postconditions,
|
|
104
|
+
"success_count": self.success_count,
|
|
105
|
+
"failure_count": self.failure_count,
|
|
106
|
+
"success_rate": self.success_rate,
|
|
107
|
+
"avg_duration": self.avg_duration,
|
|
108
|
+
"timestamp": self.timestamp.isoformat(),
|
|
109
|
+
"last_used": self.last_used.isoformat(),
|
|
110
|
+
"metadata": self.metadata,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Procedure":
|
|
115
|
+
"""Create procedure from dictionary."""
|
|
116
|
+
return cls(
|
|
117
|
+
id=data["id"],
|
|
118
|
+
name=data["name"],
|
|
119
|
+
description=data["description"],
|
|
120
|
+
steps=data["steps"],
|
|
121
|
+
preconditions=data.get("preconditions", []),
|
|
122
|
+
postconditions=data.get("postconditions", []),
|
|
123
|
+
success_count=data.get("success_count", 0),
|
|
124
|
+
failure_count=data.get("failure_count", 0),
|
|
125
|
+
avg_duration=data.get("avg_duration", 0.0),
|
|
126
|
+
timestamp=datetime.fromisoformat(data["timestamp"]) if data.get("timestamp") else None,
|
|
127
|
+
metadata=data.get("metadata", {}),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def __repr__(self) -> str:
|
|
131
|
+
"""String representation."""
|
|
132
|
+
return f"Procedure({self.name}, success_rate={self.success_rate:.2f})"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class ProceduralMemory:
|
|
136
|
+
"""Procedural memory for storing and retrieving learned procedures.
|
|
137
|
+
|
|
138
|
+
Stores:
|
|
139
|
+
- Step-by-step procedures
|
|
140
|
+
- Learned skills
|
|
141
|
+
- Execution statistics
|
|
142
|
+
- Success/failure patterns
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
def __init__(
|
|
146
|
+
self,
|
|
147
|
+
max_procedures: int = 100,
|
|
148
|
+
persistence: Optional[MemoryPersistenceConfig] = None,
|
|
149
|
+
) -> None:
|
|
150
|
+
"""Initialize procedural memory.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
max_procedures: Maximum number of procedures to store
|
|
154
|
+
"""
|
|
155
|
+
self._max_procedures = max_procedures
|
|
156
|
+
self._procedures: Dict[str, Procedure] = {}
|
|
157
|
+
self._name_index: Dict[str, str] = {} # name -> procedure_id
|
|
158
|
+
self._persistence = persistence
|
|
159
|
+
self._store = JsonMemoryStore(persistence) if persistence else None
|
|
160
|
+
|
|
161
|
+
if self._store and self._persistence and self._persistence.enabled:
|
|
162
|
+
self._load_from_disk()
|
|
163
|
+
|
|
164
|
+
logger.info("Initialized procedural memory")
|
|
165
|
+
|
|
166
|
+
async def store_procedure(
|
|
167
|
+
self,
|
|
168
|
+
name: str,
|
|
169
|
+
description: str,
|
|
170
|
+
steps: List[Dict[str, Any]],
|
|
171
|
+
preconditions: Optional[List[str]] = None,
|
|
172
|
+
postconditions: Optional[List[str]] = None,
|
|
173
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
174
|
+
) -> Procedure:
|
|
175
|
+
"""Store a new procedure.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
name: Procedure name
|
|
179
|
+
description: Description
|
|
180
|
+
steps: List of steps
|
|
181
|
+
preconditions: Required preconditions
|
|
182
|
+
postconditions: Expected postconditions
|
|
183
|
+
metadata: Additional metadata
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Created procedure
|
|
187
|
+
"""
|
|
188
|
+
# Check if procedure with same name exists
|
|
189
|
+
if name in self._name_index:
|
|
190
|
+
existing_id = self._name_index[name]
|
|
191
|
+
existing = self._procedures[existing_id]
|
|
192
|
+
logger.debug(f"Procedure '{name}' already exists, updating...")
|
|
193
|
+
|
|
194
|
+
# Update existing procedure
|
|
195
|
+
existing.description = description
|
|
196
|
+
existing.steps = steps
|
|
197
|
+
existing.preconditions = preconditions or []
|
|
198
|
+
existing.postconditions = postconditions or []
|
|
199
|
+
existing.metadata = metadata or {}
|
|
200
|
+
existing.timestamp = datetime.now()
|
|
201
|
+
|
|
202
|
+
return existing
|
|
203
|
+
|
|
204
|
+
# Create new procedure
|
|
205
|
+
procedure = Procedure(
|
|
206
|
+
id=str(uuid.uuid4()),
|
|
207
|
+
name=name,
|
|
208
|
+
description=description,
|
|
209
|
+
steps=steps,
|
|
210
|
+
preconditions=preconditions,
|
|
211
|
+
postconditions=postconditions,
|
|
212
|
+
metadata=metadata,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Store procedure
|
|
216
|
+
self._procedures[procedure.id] = procedure
|
|
217
|
+
self._name_index[name] = procedure.id
|
|
218
|
+
|
|
219
|
+
# Enforce max procedures limit
|
|
220
|
+
if len(self._procedures) > self._max_procedures:
|
|
221
|
+
# Remove least successful procedure
|
|
222
|
+
least_successful = min(
|
|
223
|
+
self._procedures.values(),
|
|
224
|
+
key=lambda p: (p.success_rate, p.total_executions)
|
|
225
|
+
)
|
|
226
|
+
await self.delete_procedure(least_successful.id)
|
|
227
|
+
|
|
228
|
+
logger.debug(f"Stored procedure: {procedure}")
|
|
229
|
+
self._persist()
|
|
230
|
+
return procedure
|
|
231
|
+
|
|
232
|
+
async def retrieve_procedure(
|
|
233
|
+
self,
|
|
234
|
+
procedure_id: Optional[str] = None,
|
|
235
|
+
name: Optional[str] = None,
|
|
236
|
+
) -> Optional[Procedure]:
|
|
237
|
+
"""Retrieve a procedure by ID or name.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
procedure_id: Procedure ID
|
|
241
|
+
name: Procedure name
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
Procedure if found, None otherwise
|
|
245
|
+
"""
|
|
246
|
+
if procedure_id:
|
|
247
|
+
return self._procedures.get(procedure_id)
|
|
248
|
+
|
|
249
|
+
if name and name in self._name_index:
|
|
250
|
+
procedure_id = self._name_index[name]
|
|
251
|
+
return self._procedures.get(procedure_id)
|
|
252
|
+
|
|
253
|
+
return None
|
|
254
|
+
|
|
255
|
+
async def retrieve_all(
|
|
256
|
+
self,
|
|
257
|
+
min_success_rate: float = 0.0,
|
|
258
|
+
sort_by: str = "success_rate",
|
|
259
|
+
) -> List[Procedure]:
|
|
260
|
+
"""Retrieve all procedures.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
min_success_rate: Minimum success rate filter
|
|
264
|
+
sort_by: Sort key ("success_rate", "executions", "recent")
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
List of procedures
|
|
268
|
+
"""
|
|
269
|
+
procedures = list(self._procedures.values())
|
|
270
|
+
|
|
271
|
+
# Filter by success rate
|
|
272
|
+
if min_success_rate > 0.0:
|
|
273
|
+
procedures = [
|
|
274
|
+
p for p in procedures
|
|
275
|
+
if p.success_rate >= min_success_rate
|
|
276
|
+
]
|
|
277
|
+
|
|
278
|
+
# Sort
|
|
279
|
+
if sort_by == "success_rate":
|
|
280
|
+
procedures.sort(key=lambda p: p.success_rate, reverse=True)
|
|
281
|
+
elif sort_by == "executions":
|
|
282
|
+
procedures.sort(key=lambda p: p.total_executions, reverse=True)
|
|
283
|
+
elif sort_by == "recent":
|
|
284
|
+
procedures.sort(key=lambda p: p.last_used, reverse=True)
|
|
285
|
+
|
|
286
|
+
return procedures
|
|
287
|
+
|
|
288
|
+
async def search_procedures(
|
|
289
|
+
self,
|
|
290
|
+
query: str,
|
|
291
|
+
limit: int = 5,
|
|
292
|
+
) -> List[Procedure]:
|
|
293
|
+
"""Search procedures by name or description.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
query: Search query
|
|
297
|
+
limit: Maximum number of results
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
List of matching procedures
|
|
301
|
+
"""
|
|
302
|
+
query_lower = query.lower()
|
|
303
|
+
matches = []
|
|
304
|
+
|
|
305
|
+
for procedure in self._procedures.values():
|
|
306
|
+
# Check name and description
|
|
307
|
+
if (query_lower in procedure.name.lower() or
|
|
308
|
+
query_lower in procedure.description.lower()):
|
|
309
|
+
matches.append(procedure)
|
|
310
|
+
|
|
311
|
+
# Sort by success rate
|
|
312
|
+
matches.sort(key=lambda p: p.success_rate, reverse=True)
|
|
313
|
+
|
|
314
|
+
return matches[:limit]
|
|
315
|
+
|
|
316
|
+
async def record_execution(
|
|
317
|
+
self,
|
|
318
|
+
procedure_id: str,
|
|
319
|
+
success: bool,
|
|
320
|
+
duration: float,
|
|
321
|
+
) -> bool:
|
|
322
|
+
"""Record an execution of a procedure.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
procedure_id: Procedure ID
|
|
326
|
+
success: Whether execution was successful
|
|
327
|
+
duration: Execution duration
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
True if recorded, False if procedure not found
|
|
331
|
+
"""
|
|
332
|
+
procedure = self._procedures.get(procedure_id)
|
|
333
|
+
if not procedure:
|
|
334
|
+
return False
|
|
335
|
+
|
|
336
|
+
procedure.record_execution(success, duration)
|
|
337
|
+
self._persist()
|
|
338
|
+
logger.debug(
|
|
339
|
+
f"Recorded execution for {procedure.name}: "
|
|
340
|
+
f"success={success}, duration={duration:.2f}s"
|
|
341
|
+
)
|
|
342
|
+
return True
|
|
343
|
+
|
|
344
|
+
async def get_best_procedures(
|
|
345
|
+
self,
|
|
346
|
+
limit: int = 10,
|
|
347
|
+
min_executions: int = 3,
|
|
348
|
+
) -> List[Procedure]:
|
|
349
|
+
"""Get best performing procedures.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
limit: Maximum number of procedures
|
|
353
|
+
min_executions: Minimum number of executions required
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
List of best procedures
|
|
357
|
+
"""
|
|
358
|
+
procedures = [
|
|
359
|
+
p for p in self._procedures.values()
|
|
360
|
+
if p.total_executions >= min_executions
|
|
361
|
+
]
|
|
362
|
+
|
|
363
|
+
# Sort by success rate, then by executions
|
|
364
|
+
procedures.sort(
|
|
365
|
+
key=lambda p: (p.success_rate, p.total_executions),
|
|
366
|
+
reverse=True
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
return procedures[:limit]
|
|
370
|
+
|
|
371
|
+
async def delete_procedure(self, procedure_id: str) -> bool:
|
|
372
|
+
"""Delete a procedure.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
procedure_id: Procedure ID
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
True if deleted, False if not found
|
|
379
|
+
"""
|
|
380
|
+
if procedure_id not in self._procedures:
|
|
381
|
+
return False
|
|
382
|
+
|
|
383
|
+
procedure = self._procedures[procedure_id]
|
|
384
|
+
|
|
385
|
+
# Remove from indexes
|
|
386
|
+
if procedure.name in self._name_index:
|
|
387
|
+
del self._name_index[procedure.name]
|
|
388
|
+
|
|
389
|
+
# Remove procedure
|
|
390
|
+
del self._procedures[procedure_id]
|
|
391
|
+
|
|
392
|
+
self._persist()
|
|
393
|
+
|
|
394
|
+
logger.debug(f"Deleted procedure: {procedure}")
|
|
395
|
+
return True
|
|
396
|
+
|
|
397
|
+
async def clear(self) -> None:
|
|
398
|
+
"""Clear all procedures."""
|
|
399
|
+
self._procedures.clear()
|
|
400
|
+
self._name_index.clear()
|
|
401
|
+
logger.info("Cleared all procedures")
|
|
402
|
+
|
|
403
|
+
self._persist()
|
|
404
|
+
|
|
405
|
+
async def get_stats(self) -> Dict[str, Any]:
|
|
406
|
+
"""Get procedural memory statistics.
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
Statistics dictionary
|
|
410
|
+
"""
|
|
411
|
+
if not self._procedures:
|
|
412
|
+
return {
|
|
413
|
+
"total_procedures": 0,
|
|
414
|
+
"persistence": bool(self._persistence and self._persistence.enabled),
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
procedures = list(self._procedures.values())
|
|
418
|
+
|
|
419
|
+
total_executions = sum(p.total_executions for p in procedures)
|
|
420
|
+
total_successes = sum(p.success_count for p in procedures)
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
"total_procedures": len(procedures),
|
|
424
|
+
"total_executions": total_executions,
|
|
425
|
+
"total_successes": total_successes,
|
|
426
|
+
"total_failures": total_executions - total_successes,
|
|
427
|
+
"overall_success_rate": total_successes / total_executions if total_executions > 0 else 0.0,
|
|
428
|
+
"avg_success_rate": sum(p.success_rate for p in procedures) / len(procedures),
|
|
429
|
+
"most_used": max(procedures, key=lambda p: p.total_executions).name,
|
|
430
|
+
"most_successful": max(procedures, key=lambda p: p.success_rate).name,
|
|
431
|
+
"oldest_procedure": min(p.timestamp for p in procedures).isoformat(),
|
|
432
|
+
"newest_procedure": max(p.timestamp for p in procedures).isoformat(),
|
|
433
|
+
"persistence": bool(self._persistence and self._persistence.enabled),
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
def _persist(self) -> None:
|
|
437
|
+
if not self._store:
|
|
438
|
+
return
|
|
439
|
+
payload = {
|
|
440
|
+
"procedures": [proc.to_dict() for proc in self._procedures.values()],
|
|
441
|
+
"name_index": self._name_index,
|
|
442
|
+
}
|
|
443
|
+
self._store.save_mapping("procedural_memory.json", payload)
|
|
444
|
+
|
|
445
|
+
def _load_from_disk(self) -> None:
|
|
446
|
+
if not self._store:
|
|
447
|
+
return
|
|
448
|
+
data = self._store.load_mapping("procedural_memory.json")
|
|
449
|
+
if not data:
|
|
450
|
+
return
|
|
451
|
+
procedures = data.get("procedures", [])
|
|
452
|
+
self._procedures = {proc["id"]: Procedure.from_dict(proc) for proc in procedures}
|
|
453
|
+
self._name_index = data.get("name_index", {})
|
|
454
|
+
|
|
455
|
+
def __len__(self) -> int:
|
|
456
|
+
"""Get number of stored procedures."""
|
|
457
|
+
return len(self._procedures)
|
|
458
|
+
|
|
459
|
+
def __repr__(self) -> str:
|
|
460
|
+
"""String representation."""
|
|
461
|
+
return f"ProceduralMemory(procedures={len(self._procedures)})"
|