naas-abi-core 1.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- assets/favicon.ico +0 -0
- assets/logo.png +0 -0
- naas_abi_core/__init__.py +1 -0
- naas_abi_core/apps/api/api.py +245 -0
- naas_abi_core/apps/api/api_test.py +281 -0
- naas_abi_core/apps/api/openapi_doc.py +144 -0
- naas_abi_core/apps/mcp/Dockerfile.mcp +35 -0
- naas_abi_core/apps/mcp/mcp_server.py +243 -0
- naas_abi_core/apps/mcp/mcp_server_test.py +163 -0
- naas_abi_core/apps/terminal_agent/main.py +555 -0
- naas_abi_core/apps/terminal_agent/terminal_style.py +175 -0
- naas_abi_core/engine/Engine.py +87 -0
- naas_abi_core/engine/EngineProxy.py +109 -0
- naas_abi_core/engine/Engine_test.py +6 -0
- naas_abi_core/engine/IEngine.py +91 -0
- naas_abi_core/engine/conftest.py +45 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration.py +216 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_Deploy.py +7 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_GenericLoader.py +49 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService.py +159 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService_test.py +26 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService.py +138 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService_test.py +74 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService.py +224 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService_test.py +109 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService.py +76 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService_test.py +33 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_test.py +9 -0
- naas_abi_core/engine/engine_configuration/utils/PydanticModelValidator.py +15 -0
- naas_abi_core/engine/engine_loaders/EngineModuleLoader.py +302 -0
- naas_abi_core/engine/engine_loaders/EngineOntologyLoader.py +16 -0
- naas_abi_core/engine/engine_loaders/EngineServiceLoader.py +47 -0
- naas_abi_core/integration/__init__.py +7 -0
- naas_abi_core/integration/integration.py +28 -0
- naas_abi_core/models/Model.py +198 -0
- naas_abi_core/models/OpenRouter.py +18 -0
- naas_abi_core/models/OpenRouter_test.py +36 -0
- naas_abi_core/module/Module.py +252 -0
- naas_abi_core/module/ModuleAgentLoader.py +50 -0
- naas_abi_core/module/ModuleUtils.py +20 -0
- naas_abi_core/modules/templatablesparqlquery/README.md +196 -0
- naas_abi_core/modules/templatablesparqlquery/__init__.py +39 -0
- naas_abi_core/modules/templatablesparqlquery/ontologies/TemplatableSparqlQueryOntology.ttl +116 -0
- naas_abi_core/modules/templatablesparqlquery/workflows/GenericWorkflow.py +48 -0
- naas_abi_core/modules/templatablesparqlquery/workflows/TemplatableSparqlQueryLoader.py +192 -0
- naas_abi_core/pipeline/__init__.py +6 -0
- naas_abi_core/pipeline/pipeline.py +70 -0
- naas_abi_core/services/__init__.py +0 -0
- naas_abi_core/services/agent/Agent.py +1619 -0
- naas_abi_core/services/agent/AgentMemory_test.py +28 -0
- naas_abi_core/services/agent/Agent_test.py +214 -0
- naas_abi_core/services/agent/IntentAgent.py +1179 -0
- naas_abi_core/services/agent/IntentAgent_test.py +139 -0
- naas_abi_core/services/agent/beta/Embeddings.py +181 -0
- naas_abi_core/services/agent/beta/IntentMapper.py +120 -0
- naas_abi_core/services/agent/beta/LocalModel.py +88 -0
- naas_abi_core/services/agent/beta/VectorStore.py +89 -0
- naas_abi_core/services/agent/test_agent_memory.py +278 -0
- naas_abi_core/services/agent/test_postgres_integration.py +145 -0
- naas_abi_core/services/cache/CacheFactory.py +31 -0
- naas_abi_core/services/cache/CachePort.py +63 -0
- naas_abi_core/services/cache/CacheService.py +246 -0
- naas_abi_core/services/cache/CacheService_test.py +85 -0
- naas_abi_core/services/cache/adapters/secondary/CacheFSAdapter.py +39 -0
- naas_abi_core/services/object_storage/ObjectStorageFactory.py +57 -0
- naas_abi_core/services/object_storage/ObjectStoragePort.py +47 -0
- naas_abi_core/services/object_storage/ObjectStorageService.py +41 -0
- naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterFS.py +52 -0
- naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterNaas.py +131 -0
- naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterS3.py +171 -0
- naas_abi_core/services/ontology/OntologyPorts.py +36 -0
- naas_abi_core/services/ontology/OntologyService.py +17 -0
- naas_abi_core/services/ontology/adaptors/secondary/OntologyService_SecondaryAdaptor_NERPort.py +37 -0
- naas_abi_core/services/secret/Secret.py +138 -0
- naas_abi_core/services/secret/SecretPorts.py +45 -0
- naas_abi_core/services/secret/Secret_test.py +65 -0
- naas_abi_core/services/secret/adaptors/secondary/Base64Secret.py +57 -0
- naas_abi_core/services/secret/adaptors/secondary/Base64Secret_test.py +39 -0
- naas_abi_core/services/secret/adaptors/secondary/NaasSecret.py +88 -0
- naas_abi_core/services/secret/adaptors/secondary/NaasSecret_test.py +25 -0
- naas_abi_core/services/secret/adaptors/secondary/dotenv_secret_secondaryadaptor.py +29 -0
- naas_abi_core/services/triple_store/TripleStoreFactory.py +116 -0
- naas_abi_core/services/triple_store/TripleStorePorts.py +223 -0
- naas_abi_core/services/triple_store/TripleStoreService.py +419 -0
- naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune.py +1300 -0
- naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune_test.py +284 -0
- naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph.py +597 -0
- naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph_test.py +1474 -0
- naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__Filesystem.py +223 -0
- naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__ObjectStorage.py +234 -0
- naas_abi_core/services/triple_store/adaptors/secondary/base/TripleStoreService__SecondaryAdaptor__FileBase.py +18 -0
- naas_abi_core/services/vector_store/IVectorStorePort.py +101 -0
- naas_abi_core/services/vector_store/IVectorStorePort_test.py +189 -0
- naas_abi_core/services/vector_store/VectorStoreFactory.py +47 -0
- naas_abi_core/services/vector_store/VectorStoreService.py +171 -0
- naas_abi_core/services/vector_store/VectorStoreService_test.py +185 -0
- naas_abi_core/services/vector_store/__init__.py +13 -0
- naas_abi_core/services/vector_store/adapters/QdrantAdapter.py +251 -0
- naas_abi_core/services/vector_store/adapters/QdrantAdapter_test.py +57 -0
- naas_abi_core/tests/test_services_imports.py +69 -0
- naas_abi_core/utils/Expose.py +55 -0
- naas_abi_core/utils/Graph.py +182 -0
- naas_abi_core/utils/JSON.py +49 -0
- naas_abi_core/utils/LazyLoader.py +44 -0
- naas_abi_core/utils/Logger.py +12 -0
- naas_abi_core/utils/OntologyReasoner.py +141 -0
- naas_abi_core/utils/OntologyYaml.py +681 -0
- naas_abi_core/utils/SPARQL.py +256 -0
- naas_abi_core/utils/Storage.py +33 -0
- naas_abi_core/utils/StorageUtils.py +398 -0
- naas_abi_core/utils/String.py +52 -0
- naas_abi_core/utils/Workers.py +114 -0
- naas_abi_core/utils/__init__.py +0 -0
- naas_abi_core/utils/onto2py/README.md +0 -0
- naas_abi_core/utils/onto2py/__init__.py +10 -0
- naas_abi_core/utils/onto2py/__main__.py +29 -0
- naas_abi_core/utils/onto2py/onto2py.py +611 -0
- naas_abi_core/utils/onto2py/tests/ttl2py_test.py +271 -0
- naas_abi_core/workflow/__init__.py +5 -0
- naas_abi_core/workflow/workflow.py +48 -0
- naas_abi_core-1.4.1.dist-info/METADATA +630 -0
- naas_abi_core-1.4.1.dist-info/RECORD +124 -0
- naas_abi_core-1.4.1.dist-info/WHEEL +4 -0
- naas_abi_core-1.4.1.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import glob
|
|
4
|
+
import os
|
|
5
|
+
from typing import Dict, List, cast
|
|
6
|
+
|
|
7
|
+
from naas_abi_core import logger
|
|
8
|
+
from naas_abi_core.engine.engine_configuration.EngineConfiguration import GlobalConfig
|
|
9
|
+
from naas_abi_core.engine.EngineProxy import EngineProxy
|
|
10
|
+
from naas_abi_core.integration.integration import Integration
|
|
11
|
+
from naas_abi_core.module.ModuleAgentLoader import ModuleAgentLoader
|
|
12
|
+
from naas_abi_core.pipeline.pipeline import Pipeline
|
|
13
|
+
from naas_abi_core.services.agent.Agent import Agent
|
|
14
|
+
from naas_abi_core.workflow.workflow import Workflow
|
|
15
|
+
from pydantic import BaseModel, ConfigDict
|
|
16
|
+
from typing_extensions import Generic, Self, TypeVar
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ModuleDependencies:
|
|
20
|
+
__modules: List[str]
|
|
21
|
+
__services: List[type]
|
|
22
|
+
|
|
23
|
+
def __init__(self, modules: List[str], services: List[type]):
|
|
24
|
+
self.__modules = modules
|
|
25
|
+
self.__services = services
|
|
26
|
+
|
|
27
|
+
def _get_modules(self) -> List[str]:
|
|
28
|
+
return self.__modules
|
|
29
|
+
|
|
30
|
+
def _set_modules(self, modules: List[str]) -> None:
|
|
31
|
+
self.__modules = modules
|
|
32
|
+
|
|
33
|
+
modules = property(_get_modules, _set_modules)
|
|
34
|
+
|
|
35
|
+
def _get_services(self) -> List[type]:
|
|
36
|
+
return self.__services
|
|
37
|
+
|
|
38
|
+
def _set_services(self, services: List[type]) -> None:
|
|
39
|
+
self.__services = services
|
|
40
|
+
|
|
41
|
+
services = property(_get_services, _set_services)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ModuleConfiguration(BaseModel):
|
|
45
|
+
model_config = ConfigDict(extra="forbid")
|
|
46
|
+
|
|
47
|
+
global_config: GlobalConfig
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
TConfig = TypeVar("TConfig", bound=ModuleConfiguration)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class BaseModule(Generic[TConfig]):
|
|
54
|
+
"""Base interface class for ABI modules."""
|
|
55
|
+
|
|
56
|
+
_instances: Dict[type, Self] = {}
|
|
57
|
+
|
|
58
|
+
_engine: EngineProxy
|
|
59
|
+
_configuration: TConfig
|
|
60
|
+
dependencies: ModuleDependencies = ModuleDependencies(modules=[], services=[])
|
|
61
|
+
|
|
62
|
+
__ontologies: List[str] = []
|
|
63
|
+
__agents: List[type[Agent]] = []
|
|
64
|
+
__integrations: List[Integration] = []
|
|
65
|
+
__workflows: List[Workflow] = []
|
|
66
|
+
__pipelines: List[Pipeline] = []
|
|
67
|
+
|
|
68
|
+
def __init__(self, engine: EngineProxy, configuration: TConfig):
|
|
69
|
+
assert isinstance(configuration, ModuleConfiguration), (
|
|
70
|
+
"configuration must be an instance of ModuleConfiguration"
|
|
71
|
+
)
|
|
72
|
+
logger.debug(f"Initializing module {self.__module__.split('.')[0]}")
|
|
73
|
+
self._engine = engine
|
|
74
|
+
self._configuration = configuration
|
|
75
|
+
|
|
76
|
+
assert hasattr(self.__class__, "Configuration"), (
|
|
77
|
+
"BaseModule must have a Configuration class"
|
|
78
|
+
)
|
|
79
|
+
assert type(self.__class__.Configuration) is type(ModuleConfiguration), (
|
|
80
|
+
"BaseModule.Configuration must be a subclass of ModuleConfiguration"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
self_instance: Self = cast(Self, self)
|
|
84
|
+
|
|
85
|
+
self._instances[self.__class__] = self_instance
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def get_dependencies(cls) -> List[str]:
|
|
89
|
+
"""Return the list of module dependencies."""
|
|
90
|
+
return getattr(cls, "dependencies", [])
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def get_instance(cls) -> Self:
|
|
94
|
+
if cls not in cls._instances:
|
|
95
|
+
raise ValueError(f"Module {cls} not initialized")
|
|
96
|
+
return cls._instances[cls]
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def engine(self) -> EngineProxy:
|
|
100
|
+
return self._engine
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def configuration(self) -> TConfig:
|
|
104
|
+
return self._configuration
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def ontologies(self) -> List[str]:
|
|
108
|
+
return self.__ontologies
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def agents(self) -> List[type[Agent]]:
|
|
112
|
+
return self.__agents
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def integrations(self) -> List[Integration]:
|
|
116
|
+
return self.__integrations
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def workflows(self) -> List[Workflow]:
|
|
120
|
+
return self.__workflows
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def pipelines(self) -> List[Pipeline]:
|
|
124
|
+
return self.__pipelines
|
|
125
|
+
|
|
126
|
+
def on_load(self):
|
|
127
|
+
logger.debug(f"on_load for module {self.__module__.split('.')[0]}")
|
|
128
|
+
self.__load_ontologies()
|
|
129
|
+
|
|
130
|
+
self.__agents = ModuleAgentLoader.load_agents(self.__class__)
|
|
131
|
+
|
|
132
|
+
def on_initialized(self):
|
|
133
|
+
"""
|
|
134
|
+
Called after all modules have been loaded and the engine is fully initialized.
|
|
135
|
+
|
|
136
|
+
Use this method to perform post-initialization steps that require other modules,
|
|
137
|
+
services, or ontologies to be available and loaded.
|
|
138
|
+
"""
|
|
139
|
+
logger.debug(f"on_initialized for module {self.__module__.split('.')[0]}")
|
|
140
|
+
|
|
141
|
+
def on_unloaded(self):
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
def __load_ontologies(self):
|
|
145
|
+
if os.path.exists(os.path.join(self.__module__.split(".")[0], "ontologies")):
|
|
146
|
+
for file in glob.glob(
|
|
147
|
+
os.path.join(self.module_path, "ontologies", "**", "*.ttl"),
|
|
148
|
+
recursive=True,
|
|
149
|
+
):
|
|
150
|
+
self.ontologies.append(file)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# class IModule(ABC):
|
|
154
|
+
# """Base interface class for ABI modules.
|
|
155
|
+
|
|
156
|
+
# This class serves as the base interface for all modules in the src/modules directory.
|
|
157
|
+
|
|
158
|
+
# The IModule class provides a default loading mechanism.
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# The default loading mechanism will:
|
|
162
|
+
# 1. Load all agents from the module's 'agents' directory
|
|
163
|
+
# 2. Initialize module configuration and state
|
|
164
|
+
|
|
165
|
+
# Attributes:
|
|
166
|
+
# agents (List[Agent]): List of agents loaded from the module
|
|
167
|
+
# """
|
|
168
|
+
|
|
169
|
+
# agents: List[Agent]
|
|
170
|
+
# triggers: List[tuple[tuple[Any, Any, Any], OntologyEvent, Callable]]
|
|
171
|
+
# ontologies: List[str]
|
|
172
|
+
|
|
173
|
+
# def __init__(self, module_path: str, module_import_path: str, imported_module: Any):
|
|
174
|
+
# self.module_path = module_path
|
|
175
|
+
# self.module_import_path = module_import_path
|
|
176
|
+
# self.imported_module = imported_module
|
|
177
|
+
# self.triggers = []
|
|
178
|
+
# self.ontologies = []
|
|
179
|
+
# self.agents = []
|
|
180
|
+
|
|
181
|
+
# def check_requirements(self):
|
|
182
|
+
# if hasattr(self.imported_module, "requirements"):
|
|
183
|
+
# if not self.imported_module.requirements():
|
|
184
|
+
# logger.error(f"❌ Module {self.module_import_path} failed to meet requirements.")
|
|
185
|
+
# raise SystemExit(f"Application crashed due to module loading failure: {self.module_import_path}")
|
|
186
|
+
|
|
187
|
+
# def load(self):
|
|
188
|
+
# try:
|
|
189
|
+
# self.check_requirements()
|
|
190
|
+
# # self.__load_agents()
|
|
191
|
+
# self.__load_triggers()
|
|
192
|
+
# self.__load_ontologies()
|
|
193
|
+
# except Exception as e:
|
|
194
|
+
# logger.error(f"❌ Critical error loading module {self.module_import_path}: {e}")
|
|
195
|
+
# raise SystemExit(f"Application crashed due to module loading failure: {self.module_import_path}")
|
|
196
|
+
|
|
197
|
+
# def load_agents(self):
|
|
198
|
+
# try:
|
|
199
|
+
# self.__load_agents()
|
|
200
|
+
# except Exception as e:
|
|
201
|
+
# import traceback
|
|
202
|
+
# logger.error(f"❌ Critical error loading agents for module {self.module_import_path}: {e}")
|
|
203
|
+
# traceback.print_exc()
|
|
204
|
+
# raise SystemExit(f"Application crashed due to agent loading failure: {self.module_import_path}")
|
|
205
|
+
|
|
206
|
+
# def __load_agents(self):
|
|
207
|
+
# # Load agents
|
|
208
|
+
# self.agents = []
|
|
209
|
+
# loaded_agent_names = set()
|
|
210
|
+
|
|
211
|
+
# # Find all agent files recursively
|
|
212
|
+
# for root, _, files in os.walk(self.module_path):
|
|
213
|
+
# for file in files:
|
|
214
|
+
# if file.endswith("Agent.py") and not file.endswith("Agent_test.py"):
|
|
215
|
+
# # Get relative path from module root to agent file
|
|
216
|
+
# rel_path = os.path.relpath(root, self.module_path)
|
|
217
|
+
# # Convert path to import format
|
|
218
|
+
# import_path = rel_path.replace(os.sep, ".")
|
|
219
|
+
# if import_path == ".":
|
|
220
|
+
# agent_path = self.module_import_path + "." + file[:-3]
|
|
221
|
+
# else:
|
|
222
|
+
# agent_path = self.module_import_path + "." + import_path + "." + file[:-3]
|
|
223
|
+
|
|
224
|
+
# module = importlib.import_module(agent_path)
|
|
225
|
+
# if hasattr(module, "create_agent"):
|
|
226
|
+
# agent = module.create_agent()
|
|
227
|
+
# if agent is not None:
|
|
228
|
+
# agent_name = getattr(agent, "name", None)
|
|
229
|
+
# if agent_name and agent_name not in loaded_agent_names:
|
|
230
|
+
# self.agents.append(agent)
|
|
231
|
+
# loaded_agent_names.add(agent_name)
|
|
232
|
+
# else:
|
|
233
|
+
# logger.warning(f"Skipping duplicate agent: {agent_name}")
|
|
234
|
+
|
|
235
|
+
# def __load_triggers(self):
|
|
236
|
+
# if os.path.exists(os.path.join(self.module_path, "triggers.py")):
|
|
237
|
+
# module = importlib.import_module(self.module_import_path + ".triggers")
|
|
238
|
+
# if hasattr(module, "triggers"):
|
|
239
|
+
# self.triggers = module.triggers
|
|
240
|
+
|
|
241
|
+
# def __load_ontologies(self):
|
|
242
|
+
# if os.path.exists(os.path.join(self.module_path, "ontologies")):
|
|
243
|
+
# for file in glob.glob(
|
|
244
|
+
# os.path.join(self.module_path, "ontologies", "**", "*.ttl"),
|
|
245
|
+
# recursive=True,
|
|
246
|
+
# ):
|
|
247
|
+
# self.ontologies.append(file)
|
|
248
|
+
|
|
249
|
+
# def on_initialized(self):
|
|
250
|
+
# if hasattr(self.imported_module, "on_initialized"):
|
|
251
|
+
# self.imported_module.on_initialized()
|
|
252
|
+
# self.imported_module.on_initialized()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import os
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from naas_abi_core.module.ModuleUtils import find_class_module_root_path
|
|
6
|
+
from naas_abi_core.services.agent.Agent import Agent
|
|
7
|
+
from naas_abi_core.utils.Logger import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ModuleAgentLoader:
|
|
11
|
+
@classmethod
|
|
12
|
+
def load_agents(cls, class_: type) -> List[type[Agent]]:
|
|
13
|
+
agents: List[type[Agent]] = []
|
|
14
|
+
module_root_path = find_class_module_root_path(class_)
|
|
15
|
+
|
|
16
|
+
agents_path = module_root_path / "agents"
|
|
17
|
+
|
|
18
|
+
logger.debug(f"Loading agents from {agents_path}")
|
|
19
|
+
|
|
20
|
+
if os.path.exists(agents_path):
|
|
21
|
+
for file in os.listdir(agents_path):
|
|
22
|
+
if file.endswith(".py") and not file.endswith("test.py"):
|
|
23
|
+
agent_module_path = (
|
|
24
|
+
f"{class_.__module__}.agents.{file.replace('.py', '')}"
|
|
25
|
+
)
|
|
26
|
+
logger.debug(f"Importing agent module from {agent_module_path}")
|
|
27
|
+
agent_module = importlib.import_module(agent_module_path)
|
|
28
|
+
for key, value in agent_module.__dict__.items():
|
|
29
|
+
if (
|
|
30
|
+
isinstance(value, type)
|
|
31
|
+
and issubclass(value, Agent)
|
|
32
|
+
and value.__module__.split(".")[0]
|
|
33
|
+
== class_.__module__.split(".")[
|
|
34
|
+
0
|
|
35
|
+
] # This makes sure we only load agents from the same module.
|
|
36
|
+
):
|
|
37
|
+
if not hasattr(key, "New") and hasattr(
|
|
38
|
+
agent_module, "create_agent"
|
|
39
|
+
):
|
|
40
|
+
setattr(
|
|
41
|
+
getattr(agent_module, key),
|
|
42
|
+
"New",
|
|
43
|
+
getattr(agent_module, "create_agent"),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
agents.append(getattr(agent_module, key))
|
|
47
|
+
|
|
48
|
+
logger.debug(f"Agents: {agents}")
|
|
49
|
+
|
|
50
|
+
return agents
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def find_class_module_root_path(class_: type) -> Path:
|
|
6
|
+
class_file_path = inspect.getfile(class_)
|
|
7
|
+
class_path = Path(class_file_path)
|
|
8
|
+
class_directory = str(class_path.parent)
|
|
9
|
+
return Path(class_directory)
|
|
10
|
+
# logger.debug(f"Class directory: {class_.__module__}")
|
|
11
|
+
# logger.debug(f"Class file path: {class_directory}")
|
|
12
|
+
# module_name = class_.__module__.split(".")[0]
|
|
13
|
+
|
|
14
|
+
# # find last occurrence of module_name in class_directory
|
|
15
|
+
# module_root_path = class_directory[
|
|
16
|
+
# : class_directory.rfind(module_name) + len(module_name)
|
|
17
|
+
# ]
|
|
18
|
+
|
|
19
|
+
# logger.debug(f"Module root path: {module_root_path}")
|
|
20
|
+
# return Path(module_root_path)
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Templatable SPARQL Query Module
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
### Description
|
|
6
|
+
|
|
7
|
+
The Templatable SPARQL Query Module provides a dynamic system for creating reusable SPARQL queries through ontology-based definitions. This module enables developers to define SPARQL query templates in RDF/TTL format with arguments and validation patterns, which are then automatically converted into executable workflows and tools at runtime.
|
|
8
|
+
|
|
9
|
+
This module enables:
|
|
10
|
+
- Dynamic SPARQL query generation from ontology definitions
|
|
11
|
+
- Automatic argument validation using regex patterns and formats
|
|
12
|
+
- Runtime tool creation for AI agents without writing Python boilerplate
|
|
13
|
+
- Template-based query execution using Jinja2 syntax
|
|
14
|
+
- Integration with knowledge graphs and triple stores
|
|
15
|
+
|
|
16
|
+
### Requirements
|
|
17
|
+
|
|
18
|
+
Triple Store Setup:
|
|
19
|
+
1. Ensure you have a running triple store service configured in your ABI instance
|
|
20
|
+
2. The module uses `services.triple_store_service` to query ontology definitions
|
|
21
|
+
|
|
22
|
+
### TL;DR
|
|
23
|
+
|
|
24
|
+
To get started with the Templatable SPARQL Query module:
|
|
25
|
+
|
|
26
|
+
1. Define your SPARQL queries in TTL format using the provided ontology
|
|
27
|
+
2. Load the TTL file into your triple store
|
|
28
|
+
3. The module automatically creates tools from your query definitions
|
|
29
|
+
|
|
30
|
+
Access tools using:
|
|
31
|
+
```python
|
|
32
|
+
from src.core.modules.templatablesparqlquery import get_tools
|
|
33
|
+
tools = get_tools()
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Structure
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
src/core/templatablesparqlquery/
|
|
40
|
+
|
|
41
|
+
├── ontologies/
|
|
42
|
+
│ └── TemplatableSparqlQueryOntology.ttl
|
|
43
|
+
├── workflows/
|
|
44
|
+
│ ├── GenericWorkflow.py
|
|
45
|
+
│ └── TemplatableSparqlQuery.py
|
|
46
|
+
└── README.md
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Core Components
|
|
50
|
+
|
|
51
|
+
The module provides a declarative approach to SPARQL query definition and execution through ontology-based configuration.
|
|
52
|
+
|
|
53
|
+
### Workflows
|
|
54
|
+
|
|
55
|
+
#### Templatable SPARQL Query Workflow
|
|
56
|
+
Dynamically loads query definitions from the triple store and creates executable workflows with proper argument validation and Jinja2 template rendering.
|
|
57
|
+
|
|
58
|
+
**Capabilities:**
|
|
59
|
+
- Loads query definitions from triple store using SPARQL
|
|
60
|
+
- Creates Pydantic models for argument validation
|
|
61
|
+
- Renders SPARQL templates with user parameters
|
|
62
|
+
- Executes queries against the triple store
|
|
63
|
+
- Returns formatted results
|
|
64
|
+
|
|
65
|
+
**Use Cases:**
|
|
66
|
+
- Dynamic query generation without hardcoded Python workflows
|
|
67
|
+
- Reusable query templates with parameter validation
|
|
68
|
+
- AI agent tool creation from ontology definitions
|
|
69
|
+
|
|
70
|
+
#### Generic Workflow
|
|
71
|
+
A generic wrapper class that handles the execution of templated SPARQL queries with type-safe argument validation.
|
|
72
|
+
|
|
73
|
+
**Configuration:**
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from src.core.modules.templatablesparqlquery.workflows.GenericWorkflow import GenericWorkflow
|
|
77
|
+
from pydantic import BaseModel, Field
|
|
78
|
+
|
|
79
|
+
# Define argument model
|
|
80
|
+
class MyQueryArguments(BaseModel):
|
|
81
|
+
department_id: str = Field(..., description="Department identifier", pattern="^[A-Z0-9]{3,}$")
|
|
82
|
+
employee_name: str = Field(..., description="Employee name filter")
|
|
83
|
+
|
|
84
|
+
# Create workflow
|
|
85
|
+
workflow = GenericWorkflow[MyQueryArguments](
|
|
86
|
+
name="find_employees",
|
|
87
|
+
description="Find employees in a department",
|
|
88
|
+
sparql_template=sparql_query_template,
|
|
89
|
+
arguments_model=MyQueryArguments
|
|
90
|
+
)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### Run
|
|
94
|
+
Execute workflows by calling the module's tool loading functions:
|
|
95
|
+
```python
|
|
96
|
+
from src.core.modules.templatablesparqlquery import get_tools
|
|
97
|
+
tools = get_tools()
|
|
98
|
+
# Tools are automatically created from ontology definitions
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### Testing
|
|
102
|
+
Currently no dedicated test files are present in the module structure.
|
|
103
|
+
|
|
104
|
+
### Ontologies
|
|
105
|
+
|
|
106
|
+
#### Templatable SPARQL Query Ontology
|
|
107
|
+
|
|
108
|
+
The core ontology defining the structure for templatable SPARQL queries and their arguments:
|
|
109
|
+
|
|
110
|
+
**Key Classes:**
|
|
111
|
+
- `abi:TemplatableSparqlQuery`: Represents a SPARQL query template with intent information
|
|
112
|
+
- `abi:QueryArgument`: Represents an argument for query templating
|
|
113
|
+
|
|
114
|
+
**Key Properties:**
|
|
115
|
+
- `abi:intentDescription`: Natural language description of query purpose
|
|
116
|
+
- `abi:sparqlTemplate`: The SPARQL query template with Jinja2 variables
|
|
117
|
+
- `abi:hasArgument`: Links queries to their arguments
|
|
118
|
+
- `abi:argumentName`: Argument name used in templates
|
|
119
|
+
- `abi:validationPattern`: Regex pattern for argument validation
|
|
120
|
+
- `abi:validationFormat`: Expected format description
|
|
121
|
+
|
|
122
|
+
#### Example Query Definition
|
|
123
|
+
|
|
124
|
+
```turtle
|
|
125
|
+
@prefix abi: <http://ontology.naas.ai/abi/> .
|
|
126
|
+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
127
|
+
|
|
128
|
+
abi:findEmployeesQuery a abi:TemplatableSparqlQuery ;
|
|
129
|
+
rdfs:label "Find Employees"@en ;
|
|
130
|
+
abi:intentDescription "Find all employees working in a specific department" ;
|
|
131
|
+
abi:sparqlTemplate """
|
|
132
|
+
SELECT ?employee ?name
|
|
133
|
+
WHERE {
|
|
134
|
+
?employee :worksIn :{{ department_id }} ;
|
|
135
|
+
:hasName ?name .
|
|
136
|
+
{% if employee_name %}
|
|
137
|
+
FILTER(CONTAINS(LCASE(?name), LCASE("{{ employee_name }}")))
|
|
138
|
+
{% endif %}
|
|
139
|
+
}
|
|
140
|
+
""" ;
|
|
141
|
+
abi:hasArgument abi:departmentArg .
|
|
142
|
+
|
|
143
|
+
abi:departmentArg a abi:QueryArgument ;
|
|
144
|
+
abi:argumentName "department_id" ;
|
|
145
|
+
abi:argumentDescription "The unique identifier of the department" ;
|
|
146
|
+
abi:validationPattern "^[A-Z0-9]{3,}$" ;
|
|
147
|
+
abi:validationFormat "department_id" .
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## How to Add New Queries
|
|
151
|
+
|
|
152
|
+
### Step 1: Define Query in TTL Format
|
|
153
|
+
Create or update a TTL file with your query definition using the templatable SPARQL query ontology.
|
|
154
|
+
|
|
155
|
+
### Step 2: Define Arguments
|
|
156
|
+
Specify all required arguments with proper validation patterns and descriptions.
|
|
157
|
+
|
|
158
|
+
### Step 3: Load into Triple Store
|
|
159
|
+
Ensure your TTL file is loaded into the triple store during application initialization.
|
|
160
|
+
|
|
161
|
+
### Step 4: Access Generated Tools
|
|
162
|
+
The module automatically creates tools from your definitions:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from src.core.modules.templatablesparqlquery import get_tools
|
|
166
|
+
tools = get_tools()
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Dependencies
|
|
170
|
+
|
|
171
|
+
### Python Libraries
|
|
172
|
+
- `pydantic`: Data validation and serialization for argument models
|
|
173
|
+
- `rdflib`: RDF graph processing and SPARQL query execution
|
|
174
|
+
- `jinja2`: Template rendering for SPARQL queries
|
|
175
|
+
- `langchain_core`: Tool integration for AI agents
|
|
176
|
+
- `asyncio`: Asynchronous processing support
|
|
177
|
+
|
|
178
|
+
### Internal Modules
|
|
179
|
+
- `src.utils.SPARQL`: SPARQL result processing utilities
|
|
180
|
+
- `src.services`: Triple store service integration
|
|
181
|
+
|
|
182
|
+
### External Services
|
|
183
|
+
- **Triple Store**: Required for storing and querying ontology definitions
|
|
184
|
+
- **Knowledge Graph**: Source data for SPARQL query execution
|
|
185
|
+
|
|
186
|
+
## Technical Implementation
|
|
187
|
+
|
|
188
|
+
The module uses a sophisticated runtime generation process:
|
|
189
|
+
|
|
190
|
+
1. **Query Discovery**: Scans the triple store for `TemplatableSparqlQuery` instances
|
|
191
|
+
2. **Argument Resolution**: Retrieves associated `QueryArgument` definitions
|
|
192
|
+
3. **Model Generation**: Creates Pydantic models with validation patterns
|
|
193
|
+
4. **Tool Creation**: Generates LangChain tools for AI agent integration
|
|
194
|
+
5. **Template Execution**: Uses Jinja2 to render SPARQL templates with parameters
|
|
195
|
+
|
|
196
|
+
This approach eliminates the need for writing repetitive Python code for similar query patterns, allowing developers to focus on query logic and ontology design.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from naas_abi_core.module.Module import (
|
|
2
|
+
BaseModule,
|
|
3
|
+
ModuleConfiguration,
|
|
4
|
+
ModuleDependencies,
|
|
5
|
+
)
|
|
6
|
+
from naas_abi_core.modules.templatablesparqlquery.workflows.TemplatableSparqlQueryLoader import (
|
|
7
|
+
TemplatableSparqlQueryLoader,
|
|
8
|
+
)
|
|
9
|
+
from naas_abi_core.services.triple_store.TripleStoreService import TripleStoreService
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ABIModule(BaseModule):
|
|
13
|
+
__workflows: list = []
|
|
14
|
+
__tools: list = []
|
|
15
|
+
|
|
16
|
+
dependencies: ModuleDependencies = ModuleDependencies(
|
|
17
|
+
modules=[], services=[TripleStoreService]
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
class Configuration(ModuleConfiguration):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
def on_initialized(self):
|
|
24
|
+
self.__templatable_sparql_query_loader = TemplatableSparqlQueryLoader(
|
|
25
|
+
self.engine.services.triple_store
|
|
26
|
+
)
|
|
27
|
+
self.__workflows = self.__templatable_sparql_query_loader.load_workflows()
|
|
28
|
+
self.__tools = [
|
|
29
|
+
tool for workflow in self.__workflows for tool in workflow.as_tools()
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
def get_workflows(self):
|
|
33
|
+
return self.__workflows
|
|
34
|
+
|
|
35
|
+
def get_tools(self, tool_names: list[str] = []):
|
|
36
|
+
if len(tool_names) == 0:
|
|
37
|
+
return self.__tools
|
|
38
|
+
else:
|
|
39
|
+
return [tool for tool in self.__tools if tool.name in tool_names]
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
|
2
|
+
@prefix dc11: <http://purl.org/dc/elements/1.1/> .
|
|
3
|
+
@prefix dc: <http://purl.org/dc/terms/> .
|
|
4
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
5
|
+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
6
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
7
|
+
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
|
|
8
|
+
@prefix bfo: <http://purl.obolibrary.org/obo/> .
|
|
9
|
+
@prefix cco: <https://www.commoncoreontologies.org/> .
|
|
10
|
+
@prefix abi: <http://ontology.naas.ai/abi/> .
|
|
11
|
+
|
|
12
|
+
<http://ontology.naas.ai/abi/TemplatableSparqlQueryOntology> a owl:Ontology ;
|
|
13
|
+
owl:versionIRI <https://github.com/jupyter-naas/abi/tree/cli/src/core/modules/abi/ontologies/domain-level/TemplatableSparqlQueryOntology.ttl> ;
|
|
14
|
+
dc11:contributor "Jeremy Ravenel" , "Maxime Jublou" , "Florent Ravenel" ;
|
|
15
|
+
dc:description "Templatable Sparql Query Ontology."@en ;
|
|
16
|
+
dc:license "" ;
|
|
17
|
+
dc:title "Templatable Sparql Query Ontology" .
|
|
18
|
+
|
|
19
|
+
#################################################################
|
|
20
|
+
# Classes
|
|
21
|
+
#################################################################
|
|
22
|
+
|
|
23
|
+
abi:TemplatableSparqlQuery a owl:Class ;
|
|
24
|
+
rdfs:subClassOf bfo:BFO_0000031 ;
|
|
25
|
+
rdfs:label "Templatable SPARQL Query"@en ;
|
|
26
|
+
rdfs:comment "A class representing a SPARQL query that can be templated with variables and includes intent information."@en .
|
|
27
|
+
|
|
28
|
+
abi:QueryArgument a owl:Class ;
|
|
29
|
+
rdfs:subClassOf bfo:BFO_0000031 ;
|
|
30
|
+
rdfs:label "Query Argument"@en ;
|
|
31
|
+
rdfs:comment "A class representing an argument that can be used to template a SPARQL query."@en .
|
|
32
|
+
|
|
33
|
+
#################################################################
|
|
34
|
+
# Object Properties
|
|
35
|
+
#################################################################
|
|
36
|
+
|
|
37
|
+
abi:hasArgument a owl:ObjectProperty ;
|
|
38
|
+
rdfs:label "has argument"@en ;
|
|
39
|
+
rdfs:domain abi:TemplatableSparqlQuery ;
|
|
40
|
+
rdfs:range abi:QueryArgument ;
|
|
41
|
+
rdfs:comment "Links a templatable SPARQL query to its arguments."@en .
|
|
42
|
+
|
|
43
|
+
#################################################################
|
|
44
|
+
# Data Properties
|
|
45
|
+
#################################################################
|
|
46
|
+
|
|
47
|
+
abi:intentDescription a owl:DatatypeProperty ;
|
|
48
|
+
rdfs:label "intent description"@en ;
|
|
49
|
+
rdfs:domain abi:TemplatableSparqlQuery ;
|
|
50
|
+
rdfs:range xsd:string ;
|
|
51
|
+
rdfs:comment "A natural language description of the query's intent."@en .
|
|
52
|
+
|
|
53
|
+
abi:sparqlTemplate a owl:DatatypeProperty ;
|
|
54
|
+
rdfs:label "SPARQL template"@en ;
|
|
55
|
+
rdfs:domain abi:TemplatableSparqlQuery ;
|
|
56
|
+
rdfs:range xsd:string ;
|
|
57
|
+
rdfs:comment "The SPARQL query template with variable placeholders."@en .
|
|
58
|
+
|
|
59
|
+
abi:argumentName a owl:DatatypeProperty ;
|
|
60
|
+
rdfs:label "argument name"@en ;
|
|
61
|
+
rdfs:domain abi:QueryArgument ;
|
|
62
|
+
rdfs:range xsd:string ;
|
|
63
|
+
rdfs:comment "The name of the argument used in the template."@en .
|
|
64
|
+
|
|
65
|
+
abi:argumentDescription a owl:DatatypeProperty ;
|
|
66
|
+
rdfs:label "argument description"@en ;
|
|
67
|
+
rdfs:domain abi:QueryArgument ;
|
|
68
|
+
rdfs:range xsd:string ;
|
|
69
|
+
rdfs:comment "A description of what the argument represents."@en .
|
|
70
|
+
|
|
71
|
+
abi:validationPattern a owl:DatatypeProperty ;
|
|
72
|
+
rdfs:label "validation pattern"@en ;
|
|
73
|
+
rdfs:domain abi:QueryArgument ;
|
|
74
|
+
rdfs:range xsd:string ;
|
|
75
|
+
rdfs:comment "A regex pattern for validating the argument value."@en .
|
|
76
|
+
|
|
77
|
+
abi:validationFormat a owl:DatatypeProperty ;
|
|
78
|
+
rdfs:label "validation format"@en ;
|
|
79
|
+
rdfs:domain abi:QueryArgument ;
|
|
80
|
+
rdfs:range xsd:string ;
|
|
81
|
+
rdfs:comment "The expected format of the argument value (e.g., date, number, URI)."@en .
|
|
82
|
+
|
|
83
|
+
#################################################################
|
|
84
|
+
# Example Usage
|
|
85
|
+
#################################################################
|
|
86
|
+
|
|
87
|
+
# # Example of a query to find employees in a department with optional name filter:
|
|
88
|
+
# abi:findEmployeesQuery a abi:TemplatableSparqlQuery ;
|
|
89
|
+
# rdfs:label "findEmployeesQuery"@en ;
|
|
90
|
+
# abi:intentDescription "Find all employees working in a specific department, optionally filtered by name" ;
|
|
91
|
+
# abi:sparqlTemplate """
|
|
92
|
+
# SELECT ?employee ?name
|
|
93
|
+
# WHERE {
|
|
94
|
+
# ?employee :worksIn :{{ department_id }} ;
|
|
95
|
+
# :hasName ?name .
|
|
96
|
+
# {% if employee_name %}
|
|
97
|
+
# FILTER(CONTAINS(LCASE(?name), LCASE("{{ employee_name }}")))
|
|
98
|
+
# {% endif %}
|
|
99
|
+
# }
|
|
100
|
+
# """ ;
|
|
101
|
+
# abi:hasArgument abi:departmentArg, abi:nameArg .
|
|
102
|
+
|
|
103
|
+
# abi:departmentArg a abi:QueryArgument ;
|
|
104
|
+
# abi:argumentName "department_id" ;
|
|
105
|
+
# abi:argumentDescription "The unique identifier of the department" ;
|
|
106
|
+
# abi:validationPattern "^[A-Z0-9]{3,}$" ;
|
|
107
|
+
# abi:validationFormat "department_id" .
|
|
108
|
+
|
|
109
|
+
# abi:nameArg a abi:QueryArgument ;
|
|
110
|
+
# abi:argumentName "employee_name" ;
|
|
111
|
+
# abi:argumentDescription "Optional name to filter employees (case-insensitive partial match)" ;
|
|
112
|
+
# abi:validationPattern "^[a-zA-Z0-9\\s-]{2,50}$" ;
|
|
113
|
+
# abi:validationFormat "employee_name" .
|
|
114
|
+
|
|
115
|
+
# #################################################################
|
|
116
|
+
|