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.
- naas_abi_core/__init__.py +1 -0
- naas_abi_core/apps/api/api.py +242 -0
- naas_abi_core/apps/api/api_test.py +281 -0
- naas_abi_core/apps/api/openapi_doc.py +307 -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/cli/__init__.py +53 -0
- naas_abi_core/cli/agent.py +30 -0
- naas_abi_core/cli/chat.py +26 -0
- naas_abi_core/cli/config.py +49 -0
- naas_abi_core/cli/init.py +13 -0
- naas_abi_core/cli/module.py +28 -0
- naas_abi_core/cli/new.py +13 -0
- naas_abi_core/cli/secret.py +79 -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 +160 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_GenericLoader.py +49 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService.py +131 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService_test.py +26 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService.py +116 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService.py +171 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService.py +65 -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 +15 -0
- naas_abi_core/models/OpenRouter_test.py +36 -0
- naas_abi_core/module/Module.py +245 -0
- naas_abi_core/module/ModuleAgentLoader.py +49 -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 +1171 -0
- naas_abi_core/services/agent/IntentAgent_test.py +139 -0
- naas_abi_core/services/agent/beta/Embeddings.py +180 -0
- naas_abi_core/services/agent/beta/IntentMapper.py +119 -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 +40 -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 +81 -0
- naas_abi_core/services/secret/adaptors/secondary/NaasSecret_test.py +25 -0
- naas_abi_core/services/secret/adaptors/secondary/dotenv_secret_secondaryadaptor.py +26 -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 +1284 -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/utils/Expose.py +53 -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.disabled.py +679 -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.0.0.dist-info/METADATA +75 -0
- naas_abi_core-1.0.0.dist-info/RECORD +124 -0
- naas_abi_core-1.0.0.dist-info/WHEEL +4 -0
- 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
|