naas-abi-core 1.0.0__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. naas_abi_core/__init__.py +1 -0
  2. naas_abi_core/apps/api/api.py +242 -0
  3. naas_abi_core/apps/api/api_test.py +281 -0
  4. naas_abi_core/apps/api/openapi_doc.py +307 -0
  5. naas_abi_core/apps/mcp/mcp_server.py +243 -0
  6. naas_abi_core/apps/mcp/mcp_server_test.py +163 -0
  7. naas_abi_core/apps/terminal_agent/main.py +555 -0
  8. naas_abi_core/apps/terminal_agent/terminal_style.py +175 -0
  9. naas_abi_core/cli/__init__.py +53 -0
  10. naas_abi_core/cli/agent.py +30 -0
  11. naas_abi_core/cli/chat.py +26 -0
  12. naas_abi_core/cli/config.py +49 -0
  13. naas_abi_core/cli/init.py +13 -0
  14. naas_abi_core/cli/module.py +28 -0
  15. naas_abi_core/cli/new.py +13 -0
  16. naas_abi_core/cli/secret.py +79 -0
  17. naas_abi_core/engine/Engine.py +87 -0
  18. naas_abi_core/engine/EngineProxy.py +109 -0
  19. naas_abi_core/engine/Engine_test.py +6 -0
  20. naas_abi_core/engine/IEngine.py +91 -0
  21. naas_abi_core/engine/conftest.py +45 -0
  22. naas_abi_core/engine/engine_configuration/EngineConfiguration.py +160 -0
  23. naas_abi_core/engine/engine_configuration/EngineConfiguration_GenericLoader.py +49 -0
  24. naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService.py +131 -0
  25. naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService_test.py +26 -0
  26. naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService.py +116 -0
  27. naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService.py +171 -0
  28. naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService.py +65 -0
  29. naas_abi_core/engine/engine_configuration/EngineConfiguration_test.py +9 -0
  30. naas_abi_core/engine/engine_configuration/utils/PydanticModelValidator.py +15 -0
  31. naas_abi_core/engine/engine_loaders/EngineModuleLoader.py +302 -0
  32. naas_abi_core/engine/engine_loaders/EngineOntologyLoader.py +16 -0
  33. naas_abi_core/engine/engine_loaders/EngineServiceLoader.py +47 -0
  34. naas_abi_core/integration/__init__.py +7 -0
  35. naas_abi_core/integration/integration.py +28 -0
  36. naas_abi_core/models/Model.py +198 -0
  37. naas_abi_core/models/OpenRouter.py +15 -0
  38. naas_abi_core/models/OpenRouter_test.py +36 -0
  39. naas_abi_core/module/Module.py +245 -0
  40. naas_abi_core/module/ModuleAgentLoader.py +49 -0
  41. naas_abi_core/module/ModuleUtils.py +20 -0
  42. naas_abi_core/modules/templatablesparqlquery/README.md +196 -0
  43. naas_abi_core/modules/templatablesparqlquery/__init__.py +39 -0
  44. naas_abi_core/modules/templatablesparqlquery/ontologies/TemplatableSparqlQueryOntology.ttl +116 -0
  45. naas_abi_core/modules/templatablesparqlquery/workflows/GenericWorkflow.py +48 -0
  46. naas_abi_core/modules/templatablesparqlquery/workflows/TemplatableSparqlQueryLoader.py +192 -0
  47. naas_abi_core/pipeline/__init__.py +6 -0
  48. naas_abi_core/pipeline/pipeline.py +70 -0
  49. naas_abi_core/services/__init__.py +0 -0
  50. naas_abi_core/services/agent/Agent.py +1619 -0
  51. naas_abi_core/services/agent/AgentMemory_test.py +28 -0
  52. naas_abi_core/services/agent/Agent_test.py +214 -0
  53. naas_abi_core/services/agent/IntentAgent.py +1171 -0
  54. naas_abi_core/services/agent/IntentAgent_test.py +139 -0
  55. naas_abi_core/services/agent/beta/Embeddings.py +180 -0
  56. naas_abi_core/services/agent/beta/IntentMapper.py +119 -0
  57. naas_abi_core/services/agent/beta/LocalModel.py +88 -0
  58. naas_abi_core/services/agent/beta/VectorStore.py +89 -0
  59. naas_abi_core/services/agent/test_agent_memory.py +278 -0
  60. naas_abi_core/services/agent/test_postgres_integration.py +145 -0
  61. naas_abi_core/services/cache/CacheFactory.py +31 -0
  62. naas_abi_core/services/cache/CachePort.py +63 -0
  63. naas_abi_core/services/cache/CacheService.py +246 -0
  64. naas_abi_core/services/cache/CacheService_test.py +85 -0
  65. naas_abi_core/services/cache/adapters/secondary/CacheFSAdapter.py +39 -0
  66. naas_abi_core/services/object_storage/ObjectStorageFactory.py +57 -0
  67. naas_abi_core/services/object_storage/ObjectStoragePort.py +47 -0
  68. naas_abi_core/services/object_storage/ObjectStorageService.py +41 -0
  69. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterFS.py +52 -0
  70. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterNaas.py +131 -0
  71. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterS3.py +171 -0
  72. naas_abi_core/services/ontology/OntologyPorts.py +36 -0
  73. naas_abi_core/services/ontology/OntologyService.py +17 -0
  74. naas_abi_core/services/ontology/adaptors/secondary/OntologyService_SecondaryAdaptor_NERPort.py +37 -0
  75. naas_abi_core/services/secret/Secret.py +138 -0
  76. naas_abi_core/services/secret/SecretPorts.py +40 -0
  77. naas_abi_core/services/secret/Secret_test.py +65 -0
  78. naas_abi_core/services/secret/adaptors/secondary/Base64Secret.py +57 -0
  79. naas_abi_core/services/secret/adaptors/secondary/Base64Secret_test.py +39 -0
  80. naas_abi_core/services/secret/adaptors/secondary/NaasSecret.py +81 -0
  81. naas_abi_core/services/secret/adaptors/secondary/NaasSecret_test.py +25 -0
  82. naas_abi_core/services/secret/adaptors/secondary/dotenv_secret_secondaryadaptor.py +26 -0
  83. naas_abi_core/services/triple_store/TripleStoreFactory.py +116 -0
  84. naas_abi_core/services/triple_store/TripleStorePorts.py +223 -0
  85. naas_abi_core/services/triple_store/TripleStoreService.py +419 -0
  86. naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune.py +1284 -0
  87. naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune_test.py +284 -0
  88. naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph.py +597 -0
  89. naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph_test.py +1474 -0
  90. naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__Filesystem.py +223 -0
  91. naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__ObjectStorage.py +234 -0
  92. naas_abi_core/services/triple_store/adaptors/secondary/base/TripleStoreService__SecondaryAdaptor__FileBase.py +18 -0
  93. naas_abi_core/services/vector_store/IVectorStorePort.py +101 -0
  94. naas_abi_core/services/vector_store/IVectorStorePort_test.py +189 -0
  95. naas_abi_core/services/vector_store/VectorStoreFactory.py +47 -0
  96. naas_abi_core/services/vector_store/VectorStoreService.py +171 -0
  97. naas_abi_core/services/vector_store/VectorStoreService_test.py +185 -0
  98. naas_abi_core/services/vector_store/__init__.py +13 -0
  99. naas_abi_core/services/vector_store/adapters/QdrantAdapter.py +251 -0
  100. naas_abi_core/services/vector_store/adapters/QdrantAdapter_test.py +57 -0
  101. naas_abi_core/utils/Expose.py +53 -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.disabled.py +679 -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.0.0.dist-info/METADATA +75 -0
  122. naas_abi_core-1.0.0.dist-info/RECORD +124 -0
  123. naas_abi_core-1.0.0.dist-info/WHEEL +4 -0
  124. naas_abi_core-1.0.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,171 @@
