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.
Files changed (156) hide show
  1. cli/__init__.py +3 -0
  2. cli/commands/__init__.py +6 -0
  3. cli/commands/approval.py +85 -0
  4. cli/commands/audit.py +127 -0
  5. cli/commands/metrics.py +25 -0
  6. cli/commands/tool.py +389 -0
  7. cli/main.py +32 -0
  8. genxai/__init__.py +81 -0
  9. genxai/api/__init__.py +5 -0
  10. genxai/api/app.py +21 -0
  11. genxai/config/__init__.py +5 -0
  12. genxai/config/settings.py +37 -0
  13. genxai/connectors/__init__.py +19 -0
  14. genxai/connectors/base.py +122 -0
  15. genxai/connectors/kafka.py +92 -0
  16. genxai/connectors/postgres_cdc.py +95 -0
  17. genxai/connectors/registry.py +44 -0
  18. genxai/connectors/sqs.py +94 -0
  19. genxai/connectors/webhook.py +73 -0
  20. genxai/core/__init__.py +37 -0
  21. genxai/core/agent/__init__.py +32 -0
  22. genxai/core/agent/base.py +206 -0
  23. genxai/core/agent/config_io.py +59 -0
  24. genxai/core/agent/registry.py +98 -0
  25. genxai/core/agent/runtime.py +970 -0
  26. genxai/core/communication/__init__.py +6 -0
  27. genxai/core/communication/collaboration.py +44 -0
  28. genxai/core/communication/message_bus.py +192 -0
  29. genxai/core/communication/protocols.py +35 -0
  30. genxai/core/execution/__init__.py +22 -0
  31. genxai/core/execution/metadata.py +181 -0
  32. genxai/core/execution/queue.py +201 -0
  33. genxai/core/graph/__init__.py +30 -0
  34. genxai/core/graph/checkpoints.py +77 -0
  35. genxai/core/graph/edges.py +131 -0
  36. genxai/core/graph/engine.py +813 -0
  37. genxai/core/graph/executor.py +516 -0
  38. genxai/core/graph/nodes.py +161 -0
  39. genxai/core/graph/trigger_runner.py +40 -0
  40. genxai/core/memory/__init__.py +19 -0
  41. genxai/core/memory/base.py +72 -0
  42. genxai/core/memory/embedding.py +327 -0
  43. genxai/core/memory/episodic.py +448 -0
  44. genxai/core/memory/long_term.py +467 -0
  45. genxai/core/memory/manager.py +543 -0
  46. genxai/core/memory/persistence.py +297 -0
  47. genxai/core/memory/procedural.py +461 -0
  48. genxai/core/memory/semantic.py +526 -0
  49. genxai/core/memory/shared.py +62 -0
  50. genxai/core/memory/short_term.py +303 -0
  51. genxai/core/memory/vector_store.py +508 -0
  52. genxai/core/memory/working.py +211 -0
  53. genxai/core/state/__init__.py +6 -0
  54. genxai/core/state/manager.py +293 -0
  55. genxai/core/state/schema.py +115 -0
  56. genxai/llm/__init__.py +14 -0
  57. genxai/llm/base.py +150 -0
  58. genxai/llm/factory.py +329 -0
  59. genxai/llm/providers/__init__.py +1 -0
  60. genxai/llm/providers/anthropic.py +249 -0
  61. genxai/llm/providers/cohere.py +274 -0
  62. genxai/llm/providers/google.py +334 -0
  63. genxai/llm/providers/ollama.py +147 -0
  64. genxai/llm/providers/openai.py +257 -0
  65. genxai/llm/routing.py +83 -0
  66. genxai/observability/__init__.py +6 -0
  67. genxai/observability/logging.py +327 -0
  68. genxai/observability/metrics.py +494 -0
  69. genxai/observability/tracing.py +372 -0
  70. genxai/performance/__init__.py +39 -0
  71. genxai/performance/cache.py +256 -0
  72. genxai/performance/pooling.py +289 -0
  73. genxai/security/audit.py +304 -0
  74. genxai/security/auth.py +315 -0
  75. genxai/security/cost_control.py +528 -0
  76. genxai/security/default_policies.py +44 -0
  77. genxai/security/jwt.py +142 -0
  78. genxai/security/oauth.py +226 -0
  79. genxai/security/pii.py +366 -0
  80. genxai/security/policy_engine.py +82 -0
  81. genxai/security/rate_limit.py +341 -0
  82. genxai/security/rbac.py +247 -0
  83. genxai/security/validation.py +218 -0
  84. genxai/tools/__init__.py +21 -0
  85. genxai/tools/base.py +383 -0
  86. genxai/tools/builtin/__init__.py +131 -0
  87. genxai/tools/builtin/communication/__init__.py +15 -0
  88. genxai/tools/builtin/communication/email_sender.py +159 -0
  89. genxai/tools/builtin/communication/notification_manager.py +167 -0
  90. genxai/tools/builtin/communication/slack_notifier.py +118 -0
  91. genxai/tools/builtin/communication/sms_sender.py +118 -0
  92. genxai/tools/builtin/communication/webhook_caller.py +136 -0
  93. genxai/tools/builtin/computation/__init__.py +15 -0
  94. genxai/tools/builtin/computation/calculator.py +101 -0
  95. genxai/tools/builtin/computation/code_executor.py +183 -0
  96. genxai/tools/builtin/computation/data_validator.py +259 -0
  97. genxai/tools/builtin/computation/hash_generator.py +129 -0
  98. genxai/tools/builtin/computation/regex_matcher.py +201 -0
  99. genxai/tools/builtin/data/__init__.py +15 -0
  100. genxai/tools/builtin/data/csv_processor.py +213 -0
  101. genxai/tools/builtin/data/data_transformer.py +299 -0
  102. genxai/tools/builtin/data/json_processor.py +233 -0
  103. genxai/tools/builtin/data/text_analyzer.py +288 -0
  104. genxai/tools/builtin/data/xml_processor.py +175 -0
  105. genxai/tools/builtin/database/__init__.py +15 -0
  106. genxai/tools/builtin/database/database_inspector.py +157 -0
  107. genxai/tools/builtin/database/mongodb_query.py +196 -0
  108. genxai/tools/builtin/database/redis_cache.py +167 -0
  109. genxai/tools/builtin/database/sql_query.py +145 -0
  110. genxai/tools/builtin/database/vector_search.py +163 -0
  111. genxai/tools/builtin/file/__init__.py +17 -0
  112. genxai/tools/builtin/file/directory_scanner.py +214 -0
  113. genxai/tools/builtin/file/file_compressor.py +237 -0
  114. genxai/tools/builtin/file/file_reader.py +102 -0
  115. genxai/tools/builtin/file/file_writer.py +122 -0
  116. genxai/tools/builtin/file/image_processor.py +186 -0
  117. genxai/tools/builtin/file/pdf_parser.py +144 -0
  118. genxai/tools/builtin/test/__init__.py +15 -0
  119. genxai/tools/builtin/test/async_simulator.py +62 -0
  120. genxai/tools/builtin/test/data_transformer.py +99 -0
  121. genxai/tools/builtin/test/error_generator.py +82 -0
  122. genxai/tools/builtin/test/simple_math.py +94 -0
  123. genxai/tools/builtin/test/string_processor.py +72 -0
  124. genxai/tools/builtin/web/__init__.py +15 -0
  125. genxai/tools/builtin/web/api_caller.py +161 -0
  126. genxai/tools/builtin/web/html_parser.py +330 -0
  127. genxai/tools/builtin/web/http_client.py +187 -0
  128. genxai/tools/builtin/web/url_validator.py +162 -0
  129. genxai/tools/builtin/web/web_scraper.py +170 -0
  130. genxai/tools/custom/my_test_tool_2.py +9 -0
  131. genxai/tools/dynamic.py +105 -0
  132. genxai/tools/mcp_server.py +167 -0
  133. genxai/tools/persistence/__init__.py +6 -0
  134. genxai/tools/persistence/models.py +55 -0
  135. genxai/tools/persistence/service.py +322 -0
  136. genxai/tools/registry.py +227 -0
  137. genxai/tools/security/__init__.py +11 -0
  138. genxai/tools/security/limits.py +214 -0
  139. genxai/tools/security/policy.py +20 -0
  140. genxai/tools/security/sandbox.py +248 -0
  141. genxai/tools/templates.py +435 -0
  142. genxai/triggers/__init__.py +19 -0
  143. genxai/triggers/base.py +104 -0
  144. genxai/triggers/file_watcher.py +75 -0
  145. genxai/triggers/queue.py +68 -0
  146. genxai/triggers/registry.py +82 -0
  147. genxai/triggers/schedule.py +66 -0
  148. genxai/triggers/webhook.py +68 -0
  149. genxai/utils/__init__.py +1 -0
  150. genxai/utils/tokens.py +295 -0
  151. genxai_framework-0.1.0.dist-info/METADATA +495 -0
  152. genxai_framework-0.1.0.dist-info/RECORD +156 -0
  153. genxai_framework-0.1.0.dist-info/WHEEL +5 -0
  154. genxai_framework-0.1.0.dist-info/entry_points.txt +2 -0
  155. genxai_framework-0.1.0.dist-info/licenses/LICENSE +21 -0
  156. genxai_framework-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,372 @@
