naas-abi-core 1.4.1__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.
- assets/favicon.ico +0 -0
- assets/logo.png +0 -0
- naas_abi_core/__init__.py +1 -0
- naas_abi_core/apps/api/api.py +245 -0
- naas_abi_core/apps/api/api_test.py +281 -0
- naas_abi_core/apps/api/openapi_doc.py +144 -0
- naas_abi_core/apps/mcp/Dockerfile.mcp +35 -0
- naas_abi_core/apps/mcp/mcp_server.py +243 -0
- naas_abi_core/apps/mcp/mcp_server_test.py +163 -0
- naas_abi_core/apps/terminal_agent/main.py +555 -0
- naas_abi_core/apps/terminal_agent/terminal_style.py +175 -0
- naas_abi_core/engine/Engine.py +87 -0
- naas_abi_core/engine/EngineProxy.py +109 -0
- naas_abi_core/engine/Engine_test.py +6 -0
- naas_abi_core/engine/IEngine.py +91 -0
- naas_abi_core/engine/conftest.py +45 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration.py +216 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_Deploy.py +7 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_GenericLoader.py +49 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService.py +159 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService_test.py +26 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService.py +138 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService_test.py +74 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService.py +224 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService_test.py +109 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService.py +76 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService_test.py +33 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_test.py +9 -0
- naas_abi_core/engine/engine_configuration/utils/PydanticModelValidator.py +15 -0
- naas_abi_core/engine/engine_loaders/EngineModuleLoader.py +302 -0
- naas_abi_core/engine/engine_loaders/EngineOntologyLoader.py +16 -0
- naas_abi_core/engine/engine_loaders/EngineServiceLoader.py +47 -0
- naas_abi_core/integration/__init__.py +7 -0
- naas_abi_core/integration/integration.py +28 -0
- naas_abi_core/models/Model.py +198 -0
- naas_abi_core/models/OpenRouter.py +18 -0
- naas_abi_core/models/OpenRouter_test.py +36 -0
- naas_abi_core/module/Module.py +252 -0
- naas_abi_core/module/ModuleAgentLoader.py +50 -0
- naas_abi_core/module/ModuleUtils.py +20 -0
- naas_abi_core/modules/templatablesparqlquery/README.md +196 -0
- naas_abi_core/modules/templatablesparqlquery/__init__.py +39 -0
- naas_abi_core/modules/templatablesparqlquery/ontologies/TemplatableSparqlQueryOntology.ttl +116 -0
- naas_abi_core/modules/templatablesparqlquery/workflows/GenericWorkflow.py +48 -0
- naas_abi_core/modules/templatablesparqlquery/workflows/TemplatableSparqlQueryLoader.py +192 -0
- naas_abi_core/pipeline/__init__.py +6 -0
- naas_abi_core/pipeline/pipeline.py +70 -0
- naas_abi_core/services/__init__.py +0 -0
- naas_abi_core/services/agent/Agent.py +1619 -0
- naas_abi_core/services/agent/AgentMemory_test.py +28 -0
- naas_abi_core/services/agent/Agent_test.py +214 -0
- naas_abi_core/services/agent/IntentAgent.py +1179 -0
- naas_abi_core/services/agent/IntentAgent_test.py +139 -0
- naas_abi_core/services/agent/beta/Embeddings.py +181 -0
- naas_abi_core/services/agent/beta/IntentMapper.py +120 -0
- naas_abi_core/services/agent/beta/LocalModel.py +88 -0
- naas_abi_core/services/agent/beta/VectorStore.py +89 -0
- naas_abi_core/services/agent/test_agent_memory.py +278 -0
- naas_abi_core/services/agent/test_postgres_integration.py +145 -0
- naas_abi_core/services/cache/CacheFactory.py +31 -0
- naas_abi_core/services/cache/CachePort.py +63 -0
- naas_abi_core/services/cache/CacheService.py +246 -0
- naas_abi_core/services/cache/CacheService_test.py +85 -0
- naas_abi_core/services/cache/adapters/secondary/CacheFSAdapter.py +39 -0
- naas_abi_core/services/object_storage/ObjectStorageFactory.py +57 -0
- naas_abi_core/services/object_storage/ObjectStoragePort.py +47 -0
- naas_abi_core/services/object_storage/ObjectStorageService.py +41 -0
- naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterFS.py +52 -0
- naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterNaas.py +131 -0
- naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterS3.py +171 -0
- naas_abi_core/services/ontology/OntologyPorts.py +36 -0
- naas_abi_core/services/ontology/OntologyService.py +17 -0
- naas_abi_core/services/ontology/adaptors/secondary/OntologyService_SecondaryAdaptor_NERPort.py +37 -0
- naas_abi_core/services/secret/Secret.py +138 -0
- naas_abi_core/services/secret/SecretPorts.py +45 -0
- naas_abi_core/services/secret/Secret_test.py +65 -0
- naas_abi_core/services/secret/adaptors/secondary/Base64Secret.py +57 -0
- naas_abi_core/services/secret/adaptors/secondary/Base64Secret_test.py +39 -0
- naas_abi_core/services/secret/adaptors/secondary/NaasSecret.py +88 -0
- naas_abi_core/services/secret/adaptors/secondary/NaasSecret_test.py +25 -0
- naas_abi_core/services/secret/adaptors/secondary/dotenv_secret_secondaryadaptor.py +29 -0
- naas_abi_core/services/triple_store/TripleStoreFactory.py +116 -0
- naas_abi_core/services/triple_store/TripleStorePorts.py +223 -0
- naas_abi_core/services/triple_store/TripleStoreService.py +419 -0
- naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune.py +1300 -0
- naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune_test.py +284 -0
- naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph.py +597 -0
- naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph_test.py +1474 -0
- naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__Filesystem.py +223 -0
- naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__ObjectStorage.py +234 -0
- naas_abi_core/services/triple_store/adaptors/secondary/base/TripleStoreService__SecondaryAdaptor__FileBase.py +18 -0
- naas_abi_core/services/vector_store/IVectorStorePort.py +101 -0
- naas_abi_core/services/vector_store/IVectorStorePort_test.py +189 -0
- naas_abi_core/services/vector_store/VectorStoreFactory.py +47 -0
- naas_abi_core/services/vector_store/VectorStoreService.py +171 -0
- naas_abi_core/services/vector_store/VectorStoreService_test.py +185 -0
- naas_abi_core/services/vector_store/__init__.py +13 -0
- naas_abi_core/services/vector_store/adapters/QdrantAdapter.py +251 -0
- naas_abi_core/services/vector_store/adapters/QdrantAdapter_test.py +57 -0
- naas_abi_core/tests/test_services_imports.py +69 -0
- naas_abi_core/utils/Expose.py +55 -0
- naas_abi_core/utils/Graph.py +182 -0
- naas_abi_core/utils/JSON.py +49 -0
- naas_abi_core/utils/LazyLoader.py +44 -0
- naas_abi_core/utils/Logger.py +12 -0
- naas_abi_core/utils/OntologyReasoner.py +141 -0
- naas_abi_core/utils/OntologyYaml.py +681 -0
- naas_abi_core/utils/SPARQL.py +256 -0
- naas_abi_core/utils/Storage.py +33 -0
- naas_abi_core/utils/StorageUtils.py +398 -0
- naas_abi_core/utils/String.py +52 -0
- naas_abi_core/utils/Workers.py +114 -0
- naas_abi_core/utils/__init__.py +0 -0
- naas_abi_core/utils/onto2py/README.md +0 -0
- naas_abi_core/utils/onto2py/__init__.py +10 -0
- naas_abi_core/utils/onto2py/__main__.py +29 -0
- naas_abi_core/utils/onto2py/onto2py.py +611 -0
- naas_abi_core/utils/onto2py/tests/ttl2py_test.py +271 -0
- naas_abi_core/workflow/__init__.py +5 -0
- naas_abi_core/workflow/workflow.py +48 -0
- naas_abi_core-1.4.1.dist-info/METADATA +630 -0
- naas_abi_core-1.4.1.dist-info/RECORD +124 -0
- naas_abi_core-1.4.1.dist-info/WHEEL +4 -0
- naas_abi_core-1.4.1.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from typing import Dict, List
|
|
3
|
+
|
|
4
|
+
import pydantic_core
|
|
5
|
+
from naas_abi_core import logger
|
|
6
|
+
from naas_abi_core.engine.engine_configuration.EngineConfiguration import (
|
|
7
|
+
EngineConfiguration,
|
|
8
|
+
)
|
|
9
|
+
from naas_abi_core.engine.EngineProxy import EngineProxy
|
|
10
|
+
from naas_abi_core.engine.IEngine import IEngine
|
|
11
|
+
from naas_abi_core.module.Module import (
|
|
12
|
+
BaseModule,
|
|
13
|
+
ModuleConfiguration,
|
|
14
|
+
ModuleDependencies,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class EngineModuleLoader:
|
|
19
|
+
__configuration: EngineConfiguration
|
|
20
|
+
|
|
21
|
+
__module_load_order: List[str] = []
|
|
22
|
+
|
|
23
|
+
__modules: Dict[str, BaseModule] = {}
|
|
24
|
+
|
|
25
|
+
__module_dependencies: Dict[str, ModuleDependencies] | None = None
|
|
26
|
+
|
|
27
|
+
def __init__(self, configuration: EngineConfiguration):
|
|
28
|
+
self.__configuration = configuration
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def modules(self) -> Dict[str, BaseModule]:
|
|
32
|
+
return self.__modules
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def module_load_order(self) -> List[str]:
|
|
36
|
+
return self.__module_load_order
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def ordered_modules(self) -> List[BaseModule]:
|
|
40
|
+
return [self.__modules[module_name] for module_name in self.__module_load_order]
|
|
41
|
+
|
|
42
|
+
def __topological_sort(self, dependencies: Dict[str, List[str]]) -> List[str]:
|
|
43
|
+
"""
|
|
44
|
+
Perform topological sort on modules based on dependencies.
|
|
45
|
+
Returns a list of module names in load order.
|
|
46
|
+
Raises ValueError if circular dependency is detected.
|
|
47
|
+
"""
|
|
48
|
+
# Build adjacency list and in-degree count
|
|
49
|
+
in_degree = {node: 0 for node in dependencies}
|
|
50
|
+
adj_list: Dict[str, List[str]] = {node: [] for node in dependencies}
|
|
51
|
+
|
|
52
|
+
for node, deps in dependencies.items():
|
|
53
|
+
for dep in deps:
|
|
54
|
+
soft_dependency = dep.endswith("#soft")
|
|
55
|
+
if dep.endswith("#soft"):
|
|
56
|
+
dep = dep.replace("#soft", "")
|
|
57
|
+
|
|
58
|
+
if (
|
|
59
|
+
dep not in dependencies and not soft_dependency
|
|
60
|
+
): # Soft modules are not required to be in the dependencies.
|
|
61
|
+
logger.error(
|
|
62
|
+
f"Module '{node}' depends on '{dep}' which is not enabled. "
|
|
63
|
+
"Aborting."
|
|
64
|
+
)
|
|
65
|
+
raise ValueError(
|
|
66
|
+
f"Module '{node}' depends on '{dep}' which is not enabled. "
|
|
67
|
+
"Aborting."
|
|
68
|
+
)
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
if dep in adj_list:
|
|
72
|
+
adj_list[dep].append(node)
|
|
73
|
+
in_degree[node] += 1
|
|
74
|
+
|
|
75
|
+
# Kahn's algorithm
|
|
76
|
+
queue = [node for node in in_degree if in_degree[node] == 0]
|
|
77
|
+
sorted_list = []
|
|
78
|
+
|
|
79
|
+
while queue:
|
|
80
|
+
# Sort queue for deterministic order when multiple nodes have in-degree 0
|
|
81
|
+
queue.sort()
|
|
82
|
+
node = queue.pop(0)
|
|
83
|
+
sorted_list.append(node)
|
|
84
|
+
|
|
85
|
+
for neighbor in adj_list[node]:
|
|
86
|
+
in_degree[neighbor] -= 1
|
|
87
|
+
if in_degree[neighbor] == 0:
|
|
88
|
+
queue.append(neighbor)
|
|
89
|
+
|
|
90
|
+
# Check for circular dependencies
|
|
91
|
+
if len(sorted_list) != len(dependencies):
|
|
92
|
+
unprocessed = [node for node in dependencies if node not in sorted_list]
|
|
93
|
+
raise ValueError(
|
|
94
|
+
f"Circular dependency detected among modules: {unprocessed}. "
|
|
95
|
+
"Cannot determine load order."
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
logger.debug(f"Module load order: {sorted_list}")
|
|
99
|
+
return sorted_list
|
|
100
|
+
|
|
101
|
+
def module_dependencies_recursive(
|
|
102
|
+
self, module_name: str, module_dependencies: Dict[str, ModuleDependencies]
|
|
103
|
+
) -> List[str]:
|
|
104
|
+
required_modules: List[str] = [module_name]
|
|
105
|
+
# This is not protected against circular dependencies.
|
|
106
|
+
# This is why it must be ran after the topological sort.
|
|
107
|
+
for dependency in module_dependencies[module_name].modules:
|
|
108
|
+
required_modules.append(dependency)
|
|
109
|
+
required_modules.extend(
|
|
110
|
+
self.module_dependencies_recursive(dependency, module_dependencies)
|
|
111
|
+
)
|
|
112
|
+
return list(set(required_modules))
|
|
113
|
+
|
|
114
|
+
def modules_dependencies_recursive(
|
|
115
|
+
self,
|
|
116
|
+
module_names: List[str],
|
|
117
|
+
module_dependencies: Dict[str, ModuleDependencies],
|
|
118
|
+
) -> List[str]:
|
|
119
|
+
dependencies: List[str] = []
|
|
120
|
+
for module_name in module_names:
|
|
121
|
+
dependencies.extend(
|
|
122
|
+
self.module_dependencies_recursive(module_name, module_dependencies)
|
|
123
|
+
)
|
|
124
|
+
return list(set(dependencies))
|
|
125
|
+
|
|
126
|
+
def get_module_dependencies(
|
|
127
|
+
self, module_name: str, scanned_modules: List[str] = []
|
|
128
|
+
) -> Dict[str, ModuleDependencies]:
|
|
129
|
+
dependencies: Dict[str, ModuleDependencies] = {}
|
|
130
|
+
logger.debug(f"Getting module dependencies for {module_name}")
|
|
131
|
+
is_soft_module = module_name.endswith("#soft")
|
|
132
|
+
module_name = module_name.replace("#soft", "")
|
|
133
|
+
if module_name in scanned_modules:
|
|
134
|
+
raise ValueError(
|
|
135
|
+
f"Circular dependency detected: {scanned_modules + [module_name]}"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
module_config = next(
|
|
139
|
+
(m for m in self.__configuration.modules if m.module == module_name),
|
|
140
|
+
None,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
if module_config is None and not is_soft_module:
|
|
144
|
+
raise ValueError(f"Module {module_name} not found in configuration")
|
|
145
|
+
elif module_config is None and is_soft_module:
|
|
146
|
+
# Soft modules are not required to be in the configuration.
|
|
147
|
+
# We return an empty dictionary.
|
|
148
|
+
return {}
|
|
149
|
+
|
|
150
|
+
assert module_config is not None, (
|
|
151
|
+
f"Module {module_name} configuration is not set in the configuration file"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if module_config.module is None:
|
|
155
|
+
raise ValueError(
|
|
156
|
+
f"Module {module_name} configuration module is not set in the configuration file"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
module = importlib.import_module(module_config.module)
|
|
160
|
+
if module is None:
|
|
161
|
+
raise ValueError(
|
|
162
|
+
f"Error while trying to import module {module_config.module}"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
assert hasattr(module, "ABIModule"), (
|
|
166
|
+
f"Module {module_config.module} does not have a ABIModule class"
|
|
167
|
+
)
|
|
168
|
+
assert hasattr(module.ABIModule, "get_dependencies"), (
|
|
169
|
+
f"Module {module_config.module} does not have a get_dependencies method"
|
|
170
|
+
)
|
|
171
|
+
dependencies[module_name] = module.ABIModule.get_dependencies()
|
|
172
|
+
assert isinstance(dependencies[module_name], ModuleDependencies), (
|
|
173
|
+
f"Module {module_config.module} get_dependencies method must return a ModuleDependencies object"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# We recursively get the dependencies of the module.
|
|
177
|
+
for module_dependency in dependencies[module_name].modules:
|
|
178
|
+
submodule_dependencies = self.get_module_dependencies(
|
|
179
|
+
module_dependency, scanned_modules + [module_name]
|
|
180
|
+
)
|
|
181
|
+
dependencies.update(submodule_dependencies)
|
|
182
|
+
|
|
183
|
+
# dependencies.modules.extend(submodule_dependencies.modules)
|
|
184
|
+
# dependencies.services.extend(submodule_dependencies.services)
|
|
185
|
+
|
|
186
|
+
# # Make sure we have unique modules and services.
|
|
187
|
+
# dependencies.modules = list(set(dependencies.modules))
|
|
188
|
+
# dependencies.services = list(set(dependencies.services))
|
|
189
|
+
|
|
190
|
+
logger.debug(f"Module dependencies for {module_name}: {dependencies}")
|
|
191
|
+
|
|
192
|
+
return dependencies
|
|
193
|
+
|
|
194
|
+
def get_modules_dependencies(
|
|
195
|
+
self, module_names: List[str] = []
|
|
196
|
+
) -> Dict[str, ModuleDependencies]:
|
|
197
|
+
module_dependencies: Dict[str, ModuleDependencies] = {}
|
|
198
|
+
for module_config in self.__configuration.modules:
|
|
199
|
+
# We check if the module is required by the configuration.
|
|
200
|
+
if (
|
|
201
|
+
len(module_names) > 0
|
|
202
|
+
and module_config.module in module_names
|
|
203
|
+
and not module_config.enabled
|
|
204
|
+
):
|
|
205
|
+
raise ValueError(
|
|
206
|
+
f"Module {module_config.module} is not enabled but is required by the configuration"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if len(module_names) > 0 and module_config.module not in module_names:
|
|
210
|
+
continue
|
|
211
|
+
|
|
212
|
+
assert module_config.module is not None, (
|
|
213
|
+
f"Module {module_config.module} configuration module is not set in the configuration file"
|
|
214
|
+
)
|
|
215
|
+
if module_config.enabled:
|
|
216
|
+
module_dependencies.update(
|
|
217
|
+
self.get_module_dependencies(module_config.module)
|
|
218
|
+
)
|
|
219
|
+
self.__module_dependencies = module_dependencies
|
|
220
|
+
return self.__module_dependencies
|
|
221
|
+
|
|
222
|
+
def load_modules(
|
|
223
|
+
self,
|
|
224
|
+
engine: IEngine,
|
|
225
|
+
module_names: List[str] = [],
|
|
226
|
+
) -> Dict[str, BaseModule]:
|
|
227
|
+
self.__modules: Dict[str, BaseModule] = {}
|
|
228
|
+
|
|
229
|
+
if self.__module_dependencies is None:
|
|
230
|
+
# Call this to hydrate the __module_dependencies attribute.
|
|
231
|
+
self.__module_dependencies = self.get_modules_dependencies(module_names)
|
|
232
|
+
|
|
233
|
+
assert self.__module_dependencies is not None, (
|
|
234
|
+
"Module dependencies are not set after getting modules dependencies"
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
logger.debug(f"Module dependencies: {self.__module_dependencies}")
|
|
238
|
+
|
|
239
|
+
module_load_order = self.__topological_sort(
|
|
240
|
+
{
|
|
241
|
+
module_name: dependencies.modules
|
|
242
|
+
for module_name, dependencies in self.__module_dependencies.items()
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
self.__module_load_order = module_load_order
|
|
247
|
+
|
|
248
|
+
# We load the modules in the order of the topological sort
|
|
249
|
+
for module_name in self.__module_load_order:
|
|
250
|
+
module_config = next(
|
|
251
|
+
(m for m in self.__configuration.modules if m.module == module_name),
|
|
252
|
+
None,
|
|
253
|
+
)
|
|
254
|
+
if module_config is None:
|
|
255
|
+
raise ValueError(f"Module {module_name} not found in configuration")
|
|
256
|
+
|
|
257
|
+
if module_config.enabled:
|
|
258
|
+
if module_config.module:
|
|
259
|
+
module = importlib.import_module(module_config.module)
|
|
260
|
+
if not hasattr(module, "ABIModule"):
|
|
261
|
+
raise ValueError(
|
|
262
|
+
f"Module {module_config.module} does not have a Module class"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if not hasattr(module.ABIModule, "Configuration"):
|
|
266
|
+
raise ValueError(
|
|
267
|
+
f"Module {module_config.module} does not have a Configuration class"
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
assert type(module.ABIModule.Configuration) is type(
|
|
271
|
+
ModuleConfiguration
|
|
272
|
+
), (
|
|
273
|
+
f"Module {module_config.module} Configuration must be a subclass of ModuleConfiguration"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
cfg = module.ABIModule.Configuration(
|
|
278
|
+
global_config=self.__configuration.global_config,
|
|
279
|
+
**module_config.config,
|
|
280
|
+
)
|
|
281
|
+
except pydantic_core._pydantic_core.ValidationError as e:
|
|
282
|
+
raise ValueError(
|
|
283
|
+
f"Error loading configuration for module {module_config.module}: {e}"
|
|
284
|
+
) from e
|
|
285
|
+
|
|
286
|
+
# This effectively creates a new instance of the module.
|
|
287
|
+
# It will call the constructor of the ABIModule class inside the module.
|
|
288
|
+
module = module.ABIModule(
|
|
289
|
+
EngineProxy(
|
|
290
|
+
engine, module_name, self.__module_dependencies[module_name]
|
|
291
|
+
),
|
|
292
|
+
cfg,
|
|
293
|
+
)
|
|
294
|
+
assert isinstance(module, BaseModule), (
|
|
295
|
+
f"Module {module_config.module} is not a subclass of BaseModule"
|
|
296
|
+
)
|
|
297
|
+
self.__modules[module_name] = module
|
|
298
|
+
module.on_load()
|
|
299
|
+
else:
|
|
300
|
+
raise ValueError("module must be provided for a module")
|
|
301
|
+
|
|
302
|
+
return self.__modules
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from naas_abi_core import logger
|
|
4
|
+
from naas_abi_core.module.Module import BaseModule
|
|
5
|
+
from naas_abi_core.services.triple_store.TripleStoreService import TripleStoreService
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class EngineOntologyLoader:
|
|
9
|
+
@classmethod
|
|
10
|
+
def load_ontologies(
|
|
11
|
+
cls, triple_store: TripleStoreService, modules: List[BaseModule]
|
|
12
|
+
) -> None:
|
|
13
|
+
logger.debug("Loading ontologies")
|
|
14
|
+
for module in modules:
|
|
15
|
+
for ontology in module.ontologies:
|
|
16
|
+
triple_store.load_schema(ontology)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from typing import Dict, List
|
|
2
|
+
|
|
3
|
+
from naas_abi_core import logger
|
|
4
|
+
from naas_abi_core.engine.engine_configuration.EngineConfiguration import (
|
|
5
|
+
EngineConfiguration,
|
|
6
|
+
)
|
|
7
|
+
from naas_abi_core.engine.IEngine import IEngine
|
|
8
|
+
from naas_abi_core.module.Module import ModuleDependencies
|
|
9
|
+
from naas_abi_core.services.object_storage.ObjectStorageService import (
|
|
10
|
+
ObjectStorageService,
|
|
11
|
+
)
|
|
12
|
+
from naas_abi_core.services.secret.Secret import Secret
|
|
13
|
+
from naas_abi_core.services.triple_store.TripleStoreService import TripleStoreService
|
|
14
|
+
from naas_abi_core.services.vector_store.VectorStoreService import VectorStoreService
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class EngineServiceLoader:
|
|
18
|
+
__configuration: EngineConfiguration
|
|
19
|
+
|
|
20
|
+
def __init__(self, configuration: EngineConfiguration):
|
|
21
|
+
self.__configuration = configuration
|
|
22
|
+
|
|
23
|
+
def load_services(
|
|
24
|
+
self, module_dependencies: Dict[str, ModuleDependencies]
|
|
25
|
+
) -> IEngine.Services:
|
|
26
|
+
services_to_load: List[type] = []
|
|
27
|
+
|
|
28
|
+
for _, module_dependency in module_dependencies.items():
|
|
29
|
+
services_to_load.extend(module_dependency.services)
|
|
30
|
+
|
|
31
|
+
services_to_load = list(set(services_to_load))
|
|
32
|
+
logger.debug(f"Services to load: {services_to_load}")
|
|
33
|
+
|
|
34
|
+
return IEngine.Services(
|
|
35
|
+
self.__configuration.services.object_storage.load()
|
|
36
|
+
if ObjectStorageService in services_to_load
|
|
37
|
+
else None,
|
|
38
|
+
self.__configuration.services.triple_store.load()
|
|
39
|
+
if TripleStoreService in services_to_load
|
|
40
|
+
else None,
|
|
41
|
+
self.__configuration.services.vector_store.load()
|
|
42
|
+
if VectorStoreService in services_to_load
|
|
43
|
+
else None,
|
|
44
|
+
self.__configuration.services.secret.load()
|
|
45
|
+
if Secret in services_to_load
|
|
46
|
+
else None,
|
|
47
|
+
)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from naas_abi_core.integration.integration import Integration as Integration
|
|
2
|
+
from naas_abi_core.integration.integration import (
|
|
3
|
+
IntegrationConfiguration as IntegrationConfiguration,
|
|
4
|
+
)
|
|
5
|
+
from naas_abi_core.integration.integration import (
|
|
6
|
+
IntegrationConnectionError as IntegrationConnectionError,
|
|
7
|
+
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class IntegrationConnectionError(Exception):
|
|
5
|
+
pass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class IntegrationConfiguration:
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Integration:
|
|
14
|
+
"""An Integration represents a way to interact with a third-party tool.
|
|
15
|
+
|
|
16
|
+
The Integration class serves as a base class for implementing connections to external services,
|
|
17
|
+
APIs, or tools. It provides a standardized interface for configuring and establishing these
|
|
18
|
+
connections.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
__configuration (IntegrationConfiguration): Configuration instance containing
|
|
22
|
+
necessary credentials and settings for connecting to the third-party tool.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
__configuration: IntegrationConfiguration
|
|
26
|
+
|
|
27
|
+
def __init__(self, configuration: IntegrationConfiguration):
|
|
28
|
+
self.__configuration = configuration
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Annotated, Dict, Optional
|
|
5
|
+
|
|
6
|
+
from langchain_core.language_models.chat_models import BaseChatModel
|
|
7
|
+
from naas_abi_core.models.OpenRouter import ChatOpenRouter
|
|
8
|
+
from pydantic import Field
|
|
9
|
+
|
|
10
|
+
OPENROUTER_MODEL_MAPPING: Dict[str, str] = {
|
|
11
|
+
"gpt-5": "openai/gpt-5",
|
|
12
|
+
"gpt-5-mini": "openai/gpt-5-mini",
|
|
13
|
+
"gpt-5-nano": "openai/gpt-5-nano",
|
|
14
|
+
"gpt-4.1": "openai/gpt-4.1",
|
|
15
|
+
"gpt-4.1-mini": "openai/gpt-4.1-mini",
|
|
16
|
+
"o3-deep-research": "openai/o3-deep-research",
|
|
17
|
+
"o4-mini-deep-research": "openai/o4-mini-deep-research",
|
|
18
|
+
"o3-mini": "openai/o3-mini",
|
|
19
|
+
"sonar-pro-search": "perplexity/sonar-pro-search",
|
|
20
|
+
"sonar-reasoning-pro": "perplexity/sonar-reasoning-pro",
|
|
21
|
+
"sonar-pro": "perplexity/sonar-pro",
|
|
22
|
+
"sonar-deep-research": "perplexity/sonar-deep-research",
|
|
23
|
+
"sonar-reasoning": "perplexity/sonar-reasoning",
|
|
24
|
+
"sonar": "perplexity/sonar",
|
|
25
|
+
"claude-haiku-4-5-20251001": "anthropic/claude-haiku-4.5",
|
|
26
|
+
"claude-sonnet-4-5-20250929": "anthropic/claude-sonnet-4.5",
|
|
27
|
+
"claude-opus-4-1-20250805": "anthropic/claude-opus-4.1",
|
|
28
|
+
"claude-opus-4-20250514": "anthropic/claude-opus-4",
|
|
29
|
+
"claude-sonnet-4-20250514": "anthropic/claude-sonnet-4",
|
|
30
|
+
"claude-3-7-sonnet-20250219": "anthropic/claude-3.7-sonnet",
|
|
31
|
+
"qwen3:8b": "qwen/qwen3-8b",
|
|
32
|
+
"mistral-large-2411": "mistralai/mistral-large-2411",
|
|
33
|
+
"mistral-medium-2508": "mistralai/mistral-medium-3.1",
|
|
34
|
+
"mistral-small-2506": "mistralai/mistral-small",
|
|
35
|
+
"grok-4": "x-ai/grok-4",
|
|
36
|
+
"gemini-2.5-flash": "google/gemini-2.5-flash",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ModelType(Enum):
|
|
41
|
+
CHAT = "chat"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Model:
|
|
45
|
+
model_id: Annotated[str, Field(description="Unique identifier for the model")]
|
|
46
|
+
provider: Annotated[
|
|
47
|
+
str,
|
|
48
|
+
Field(
|
|
49
|
+
description="The provider of the model (e.g. 'openai', 'anthropic', 'openrouter', 'aws bedrock', etc.)"
|
|
50
|
+
),
|
|
51
|
+
]
|
|
52
|
+
model: Annotated[
|
|
53
|
+
BaseChatModel, Field(description="The base model chat from Langchain")
|
|
54
|
+
]
|
|
55
|
+
name: Annotated[
|
|
56
|
+
Optional[str],
|
|
57
|
+
Field(
|
|
58
|
+
description="Display name of the model (e.g. 'GPT-4.1', 'Claude Sonnet 4.5', 'Grok 4', 'Mistral Large', 'Gemini 2.5 Flash', etc.)"
|
|
59
|
+
),
|
|
60
|
+
]
|
|
61
|
+
owner: Annotated[Optional[str], Field(description="The owner/creator of the model")]
|
|
62
|
+
description: Annotated[
|
|
63
|
+
Optional[str],
|
|
64
|
+
Field(
|
|
65
|
+
description="The description of the model (e.g. 'GPT-4.1 is OpenAI's most advanced model with superior performance across text, code, and reasoning tasks.', 'Claude Sonnet 4.5 is Anthropic's most advanced Sonnet model to date, optimized for real-world agents and coding workflows.', 'Grok 4 is xAI's latest multimodal model with SOTA cost-efficiency and a 2M token context window. It comes in two flavors: non-reasoning and reasoning. Read more about the model on xAI's [news post](http://x.ai/news/grok-4-fast). Reasoning can be enabled using the `reasoning` `enabled` parameter in the API. [Learn more in our docs](https://openrouter.ai/docs/use-cases/reasoning-tokens#controlling-reasoning-tokens)', 'Mistral Large is Mistral's latest large model with superior performance across text, code, and reasoning tasks.', 'Gemini 2.5 Flash is Google's latest multimodal model with superior performance across text, code, and reasoning tasks.', etc.)"
|
|
66
|
+
),
|
|
67
|
+
]
|
|
68
|
+
image: Annotated[Optional[str], Field(description="The image of the model")]
|
|
69
|
+
created_at: Annotated[
|
|
70
|
+
Optional[datetime], Field(description="The date and time the model was created")
|
|
71
|
+
]
|
|
72
|
+
canonical_slug: Annotated[
|
|
73
|
+
Optional[str], Field(description="Canonical slug for the model")
|
|
74
|
+
]
|
|
75
|
+
hugging_face_id: Annotated[
|
|
76
|
+
Optional[str], Field(description="Hugging Face model identifier, if applicable")
|
|
77
|
+
]
|
|
78
|
+
pricing: Annotated[
|
|
79
|
+
Optional[dict], Field(description="Pricing information for the model")
|
|
80
|
+
]
|
|
81
|
+
architecture: Annotated[
|
|
82
|
+
Optional[dict], Field(description="Model architecture information")
|
|
83
|
+
]
|
|
84
|
+
top_provider: Annotated[
|
|
85
|
+
Optional[dict],
|
|
86
|
+
Field(description="Information about the top provider for this model"),
|
|
87
|
+
]
|
|
88
|
+
per_request_limits: Annotated[
|
|
89
|
+
Optional[dict], Field(description="Per-request token limits")
|
|
90
|
+
]
|
|
91
|
+
supported_parameters: Annotated[
|
|
92
|
+
Optional[list], Field(description="List of supported parameters for this model")
|
|
93
|
+
]
|
|
94
|
+
default_parameters: Annotated[
|
|
95
|
+
Optional[dict], Field(description="Default parameters for this model")
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
model_id: str,
|
|
101
|
+
provider: str,
|
|
102
|
+
model: BaseChatModel,
|
|
103
|
+
name: Optional[str] = None,
|
|
104
|
+
owner: Optional[str] = None,
|
|
105
|
+
description: Optional[str] = None,
|
|
106
|
+
image: Optional[str] = None,
|
|
107
|
+
created_at: Optional[datetime] = None,
|
|
108
|
+
canonical_slug: Optional[str] = None,
|
|
109
|
+
hugging_face_id: Optional[str] = None,
|
|
110
|
+
pricing: Optional[dict] = None,
|
|
111
|
+
architecture: Optional[dict] = None,
|
|
112
|
+
top_provider: Optional[dict] = None,
|
|
113
|
+
per_request_limits: Optional[dict] = None,
|
|
114
|
+
supported_parameters: Optional[list] = None,
|
|
115
|
+
default_parameters: Optional[dict] = None,
|
|
116
|
+
):
|
|
117
|
+
self.model_id = model_id
|
|
118
|
+
self.provider = provider
|
|
119
|
+
|
|
120
|
+
# If OPENROUTER_API_KEY is set, use ChatOpenRouter as BaseChatModel
|
|
121
|
+
if (
|
|
122
|
+
os.getenv("OPENROUTER_API_KEY")
|
|
123
|
+
and os.getenv("AI_MODE") == "cloud"
|
|
124
|
+
and isinstance(model, BaseChatModel)
|
|
125
|
+
and not isinstance(model, ChatOpenRouter)
|
|
126
|
+
):
|
|
127
|
+
if model_id in OPENROUTER_MODEL_MAPPING:
|
|
128
|
+
self.model = ChatOpenRouter(
|
|
129
|
+
model_name=OPENROUTER_MODEL_MAPPING[model_id]
|
|
130
|
+
)
|
|
131
|
+
else:
|
|
132
|
+
raise ValueError(f"""Model '{model_id}' from provider '{provider}' not found in OPENROUTER_MODEL_MAPPING.
|
|
133
|
+
Please add it to the mapping in lib/abi/models/Model.py.""")
|
|
134
|
+
else:
|
|
135
|
+
self.model = model
|
|
136
|
+
|
|
137
|
+
self.name = name
|
|
138
|
+
self.owner = owner
|
|
139
|
+
self.description = description
|
|
140
|
+
self.image = image
|
|
141
|
+
self.created_at = created_at
|
|
142
|
+
self.canonical_slug = canonical_slug
|
|
143
|
+
self.hugging_face_id = hugging_face_id
|
|
144
|
+
self.pricing = pricing
|
|
145
|
+
self.architecture = architecture
|
|
146
|
+
self.top_provider = top_provider
|
|
147
|
+
self.per_request_limits = per_request_limits
|
|
148
|
+
self.supported_parameters = supported_parameters
|
|
149
|
+
self.default_parameters = default_parameters
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class ChatModel(Model):
|
|
153
|
+
model: BaseChatModel
|
|
154
|
+
context_window: Annotated[
|
|
155
|
+
Optional[int], Field(description="Maximum context length in tokens")
|
|
156
|
+
]
|
|
157
|
+
model_type: ModelType = ModelType.CHAT
|
|
158
|
+
|
|
159
|
+
def __init__(
|
|
160
|
+
self,
|
|
161
|
+
model_id: str,
|
|
162
|
+
provider: str,
|
|
163
|
+
model: BaseChatModel,
|
|
164
|
+
context_window: Optional[int] = None,
|
|
165
|
+
name: Optional[str] = None,
|
|
166
|
+
owner: Optional[str] = None,
|
|
167
|
+
description: Optional[str] = None,
|
|
168
|
+
image: Optional[str] = None,
|
|
169
|
+
created_at: Optional[datetime] = None,
|
|
170
|
+
canonical_slug: Optional[str] = None,
|
|
171
|
+
hugging_face_id: Optional[str] = None,
|
|
172
|
+
pricing: Optional[dict] = None,
|
|
173
|
+
architecture: Optional[dict] = None,
|
|
174
|
+
top_provider: Optional[dict] = None,
|
|
175
|
+
per_request_limits: Optional[dict] = None,
|
|
176
|
+
supported_parameters: Optional[list] = None,
|
|
177
|
+
default_parameters: Optional[dict] = None,
|
|
178
|
+
):
|
|
179
|
+
super().__init__(
|
|
180
|
+
model_id=model_id,
|
|
181
|
+
provider=provider,
|
|
182
|
+
model=model,
|
|
183
|
+
name=name,
|
|
184
|
+
owner=owner,
|
|
185
|
+
description=description,
|
|
186
|
+
image=image,
|
|
187
|
+
created_at=created_at,
|
|
188
|
+
canonical_slug=canonical_slug,
|
|
189
|
+
hugging_face_id=hugging_face_id,
|
|
190
|
+
pricing=pricing,
|
|
191
|
+
architecture=architecture,
|
|
192
|
+
top_provider=top_provider,
|
|
193
|
+
per_request_limits=per_request_limits,
|
|
194
|
+
supported_parameters=supported_parameters,
|
|
195
|
+
default_parameters=default_parameters,
|
|
196
|
+
)
|
|
197
|
+
self.model_type = ModelType.CHAT
|
|
198
|
+
self.context_window = context_window
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
# from dotenv import load_dotenv
|
|
4
|
+
from langchain_openai import ChatOpenAI # using the OpenAI LLM class as wrapper
|
|
5
|
+
from pydantic import SecretStr
|
|
6
|
+
|
|
7
|
+
# load_dotenv()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ChatOpenRouter(ChatOpenAI):
|
|
11
|
+
def __init__(self, model_name: str, **kwargs):
|
|
12
|
+
api_key = SecretStr(os.environ["OPENROUTER_API_KEY"])
|
|
13
|
+
super().__init__(
|
|
14
|
+
model=model_name,
|
|
15
|
+
api_key=api_key,
|
|
16
|
+
base_url="https://openrouter.ai/api/v1",
|
|
17
|
+
**kwargs,
|
|
18
|
+
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from naas_abi_core.models.OpenRouter import ChatOpenRouter
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_openai():
|
|
5
|
+
llm = ChatOpenRouter(model_name="openai/gpt-4.1")
|
|
6
|
+
result = llm.invoke("What is the capital of France?").content
|
|
7
|
+
assert result is not None, result
|
|
8
|
+
assert "Paris" in result, result
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_anthropic():
|
|
12
|
+
llm = ChatOpenRouter(model_name="anthropic/claude-sonnet-4.5")
|
|
13
|
+
result = llm.invoke("What is the capital of France?").content
|
|
14
|
+
assert result is not None, result
|
|
15
|
+
assert "Paris" in result, result
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_xai():
|
|
19
|
+
llm = ChatOpenRouter(model_name="x-ai/grok-4")
|
|
20
|
+
result = llm.invoke("What is the capital of France?").content
|
|
21
|
+
assert result is not None, result
|
|
22
|
+
assert "Paris" in result, result
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_mistral():
|
|
26
|
+
llm = ChatOpenRouter(model_name="mistralai/mistral-large")
|
|
27
|
+
result = llm.invoke("What is the capital of France?").content
|
|
28
|
+
assert result is not None, result
|
|
29
|
+
assert "Paris" in result, result
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_gemini():
|
|
33
|
+
llm = ChatOpenRouter(model_name="google/gemini-2.5-flash")
|
|
34
|
+
result = llm.invoke("What is the capital of France?").content
|
|
35
|
+
assert result is not None, result
|
|
36
|
+
assert "Paris" in result, result
|