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,374 @@
1
+ """
2
+ Tool Loader for automatic tool discovery and registration from configuration.
3
+ Supports loading tools from YAML/JSON config files and Python modules.
4
+ """
5
+ from __future__ import annotations
6
+ from typing import Any, Dict, List, Optional, Callable
7
+ import importlib
8
+ import inspect
9
+ import yaml
10
+ import json
11
+ from pathlib import Path
12
+ from .tool_registry import ToolRegistry
13
+ from .tool import ToolComponent
14
+
15
+
16
+ class ToolLoadError(Exception):
17
+ """Base exception for tool loading errors."""
18
+ pass
19
+
20
+
21
+ class ToolLoader:
22
+ """
23
+ Load and register tools from configuration files.
24
+
25
+ Supports:
26
+ - YAML and JSON configuration files
27
+ - Auto-discovery of tool functions from Python modules
28
+ - Dynamic import and registration
29
+ - Validation of tool definitions
30
+
31
+ Configuration Format (YAML):
32
+ tools:
33
+ - name: calculate
34
+ type: direct
35
+ module: my_tools.math_tools
36
+ function: calculate
37
+ description: Add two numbers
38
+ category: math
39
+ tags: [arithmetic, basic]
40
+ schema:
41
+ type: object
42
+ properties:
43
+ x:
44
+ type: integer
45
+ description: First number
46
+ y:
47
+ type: integer
48
+ description: Second number
49
+ required: [x, y]
50
+
51
+ - name: web_search
52
+ type: mcp
53
+ mcp_server: search_server
54
+ mcp_tool_name: search
55
+ description: Search the web
56
+ category: search
57
+ tags: [web, external]
58
+
59
+ Example:
60
+ >>> loader = ToolLoader(registry)
61
+ >>> loader.load_from_yaml("tools.yaml")
62
+ >>> print(f"Loaded {len(registry)} tools")
63
+ """
64
+
65
+ def __init__(self, registry: ToolRegistry):
66
+ """
67
+ Initialize the ToolLoader.
68
+
69
+ Args:
70
+ registry: ToolRegistry instance to register tools into
71
+ """
72
+ self.registry = registry
73
+
74
+ def load_from_yaml(self, file_path: str) -> int:
75
+ """
76
+ Load tools from a YAML configuration file.
77
+
78
+ Args:
79
+ file_path: Path to YAML configuration file
80
+
81
+ Returns:
82
+ Number of tools loaded
83
+
84
+ Raises:
85
+ ToolLoadError: If file cannot be loaded or parsed
86
+ """
87
+ try:
88
+ with open(file_path, 'r') as f:
89
+ config = yaml.safe_load(f)
90
+ return self.load_from_config(config)
91
+ except FileNotFoundError:
92
+ raise ToolLoadError(f"Configuration file not found: {file_path}")
93
+ except yaml.YAMLError as e:
94
+ raise ToolLoadError(f"Failed to parse YAML file: {str(e)}") from e
95
+
96
+ def load_from_json(self, file_path: str) -> int:
97
+ """
98
+ Load tools from a JSON configuration file.
99
+
100
+ Args:
101
+ file_path: Path to JSON configuration file
102
+
103
+ Returns:
104
+ Number of tools loaded
105
+
106
+ Raises:
107
+ ToolLoadError: If file cannot be loaded or parsed
108
+ """
109
+ try:
110
+ with open(file_path, 'r') as f:
111
+ config = json.load(f)
112
+ return self.load_from_config(config)
113
+ except FileNotFoundError:
114
+ raise ToolLoadError(f"Configuration file not found: {file_path}")
115
+ except json.JSONDecodeError as e:
116
+ raise ToolLoadError(f"Failed to parse JSON file: {str(e)}") from e
117
+
118
+ def load_from_config(self, config: Dict[str, Any]) -> int:
119
+ """
120
+ Load tools from a configuration dictionary.
121
+
122
+ Args:
123
+ config: Configuration dictionary
124
+
125
+ Returns:
126
+ Number of tools loaded
127
+
128
+ Raises:
129
+ ToolLoadError: If configuration is invalid
130
+ """
131
+ if not isinstance(config, dict):
132
+ raise ToolLoadError("Configuration must be a dictionary")
133
+
134
+ tools_config = config.get('tools', [])
135
+ if not isinstance(tools_config, list):
136
+ raise ToolLoadError("'tools' must be a list")
137
+
138
+ loaded_count = 0
139
+ errors = []
140
+
141
+ for tool_config in tools_config:
142
+ try:
143
+ self._load_tool(tool_config)
144
+ loaded_count += 1
145
+ except Exception as e:
146
+ tool_name = tool_config.get('name', 'unknown')
147
+ errors.append(f"Failed to load tool '{tool_name}': {str(e)}")
148
+
149
+ if errors:
150
+ error_msg = "\n".join(errors)
151
+ raise ToolLoadError(f"Errors loading tools:\n{error_msg}")
152
+
153
+ return loaded_count
154
+
155
+ def _load_tool(self, tool_config: Dict[str, Any]) -> None:
156
+ """Load a single tool from configuration."""
157
+ # Validate required fields
158
+ if 'name' not in tool_config:
159
+ raise ToolLoadError("Tool configuration must have 'name' field")
160
+
161
+ name = tool_config['name']
162
+ tool_type = tool_config.get('type', 'direct')
163
+ description = tool_config.get('description', f"Tool: {name}")
164
+ category = tool_config.get('category')
165
+ tags = tool_config.get('tags', [])
166
+
167
+ if tool_type == 'direct':
168
+ self._load_direct_tool(name, tool_config, description, category, tags)
169
+ elif tool_type == 'mcp':
170
+ self._load_mcp_tool(name, tool_config, description, category, tags)
171
+ else:
172
+ raise ToolLoadError(f"Unknown tool type: {tool_type}")
173
+
174
+ def _load_direct_tool(
175
+ self,
176
+ name: str,
177
+ config: Dict[str, Any],
178
+ description: str,
179
+ category: Optional[str],
180
+ tags: List[str]
181
+ ) -> None:
182
+ """Load a direct tool from configuration."""
183
+ # Get module and function name
184
+ module_name = config.get('module')
185
+ function_name = config.get('function')
186
+
187
+ if not module_name or not function_name:
188
+ raise ToolLoadError(
189
+ f"Direct tool '{name}' must specify 'module' and 'function'"
190
+ )
191
+
192
+ # Import the function
193
+ try:
194
+ module = importlib.import_module(module_name)
195
+ function = getattr(module, function_name)
196
+ except ImportError as e:
197
+ raise ToolLoadError(
198
+ f"Failed to import module '{module_name}': {str(e)}"
199
+ ) from e
200
+ except AttributeError as e:
201
+ raise ToolLoadError(
202
+ f"Function '{function_name}' not found in module '{module_name}'"
203
+ ) from e
204
+
205
+ if not callable(function):
206
+ raise ToolLoadError(
207
+ f"'{module_name}.{function_name}' is not callable"
208
+ )
209
+
210
+ # Get or generate JSON schema
211
+ json_schema = config.get('schema')
212
+ if json_schema is None:
213
+ # Try to auto-generate schema from function signature
214
+ json_schema = self._generate_schema_from_function(function)
215
+
216
+ # Check if function takes context
217
+ takes_ctx = config.get('takes_ctx', False)
218
+
219
+ # Register the tool
220
+ self.registry.register_direct_tool(
221
+ name=name,
222
+ function=function,
223
+ description=description,
224
+ json_schema=json_schema,
225
+ takes_ctx=takes_ctx,
226
+ category=category,
227
+ tags=tags,
228
+ )
229
+
230
+ def _load_mcp_tool(
231
+ self,
232
+ name: str,
233
+ config: Dict[str, Any],
234
+ description: str,
235
+ category: Optional[str],
236
+ tags: List[str]
237
+ ) -> None:
238
+ """Load an MCP tool from configuration."""
239
+ mcp_server = config.get('mcp_server')
240
+ mcp_tool_name = config.get('mcp_tool_name', name)
241
+
242
+ if not mcp_server:
243
+ raise ToolLoadError(f"MCP tool '{name}' must specify 'mcp_server'")
244
+
245
+ # Register the MCP tool
246
+ self.registry.register_mcp_tool(
247
+ name=name,
248
+ mcp_server=mcp_server,
249
+ mcp_tool_name=mcp_tool_name,
250
+ description=description,
251
+ category=category,
252
+ tags=tags,
253
+ )
254
+
255
+ def _generate_schema_from_function(self, function: Callable) -> Dict[str, Any]:
256
+ """
257
+ Auto-generate a JSON schema from function signature.
258
+
259
+ Note: This is a basic implementation. For production use,
260
+ consider using pydantic or similar for better type inference.
261
+ """
262
+ sig = inspect.signature(function)
263
+ properties = {}
264
+ required = []
265
+
266
+ for param_name, param in sig.parameters.items():
267
+ # Skip self, cls, and context parameters
268
+ if param_name in ('self', 'cls', 'ctx', 'context'):
269
+ continue
270
+
271
+ param_schema = {'type': 'string'} # Default type
272
+
273
+ # Try to infer type from annotation
274
+ if param.annotation != inspect.Parameter.empty:
275
+ param_schema['type'] = self._python_type_to_json_type(param.annotation)
276
+
277
+ properties[param_name] = param_schema
278
+
279
+ # Check if parameter is required
280
+ if param.default == inspect.Parameter.empty:
281
+ required.append(param_name)
282
+
283
+ schema = {
284
+ 'type': 'object',
285
+ 'properties': properties,
286
+ 'additionalProperties': False,
287
+ }
288
+
289
+ if required:
290
+ schema['required'] = required
291
+
292
+ return schema
293
+
294
+ def _python_type_to_json_type(self, python_type: Any) -> str:
295
+ """Convert Python type annotation to JSON schema type."""
296
+ type_mapping = {
297
+ int: 'integer',
298
+ float: 'number',
299
+ str: 'string',
300
+ bool: 'boolean',
301
+ list: 'array',
302
+ dict: 'object',
303
+ }
304
+
305
+ # Handle Optional types
306
+ if hasattr(python_type, '__origin__'):
307
+ origin = python_type.__origin__
308
+ if origin is list:
309
+ return 'array'
310
+ elif origin is dict:
311
+ return 'object'
312
+
313
+ # Direct type mapping
314
+ return type_mapping.get(python_type, 'string')
315
+
316
+ def load_from_directory(
317
+ self,
318
+ directory: str,
319
+ pattern: str = "*.yaml",
320
+ recursive: bool = False
321
+ ) -> int:
322
+ """
323
+ Load tools from all configuration files in a directory.
324
+
325
+ Args:
326
+ directory: Path to directory containing configuration files
327
+ pattern: Glob pattern for configuration files (default: "*.yaml")
328
+ recursive: Whether to search recursively (default: False)
329
+
330
+ Returns:
331
+ Total number of tools loaded
332
+
333
+ Raises:
334
+ ToolLoadError: If directory not found or files cannot be loaded
335
+ """
336
+ path = Path(directory)
337
+ if not path.exists():
338
+ raise ToolLoadError(f"Directory not found: {directory}")
339
+
340
+ if not path.is_dir():
341
+ raise ToolLoadError(f"Not a directory: {directory}")
342
+
343
+ # Find all matching files
344
+ if recursive:
345
+ files = list(path.rglob(pattern))
346
+ else:
347
+ files = list(path.glob(pattern))
348
+
349
+ if not files:
350
+ raise ToolLoadError(
351
+ f"No configuration files found matching '{pattern}' in {directory}"
352
+ )
353
+
354
+ total_loaded = 0
355
+ errors = []
356
+
357
+ for file_path in files:
358
+ try:
359
+ if file_path.suffix in ['.yaml', '.yml']:
360
+ count = self.load_from_yaml(str(file_path))
361
+ elif file_path.suffix == '.json':
362
+ count = self.load_from_json(str(file_path))
363
+ else:
364
+ continue
365
+ total_loaded += count
366
+ except Exception as e:
367
+ errors.append(f"Error loading {file_path}: {str(e)}")
368
+
369
+ if errors:
370
+ error_msg = "\n".join(errors)
371
+ raise ToolLoadError(f"Errors loading tools:\n{error_msg}")
372
+
373
+ return total_loaded
374
+
@@ -0,0 +1,287 @@
1
+ """
2
+ Tool Registry for managing and discovering tools across the system.
3
+ Provides a centralized registry for both direct tool invocation and MCP-based tools.
4
+ """
5
+ from __future__ import annotations
6
+ from typing import Dict, List, Optional, Any, Callable, Union
7
+ from abc import ABC, abstractmethod
8
+ import inspect
9
+ from .tool import ToolComponent
10
+
11
+
12
+ class ToolMode:
13
+ """Constants for tool invocation modes."""
14
+ DIRECT = "direct"
15
+ MCP = "mcp"
16
+
17
+
18
+ class ToolMetadata:
19
+ """Metadata for a registered tool."""
20
+
21
+ def __init__(
22
+ self,
23
+ name: str,
24
+ description: str,
25
+ mode: str,
26
+ tool_instance: Optional[ToolComponent] = None,
27
+ mcp_server: Optional[str] = None,
28
+ mcp_tool_name: Optional[str] = None,
29
+ category: Optional[str] = None,
30
+ tags: Optional[List[str]] = None,
31
+ ):
32
+ self.name = name
33
+ self.description = description
34
+ self.mode = mode
35
+ self.tool_instance = tool_instance
36
+ self.mcp_server = mcp_server
37
+ self.mcp_tool_name = mcp_tool_name
38
+ self.category = category or "general"
39
+ self.tags = tags or []
40
+
41
+ def to_dict(self) -> Dict[str, Any]:
42
+ """Convert metadata to dictionary format."""
43
+ return {
44
+ "name": self.name,
45
+ "description": self.description,
46
+ "mode": self.mode,
47
+ "mcp_server": self.mcp_server,
48
+ "mcp_tool_name": self.mcp_tool_name,
49
+ "category": self.category,
50
+ "tags": self.tags,
51
+ }
52
+
53
+
54
+ class ToolRegistry:
55
+ """
56
+ Central registry for managing tools in the system.
57
+
58
+ Supports:
59
+ - Direct tool registration (callable functions or Tool instances)
60
+ - MCP-based tool registration (tools exposed via MCP servers)
61
+ - Tool discovery and lookup by name, category, or tags
62
+ - Automatic tool conversion for different agent frameworks
63
+
64
+ Example:
65
+ >>> registry = ToolRegistry()
66
+ >>>
67
+ >>> # Register a direct tool
68
+ >>> def calculate(x: int, y: int) -> int:
69
+ ... return x + y
70
+ >>> registry.register_direct_tool(
71
+ ... name="calculate",
72
+ ... function=calculate,
73
+ ... description="Add two numbers",
74
+ ... json_schema={...}
75
+ ... )
76
+ >>>
77
+ >>> # Register an MCP tool
78
+ >>> registry.register_mcp_tool(
79
+ ... name="search",
80
+ ... mcp_server="search_server",
81
+ ... mcp_tool_name="web_search",
82
+ ... description="Search the web"
83
+ ... )
84
+ >>>
85
+ >>> # Get all tools
86
+ >>> tools = registry.get_all_tools()
87
+ """
88
+
89
+ def __init__(self):
90
+ self._tools: Dict[str, ToolMetadata] = {}
91
+ self._categories: Dict[str, List[str]] = {}
92
+ self._tags: Dict[str, List[str]] = {}
93
+
94
+ def register_direct_tool(
95
+ self,
96
+ name: str,
97
+ function: Callable[..., Any],
98
+ description: str,
99
+ json_schema: Dict[str, Any],
100
+ takes_ctx: bool = False,
101
+ category: Optional[str] = None,
102
+ tags: Optional[List[str]] = None,
103
+ ) -> None:
104
+ """
105
+ Register a tool for direct invocation.
106
+
107
+ Args:
108
+ name: Unique name for the tool
109
+ function: The callable function
110
+ description: Human-readable description
111
+ json_schema: JSON schema for the tool parameters
112
+ takes_ctx: Whether the tool takes context as first argument
113
+ category: Optional category for organizing tools
114
+ tags: Optional tags for filtering tools
115
+ """
116
+ # Create a ToolComponent from the function
117
+ tool = ToolComponent.from_function(
118
+ function=function,
119
+ name=name,
120
+ description=description,
121
+ json_schema=json_schema,
122
+ takes_ctx=takes_ctx,
123
+ )
124
+
125
+ metadata = ToolMetadata(
126
+ name=name,
127
+ description=description,
128
+ mode=ToolMode.DIRECT,
129
+ tool_instance=tool,
130
+ category=category,
131
+ tags=tags,
132
+ )
133
+
134
+ self._register_metadata(metadata)
135
+
136
+ def register_tool_instance(
137
+ self,
138
+ tool: ToolComponent,
139
+ category: Optional[str] = None,
140
+ tags: Optional[List[str]] = None,
141
+ ) -> None:
142
+ """
143
+ Register an existing ToolComponent instance.
144
+
145
+ Args:
146
+ tool: ToolComponent instance
147
+ category: Optional category for organizing tools
148
+ tags: Optional tags for filtering tools
149
+ """
150
+ if not isinstance(tool, ToolComponent):
151
+ raise ValueError(f"Unsupported tool type: {type(tool)}. Expected ToolComponent.")
152
+
153
+ metadata = ToolMetadata(
154
+ name=tool.name,
155
+ description=tool.description,
156
+ mode=ToolMode.DIRECT,
157
+ tool_instance=tool,
158
+ category=category,
159
+ tags=tags,
160
+ )
161
+
162
+ self._register_metadata(metadata)
163
+
164
+ def register_mcp_tool(
165
+ self,
166
+ name: str,
167
+ mcp_server: str,
168
+ mcp_tool_name: str,
169
+ description: str,
170
+ category: Optional[str] = None,
171
+ tags: Optional[List[str]] = None,
172
+ ) -> None:
173
+ """
174
+ Register a tool that will be invoked via MCP server.
175
+
176
+ Args:
177
+ name: Unique name for the tool in this registry
178
+ mcp_server: Name of the MCP server hosting the tool
179
+ mcp_tool_name: Name of the tool on the MCP server
180
+ description: Human-readable description
181
+ category: Optional category for organizing tools
182
+ tags: Optional tags for filtering tools
183
+ """
184
+ metadata = ToolMetadata(
185
+ name=name,
186
+ description=description,
187
+ mode=ToolMode.MCP,
188
+ mcp_server=mcp_server,
189
+ mcp_tool_name=mcp_tool_name,
190
+ category=category,
191
+ tags=tags,
192
+ )
193
+
194
+ self._register_metadata(metadata)
195
+
196
+ def _register_metadata(self, metadata: ToolMetadata) -> None:
197
+ """Internal method to register tool metadata and update indices."""
198
+ if metadata.name in self._tools:
199
+ raise ValueError(f"Tool '{metadata.name}' is already registered")
200
+
201
+ self._tools[metadata.name] = metadata
202
+
203
+ # Update category index
204
+ if metadata.category not in self._categories:
205
+ self._categories[metadata.category] = []
206
+ self._categories[metadata.category].append(metadata.name)
207
+
208
+ # Update tag index
209
+ for tag in metadata.tags:
210
+ if tag not in self._tags:
211
+ self._tags[tag] = []
212
+ self._tags[tag].append(metadata.name)
213
+
214
+ def get_tool(self, name: str) -> Optional[ToolMetadata]:
215
+ """Get tool metadata by name."""
216
+ return self._tools.get(name)
217
+
218
+ def get_all_tools(self) -> List[ToolMetadata]:
219
+ """Get all registered tools."""
220
+ return list(self._tools.values())
221
+
222
+ def get_tools_by_category(self, category: str) -> List[ToolMetadata]:
223
+ """Get all tools in a specific category."""
224
+ tool_names = self._categories.get(category, [])
225
+ return [self._tools[name] for name in tool_names]
226
+
227
+ def get_tools_by_tag(self, tag: str) -> List[ToolMetadata]:
228
+ """Get all tools with a specific tag."""
229
+ tool_names = self._tags.get(tag, [])
230
+ return [self._tools[name] for name in tool_names]
231
+
232
+ def get_tools_by_mode(self, mode: str) -> List[ToolMetadata]:
233
+ """Get all tools for a specific invocation mode."""
234
+ return [tool for tool in self._tools.values() if tool.mode == mode]
235
+
236
+ def list_categories(self) -> List[str]:
237
+ """List all registered categories."""
238
+ return list(self._categories.keys())
239
+
240
+ def list_tags(self) -> List[str]:
241
+ """List all registered tags."""
242
+ return list(self._tags.keys())
243
+
244
+ def unregister_tool(self, name: str) -> bool:
245
+ """
246
+ Unregister a tool by name.
247
+
248
+ Returns:
249
+ True if tool was unregistered, False if not found
250
+ """
251
+ if name not in self._tools:
252
+ return False
253
+
254
+ metadata = self._tools[name]
255
+
256
+ # Remove from indices
257
+ if metadata.category in self._categories:
258
+ self._categories[metadata.category].remove(name)
259
+ if not self._categories[metadata.category]:
260
+ del self._categories[metadata.category]
261
+
262
+ for tag in metadata.tags:
263
+ if tag in self._tags:
264
+ self._tags[tag].remove(name)
265
+ if not self._tags[tag]:
266
+ del self._tags[tag]
267
+
268
+ del self._tools[name]
269
+ return True
270
+
271
+ def clear(self) -> None:
272
+ """Clear all registered tools."""
273
+ self._tools.clear()
274
+ self._categories.clear()
275
+ self._tags.clear()
276
+
277
+ def __len__(self) -> int:
278
+ """Return number of registered tools."""
279
+ return len(self._tools)
280
+
281
+ def __contains__(self, name: str) -> bool:
282
+ """Check if a tool is registered."""
283
+ return name in self._tools
284
+
285
+ def __repr__(self) -> str:
286
+ return f"ToolRegistry(tools={len(self._tools)}, categories={len(self._categories)}, tags={len(self._tags)})"
287
+
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+ from abc import ABC, abstractmethod
3
+ from typing import Any, Dict, List, Optional
4
+ from ..base import BaseComponent
5
+
6
+
7
+ class VectorStore(BaseComponent, ABC):
8
+ @abstractmethod
9
+ def add(self, vectors: List[List[float]], metadatas: List[Dict[str, Any]]) -> Any:
10
+ raise NotImplementedError
11
+
12
+ @abstractmethod
13
+ def query(self, vector: List[float], top_k: int = 5) -> List[Dict[str, Any]]:
14
+ raise NotImplementedError
15
+
16
+ def run(self, *args, **kwargs):
17
+ """
18
+ Convenience default so subclasses don't *have* to implement run().
19
+ If called with a 'vector' (or first positional arg), proxies to query().
20
+ """
21
+ vector = kwargs.get("vector")
22
+ if vector is None and args:
23
+ vector = args[0]
24
+ top_k = kwargs.get("top_k", 5)
25
+ if vector is None:
26
+ raise NotImplementedError(
27
+ "VectorStore.run expects a 'vector' argument or an override."
28
+ )
29
+ return self.query(vector, top_k=top_k)
30
+
31
+ def count(self) -> Optional[int]:
32
+ return None
33
+
34
+
35
+ # compatibility alias
36
+ VectorStoreComponent = VectorStore
37
+ __all__ = ["VectorStore", "VectorStoreComponent"]