naas-abi 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 (62) hide show
  1. naas_abi/__init__.py +35 -0
  2. naas_abi/agents/AbiAgent.py +442 -0
  3. naas_abi/agents/AbiAgent_test.py +157 -0
  4. naas_abi/agents/EntitytoSPARQLAgent.py +952 -0
  5. naas_abi/agents/EntitytoSPARQLAgent_test.py +66 -0
  6. naas_abi/agents/KnowledgeGraphBuilderAgent.py +321 -0
  7. naas_abi/agents/KnowledgeGraphBuilderAgent_test.py +86 -0
  8. naas_abi/agents/OntologyEngineerAgent.py +115 -0
  9. naas_abi/agents/OntologyEngineerAgent_test.py +42 -0
  10. naas_abi/apps/oxigraph_admin/main.py +392 -0
  11. naas_abi/apps/oxigraph_admin/terminal_style.py +151 -0
  12. naas_abi/apps/sparql_terminal/main.py +68 -0
  13. naas_abi/apps/sparql_terminal/terminal_style.py +236 -0
  14. naas_abi/apps/terminal_agent/main.py +553 -0
  15. naas_abi/apps/terminal_agent/terminal_style.py +175 -0
  16. naas_abi/cli.py +714 -0
  17. naas_abi/mappings.py +83 -0
  18. naas_abi/models/airgap_gemma.py +220 -0
  19. naas_abi/models/airgap_qwen.py +24 -0
  20. naas_abi/models/default.py +23 -0
  21. naas_abi/models/gpt_4_1.py +25 -0
  22. naas_abi/pipelines/AIAgentOntologyGenerationPipeline.py +635 -0
  23. naas_abi/pipelines/AIAgentOntologyGenerationPipeline_test.py +133 -0
  24. naas_abi/pipelines/AddIndividualPipeline.py +215 -0
  25. naas_abi/pipelines/AddIndividualPipeline_test.py +66 -0
  26. naas_abi/pipelines/InsertDataSPARQLPipeline.py +197 -0
  27. naas_abi/pipelines/InsertDataSPARQLPipeline_test.py +96 -0
  28. naas_abi/pipelines/MergeIndividualsPipeline.py +245 -0
  29. naas_abi/pipelines/MergeIndividualsPipeline_test.py +98 -0
  30. naas_abi/pipelines/RemoveIndividualPipeline.py +166 -0
  31. naas_abi/pipelines/RemoveIndividualPipeline_test.py +58 -0
  32. naas_abi/pipelines/UpdateCommercialOrganizationPipeline.py +198 -0
  33. naas_abi/pipelines/UpdateDataPropertyPipeline.py +175 -0
  34. naas_abi/pipelines/UpdateLegalNamePipeline.py +107 -0
  35. naas_abi/pipelines/UpdateLinkedInPagePipeline.py +179 -0
  36. naas_abi/pipelines/UpdatePersonPipeline.py +184 -0
  37. naas_abi/pipelines/UpdateSkillPipeline.py +118 -0
  38. naas_abi/pipelines/UpdateTickerPipeline.py +104 -0
  39. naas_abi/pipelines/UpdateWebsitePipeline.py +106 -0
  40. naas_abi/triggers.py +131 -0
  41. naas_abi/workflows/AgentRecommendationWorkflow.py +321 -0
  42. naas_abi/workflows/AgentRecommendationWorkflow_test.py +160 -0
  43. naas_abi/workflows/ArtificialAnalysisWorkflow.py +337 -0
  44. naas_abi/workflows/ArtificialAnalysisWorkflow_test.py +57 -0
  45. naas_abi/workflows/ConvertOntologyGraphToYamlWorkflow.py +210 -0
  46. naas_abi/workflows/ConvertOntologyGraphToYamlWorkflow_test.py +78 -0
  47. naas_abi/workflows/CreateClassOntologyYamlWorkflow.py +208 -0
  48. naas_abi/workflows/CreateClassOntologyYamlWorkflow_test.py +65 -0
  49. naas_abi/workflows/CreateIndividualOntologyYamlWorkflow.py +183 -0
  50. naas_abi/workflows/CreateIndividualOntologyYamlWorkflow_test.py +86 -0
  51. naas_abi/workflows/ExportGraphInstancesToExcelWorkflow.py +450 -0
  52. naas_abi/workflows/ExportGraphInstancesToExcelWorkflow_test.py +33 -0
  53. naas_abi/workflows/GetObjectPropertiesFromClassWorkflow.py +385 -0
  54. naas_abi/workflows/GetObjectPropertiesFromClassWorkflow_test.py +57 -0
  55. naas_abi/workflows/GetSubjectGraphWorkflow.py +84 -0
  56. naas_abi/workflows/GetSubjectGraphWorkflow_test.py +71 -0
  57. naas_abi/workflows/SearchIndividualWorkflow.py +190 -0
  58. naas_abi/workflows/SearchIndividualWorkflow_test.py +98 -0
  59. naas_abi-1.0.0.dist-info/METADATA +9 -0
  60. naas_abi-1.0.0.dist-info/RECORD +62 -0
  61. naas_abi-1.0.0.dist-info/WHEEL +5 -0
  62. naas_abi-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,385 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+ from typing import Annotated, Optional
