rakam-systems-core 0.1.1rc7__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 (34) hide show
  1. rakam_systems_core/__init__.py +41 -0
  2. rakam_systems_core/ai_core/__init__.py +68 -0
  3. rakam_systems_core/ai_core/base.py +142 -0
  4. rakam_systems_core/ai_core/config.py +12 -0
  5. rakam_systems_core/ai_core/config_loader.py +580 -0
  6. rakam_systems_core/ai_core/config_schema.py +395 -0
  7. rakam_systems_core/ai_core/interfaces/__init__.py +30 -0
  8. rakam_systems_core/ai_core/interfaces/agent.py +83 -0
  9. rakam_systems_core/ai_core/interfaces/chat_history.py +122 -0
  10. rakam_systems_core/ai_core/interfaces/chunker.py +11 -0
  11. rakam_systems_core/ai_core/interfaces/embedding_model.py +10 -0
  12. rakam_systems_core/ai_core/interfaces/indexer.py +10 -0
  13. rakam_systems_core/ai_core/interfaces/llm_gateway.py +139 -0
  14. rakam_systems_core/ai_core/interfaces/loader.py +86 -0
  15. rakam_systems_core/ai_core/interfaces/reranker.py +10 -0
  16. rakam_systems_core/ai_core/interfaces/retriever.py +11 -0
  17. rakam_systems_core/ai_core/interfaces/tool.py +162 -0
  18. rakam_systems_core/ai_core/interfaces/tool_invoker.py +260 -0
  19. rakam_systems_core/ai_core/interfaces/tool_loader.py +374 -0
  20. rakam_systems_core/ai_core/interfaces/tool_registry.py +287 -0
  21. rakam_systems_core/ai_core/interfaces/vectorstore.py +37 -0
  22. rakam_systems_core/ai_core/mcp/README.md +545 -0
  23. rakam_systems_core/ai_core/mcp/__init__.py +0 -0
  24. rakam_systems_core/ai_core/mcp/mcp_server.py +334 -0
  25. rakam_systems_core/ai_core/tracking.py +602 -0
  26. rakam_systems_core/ai_core/vs_core.py +55 -0
  27. rakam_systems_core/ai_utils/__init__.py +16 -0
  28. rakam_systems_core/ai_utils/logging.py +126 -0
  29. rakam_systems_core/ai_utils/metrics.py +10 -0
  30. rakam_systems_core/ai_utils/s3.py +480 -0
  31. rakam_systems_core/ai_utils/tracing.py +5 -0
  32. rakam_systems_core-0.1.1rc7.dist-info/METADATA +162 -0
  33. rakam_systems_core-0.1.1rc7.dist-info/RECORD +34 -0
  34. rakam_systems_core-0.1.1rc7.dist-info/WHEEL +4 -0
