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.
- rakam_systems_core/__init__.py +41 -0
- rakam_systems_core/ai_core/__init__.py +68 -0
- rakam_systems_core/ai_core/base.py +142 -0
- rakam_systems_core/ai_core/config.py +12 -0
- rakam_systems_core/ai_core/config_loader.py +580 -0
- rakam_systems_core/ai_core/config_schema.py +395 -0
- rakam_systems_core/ai_core/interfaces/__init__.py +30 -0
- rakam_systems_core/ai_core/interfaces/agent.py +83 -0
- rakam_systems_core/ai_core/interfaces/chat_history.py +122 -0
- rakam_systems_core/ai_core/interfaces/chunker.py +11 -0
- rakam_systems_core/ai_core/interfaces/embedding_model.py +10 -0
- rakam_systems_core/ai_core/interfaces/indexer.py +10 -0
- rakam_systems_core/ai_core/interfaces/llm_gateway.py +139 -0
- rakam_systems_core/ai_core/interfaces/loader.py +86 -0
- rakam_systems_core/ai_core/interfaces/reranker.py +10 -0
- rakam_systems_core/ai_core/interfaces/retriever.py +11 -0
- rakam_systems_core/ai_core/interfaces/tool.py +162 -0
- rakam_systems_core/ai_core/interfaces/tool_invoker.py +260 -0
- rakam_systems_core/ai_core/interfaces/tool_loader.py +374 -0
- rakam_systems_core/ai_core/interfaces/tool_registry.py +287 -0
- rakam_systems_core/ai_core/interfaces/vectorstore.py +37 -0
- rakam_systems_core/ai_core/mcp/README.md +545 -0
- rakam_systems_core/ai_core/mcp/__init__.py +0 -0
- rakam_systems_core/ai_core/mcp/mcp_server.py +334 -0
- rakam_systems_core/ai_core/tracking.py +602 -0
- rakam_systems_core/ai_core/vs_core.py +55 -0
- rakam_systems_core/ai_utils/__init__.py +16 -0
- rakam_systems_core/ai_utils/logging.py +126 -0
- rakam_systems_core/ai_utils/metrics.py +10 -0
- rakam_systems_core/ai_utils/s3.py +480 -0
- rakam_systems_core/ai_utils/tracing.py +5 -0
- rakam_systems_core-0.1.1rc7.dist-info/METADATA +162 -0
- rakam_systems_core-0.1.1rc7.dist-info/RECORD +34 -0
- 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
|