1
+ from typing import TYPE_CHECKING, Literal, Union
2
+
3
+ from naas_abi_core.engine.engine_configuration.EngineConfiguration_GenericLoader import (
4
+ GenericLoader,
5
+ )
6
+ from naas_abi_core.engine.engine_configuration.EngineConfiguration_ObjectStorageService import (
7
+ ObjectStorageServiceConfiguration,
8
+ )
9
+ from naas_abi_core.engine.engine_configuration.utils.PydanticModelValidator import (
10
+ pydantic_model_validator,
11
+ )
12
+ from naas_abi_core.services.triple_store.TripleStorePorts import ITripleStorePort
13
+ from naas_abi_core.services.triple_store.TripleStoreService import TripleStoreService
14
+ from pydantic import BaseModel, model_validator
15
+ from typing_extensions import Self
16
+
17
+ # Only import for type checking, not at runtime
18
+ if TYPE_CHECKING:
19
+ pass
20
+
21
+
22
+ class OxigraphAdapterConfiguration(BaseModel):
23
+ oxigraph_url: str = "http://localhost:7878"
24
+ timeout: int = 60
25
+
26
+
27
+ class AWSNeptuneAdapterConfiguration(BaseModel):
28
+ aws_region_name: str
29
+ aws_access_key_id: str
30
+ aws_secret_access_key: str
31
+ db_instance_identifier: str
32
+
33
+
34
+ class AWSNeptuneSSHTunnelAdapterConfiguration(AWSNeptuneAdapterConfiguration):
35
+ bastion_host: str
36
+ bastion_port: int
37
+ bastion_user: str
38
+ bastion_private_key: str
39
+
40
+
41
+ class TripleStoreAdapterFilesystemConfiguration(BaseModel):
42
+ store_path: str
43
+ triples_path: str = "triples"
44
+
45
+
46
+ class TripleStoreAdapterObjectStorageConfiguration(BaseModel):
47
+ object_storage_service: ObjectStorageServiceConfiguration
48
+ triples_prefix: str = "triples"
49
+
50
+
51
+ class TripleStoreAdapterConfiguration(GenericLoader):
52
+ adapter: Literal[
53
+ "oxigraph",
54
+ "aws_neptune_sshtunnel",
55
+ "aws_neptune",
56
+ "fs",
57
+ "object_storage",
58
+ "custom",
59
+ ]
60
+ config: (
61
+ Union[
62
+ OxigraphAdapterConfiguration,
63
+ AWSNeptuneAdapterConfiguration,
64
+ AWSNeptuneSSHTunnelAdapterConfiguration,
65
+ TripleStoreAdapterFilesystemConfiguration,
66
+ TripleStoreAdapterObjectStorageConfiguration,
67
+ dict,
68
+ ]
69
+ | None
70
+ ) = None
71
+
72
+ @model_validator(mode="after")
73
+ def validate_adapter(self) -> Self:
74
+ if self.adapter != "custom":
75
+ assert self.config is not None, (
76
+ "config is required if adapter is not custom"
77
+ )
78
+
79
+ if self.adapter == "fs":
80
+ pydantic_model_validator(
81
+ TripleStoreAdapterFilesystemConfiguration,
82
+ self.config,
83
+ "Invalid configuration for services.triple_store.triple_store_adapter 'fs' adapter",
84
+ )
85
+ if self.adapter == "object_storage":
86
+ pydantic_model_validator(
87
+ TripleStoreAdapterObjectStorageConfiguration,
88
+ self.config,
89
+ "Invalid configuration for services.triple_store.triple_store_adapter 'object_storage' adapter",
90
+ )
91
+ if self.adapter == "oxigraph":
92
+ pydantic_model_validator(
93
+ OxigraphAdapterConfiguration,
94
+ self.config,
95
+ "Invalid configuration for services.triple_store.triple_store_adapter 'oxigraph' adapter",
96
+ )
97
+ if self.adapter == "aws_neptune":
98
+ pydantic_model_validator(
99
+ AWSNeptuneAdapterConfiguration,
100
+ self.config,
101
+ "Invalid configuration for services.triple_store.triple_store_adapter 'aws_neptune' adapter",
102
+ )
103
+ if self.adapter == "aws_neptune_sshtunnel":
104
+ pydantic_model_validator(
105
+ AWSNeptuneSSHTunnelAdapterConfiguration,
106
+ self.config,
107
+ "Invalid configuration for services.triple_store.triple_store_adapter 'aws_neptune_sshtunnel' adapter",
108
+ )
109
+
110
+ return self
111
+
112
+ def load(self) -> ITripleStorePort:
113
+ if self.adapter != "custom":
114
+ assert self.config is not None, "config is required"
115
+
116
+ arguments = (
117
+ self.config.model_dump()
118
+ if not isinstance(self.config, dict)
119
+ else self.config
120
+ )
121
+
122
+ # Lazy import: only import the adapter that's actually configured
123
+ if self.adapter == "oxigraph":
124
+ from naas_abi_core.services.triple_store.adaptors.secondary.Oxigraph import (
125
+ Oxigraph,
126
+ )
127
+
128
+ OxigraphAdapterConfiguration.model_validate(arguments)
129
+
130
+ return Oxigraph(**arguments)
131
+ elif self.adapter == "aws_neptune":
132
+ from naas_abi_core.services.triple_store.adaptors.secondary.AWSNeptune import (
133
+ AWSNeptune,
134
+ )
135
+
136
+ AWSNeptuneAdapterConfiguration.model_validate(arguments)
137
+
138
+ return AWSNeptune(**arguments)
139
+ elif self.adapter == "aws_neptune_sshtunnel":
140
+ from naas_abi_core.services.triple_store.adaptors.secondary.AWSNeptune import (
141
+ AWSNeptuneSSHTunnel,
142
+ )
143
+
144
+ AWSNeptuneSSHTunnelAdapterConfiguration.model_validate(arguments)
145
+
146
+ return AWSNeptuneSSHTunnel(**arguments)
147
+ elif self.adapter == "fs":
148
+ from naas_abi_core.services.triple_store.adaptors.secondary.TripleStoreService__SecondaryAdaptor__Filesystem import (
149
+ TripleStoreService__SecondaryAdaptor__Filesystem,
150
+ )
151
+
152
+ TripleStoreAdapterFilesystemConfiguration.model_validate(arguments)
153
+
154
+ return TripleStoreService__SecondaryAdaptor__Filesystem(**arguments)
155
+ elif self.adapter == "object_storage":
156
+ from naas_abi_core.services.triple_store.adaptors.secondary.TripleStoreService__SecondaryAdaptor__ObjectStorage import (
157
+ TripleStoreService__SecondaryAdaptor__ObjectStorage,
158
+ )
159
+
160
+ return TripleStoreService__SecondaryAdaptor__ObjectStorage(**arguments)
161
+ else:
162
+ raise ValueError(f"Adapter {self.adapter} not supported")
163
+ else:
164
+ return super().load()
165
+
166
+
167
+ class TripleStoreServiceConfiguration(BaseModel):
168
+ triple_store_adapter: TripleStoreAdapterConfiguration
169
+
170
+ def load(self) -> TripleStoreService:
171
+ return TripleStoreService(triple_store_adapter=self.triple_store_adapter.load())
@@ -0,0 +1,65 @@
1
+ from typing import Literal
2
+
3
+ from naas_abi_core.engine.engine_configuration.EngineConfiguration_GenericLoader import (
4
+ GenericLoader,
5
+ )
6
+ from naas_abi_core.engine.engine_configuration.utils.PydanticModelValidator import (
7
+ pydantic_model_validator,
8
+ )
9
+ from naas_abi_core.services.vector_store.IVectorStorePort import IVectorStorePort
10
+ from naas_abi_core.services.vector_store.VectorStoreService import VectorStoreService
11
+ from pydantic import BaseModel, model_validator
12
+
13
+
14
+ class VectorStoreAdapterQdrantConfiguration(BaseModel):
15
+ host: str = "localhost"
16
+ port: int = 6333
17
+ api_key: str | None = None
18
+ https: bool = False
19
+ timeout: int = 30
20
+
21
+
22
+ class VectorStoreAdapterConfiguration(GenericLoader):
23
+ adapter: Literal["qdrant", "custom"]
24
+ config: dict | None = None
25
+
26
+ @model_validator(mode="after")
27
+ def validate_adapter(self) -> "VectorStoreAdapterConfiguration":
28
+ if self.adapter != "custom":
29
+ assert self.config is not None, (
30
+ "config is required if adapter is not custom"
31
+ )
32
+
33
+ if self.adapter == "qdrant":
34
+ pydantic_model_validator(
35
+ VectorStoreAdapterQdrantConfiguration,
36
+ self.config,
37
+ "Invalid configuration for services.vector_store.vector_store_adapter 'qdrant' adapter",
38
+ )
39
+
40
+ return self
41
+
42
+ def load(self) -> IVectorStorePort:
43
+ if self.adapter != "custom":
44
+ assert self.config is not None, (
45
+ "config is required if adapter is not custom"
46
+ )
47
+
48
+ # Lazy import: only import when actually loading
49
+ if self.adapter == "qdrant":
50
+ from naas_abi_core.services.vector_store.adapters.QdrantAdapter import (
51
+ QdrantAdapter,
52
+ )
53
+
54
+ return QdrantAdapter(**self.config)
55
+ else:
56
+ raise ValueError(f"Unknown adapter: {self.adapter}")
57
+ else:
58
+ return super().load()
59
+
60
+
61
+ class VectorStoreServiceConfiguration(BaseModel):
62
+ vector_store_adapter: VectorStoreAdapterConfiguration
63
+
64
+ def load(self) -> VectorStoreService:
65
+ return VectorStoreService(adapter=self.vector_store_adapter.load())
@@ -0,0 +1,9 @@
1
+ from naas_abi_core.engine.engine_configuration.EngineConfiguration import (
2
+ EngineConfiguration,
3
+ )
4
+
5
+
6
+ def test_configuration_load(test_configuration: str):
7
+ config = EngineConfiguration.load_configuration(test_configuration)
8
+ assert config is not None
9
+ assert False is True
@@ -0,0 +1,15 @@
1
+ from typing import Any
2
+
3
+ from naas_abi_core.utils.Logger import logger
4
+ from pydantic import BaseModel, ValidationError
5
+
6
+
7
+ def pydantic_model_validator(model: Any, payload: Any, message: str):
8
+ if not isinstance(model, type) or not issubclass(model, BaseModel):
9
+ raise TypeError(
10
+ "The model argument must be a class that inherits from pydantic.BaseModel"
11
+ )
12
+ try:
13
+ model.model_validate(payload)
14
+ except ValidationError as e:
15
+ logger.error(f"{message}: {e}")
@@ -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