@@ -0,0 +1,602 @@
1
+ """
2
+ Input/Output tracking system for agent methods.
3
+
4
+ This module provides:
5
+ 1. Decorator for tracking method inputs/outputs
6
+ 2. TrackingMixin for agents to enable tracking
7
+ 3. CSV export functionality
8
+ 4. Session management
9
+ """
10
+ from __future__ import annotations
11
+ from typing import Any, Callable, Dict, List, Optional, TypeVar, Union
12
+ from functools import wraps
13
+ import asyncio
14
+ import time
15
+ import uuid
16
+ from pathlib import Path
17
+ from datetime import datetime
18
+ import csv
19
+ import json
20
+
21
+ from .config_schema import (
22
+ MethodInputSchema,
23
+ MethodOutputSchema,
24
+ MethodCallRecordSchema,
25
+ TrackingSessionSchema,
26
+ )
27
+ from .interfaces.agent import AgentInput, AgentOutput
28
+
29
+
30
+ F = TypeVar('F', bound=Callable[..., Any])
31
+
32
+
33
+ class TrackingManager:
34
+ """
35
+ Manages tracking of agent method calls.
36
+
37
+ Features:
38
+ - Track inputs and outputs
39
+ - Session management
40
+ - CSV export
41
+ - JSON export
42
+ - Query and analysis
43
+ """
44
+
45
+ def __init__(self, output_dir: str = "./agent_tracking"):
46
+ self.output_dir = Path(output_dir)
47
+ self.output_dir.mkdir(parents=True, exist_ok=True)
48
+
49
+ self.sessions: Dict[str, TrackingSessionSchema] = {}
50
+ self.current_session_id: Optional[str] = None
51
+ self.call_records: List[MethodCallRecordSchema] = []
52
+
53
+ def start_session(self, agent_name: str, session_id: Optional[str] = None) -> str:
54
+ """
55
+ Start a new tracking session.
56
+
57
+ Args:
58
+ agent_name: Name of the agent
59
+ session_id: Optional session ID (generates UUID if None)
60
+
61
+ Returns:
62
+ Session ID
63
+ """
64
+ session_id = session_id or str(uuid.uuid4())
65
+
66
+ session = TrackingSessionSchema(
67
+ session_id=session_id,
68
+ agent_name=agent_name,
69
+ started_at=datetime.now(),
70
+ )
71
+
72
+ self.sessions[session_id] = session
73
+ self.current_session_id = session_id
74
+
75
+ return session_id
76
+
77
+ def end_session(self, session_id: Optional[str] = None) -> None:
78
+ """
79
+ End a tracking session.
80
+
81
+ Args:
82
+ session_id: Session to end (uses current if None)
83
+ """
84
+ session_id = session_id or self.current_session_id
85
+ if session_id and session_id in self.sessions:
86
+ self.sessions[session_id].end_session()
87
+
88
+ def get_session(self, session_id: Optional[str] = None) -> Optional[TrackingSessionSchema]:
89
+ """Get a session by ID (current session if None)."""
90
+ session_id = session_id or self.current_session_id
91
+ return self.sessions.get(session_id) if session_id else None
92
+
93
+ def record_call(
94
+ self,
95
+ agent_name: str,
96
+ method_name: str,
97
+ input_data: MethodInputSchema,
98
+ output_data: MethodOutputSchema,
99
+ session_id: Optional[str] = None,
100
+ ) -> MethodCallRecordSchema:
101
+ """
102
+ Record a complete method call.
103
+
104
+ Args:
105
+ agent_name: Name of the agent
106
+ method_name: Name of the method
107
+ input_data: Input data schema
108
+ output_data: Output data schema
109
+ session_id: Session to add to (uses current if None)
110
+
111
+ Returns:
112
+ Created call record
113
+ """
114
+ record = MethodCallRecordSchema(
115
+ call_id=input_data.call_id,
116
+ agent_name=agent_name,
117
+ method_name=method_name,
118
+ input_data=input_data,
119
+ output_data=output_data,
120
+ started_at=input_data.timestamp,
121
+ completed_at=output_data.timestamp,
122
+ duration_seconds=output_data.duration_seconds,
123
+ )
124
+
125
+ self.call_records.append(record)
126
+
127
+ # Add to session if exists
128
+ session_id = session_id or self.current_session_id
129
+ if session_id and session_id in self.sessions:
130
+ self.sessions[session_id].add_call(record)
131
+
132
+ return record
133
+
134
+ def export_to_csv(
135
+ self,
136
+ filename: Optional[str] = None,
137
+ session_id: Optional[str] = None,
138
+ ) -> Path:
139
+ """
140
+ Export tracking data to CSV.
141
+
142
+ Args:
143
+ filename: Output filename (auto-generates if None)
144
+ session_id: Session to export (all records if None)
145
+
146
+ Returns:
147
+ Path to created CSV file
148
+ """
149
+ if filename is None:
150
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
151
+ filename = f"tracking_{timestamp}.csv"
152
+
153
+ output_path = self.output_dir / filename
154
+
155
+ # Determine records to export
156
+ if session_id:
157
+ session = self.sessions.get(session_id)
158
+ records = session.calls if session else []
159
+ else:
160
+ records = self.call_records
161
+
162
+ # Write CSV
163
+ with open(output_path, 'w', newline='', encoding='utf-8') as f:
164
+ writer = csv.DictWriter(f, fieldnames=self._get_csv_fieldnames())
165
+ writer.writeheader()
166
+
167
+ for record in records:
168
+ row = self._record_to_csv_row(record)
169
+ writer.writerow(row)
170
+
171
+ return output_path
172
+
173
+ def _get_csv_fieldnames(self) -> List[str]:
174
+ """Get CSV column names."""
175
+ return [
176
+ 'call_id',
177
+ 'agent_name',
178
+ 'method_name',
179
+ 'started_at',
180
+ 'completed_at',
181
+ 'duration_seconds',
182
+ 'success',
183
+ 'input_text',
184
+ 'output_text',
185
+ 'error',
186
+ 'evaluation_score',
187
+ 'evaluation_notes',
188
+ # Metadata fields
189
+ 'model',
190
+ 'temperature',
191
+ 'max_tokens',
192
+ 'parallel_tool_calls',
193
+ 'tool_calls_count',
194
+ 'usage_prompt_tokens',
195
+ 'usage_completion_tokens',
196
+ 'usage_total_tokens',
197
+ ]
198
+
199
+ def _record_to_csv_row(self, record: MethodCallRecordSchema) -> Dict[str, Any]:
200
+ """Convert a record to a CSV row."""
201
+ # Extract metadata
202
+ metadata = record.output_data.metadata or {}
203
+ usage_obj = metadata.get('usage')
204
+
205
+ # Extract usage data - handle both dict and object types
206
+ usage_prompt = ''
207
+ usage_completion = ''
208
+ usage_total = ''
209
+ if usage_obj:
210
+ if isinstance(usage_obj, dict):
211
+ usage_prompt = usage_obj.get('request_tokens', '') or usage_obj.get('prompt_tokens', '')
212
+ usage_completion = usage_obj.get('response_tokens', '') or usage_obj.get('completion_tokens', '')
213
+ usage_total = usage_obj.get('total_tokens', '')
214
+ elif hasattr(usage_obj, 'request_tokens'):
215
+ usage_prompt = getattr(usage_obj, 'request_tokens', '')
216
+ usage_completion = getattr(usage_obj, 'response_tokens', '')
217
+ usage_total = getattr(usage_obj, 'total_tokens', '')
218
+
219
+ # Count tool calls if messages available
220
+ tool_calls_count = 0
221
+ messages = metadata.get('messages', [])
222
+ if messages:
223
+ for msg in messages:
224
+ if hasattr(msg, 'parts'):
225
+ for part in msg.parts:
226
+ if hasattr(part, 'tool_name'):
227
+ tool_calls_count += 1
228
+
229
+ # Safely extract model settings from kwargs
230
+ kwargs = record.input_data.kwargs or {}
231
+ model_settings = kwargs.get('model_settings', {}) or {}
232
+
233
+ return {
234
+ 'call_id': record.call_id,
235
+ 'agent_name': record.agent_name,
236
+ 'method_name': record.method_name,
237
+ 'started_at': record.started_at.isoformat() if record.started_at else '',
238
+ 'completed_at': record.completed_at.isoformat() if record.completed_at else '',
239
+ 'duration_seconds': record.duration_seconds,
240
+ 'success': record.output_data.success,
241
+ 'input_text': record.input_data.input_text or '',
242
+ 'output_text': record.output_data.output_text or '',
243
+ 'error': record.output_data.error or '',
244
+ 'evaluation_score': record.evaluation_score or '',
245
+ 'evaluation_notes': record.evaluation_notes or '',
246
+ # Metadata
247
+ 'model': model_settings.get('model', ''),
248
+ 'temperature': model_settings.get('temperature', ''),
249
+ 'max_tokens': model_settings.get('max_tokens', ''),
250
+ 'parallel_tool_calls': model_settings.get('parallel_tool_calls', ''),
251
+ 'tool_calls_count': tool_calls_count,
252
+ 'usage_prompt_tokens': usage_prompt,
253
+ 'usage_completion_tokens': usage_completion,
254
+ 'usage_total_tokens': usage_total,
255
+ }
256
+
257
+ def export_to_json(
258
+ self,
259
+ filename: Optional[str] = None,
260
+ session_id: Optional[str] = None,
261
+ ) -> Path:
262
+ """
263
+ Export tracking data to JSON.
264
+
265
+ Args:
266
+ filename: Output filename (auto-generates if None)
267
+ session_id: Session to export (all records if None)
268
+
269
+ Returns:
270
+ Path to created JSON file
271
+ """
272
+ if filename is None:
273
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
274
+ filename = f"tracking_{timestamp}.json"
275
+
276
+ output_path = self.output_dir / filename
277
+
278
+ # Determine records to export
279
+ if session_id:
280
+ session = self.sessions.get(session_id)
281
+ if session:
282
+ data = session.dict()
283
+ else:
284
+ data = {"error": "Session not found"}
285
+ else:
286
+ data = {
287
+ "records": [record.dict() for record in self.call_records],
288
+ "total_records": len(self.call_records),
289
+ }
290
+
291
+ # Write JSON
292
+ with open(output_path, 'w', encoding='utf-8') as f:
293
+ json.dump(data, f, indent=2, default=str)
294
+
295
+ return output_path
296
+
297
+ def get_statistics(self, session_id: Optional[str] = None) -> Dict[str, Any]:
298
+ """
299
+ Get tracking statistics.
300
+
301
+ Args:
302
+ session_id: Session to analyze (all records if None)
303
+
304
+ Returns:
305
+ Statistics dictionary
306
+ """
307
+ if session_id:
308
+ session = self.sessions.get(session_id)
309
+ records = session.calls if session else []
310
+ else:
311
+ records = self.call_records
312
+
313
+ if not records:
314
+ return {"total_calls": 0}
315
+
316
+ successful = sum(1 for r in records if r.output_data.success)
317
+ failed = len(records) - successful
318
+ total_duration = sum(r.duration_seconds for r in records)
319
+ avg_duration = total_duration / len(records)
320
+
321
+ return {
322
+ "total_calls": len(records),
323
+ "successful_calls": successful,
324
+ "failed_calls": failed,
325
+ "success_rate": successful / len(records) if records else 0,
326
+ "total_duration_seconds": total_duration,
327
+ "average_duration_seconds": avg_duration,
328
+ "min_duration_seconds": min(r.duration_seconds for r in records),
329
+ "max_duration_seconds": max(r.duration_seconds for r in records),
330
+ }
331
+
332
+
333
+ # Global tracking manager instance
334
+ _global_tracking_manager: Optional[TrackingManager] = None
335
+
336
+
337
+ def get_tracking_manager(output_dir: str = "./agent_tracking") -> TrackingManager:
338
+ """Get or create the global tracking manager."""
339
+ global _global_tracking_manager
340
+ if _global_tracking_manager is None:
341
+ _global_tracking_manager = TrackingManager(output_dir)
342
+ return _global_tracking_manager
343
+
344
+
345
+ def track_method(
346
+ method_name: Optional[str] = None,
347
+ track_args: bool = True,
348
+ track_kwargs: bool = True,
349
+ ) -> Callable[[F], F]:
350
+ """
351
+ Decorator to track method inputs and outputs.
352
+
353
+ Args:
354
+ method_name: Override method name (uses actual name if None)
355
+ track_args: Whether to track positional arguments
356
+ track_kwargs: Whether to track keyword arguments
357
+
358
+ Returns:
359
+ Decorated function
360
+
361
+ Example:
362
+ >>> @track_method()
363
+ >>> async def arun(self, input_data, deps=None):
364
+ >>> return result
365
+ """
366
+ def decorator(func: F) -> F:
367
+ actual_method_name = method_name or func.__name__
368
+
369
+ @wraps(func)
370
+ async def async_wrapper(self, *args, **kwargs):
371
+ # Check if tracking is enabled
372
+ if not getattr(self, '_tracking_enabled', False):
373
+ return await func(self, *args, **kwargs)
374
+
375
+ # Get tracking manager
376
+ output_dir = getattr(self, '_tracking_output_dir', './agent_tracking')
377
+ manager = get_tracking_manager(output_dir)
378
+
379
+ # Generate call ID
380
+ call_id = str(uuid.uuid4())
381
+
382
+ # Extract input text if available
383
+ input_text = None
384
+ if args:
385
+ if isinstance(args[0], str):
386
+ input_text = args[0]
387
+ elif isinstance(args[0], AgentInput):
388
+ input_text = args[0].input_text
389
+
390
+ # Create input record
391
+ input_data = MethodInputSchema(
392
+ timestamp=datetime.now(),
393
+ method_name=actual_method_name,
394
+ agent_name=self.name,
395
+ input_text=input_text,
396
+ args=list(args) if track_args else [],
397
+ kwargs=dict(kwargs) if track_kwargs else {},
398
+ call_id=call_id,
399
+ )
400
+
401
+ # Execute method
402
+ start_time = time.time()
403
+ success = True
404
+ error = None
405
+ result = None
406
+ output_text = None
407
+ metadata = {}
408
+
409
+ try:
410
+ result = await func(self, *args, **kwargs)
411
+
412
+ # Extract output text if available
413
+ if isinstance(result, AgentOutput):
414
+ output_text = result.output_text
415
+ metadata = result.metadata or {}
416
+ elif isinstance(result, str):
417
+ output_text = result
418
+
419
+ except Exception as e:
420
+ success = False
421
+ error = str(e)
422
+ raise
423
+
424
+ finally:
425
+ duration = time.time() - start_time
426
+
427
+ # Create output record
428
+ output_data = MethodOutputSchema(
429
+ timestamp=datetime.now(),
430
+ method_name=actual_method_name,
431
+ agent_name=self.name,
432
+ output_text=output_text,
433
+ result=result if success else None,
434
+ duration_seconds=duration,
435
+ success=success,
436
+ error=error,
437
+ metadata=metadata,
438
+ call_id=call_id,
439
+ )
440
+
441
+ # Record the call
442
+ manager.record_call(
443
+ agent_name=self.name,
444
+ method_name=actual_method_name,
445
+ input_data=input_data,
446
+ output_data=output_data,
447
+ )
448
+
449
+ return result
450
+
451
+ @wraps(func)
452
+ def sync_wrapper(self, *args, **kwargs):
453
+ # Check if tracking is enabled
454
+ if not getattr(self, '_tracking_enabled', False):
455
+ return func(self, *args, **kwargs)
456
+
457
+ # Get tracking manager
458
+ output_dir = getattr(self, '_tracking_output_dir', './agent_tracking')
459
+ manager = get_tracking_manager(output_dir)
460
+
461
+ # Generate call ID
462
+ call_id = str(uuid.uuid4())
463
+
464
+ # Extract input text if available
465
+ input_text = None
466
+ if args:
467
+ if isinstance(args[0], str):
468
+ input_text = args[0]
469
+ elif isinstance(args[0], AgentInput):
470
+ input_text = args[0].input_text
471
+
472
+ # Create input record
473
+ input_data = MethodInputSchema(
474
+ timestamp=datetime.now(),
475
+ method_name=actual_method_name,
476
+ agent_name=self.name,
477
+ input_text=input_text,
478
+ args=list(args) if track_args else [],
479
+ kwargs=dict(kwargs) if track_kwargs else {},
480
+ call_id=call_id,
481
+ )
482
+
483
+ # Execute method
484
+ start_time = time.time()
485
+ success = True
486
+ error = None
487
+ result = None
488
+ output_text = None
489
+ metadata = {}
490
+
491
+ try:
492
+ result = func(self, *args, **kwargs)
493
+
494
+ # Extract output text if available
495
+ if isinstance(result, AgentOutput):
496
+ output_text = result.output_text
497
+ metadata = result.metadata or {}
498
+ elif isinstance(result, str):
499
+ output_text = result
500
+
501
+ except Exception as e:
502
+ success = False
503
+ error = str(e)
504
+ raise
505
+
506
+ finally:
507
+ duration = time.time() - start_time
508
+
509
+ # Create output record
510
+ output_data = MethodOutputSchema(
511
+ timestamp=datetime.now(),
512
+ method_name=actual_method_name,
513
+ agent_name=self.name,
514
+ output_text=output_text,
515
+ result=result if success else None,
516
+ duration_seconds=duration,
517
+ success=success,
518
+ error=error,
519
+ metadata=metadata,
520
+ call_id=call_id,
521
+ )
522
+
523
+ # Record the call
524
+ manager.record_call(
525
+ agent_name=self.name,
526
+ method_name=actual_method_name,
527
+ input_data=input_data,
528
+ output_data=output_data,
529
+ )
530
+
531
+ return result
532
+
533
+ # Return appropriate wrapper based on whether function is async
534
+ if asyncio.iscoroutinefunction(func):
535
+ return async_wrapper # type: ignore
536
+ else:
537
+ return sync_wrapper # type: ignore
538
+
539
+ return decorator
540
+
541
+
542
+ class TrackingMixin:
543
+ """
544
+ Mixin class to add tracking capabilities to agents.
545
+
546
+ Usage:
547
+ >>> class MyAgent(TrackingMixin, BaseAgent):
548
+ >>> pass
549
+ """
550
+
551
+ def __init__(self, *args, **kwargs):
552
+ # Extract tracking-specific kwargs before passing to super
553
+ self._tracking_enabled = kwargs.pop('enable_tracking', False)
554
+ self._tracking_output_dir = kwargs.pop('tracking_output_dir', './agent_tracking')
555
+ self._tracking_manager: Optional[TrackingManager] = None
556
+ super().__init__(*args, **kwargs)
557
+
558
+ def enable_tracking(self, output_dir: Optional[str] = None) -> None:
559
+ """Enable tracking for this agent."""
560
+ self._tracking_enabled = True
561
+ if output_dir:
562
+ self._tracking_output_dir = output_dir
563
+
564
+ def disable_tracking(self) -> None:
565
+ """Disable tracking for this agent."""
566
+ self._tracking_enabled = False
567
+
568
+ def get_tracking_manager(self) -> TrackingManager:
569
+ """Get the tracking manager for this agent."""
570
+ if self._tracking_manager is None:
571
+ self._tracking_manager = get_tracking_manager(self._tracking_output_dir)
572
+ return self._tracking_manager
573
+
574
+ def export_tracking_data(
575
+ self,
576
+ format: str = 'csv',
577
+ filename: Optional[str] = None,
578
+ ) -> Path:
579
+ """
580
+ Export tracking data.
581
+
582
+ Args:
583
+ format: 'csv' or 'json'
584
+ filename: Output filename (auto-generates if None)
585
+
586
+ Returns:
587
+ Path to exported file
588
+ """
589
+ manager = self.get_tracking_manager()
590
+
591
+ if format == 'csv':
592
+ return manager.export_to_csv(filename)
593
+ elif format == 'json':
594
+ return manager.export_to_json(filename)
595
+ else:
596
+ raise ValueError(f"Unsupported format: {format}")
597
+
598
+ def get_tracking_statistics(self) -> Dict[str, Any]:
599
+ """Get tracking statistics for this agent."""
600
+ manager = self.get_tracking_manager()
601
+ return manager.get_statistics()
602
+
@@ -0,0 +1,55 @@
1
+ import mimetypes
2
+ import uuid
3
+ from typing import Any
4
+ from typing import Dict
5
+ from typing import List
6
+ from typing import Optional
7
+
8
+
9
+ class VSFile:
10
+ """
11
+ A data source to be processed. Its nodes will become entries in the VectorStore.
12
+ """
13
+
14
+ def __init__(self, file_path: str) -> None:
15
+ self.uuid: str = uuid.uuid4()
16
+ self.file_path: str = file_path
17
+ self.file_name: str = file_path.split("/")[-1]
18
+ self.mime_type, _ = mimetypes.guess_type(self.file_path)
19
+ self.nodes: Optional[Node] = []
20
+ self.processed: bool = (
21
+ False # whether the nodes of this file have been processed
22
+ )
23
+
24
+
25
+ class NodeMetadata:
26
+ def __init__(
27
+ self, source_file_uuid: str, position: int, custom: dict = None
28
+ ) -> None:
29
+ self.node_id: Optional[int] = None
30
+ self.source_file_uuid: str = source_file_uuid
31
+ self.position: Optional[int] = position # page_number
32
+ self.custom: Optional[Dict] = custom
33
+
34
+ def __str__(self) -> str:
35
+ custom_str = ", ".join(
36
+ f"{key}: {value}" for key, value in (self.custom or {}).items()
37
+ )
38
+ return (
39
+ f"NodeMetadata(node_id={self.node_id}, source_file_uuid='{self.source_file_uuid}', "
40
+ f"position={self.position}, custom={{ {custom_str} }})"
41
+ )
42
+
43
+
44
+ class Node:
45
+ """
46
+ A node with content and associated metadata.
47
+ """
48
+
49
+ def __init__(self, content: str, metadata: NodeMetadata) -> None:
50
+ self.content: str = content
51
+ self.metadata: Optional[NodeMetadata] = metadata
52
+ self.embedding: Optional[Any] = None
53
+
54
+ def __str__(self) -> str:
55
+ return f"Node(content='{self.content[:30]}...', metadata={self.metadata})"
@@ -0,0 +1,16 @@
1
+ """
2
+ AI utilities for the Rakam System Core.
3
+ """
4
+
5
+ from . import s3
6
+ from . import logging
7
+ from . import metrics
8
+ from . import tracing
9
+
10
+ __all__ = [
11
+ "s3",
12
+ "logging",
13
+ "metrics",
14
+ "tracing",
15
+ ]
16
+