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.
Files changed (124) hide show
  1. assets/favicon.ico +0 -0
  2. assets/logo.png +0 -0
  3. naas_abi_core/__init__.py +1 -0
  4. naas_abi_core/apps/api/api.py +245 -0
  5. naas_abi_core/apps/api/api_test.py +281 -0
  6. naas_abi_core/apps/api/openapi_doc.py +144 -0
  7. naas_abi_core/apps/mcp/Dockerfile.mcp +35 -0
  8. naas_abi_core/apps/mcp/mcp_server.py +243 -0
  9. naas_abi_core/apps/mcp/mcp_server_test.py +163 -0
  10. naas_abi_core/apps/terminal_agent/main.py +555 -0
  11. naas_abi_core/apps/terminal_agent/terminal_style.py +175 -0
  12. naas_abi_core/engine/Engine.py +87 -0
  13. naas_abi_core/engine/EngineProxy.py +109 -0
  14. naas_abi_core/engine/Engine_test.py +6 -0
  15. naas_abi_core/engine/IEngine.py +91 -0
  16. naas_abi_core/engine/conftest.py +45 -0
  17. naas_abi_core/engine/engine_configuration/EngineConfiguration.py +216 -0
  18. naas_abi_core/engine/engine_configuration/EngineConfiguration_Deploy.py +7 -0
  19. naas_abi_core/engine/engine_configuration/EngineConfiguration_GenericLoader.py +49 -0
  20. naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService.py +159 -0
  21. naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService_test.py +26 -0
  22. naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService.py +138 -0
  23. naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService_test.py +74 -0
  24. naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService.py +224 -0
  25. naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService_test.py +109 -0
  26. naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService.py +76 -0
  27. naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService_test.py +33 -0
  28. naas_abi_core/engine/engine_configuration/EngineConfiguration_test.py +9 -0
  29. naas_abi_core/engine/engine_configuration/utils/PydanticModelValidator.py +15 -0
  30. naas_abi_core/engine/engine_loaders/EngineModuleLoader.py +302 -0
  31. naas_abi_core/engine/engine_loaders/EngineOntologyLoader.py +16 -0
  32. naas_abi_core/engine/engine_loaders/EngineServiceLoader.py +47 -0
  33. naas_abi_core/integration/__init__.py +7 -0
  34. naas_abi_core/integration/integration.py +28 -0
  35. naas_abi_core/models/Model.py +198 -0
  36. naas_abi_core/models/OpenRouter.py +18 -0
  37. naas_abi_core/models/OpenRouter_test.py +36 -0
  38. naas_abi_core/module/Module.py +252 -0
  39. naas_abi_core/module/ModuleAgentLoader.py +50 -0
  40. naas_abi_core/module/ModuleUtils.py +20 -0
  41. naas_abi_core/modules/templatablesparqlquery/README.md +196 -0
  42. naas_abi_core/modules/templatablesparqlquery/__init__.py +39 -0
  43. naas_abi_core/modules/templatablesparqlquery/ontologies/TemplatableSparqlQueryOntology.ttl +116 -0
  44. naas_abi_core/modules/templatablesparqlquery/workflows/GenericWorkflow.py +48 -0
  45. naas_abi_core/modules/templatablesparqlquery/workflows/TemplatableSparqlQueryLoader.py +192 -0
  46. naas_abi_core/pipeline/__init__.py +6 -0
  47. naas_abi_core/pipeline/pipeline.py +70 -0
  48. naas_abi_core/services/__init__.py +0 -0
  49. naas_abi_core/services/agent/Agent.py +1619 -0
  50. naas_abi_core/services/agent/AgentMemory_test.py +28 -0
  51. naas_abi_core/services/agent/Agent_test.py +214 -0
  52. naas_abi_core/services/agent/IntentAgent.py +1179 -0
  53. naas_abi_core/services/agent/IntentAgent_test.py +139 -0
  54. naas_abi_core/services/agent/beta/Embeddings.py +181 -0
  55. naas_abi_core/services/agent/beta/IntentMapper.py +120 -0
  56. naas_abi_core/services/agent/beta/LocalModel.py +88 -0
  57. naas_abi_core/services/agent/beta/VectorStore.py +89 -0
  58. naas_abi_core/services/agent/test_agent_memory.py +278 -0
  59. naas_abi_core/services/agent/test_postgres_integration.py +145 -0
  60. naas_abi_core/services/cache/CacheFactory.py +31 -0
  61. naas_abi_core/services/cache/CachePort.py +63 -0
  62. naas_abi_core/services/cache/CacheService.py +246 -0
  63. naas_abi_core/services/cache/CacheService_test.py +85 -0
  64. naas_abi_core/services/cache/adapters/secondary/CacheFSAdapter.py +39 -0
  65. naas_abi_core/services/object_storage/ObjectStorageFactory.py +57 -0
  66. naas_abi_core/services/object_storage/ObjectStoragePort.py +47 -0
  67. naas_abi_core/services/object_storage/ObjectStorageService.py +41 -0
  68. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterFS.py +52 -0
  69. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterNaas.py +131 -0
  70. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterS3.py +171 -0
  71. naas_abi_core/services/ontology/OntologyPorts.py +36 -0
  72. naas_abi_core/services/ontology/OntologyService.py +17 -0
  73. naas_abi_core/services/ontology/adaptors/secondary/OntologyService_SecondaryAdaptor_NERPort.py +37 -0
  74. naas_abi_core/services/secret/Secret.py +138 -0
  75. naas_abi_core/services/secret/SecretPorts.py +45 -0
  76. naas_abi_core/services/secret/Secret_test.py +65 -0
  77. naas_abi_core/services/secret/adaptors/secondary/Base64Secret.py +57 -0
  78. naas_abi_core/services/secret/adaptors/secondary/Base64Secret_test.py +39 -0
  79. naas_abi_core/services/secret/adaptors/secondary/NaasSecret.py +88 -0
  80. naas_abi_core/services/secret/adaptors/secondary/NaasSecret_test.py +25 -0
  81. naas_abi_core/services/secret/adaptors/secondary/dotenv_secret_secondaryadaptor.py +29 -0
  82. naas_abi_core/services/triple_store/TripleStoreFactory.py +116 -0
  83. naas_abi_core/services/triple_store/TripleStorePorts.py +223 -0
  84. naas_abi_core/services/triple_store/TripleStoreService.py +419 -0
  85. naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune.py +1300 -0
  86. naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune_test.py +284 -0
  87. naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph.py +597 -0
  88. naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph_test.py +1474 -0
  89. naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__Filesystem.py +223 -0
  90. naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__ObjectStorage.py +234 -0
  91. naas_abi_core/services/triple_store/adaptors/secondary/base/TripleStoreService__SecondaryAdaptor__FileBase.py +18 -0
  92. naas_abi_core/services/vector_store/IVectorStorePort.py +101 -0
  93. naas_abi_core/services/vector_store/IVectorStorePort_test.py +189 -0
  94. naas_abi_core/services/vector_store/VectorStoreFactory.py +47 -0
  95. naas_abi_core/services/vector_store/VectorStoreService.py +171 -0
  96. naas_abi_core/services/vector_store/VectorStoreService_test.py +185 -0
  97. naas_abi_core/services/vector_store/__init__.py +13 -0
  98. naas_abi_core/services/vector_store/adapters/QdrantAdapter.py +251 -0
  99. naas_abi_core/services/vector_store/adapters/QdrantAdapter_test.py +57 -0
  100. naas_abi_core/tests/test_services_imports.py +69 -0
  101. naas_abi_core/utils/Expose.py +55 -0
  102. naas_abi_core/utils/Graph.py +182 -0
  103. naas_abi_core/utils/JSON.py +49 -0
  104. naas_abi_core/utils/LazyLoader.py +44 -0
  105. naas_abi_core/utils/Logger.py +12 -0
  106. naas_abi_core/utils/OntologyReasoner.py +141 -0
  107. naas_abi_core/utils/OntologyYaml.py +681 -0
  108. naas_abi_core/utils/SPARQL.py +256 -0
  109. naas_abi_core/utils/Storage.py +33 -0
  110. naas_abi_core/utils/StorageUtils.py +398 -0
  111. naas_abi_core/utils/String.py +52 -0
  112. naas_abi_core/utils/Workers.py +114 -0
  113. naas_abi_core/utils/__init__.py +0 -0
  114. naas_abi_core/utils/onto2py/README.md +0 -0
  115. naas_abi_core/utils/onto2py/__init__.py +10 -0
  116. naas_abi_core/utils/onto2py/__main__.py +29 -0
  117. naas_abi_core/utils/onto2py/onto2py.py +611 -0
  118. naas_abi_core/utils/onto2py/tests/ttl2py_test.py +271 -0
  119. naas_abi_core/workflow/__init__.py +5 -0
  120. naas_abi_core/workflow/workflow.py +48 -0
  121. naas_abi_core-1.4.1.dist-info/METADATA +630 -0
  122. naas_abi_core-1.4.1.dist-info/RECORD +124 -0
  123. naas_abi_core-1.4.1.dist-info/WHEEL +4 -0
  124. 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