1
+ """OpenTelemetry tracing for GenXAI."""
2
+
3
+ from contextlib import contextmanager
4
+ from functools import wraps
5
+ from typing import Any, Callable, Dict, Optional
6
+ import os
7
+
8
+ try:
9
+ from opentelemetry import trace
10
+ from opentelemetry.sdk.trace import TracerProvider
11
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
12
+ from opentelemetry.sdk.resources import Resource
13
+ from opentelemetry.exporter.jaeger.thrift import JaegerExporter
14
+ from opentelemetry.instrumentation.requests import RequestsInstrumentor
15
+ OTEL_AVAILABLE = True
16
+ except ImportError:
17
+ OTEL_AVAILABLE = False
18
+
19
+
20
+ class TracingManager:
21
+ """Manage OpenTelemetry tracing for GenXAI."""
22
+
23
+ def __init__(
24
+ self,
25
+ service_name: str = "genxai",
26
+ jaeger_host: str = "localhost",
27
+ jaeger_port: int = 6831,
28
+ enable_console: bool = False
29
+ ):
30
+ """Initialize tracing manager.
31
+
32
+ Args:
33
+ service_name: Name of the service
34
+ jaeger_host: Jaeger agent host
35
+ jaeger_port: Jaeger agent port
36
+ enable_console: Enable console exporter for debugging
37
+ """
38
+ self.service_name = service_name
39
+ self.jaeger_host = jaeger_host
40
+ self.jaeger_port = jaeger_port
41
+ self.enable_console = enable_console
42
+ self.tracer = None
43
+
44
+ if OTEL_AVAILABLE:
45
+ self._setup_tracing()
46
+
47
+ def _setup_tracing(self):
48
+ """Set up OpenTelemetry tracing."""
49
+ # Create resource
50
+ resource = Resource.create({
51
+ "service.name": self.service_name,
52
+ "service.version": "1.0.0",
53
+ })
54
+
55
+ # Create tracer provider
56
+ tracer_provider = TracerProvider(resource=resource)
57
+
58
+ # Add Jaeger exporter
59
+ try:
60
+ jaeger_exporter = JaegerExporter(
61
+ agent_host_name=self.jaeger_host,
62
+ agent_port=self.jaeger_port,
63
+ )
64
+ tracer_provider.add_span_processor(
65
+ BatchSpanProcessor(jaeger_exporter)
66
+ )
67
+ except Exception:
68
+ # Jaeger not available, continue without it
69
+ pass
70
+
71
+ # Add console exporter if enabled
72
+ if self.enable_console:
73
+ console_exporter = ConsoleSpanExporter()
74
+ tracer_provider.add_span_processor(
75
+ BatchSpanProcessor(console_exporter)
76
+ )
77
+
78
+ # Set global tracer provider
79
+ trace.set_tracer_provider(tracer_provider)
80
+
81
+ # Get tracer
82
+ self.tracer = trace.get_tracer(__name__)
83
+
84
+ # Instrument requests library
85
+ try:
86
+ RequestsInstrumentor().instrument()
87
+ except Exception:
88
+ pass
89
+
90
+ def start_span(
91
+ self,
92
+ name: str,
93
+ attributes: Optional[Dict[str, Any]] = None
94
+ ):
95
+ """Start a new span.
96
+
97
+ Args:
98
+ name: Span name
99
+ attributes: Optional span attributes
100
+
101
+ Returns:
102
+ Span context manager
103
+ """
104
+ if not self.tracer:
105
+ return _NoOpSpan()
106
+
107
+ span = self.tracer.start_as_current_span(name)
108
+
109
+ if attributes:
110
+ for key, value in attributes.items():
111
+ span.set_attribute(key, value)
112
+
113
+ return span
114
+
115
+ @contextmanager
116
+ def span(
117
+ self,
118
+ name: str,
119
+ attributes: Optional[Dict[str, Any]] = None,
120
+ ):
121
+ """Context manager for a tracing span.
122
+
123
+ Args:
124
+ name: Span name
125
+ attributes: Optional span attributes
126
+ """
127
+ if not self.tracer:
128
+ yield _NoOpSpan()
129
+ return
130
+
131
+ with self.tracer.start_as_current_span(name) as span:
132
+ if attributes:
133
+ for key, value in attributes.items():
134
+ span.set_attribute(key, value)
135
+ yield span
136
+
137
+ def trace(
138
+ self,
139
+ span_name: Optional[str] = None,
140
+ attributes: Optional[Dict[str, Any]] = None
141
+ ):
142
+ """Decorator to trace a function.
143
+
144
+ Args:
145
+ span_name: Optional span name (defaults to function name)
146
+ attributes: Optional span attributes
147
+
148
+ Returns:
149
+ Decorated function
150
+ """
151
+ def decorator(func: Callable) -> Callable:
152
+ @wraps(func)
153
+ async def async_wrapper(*args, **kwargs):
154
+ name = span_name or f"{func.__module__}.{func.__name__}"
155
+
156
+ if not self.tracer:
157
+ return await func(*args, **kwargs)
158
+
159
+ with self.tracer.start_as_current_span(name) as span:
160
+ # Add attributes
161
+ if attributes:
162
+ for key, value in attributes.items():
163
+ span.set_attribute(key, value)
164
+
165
+ # Add function info
166
+ span.set_attribute("function.name", func.__name__)
167
+ span.set_attribute("function.module", func.__module__)
168
+
169
+ try:
170
+ result = await func(*args, **kwargs)
171
+ span.set_attribute("status", "success")
172
+ return result
173
+ except Exception as e:
174
+ span.set_attribute("status", "error")
175
+ span.set_attribute("error.type", type(e).__name__)
176
+ span.set_attribute("error.message", str(e))
177
+ span.record_exception(e)
178
+ raise
179
+
180
+ @wraps(func)
181
+ def sync_wrapper(*args, **kwargs):
182
+ name = span_name or f"{func.__module__}.{func.__name__}"
183
+
184
+ if not self.tracer:
185
+ return func(*args, **kwargs)
186
+
187
+ with self.tracer.start_as_current_span(name) as span:
188
+ # Add attributes
189
+ if attributes:
190
+ for key, value in attributes.items():
191
+ span.set_attribute(key, value)
192
+
193
+ # Add function info
194
+ span.set_attribute("function.name", func.__name__)
195
+ span.set_attribute("function.module", func.__module__)
196
+
197
+ try:
198
+ result = func(*args, **kwargs)
199
+ span.set_attribute("status", "success")
200
+ return result
201
+ except Exception as e:
202
+ span.set_attribute("status", "error")
203
+ span.set_attribute("error.type", type(e).__name__)
204
+ span.set_attribute("error.message", str(e))
205
+ span.record_exception(e)
206
+ raise
207
+
208
+ # Return appropriate wrapper based on function type
209
+ import asyncio
210
+ if asyncio.iscoroutinefunction(func):
211
+ return async_wrapper
212
+ else:
213
+ return sync_wrapper
214
+
215
+ return decorator
216
+
217
+ def add_event(self, name: str, attributes: Optional[Dict[str, Any]] = None):
218
+ """Add an event to the current span.
219
+
220
+ Args:
221
+ name: Event name
222
+ attributes: Optional event attributes
223
+ """
224
+ if not self.tracer:
225
+ return
226
+
227
+ span = trace.get_current_span()
228
+ if span:
229
+ span.add_event(name, attributes=attributes or {})
230
+
231
+ def set_attribute(self, key: str, value: Any):
232
+ """Set an attribute on the current span.
233
+
234
+ Args:
235
+ key: Attribute key
236
+ value: Attribute value
237
+ """
238
+ if not self.tracer:
239
+ return
240
+
241
+ span = trace.get_current_span()
242
+ if span:
243
+ span.set_attribute(key, value)
244
+
245
+ def record_exception(self, exception: Exception):
246
+ """Record an exception in the current span.
247
+
248
+ Args:
249
+ exception: Exception to record
250
+ """
251
+ if not self.tracer:
252
+ return
253
+
254
+ span = trace.get_current_span()
255
+ if span:
256
+ span.record_exception(exception)
257
+
258
+
259
+ class _NoOpSpan:
260
+ """No-op span when tracing is not available."""
261
+
262
+ def __enter__(self):
263
+ return self
264
+
265
+ def __exit__(self, *args):
266
+ pass
267
+
268
+ def set_attribute(self, key: str, value: Any):
269
+ pass
270
+
271
+ def add_event(self, name: str, attributes: Optional[Dict[str, Any]] = None):
272
+ pass
273
+
274
+ def record_exception(self, exception: Exception):
275
+ pass
276
+
277
+
278
+ # Global tracing manager
279
+ _global_tracing = None
280
+
281
+
282
+ def get_tracing_manager() -> TracingManager:
283
+ """Get global tracing manager.
284
+
285
+ Returns:
286
+ Global tracing manager instance
287
+ """
288
+ global _global_tracing
289
+
290
+ if _global_tracing is None:
291
+ # Initialize from environment variables
292
+ service_name = os.getenv("GENXAI_SERVICE_NAME", "genxai")
293
+ jaeger_host = os.getenv("JAEGER_AGENT_HOST", "localhost")
294
+ jaeger_port = int(os.getenv("JAEGER_AGENT_PORT", "6831"))
295
+ enable_console = os.getenv("GENXAI_TRACE_CONSOLE", "false").lower() == "true"
296
+
297
+ _global_tracing = TracingManager(
298
+ service_name=service_name,
299
+ jaeger_host=jaeger_host,
300
+ jaeger_port=jaeger_port,
301
+ enable_console=enable_console
302
+ )
303
+
304
+ return _global_tracing
305
+
306
+
307
+ def trace(span_name: Optional[str] = None, attributes: Optional[Dict[str, Any]] = None):
308
+ """Decorator to trace a function.
309
+
310
+ Args:
311
+ span_name: Optional span name (defaults to function name)
312
+ attributes: Optional span attributes
313
+
314
+ Returns:
315
+ Decorated function
316
+ """
317
+ return get_tracing_manager().trace(span_name, attributes)
318
+
319
+
320
+ def start_span(name: str, attributes: Optional[Dict[str, Any]] = None):
321
+ """Start a new span.
322
+
323
+ Args:
324
+ name: Span name
325
+ attributes: Optional span attributes
326
+
327
+ Returns:
328
+ Span context manager
329
+ """
330
+ return get_tracing_manager().start_span(name, attributes)
331
+
332
+
333
+ def span(name: str, attributes: Optional[Dict[str, Any]] = None):
334
+ """Context manager for a tracing span.
335
+
336
+ Args:
337
+ name: Span name
338
+ attributes: Optional span attributes
339
+
340
+ Returns:
341
+ Context manager yielding the span
342
+ """
343
+ return get_tracing_manager().span(name, attributes)
344
+
345
+
346
+ def add_event(name: str, attributes: Optional[Dict[str, Any]] = None):
347
+ """Add an event to the current span.
348
+
349
+ Args:
350
+ name: Event name
351
+ attributes: Optional event attributes
352
+ """
353
+ get_tracing_manager().add_event(name, attributes)
354
+
355
+
356
+ def set_attribute(key: str, value: Any):
357
+ """Set an attribute on the current span.
358
+
359
+ Args:
360
+ key: Attribute key
361
+ value: Attribute value
362
+ """
363
+ get_tracing_manager().set_attribute(key, value)
364
+
365
+
366
+ def record_exception(exception: Exception):
367
+ """Record an exception in the current span.
368
+
369
+ Args:
370
+ exception: Exception to record
371
+ """
372
+ get_tracing_manager().record_exception(exception)
@@ -0,0 +1,39 @@
1
+ """Performance optimization utilities for GenXAI."""
2
+
3
+ from genxai.performance.cache import (
4
+ CacheManager,
5
+ MemoryCache,
6
+ RedisCache,
7
+ LRUCache,
8
+ cached,
9
+ get_cache_manager
10
+ )
11
+
12
+ from genxai.performance.pooling import (
13
+ ConnectionPool,
14
+ DatabaseConnectionPool,
15
+ HTTPConnectionPool,
16
+ VectorStoreConnectionPool,
17
+ get_db_pool,
18
+ get_http_pool,
19
+ get_vector_pool
20
+ )
21
+
22
+ __all__ = [
23
+ # Caching
24
+ "CacheManager",
25
+ "MemoryCache",
26
+ "RedisCache",
27
+ "LRUCache",
28
+ "cached",
29
+ "get_cache_manager",
30
+
31
+ # Connection pooling
32
+ "ConnectionPool",
33
+ "DatabaseConnectionPool",
34
+ "HTTPConnectionPool",
35
+ "VectorStoreConnectionPool",
36
+ "get_db_pool",
37
+ "get_http_pool",
38
+ "get_vector_pool",
39
+ ]
@@ -0,0 +1,256 @@
1
+ """Caching system for GenXAI performance optimization."""
2
+
3
+ import hashlib
4
+ import json
5
+ import pickle
6
+ from typing import Any, Optional, Callable
7
+ from functools import wraps
8
+ import time
9
+ from datetime import timedelta
10
+
11
+
12
+ class CacheBackend:
13
+ """Base cache backend."""
14
+
15
+ def get(self, key: str) -> Optional[Any]:
16
+ """Get value from cache."""
17
+ raise NotImplementedError
18
+
19
+ def set(self, key: str, value: Any, ttl: Optional[int] = None):
20
+ """Set value in cache."""
21
+ raise NotImplementedError
22
+
23
+ def delete(self, key: str):
24
+ """Delete value from cache."""
25
+ raise NotImplementedError
26
+
27
+ def clear(self):
28
+ """Clear all cache."""
29
+ raise NotImplementedError
30
+
31
+
32
+ class MemoryCache(CacheBackend):
33
+ """In-memory cache backend."""
34
+
35
+ def __init__(self):
36
+ self._cache = {}
37
+ self._expiry = {}
38
+
39
+ def get(self, key: str) -> Optional[Any]:
40
+ """Get value from cache."""
41
+ # Check expiry
42
+ if key in self._expiry:
43
+ if time.time() > self._expiry[key]:
44
+ self.delete(key)
45
+ return None
46
+
47
+ return self._cache.get(key)
48
+
49
+ def set(self, key: str, value: Any, ttl: Optional[int] = None):
50
+ """Set value in cache."""
51
+ self._cache[key] = value
52
+
53
+ if ttl:
54
+ self._expiry[key] = time.time() + ttl
55
+
56
+ def delete(self, key: str):
57
+ """Delete value from cache."""
58
+ self._cache.pop(key, None)
59
+ self._expiry.pop(key, None)
60
+
61
+ def clear(self):
62
+ """Clear all cache."""
63
+ self._cache.clear()
64
+ self._expiry.clear()
65
+
66
+
67
+ class RedisCache(CacheBackend):
68
+ """Redis cache backend."""
69
+
70
+ def __init__(self, redis_url: str = "redis://localhost:6379"):
71
+ try:
72
+ import redis
73
+ self.client = redis.from_url(redis_url)
74
+ except ImportError:
75
+ raise ImportError("Redis not installed. Install with: pip install redis")
76
+
77
+ def get(self, key: str) -> Optional[Any]:
78
+ """Get value from cache."""
79
+ value = self.client.get(key)
80
+ if value:
81
+ return pickle.loads(value)
82
+ return None
83
+
84
+ def set(self, key: str, value: Any, ttl: Optional[int] = None):
85
+ """Set value in cache."""
86
+ serialized = pickle.dumps(value)
87
+ if ttl:
88
+ self.client.setex(key, ttl, serialized)
89
+ else:
90
+ self.client.set(key, serialized)
91
+
92
+ def delete(self, key: str):
93
+ """Delete value from cache."""
94
+ self.client.delete(key)
95
+
96
+ def clear(self):
97
+ """Clear all cache."""
98
+ self.client.flushdb()
99
+
100
+
101
+ class CacheManager:
102
+ """Manage caching for different components."""
103
+
104
+ def __init__(self, backend: CacheBackend):
105
+ self.backend = backend
106
+
107
+ def cache_key(self, prefix: str, *args, **kwargs) -> str:
108
+ """Generate cache key."""
109
+ # Create deterministic key from arguments
110
+ key_data = {
111
+ "args": args,
112
+ "kwargs": sorted(kwargs.items())
113
+ }
114
+ key_str = json.dumps(key_data, sort_keys=True)
115
+ key_hash = hashlib.md5(key_str.encode()).hexdigest()
116
+ return f"{prefix}:{key_hash}"
117
+
118
+ def get(self, key: str) -> Optional[Any]:
119
+ """Get from cache."""
120
+ return self.backend.get(key)
121
+
122
+ def set(self, key: str, value: Any, ttl: Optional[int] = None):
123
+ """Set in cache."""
124
+ self.backend.set(key, value, ttl)
125
+
126
+ def delete(self, key: str):
127
+ """Delete from cache."""
128
+ self.backend.delete(key)
129
+
130
+ def clear(self):
131
+ """Clear cache."""
132
+ self.backend.clear()
133
+
134
+
135
+ # Global cache manager
136
+ _cache_manager = None
137
+
138
+
139
+ def get_cache_manager() -> CacheManager:
140
+ """Get global cache manager."""
141
+ global _cache_manager
142
+
143
+ if _cache_manager is None:
144
+ import os
145
+ backend_type = os.getenv("CACHE_BACKEND", "memory")
146
+
147
+ if backend_type == "redis":
148
+ redis_url = os.getenv("REDIS_URL", "redis://localhost:6379")
149
+ backend = RedisCache(redis_url)
150
+ else:
151
+ backend = MemoryCache()
152
+
153
+ _cache_manager = CacheManager(backend)
154
+
155
+ return _cache_manager
156
+
157
+
158
+ def cached(prefix: str, ttl: Optional[int] = 3600):
159
+ """Decorator to cache function results.
160
+
161
+ Args:
162
+ prefix: Cache key prefix
163
+ ttl: Time to live in seconds (default: 1 hour)
164
+
165
+ Usage:
166
+ @cached("llm_response", ttl=3600)
167
+ async def get_llm_response(prompt):
168
+ return await llm.generate(prompt)
169
+ """
170
+ def decorator(func: Callable):
171
+ @wraps(func)
172
+ async def async_wrapper(*args, **kwargs):
173
+ cache = get_cache_manager()
174
+
175
+ # Generate cache key
176
+ cache_key = cache.cache_key(prefix, *args, **kwargs)
177
+
178
+ # Try to get from cache
179
+ cached_value = cache.get(cache_key)
180
+ if cached_value is not None:
181
+ return cached_value
182
+
183
+ # Call function
184
+ result = await func(*args, **kwargs)
185
+
186
+ # Store in cache
187
+ cache.set(cache_key, result, ttl)
188
+
189
+ return result
190
+
191
+ @wraps(func)
192
+ def sync_wrapper(*args, **kwargs):
193
+ cache = get_cache_manager()
194
+
195
+ # Generate cache key
196
+ cache_key = cache.cache_key(prefix, *args, **kwargs)
197
+
198
+ # Try to get from cache
199
+ cached_value = cache.get(cache_key)
200
+ if cached_value is not None:
201
+ return cached_value
202
+
203
+ # Call function
204
+ result = func(*args, **kwargs)
205
+
206
+ # Store in cache
207
+ cache.set(cache_key, result, ttl)
208
+
209
+ return result
210
+
211
+ # Return appropriate wrapper
212
+ import asyncio
213
+ if asyncio.iscoroutinefunction(func):
214
+ return async_wrapper
215
+ else:
216
+ return sync_wrapper
217
+
218
+ return decorator
219
+
220
+
221
+ class LRUCache:
222
+ """LRU (Least Recently Used) cache implementation."""
223
+
224
+ def __init__(self, capacity: int = 1000):
225
+ self.capacity = capacity
226
+ self.cache = {}
227
+ self.access_order = []
228
+
229
+ def get(self, key: str) -> Optional[Any]:
230
+ """Get value and update access order."""
231
+ if key not in self.cache:
232
+ return None
233
+
234
+ # Update access order
235
+ self.access_order.remove(key)
236
+ self.access_order.append(key)
237
+
238
+ return self.cache[key]
239
+
240
+ def set(self, key: str, value: Any):
241
+ """Set value and manage capacity."""
242
+ if key in self.cache:
243
+ # Update existing
244
+ self.access_order.remove(key)
245
+ elif len(self.cache) >= self.capacity:
246
+ # Evict least recently used
247
+ lru_key = self.access_order.pop(0)
248
+ del self.cache[lru_key]
249
+
250
+ self.cache[key] = value
251
+ self.access_order.append(key)
252
+
253
+ def clear(self):
254
+ """Clear cache."""
255
+ self.cache.clear()
256
+ self.access_order.clear()