4
+
5
+ import pydash
6
+ from fastapi import APIRouter
7
+ from langchain_core.tools import BaseTool, StructuredTool
8
+ from naas_abi_core.services.triple_store.TripleStorePorts import ITripleStoreService
9
+ from naas_abi_core.workflow import Workflow, WorkflowConfiguration
10
+ from naas_abi_core.workflow.workflow import WorkflowParameters
11
+ from pydantic import Field
12
+ from rdflib import OWL, RDF, RDFS, Graph, URIRef
13
+
14
+ # Ontology operators mapping
15
+ ONTOLOGY_OPERATORS = {"unionOf": "or", "intersectionOf": "and", "complementOf": "not"}
16
+
17
+
18
+ @dataclass
19
+ class GetObjectPropertiesFromClassWorkflowConfiguration(WorkflowConfiguration):
20
+ """Configuration for GetObjectPropertiesFromClassWorkflow.
21
+
22
+ Attributes:
23
+ triple_store (ITripleStoreService): The triple store service to use
24
+ ontology_file_path (str): Path to the ontology file
25
+ """
26
+
27
+ triple_store: ITripleStoreService
28
+ ontology_file_path: Optional[str] = None
29
+
30
+
31
+ class GetObjectPropertiesFromClassWorkflowParameters(WorkflowParameters):
32
+ """Parameters for GetObjectPropertiesFromClassWorkflow execution.
33
+
34
+ Attributes:
35
+ class_uri (str): URI of the class to get object properties for
36
+ """
37
+
38
+ class_uri: Annotated[
39
+ str,
40
+ Field(
41
+ ...,
42
+ description="URI of the class to get object properties for",
43
+ example="http://purl.obolibrary.org/obo/BFO_0000040",
44
+ pattern="^http.*",
45
+ ),
46
+ ]
47
+
48
+
49
+ class GetObjectPropertiesFromClassWorkflow(Workflow):
50
+ """Workflow for getting object properties of a given class from ontology."""
51
+
52
+ __configuration: GetObjectPropertiesFromClassWorkflowConfiguration
53
+
54
+ def __init__(
55
+ self, configuration: GetObjectPropertiesFromClassWorkflowConfiguration
56
+ ):
57
+ super().__init__(configuration)
58
+ self.__configuration = configuration
59
+ self.graph = Graph()
60
+ if self.__configuration.ontology_file_path:
61
+ self.graph.parse(self.__configuration.ontology_file_path, format="turtle")
62
+ else:
63
+ self.graph = self.__configuration.triple_store.get_schema_graph()
64
+ self.operators = ONTOLOGY_OPERATORS
65
+ self._ = pydash
66
+
67
+ # Initialize data structures like in generate_docs
68
+ self.onto_tuples: dict = {}
69
+ self.onto_classes: dict = {}
70
+ self.onto: dict = {}
71
+
72
+ # Create basic mapping for common properties
73
+ self.mapping: dict = {
74
+ "http://www.w3.org/2000/01/rdf-schema#label": "label",
75
+ "http://www.w3.org/2000/01/rdf-schema#domain": "domain",
76
+ "http://www.w3.org/2000/01/rdf-schema#range": "range",
77
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#type": "type",
78
+ "http://www.w3.org/2000/01/rdf-schema#subClassOf": "subclassOf",
79
+ "http://www.w3.org/2002/07/owl#unionOf": "unionOf",
80
+ "http://www.w3.org/2002/07/owl#intersectionOf": "intersectionOf",
81
+ "http://www.w3.org/2002/07/owl#complementOf": "complementOf",
82
+ }
83
+
84
+ # Load triples into onto_tuples structure
85
+ self._load_triples()
86
+
87
+ # Load classes
88
+ self._load_classes()
89
+
90
+ def _load_triples(self):
91
+ """Load SPO triples into onto_tuples dictionary structure."""
92
+ for s, p, o in self.graph:
93
+ self._handle_onto_tuples(s, p, o)
94
+
95
+ # Keep only the predicates that are in the mapping.
96
+ if str(p) not in self.mapping:
97
+ continue
98
+
99
+ # If the subject is not in the onto dictionary, we create a new dict for it.
100
+ if str(s) not in self.onto:
101
+ self.onto[str(s)] = {"__id": str(s)}
102
+
103
+ # If the predicate is not in the onto dict, we create a new list for it.
104
+ if self.mapping[str(p)] not in self.onto[str(s)]:
105
+ self.onto[str(s)][self.mapping[str(p)]] = []
106
+
107
+ # We append the object to the list of the predicate.
108
+ self.onto[str(s)][self.mapping[str(p)]].append(str(o))
109
+
110
+ def _handle_onto_tuples(self, s, p, o):
111
+ """Load SPO in onto_tuples dictionary."""
112
+ if str(s) not in self.onto_tuples:
113
+ self.onto_tuples[str(s)] = []
114
+ self.onto_tuples[str(s)].append((p, o))
115
+
116
+ def _load_classes(self):
117
+ """Load classes from the ontology."""
118
+ # Filter the classes from the ontology.
119
+ _onto_classes = self._.filter_(
120
+ self.onto,
121
+ lambda x: "http://www.w3.org/2002/07/owl#Class" in x["type"]
122
+ if "type" in x
123
+ else None,
124
+ )
125
+
126
+ # Remove subclassOf that are restrictions to keep it simple for now.
127
+ for cls in _onto_classes:
128
+ cls["subclassOf"] = self._.filter_(
129
+ cls.get("subclassOf", []), lambda x: True if "http" in x else False
130
+ )
131
+
132
+ # Rebuild dictionary with __id as the key
133
+ self.onto_classes = {e["__id"]: e for e in _onto_classes}
134
+
135
+ def _get_first_rest(self, tpl):
136
+ """Get the values from unionOf, intersectionOf and complementOf."""
137
+ first = None
138
+ rest = None
139
+ for i in tpl:
140
+ a, b = i
141
+
142
+ if str(a) == "http://www.w3.org/1999/02/22-rdf-syntax-ns#first":
143
+ first = str(b)
144
+
145
+ if (
146
+ str(a) == "http://www.w3.org/1999/02/22-rdf-syntax-ns#rest"
147
+ and str(b) != "http://www.w3.org/1999/02/22-rdf-syntax-ns#nil"
148
+ ):
149
+ rest = str(b)
150
+ return first, rest
151
+
152
+ def get_linked_classes(self, cls_id, rel_type=None):
153
+ """Recursive function to build a tree of classes based on unionOf, intersectionOf and complementOf."""
154
+ # Handle None case
155
+ if cls_id is None:
156
+ return []
157
+
158
+ # If it's a leaf we return a dict with the class id and the operator.
159
+ if "http" in cls_id:
160
+ if rel_type is not None and rel_type in self.operators:
161
+ return {self.operators[rel_type]: [cls_id]}
162
+ return [cls_id]
163
+
164
+ # If it's a class, we want to go over the unionOf, intersectionOf and complementOf.
165
+ if cls_id in self.onto_classes:
166
+ cls = self.onto_classes[cls_id]
167
+ res = (
168
+ [
169
+ self.get_linked_classes(e, "unionOf")
170
+ for e in self._.get(cls, "unionOf", [])
171
+ ]
172
+ + [
173
+ self.get_linked_classes(e, "intersectionOf")
174
+ for e in self._.get(cls, "intersectionOf", [])
175
+ ]
176
+ + [
177
+ self.get_linked_classes(e, "complementOf")
178
+ for e in self._.get(cls, "complementOf", [])
179
+ ]
180
+ )
181
+ return res
182
+ else:
183
+ # If it's not a class, then we will have a 'first' and a 'rest' to handle.
184
+ first, rest = self._get_first_rest(self.onto_tuples[cls_id])
185
+
186
+ # We grab the operator based on the rel_type.
187
+ operator = self.operators[rel_type]
188
+
189
+ # We get the left/first value.
190
+ left = self.get_linked_classes(first, rel_type)
191
+ if rest:
192
+ # We get the right/rest value.
193
+ right = self.get_linked_classes(rest, rel_type)
194
+
195
+ if operator in right and operator in left:
196
+ if (
197
+ operator in right
198
+ and type(right[operator]) is dict
199
+ and operator in right[operator]
200
+ and type(right[operator][operator]) is list
201
+ ):
202
+ right[operator] = right[operator][operator]
203
+
204
+ return {operator: self._.flatten([left[operator], right[operator]])}
205
+ else:
206
+ return {operator: self._.flatten([left, right])}
207
+ else:
208
+ return {operator: left}
209
+
210
+ # We map the ranges and domains to the classes by calling get_linked_classes.
211
+ def map_ranges_domains(self, x):
212
+ if "domain" in x:
213
+ x["domain"] = self._.map_(
214
+ x["domain"],
215
+ lambda x: x
216
+ if (x is not None and "http" in x)
217
+ else (
218
+ self.get_linked_classes(x)[0]
219
+ if self.get_linked_classes(x)
220
+ else None
221
+ ),
222
+ )
223
+ if "range" in x:
224
+ x["range"] = self._.map_(
225
+ x["range"],
226
+ lambda x: x
227
+ if (x is not None and "http" in x)
228
+ else (
229
+ self.get_linked_classes(x)[0]
230
+ if self.get_linked_classes(x)
231
+ else None
232
+ ),
233
+ )
234
+ return x
235
+
236
+ def is_class(self, uri):
237
+ """Check if the given URI is a class in the ontology."""
238
+ uri_ref = URIRef(uri)
239
+ # Check if it's declared as an OWL Class
240
+ return (uri_ref, RDF.type, OWL.Class) in self.graph
241
+
242
+ def _class_matches_domain(self, class_uri, domains):
243
+ """Check if class_uri matches any of the domains (including complex expressions)."""
244
+ for domain in domains:
245
+ if isinstance(domain, str) and domain == class_uri:
246
+ return True
247
+ elif isinstance(domain, dict):
248
+ # Handle complex expressions
249
+ if "or" in domain and class_uri in domain["or"]:
250
+ return True
251
+ if "and" in domain and class_uri in domain["and"]:
252
+ return True
253
+ return False
254
+
255
+ def _process_ranges(self, ranges):
256
+ """Process ranges with sophisticated logic to handle complex expressions."""
257
+ processed_ranges = []
258
+
259
+ def _get_labeled_uri(uri):
260
+ """Get label for a URI."""
261
+ range_uri = URIRef(uri)
262
+ range_label = self.graph.value(range_uri, RDFS.label)
263
+ return {"uri": uri, "label": str(range_label) if range_label else None}
264
+
265
+ def _process_range_item(range_item):
266
+ """Process range item recursively."""
267
+ if isinstance(range_item, str):
268
+ return _get_labeled_uri(range_item)
269
+ elif isinstance(range_item, dict):
270
+ processed_dict = {}
271
+ for key, value in range_item.items():
272
+ if key in ["or", "and", "not"]:
273
+ if isinstance(value, list):
274
+ processed_dict[key] = [
275
+ _process_range_item(item) for item in value
276
+ ]
277
+ else:
278
+ processed_dict[key] = _process_range_item(value)
279
+ return processed_dict
280
+ return range_item
281
+
282
+ for range_item in ranges:
283
+ processed_range = _process_range_item(range_item)
284
+ processed_ranges.append(processed_range)
285
+
286
+ return processed_ranges
287
+
288
+ def get_object_properties_from_class(
289
+ self, parameters: GetObjectPropertiesFromClassWorkflowParameters
290
+ ) -> dict:
291
+ """Get all object properties for a given class using sophisticated logic."""
292
+ # First, validate that the URI is actually a class
293
+ if not self.is_class(parameters.class_uri):
294
+ raise ValueError(
295
+ f"URI {parameters.class_uri} is not a class in the ontology"
296
+ )
297
+
298
+ class_uri = URIRef(parameters.class_uri)
299
+
300
+ object_properties: list[dict] = []
301
+
302
+ # Query for object properties with domain class_uri or union containing it
303
+ for s, p, o in self.graph.triples((None, RDF.type, OWL.ObjectProperty)):
304
+ property_data = {
305
+ "object_property_uri": str(s),
306
+ "object_property_label": str(self.graph.value(s, RDFS.label) or ""),
307
+ "domain": [],
308
+ "range": [],
309
+ }
310
+
311
+ # Collect all domains
312
+ property_data["domain"] = [
313
+ str(domain_uri) for domain_uri in self.graph.objects(s, RDFS.domain)
314
+ ]
315
+
316
+ # Collect all ranges
317
+ property_data["range"] = [
318
+ str(range_uri) for range_uri in self.graph.objects(s, RDFS.range)
319
+ ]
320
+
321
+ # Apply sophisticated mapping logic from generate_docs
322
+ property_data = self.map_ranges_domains(property_data)
323
+
324
+ # Check if class_uri is in the domain
325
+ if self._class_matches_domain(str(class_uri), property_data["domain"]):
326
+ # Process ranges with sophisticated logic
327
+ processed_ranges = self._process_ranges(property_data["range"])
328
+
329
+ object_properties.append(
330
+ {
331
+ "object_property_uri": str(s),
332
+ "object_property_label": property_data["object_property_label"],
333
+ "ranges": processed_ranges,
334
+ }
335
+ )
336
+
337
+ return {
338
+ "class_uri": parameters.class_uri,
339
+ "object_properties": object_properties,
340
+ }
341
+
342
+ def as_tools(self) -> list[BaseTool]:
343
+ """Returns a list of LangChain tools for this workflow.
344
+
345
+ Returns:
346
+ list[BaseTool]: List containing the workflow tool
347
+ """
348
+ return [
349
+ StructuredTool(
350
+ name="get_object_properties_from_class",
351
+ description="Get all object properties for a given class from the ontology.",
352
+ func=lambda **kwargs: self.get_object_properties_from_class(
353
+ GetObjectPropertiesFromClassWorkflowParameters(**kwargs)
354
+ ),
355
+ args_schema=GetObjectPropertiesFromClassWorkflowParameters,
356
+ )
357
+ ]
358
+
359
+ def as_api(
360
+ self,
361
+ router: APIRouter,
362
+ route_name: str = "",
363
+ name: str = "",
364
+ description: str = "",
365
+ description_stream: str = "",
366
+ tags: list[str | Enum] | None = None,
367
+ ) -> None:
368
+ if tags is None:
369
+ tags = []
370
+ return None
371
+
372
+
373
+ if __name__ == "__main__":
374
+ from naas_abi_core import logger, services
375
+
376
+ workflow = GetObjectPropertiesFromClassWorkflow(
377
+ GetObjectPropertiesFromClassWorkflowConfiguration(
378
+ triple_store=services.triple_store_service
379
+ )
380
+ )
381
+ class_uri = "http://purl.obolibrary.org/obo/BFO_0000015"
382
+ result = workflow.get_object_properties_from_class(
383
+ GetObjectPropertiesFromClassWorkflowParameters(class_uri=class_uri)
384
+ )
385
+ logger.info(result)
@@ -0,0 +1,57 @@
1
+ import pytest
2
+ from naas_abi.workflows.GetObjectPropertiesFromClassWorkflow import (
3
+ GetObjectPropertiesFromClassWorkflow,
4
+ GetObjectPropertiesFromClassWorkflowConfiguration,
5
+ GetObjectPropertiesFromClassWorkflowParameters,
6
+ )
7
+ from naas_abi_core import logger, services
8
+
9
+
10
+ @pytest.fixture
11
+ def workflow() -> GetObjectPropertiesFromClassWorkflow:
12
+ return GetObjectPropertiesFromClassWorkflow(
13
+ GetObjectPropertiesFromClassWorkflowConfiguration(
14
+ triple_store=services.triple_store_service
15
+ )
16
+ )
17
+
18
+
19
+ def test_get_object_properties_from_bfo_core(
20
+ workflow: GetObjectPropertiesFromClassWorkflow,
21
+ ):
22
+ class_uri = "http://purl.obolibrary.org/obo/BFO_0000015"
23
+
24
+ result = workflow.get_object_properties_from_class(
25
+ GetObjectPropertiesFromClassWorkflowParameters(class_uri=class_uri)
26
+ )
27
+
28
+ logger.info(result)
29
+ assert result is not None, result
30
+ assert len(result.get("object_properties", [])) > 0, result
31
+ assert result.get("class_uri") == class_uri, result
32
+
33
+
34
+ def test_get_object_properties_from_cco(workflow: GetObjectPropertiesFromClassWorkflow):
35
+ class_uri = "https://www.commoncoreontologies.org/ont00000443"
36
+
37
+ result = workflow.get_object_properties_from_class(
38
+ GetObjectPropertiesFromClassWorkflowParameters(class_uri=class_uri)
39
+ )
40
+
41
+ logger.info(result)
42
+ assert result is not None, result
43
+ assert len(result.get("object_properties", [])) == 0, result
44
+ assert result.get("class_uri") == class_uri, result
45
+
46
+
47
+ def test_get_object_properties_from_abi(workflow: GetObjectPropertiesFromClassWorkflow):
48
+ class_uri = "http://ontology.naas.ai/abi/Website"
49
+
50
+ result = workflow.get_object_properties_from_class(
51
+ GetObjectPropertiesFromClassWorkflowParameters(class_uri=class_uri)
52
+ )
53
+
54
+ logger.info(result)
55
+ assert result is not None, result
56
+ assert len(result.get("object_properties", [])) > 0, result
57
+ assert result.get("class_uri") == class_uri, result
@@ -0,0 +1,84 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+ from typing import Annotated
4
+
5
+ from fastapi import APIRouter
6
+ from langchain_core.tools import BaseTool, StructuredTool
7
+ from naas_abi import ABIModule
8
+ from naas_abi_core.utils.Graph import URI_REGEX
9
+ from naas_abi_core.utils.SPARQL import SPARQLUtils
10
+ from naas_abi_core.workflow import Workflow, WorkflowConfiguration
11
+ from naas_abi_core.workflow.workflow import WorkflowParameters
12
+ from pydantic import Field
13
+
14
+
15
+ @dataclass
16
+ class GetSubjectGraphWorkflowConfiguration(WorkflowConfiguration):
17
+ """Configuration for SearchIndividual workflow."""
18
+
19
+ pass
20
+
21
+
22
+ class GetSubjectGraphWorkflowParameters(WorkflowParameters):
23
+ """Parameters for GetSubjectGraph workflow."""
24
+
25
+ uri: Annotated[
26
+ str,
27
+ Field(
28
+ ...,
29
+ description="URI of the individual/instance to get the subject graph of.",
30
+ example="http://ontology.naas.ai/abi/a25ef0cc-56cf-458a-88c0-fabccb69e9b7",
31
+ pattern=URI_REGEX,
32
+ ),
33
+ ]
34
+ depth: Annotated[
35
+ int,
36
+ Field(
37
+ 2,
38
+ description="Depth of the subject graph to get. 1 means the individual and its direct properties, 2 means the individual and its direct properties and the properties of the properties, etc.",
39
+ example=2,
40
+ ),
41
+ ] = 2
42
+
43
+
44
+ class GetSubjectGraphWorkflow(Workflow):
45
+ """Workflow for getting the subject graph of an ontology individual."""
46
+
47
+ __configuration: GetSubjectGraphWorkflowConfiguration
48
+ __sparql_utils: SPARQLUtils
49
+
50
+ def __init__(self, configuration: GetSubjectGraphWorkflowConfiguration):
51
+ super().__init__(configuration)
52
+ self.__configuration = configuration
53
+ self.__sparql_utils: SPARQLUtils = SPARQLUtils(
54
+ ABIModule.get_instance().engine.services.triple_store
55
+ )
56
+
57
+ def get_subject_graph(self, parameters: GetSubjectGraphWorkflowParameters) -> str:
58
+ graph = self.__sparql_utils.get_subject_graph(parameters.uri, parameters.depth)
59
+ return graph.serialize(format="turtle")
60
+
61
+ def as_tools(self) -> list[BaseTool]:
62
+ return [
63
+ StructuredTool(
64
+ name="get_subject_graph",
65
+ description="Get the data about a specific individual/instance from the triplestore.",
66
+ func=lambda **kwargs: self.get_subject_graph(
67
+ GetSubjectGraphWorkflowParameters(**kwargs)
68
+ ),
69
+ args_schema=GetSubjectGraphWorkflowParameters,
70
+ )
71
+ ]
72
+
73
+ def as_api(
74
+ self,
75
+ router: APIRouter,
76
+ route_name: str = "",
77
+ name: str = "",
78
+ description: str = "",
79
+ description_stream: str = "",
80
+ tags: list[str | Enum] | None = None,
81
+ ) -> None:
82
+ if tags is None:
83
+ tags = []
84
+ return None
@@ -0,0 +1,71 @@
1
+ import pytest
2
+ from naas_abi.workflows.GetSubjectGraphWorkflow import (
3
+ GetSubjectGraphWorkflow,
4
+ GetSubjectGraphWorkflowConfiguration,
5
+ GetSubjectGraphWorkflowParameters,
6
+ )
7
+
8
+
9
+ @pytest.fixture
10
+ def get_subject_graph_workflow() -> GetSubjectGraphWorkflow:
11
+ # Configurations
12
+ get_subject_graph_workflow_configuration = GetSubjectGraphWorkflowConfiguration()
13
+
14
+ # Workflow
15
+ get_subject_graph_workflow = GetSubjectGraphWorkflow(
16
+ configuration=get_subject_graph_workflow_configuration
17
+ )
18
+
19
+ return get_subject_graph_workflow
20
+
21
+
22
+ def test_get_subject_graph_workflow(
23
+ get_subject_graph_workflow: GetSubjectGraphWorkflow,
24
+ ):
25
+ from uuid import uuid4
26
+
27
+ from naas_abi_core import logger, services
28
+ from naas_abi_core.utils.Graph import TEST
29
+ from rdflib import OWL, RDFS, Graph, Literal, URIRef
30
+
31
+ graph = Graph()
32
+ node_id = str(uuid4())
33
+ uri = TEST[node_id]
34
+ logger.debug(f"Creating graph with URI: {uri}")
35
+ graph.add(
36
+ (
37
+ uri,
38
+ URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
39
+ URIRef("https://www.commoncoreontologies.org/ont00000443"),
40
+ )
41
+ )
42
+ graph.add(
43
+ (
44
+ uri,
45
+ URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
46
+ OWL.NamedIndividual,
47
+ )
48
+ )
49
+ graph.add((uri, RDFS.label, Literal(node_id)))
50
+
51
+ services.triple_store_service.insert(graph)
52
+
53
+ result = get_subject_graph_workflow.get_subject_graph(
54
+ GetSubjectGraphWorkflowParameters(
55
+ uri=str(uri),
56
+ )
57
+ )
58
+
59
+ assert isinstance(result, str), result
60
+ assert result != "", result
61
+
62
+ services.triple_store_service.remove(graph)
63
+
64
+ # Test
65
+ result = get_subject_graph_workflow.get_subject_graph(
66
+ GetSubjectGraphWorkflowParameters(
67
+ uri=str(uri),
68
+ )
69
+ )
70
+
71
+ assert isinstance(result, str), result