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,334 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from typing import Dict, Any, Optional, List
5
+
6
+ from rakam_systems_core.ai_utils import logging
7
+ from ..base import BaseComponent
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class MCPServer(BaseComponent):
13
+ """
14
+ Model Context Protocol (MCP) Server.
15
+
16
+ A message-based component registry that routes messages between components.
17
+ Supports both synchronous and asynchronous operations.
18
+
19
+ Features:
20
+ - Component registration and discovery
21
+ - Message routing with sender/receiver pattern
22
+ - Support for both sync and async message handlers
23
+ - Automatic argument extraction from messages
24
+ - Error handling and logging
25
+
26
+ Example:
27
+ >>> server = MCPServer(name="my_mcp_server")
28
+ >>> server.setup()
29
+ >>>
30
+ >>> # Register components
31
+ >>> search_tool = SearchTool(name="search")
32
+ >>> server.register_component(search_tool)
33
+ >>>
34
+ >>> # Send messages
35
+ >>> result = server.send_message(
36
+ ... sender="client",
37
+ ... receiver="search",
38
+ ... message={'arguments': {'query': 'test'}}
39
+ ... )
40
+ >>>
41
+ >>> # Async version
42
+ >>> result = await server.asend_message(
43
+ ... sender="client",
44
+ ... receiver="search",
45
+ ... message={'arguments': {'query': 'test'}}
46
+ ... )
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ name: str = "mcp_server",
52
+ config: Optional[Dict[str, Any]] = None,
53
+ enable_logging: bool = True
54
+ ) -> None:
55
+ """
56
+ Initialize MCP Server.
57
+
58
+ Args:
59
+ name: Name of the MCP server
60
+ config: Optional configuration dictionary
61
+ enable_logging: Whether to enable detailed logging
62
+ """
63
+ super().__init__(name, config)
64
+ self._registry: Dict[str, BaseComponent] = {}
65
+ self._enable_logging = enable_logging
66
+
67
+ if self._enable_logging:
68
+ logger.debug(f"Initialized MCPServer: {name}")
69
+
70
+ def register_component(self, component: BaseComponent) -> None:
71
+ """
72
+ Register a component with the MCP server.
73
+
74
+ Args:
75
+ component: Component to register
76
+
77
+ Raises:
78
+ ValueError: If component with same name already registered
79
+ """
80
+ if component.name in self._registry:
81
+ logger.warning(
82
+ f"Component '{component.name}' already registered, overwriting"
83
+ )
84
+
85
+ self._registry[component.name] = component
86
+
87
+ if self._enable_logging:
88
+ logger.debug(f"Registered component: {component.name}")
89
+
90
+ def unregister_component(self, component_name: str) -> bool:
91
+ """
92
+ Unregister a component from the MCP server.
93
+
94
+ Args:
95
+ component_name: Name of component to unregister
96
+
97
+ Returns:
98
+ True if component was unregistered, False if not found
99
+ """
100
+ if component_name in self._registry:
101
+ del self._registry[component_name]
102
+ if self._enable_logging:
103
+ logger.debug(f"Unregistered component: {component_name}")
104
+ return True
105
+ return False
106
+
107
+ def get_component(self, component_name: str) -> Optional[BaseComponent]:
108
+ """
109
+ Get a registered component by name.
110
+
111
+ Args:
112
+ component_name: Name of the component
113
+
114
+ Returns:
115
+ The component if found, None otherwise
116
+ """
117
+ return self._registry.get(component_name)
118
+
119
+ def list_components(self) -> List[str]:
120
+ """
121
+ List all registered component names.
122
+
123
+ Returns:
124
+ Sorted list of component names
125
+ """
126
+ return sorted(self._registry.keys())
127
+
128
+ def has_component(self, component_name: str) -> bool:
129
+ """
130
+ Check if a component is registered.
131
+
132
+ Args:
133
+ component_name: Name of the component
134
+
135
+ Returns:
136
+ True if component is registered, False otherwise
137
+ """
138
+ return component_name in self._registry
139
+
140
+ def send_message(
141
+ self,
142
+ sender: str,
143
+ receiver: str,
144
+ message: Dict[str, Any]
145
+ ) -> Any:
146
+ """
147
+ Send a message to a registered component (synchronous).
148
+
149
+ Args:
150
+ sender: Name of the message sender
151
+ receiver: Name of the target component
152
+ message: Message dictionary containing action and arguments
153
+
154
+ Returns:
155
+ Result from the target component
156
+
157
+ Raises:
158
+ KeyError: If receiver component is not registered
159
+ Exception: Any exception raised by the component
160
+
161
+ Example:
162
+ >>> result = server.send_message(
163
+ ... sender="client",
164
+ ... receiver="search_tool",
165
+ ... message={
166
+ ... 'action': 'invoke_tool',
167
+ ... 'arguments': {'query': 'test', 'top_k': 5}
168
+ ... }
169
+ ... )
170
+ """
171
+ if receiver not in self._registry:
172
+ error_msg = f"Receiver '{receiver}' not registered. Available: {self.list_components()}"
173
+ logger.error(error_msg)
174
+ raise KeyError(error_msg)
175
+
176
+ target = self._registry[receiver]
177
+
178
+ if self._enable_logging:
179
+ logger.debug(
180
+ f"Routing message from '{sender}' to '{receiver}': "
181
+ f"{message.get('action', 'unknown')}"
182
+ )
183
+
184
+ try:
185
+ # Check for custom message handler
186
+ handler = getattr(target, "handle_message", None)
187
+ if callable(handler):
188
+ return handler(sender=sender, message=message)
189
+
190
+ # Default: extract arguments and call run()
191
+ if isinstance(message, dict) and 'arguments' in message:
192
+ arguments = message['arguments']
193
+ if isinstance(arguments, dict):
194
+ return target.run(**arguments)
195
+ else:
196
+ return target.run(arguments)
197
+
198
+ return target.run(message)
199
+
200
+ except Exception as e:
201
+ logger.error(
202
+ f"Error routing message to '{receiver}': {str(e)}",
203
+ exc_info=True
204
+ )
205
+ raise
206
+
207
+ async def asend_message(
208
+ self,
209
+ sender: str,
210
+ receiver: str,
211
+ message: Dict[str, Any]
212
+ ) -> Any:
213
+ """
214
+ Send a message to a registered component (asynchronous).
215
+
216
+ Supports both async and sync components. If the component has
217
+ async handlers, they will be awaited. Otherwise, sync handlers
218
+ will be executed in the event loop.
219
+
220
+ Args:
221
+ sender: Name of the message sender
222
+ receiver: Name of the target component
223
+ message: Message dictionary containing action and arguments
224
+
225
+ Returns:
226
+ Result from the target component
227
+
228
+ Raises:
229
+ KeyError: If receiver component is not registered
230
+ Exception: Any exception raised by the component
231
+
232
+ Example:
233
+ >>> result = await server.asend_message(
234
+ ... sender="client",
235
+ ... receiver="search_tool",
236
+ ... message={
237
+ ... 'action': 'invoke_tool',
238
+ ... 'arguments': {'query': 'test', 'top_k': 5}
239
+ ... }
240
+ ... )
241
+ """
242
+ if receiver not in self._registry:
243
+ error_msg = f"Receiver '{receiver}' not registered. Available: {self.list_components()}"
244
+ logger.error(error_msg)
245
+ raise KeyError(error_msg)
246
+
247
+ target = self._registry[receiver]
248
+
249
+ if self._enable_logging:
250
+ logger.debug(
251
+ f"Routing async message from '{sender}' to '{receiver}': "
252
+ f"{message.get('action', 'unknown')}"
253
+ )
254
+
255
+ try:
256
+ # Check for async message handler
257
+ handler = getattr(target, "handle_message", None)
258
+ if callable(handler):
259
+ if asyncio.iscoroutinefunction(handler):
260
+ return await handler(sender=sender, message=message)
261
+ else:
262
+ return handler(sender=sender, message=message)
263
+
264
+ # Check for async run method
265
+ run_method = getattr(target, "run", None)
266
+ if run_method is None:
267
+ raise AttributeError(
268
+ f"Component '{receiver}' has no 'run' method")
269
+
270
+ # Extract arguments
271
+ if isinstance(message, dict) and 'arguments' in message:
272
+ arguments = message['arguments']
273
+
274
+ # Call async or sync run method
275
+ if asyncio.iscoroutinefunction(run_method):
276
+ if isinstance(arguments, dict):
277
+ return await run_method(**arguments)
278
+ else:
279
+ return await run_method(arguments)
280
+ else:
281
+ # Sync method - call directly
282
+ if isinstance(arguments, dict):
283
+ return run_method(**arguments)
284
+ else:
285
+ return run_method(arguments)
286
+
287
+ # No arguments, just call run
288
+ if asyncio.iscoroutinefunction(run_method):
289
+ return await run_method(message)
290
+ else:
291
+ return run_method(message)
292
+
293
+ except Exception as e:
294
+ logger.error(
295
+ f"Error routing async message to '{receiver}': {str(e)}",
296
+ exc_info=True
297
+ )
298
+ raise
299
+
300
+ def run(self, *args, **kwargs) -> List[str]:
301
+ """
302
+ Return list of registered components.
303
+
304
+ This is the BaseComponent interface implementation.
305
+
306
+ Returns:
307
+ Sorted list of component names
308
+ """
309
+ return self.list_components()
310
+
311
+ def get_stats(self) -> Dict[str, Any]:
312
+ """
313
+ Get server statistics.
314
+
315
+ Returns:
316
+ Dictionary with server stats
317
+ """
318
+ return {
319
+ 'name': self.name,
320
+ 'component_count': len(self._registry),
321
+ 'components': self.list_components(),
322
+ 'logging_enabled': self._enable_logging
323
+ }
324
+
325
+ def __repr__(self) -> str:
326
+ return f"MCPServer(name='{self.name}', components={len(self._registry)})"
327
+
328
+ def __len__(self) -> int:
329
+ """Return number of registered components."""
330
+ return len(self._registry)
331
+
332
+ def __contains__(self, component_name: str) -> bool:
333
+ """Check if component is registered."""
334
+ return component_name in self._registry