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.
- naas_abi/__init__.py +35 -0
- naas_abi/agents/AbiAgent.py +442 -0
- naas_abi/agents/AbiAgent_test.py +157 -0
- naas_abi/agents/EntitytoSPARQLAgent.py +952 -0
- naas_abi/agents/EntitytoSPARQLAgent_test.py +66 -0
- naas_abi/agents/KnowledgeGraphBuilderAgent.py +321 -0
- naas_abi/agents/KnowledgeGraphBuilderAgent_test.py +86 -0
- naas_abi/agents/OntologyEngineerAgent.py +115 -0
- naas_abi/agents/OntologyEngineerAgent_test.py +42 -0
- naas_abi/apps/oxigraph_admin/main.py +392 -0
- naas_abi/apps/oxigraph_admin/terminal_style.py +151 -0
- naas_abi/apps/sparql_terminal/main.py +68 -0
- naas_abi/apps/sparql_terminal/terminal_style.py +236 -0
- naas_abi/apps/terminal_agent/main.py +553 -0
- naas_abi/apps/terminal_agent/terminal_style.py +175 -0
- naas_abi/cli.py +714 -0
- naas_abi/mappings.py +83 -0
- naas_abi/models/airgap_gemma.py +220 -0
- naas_abi/models/airgap_qwen.py +24 -0
- naas_abi/models/default.py +23 -0
- naas_abi/models/gpt_4_1.py +25 -0
- naas_abi/pipelines/AIAgentOntologyGenerationPipeline.py +635 -0
- naas_abi/pipelines/AIAgentOntologyGenerationPipeline_test.py +133 -0
- naas_abi/pipelines/AddIndividualPipeline.py +215 -0
- naas_abi/pipelines/AddIndividualPipeline_test.py +66 -0
- naas_abi/pipelines/InsertDataSPARQLPipeline.py +197 -0
- naas_abi/pipelines/InsertDataSPARQLPipeline_test.py +96 -0
- naas_abi/pipelines/MergeIndividualsPipeline.py +245 -0
- naas_abi/pipelines/MergeIndividualsPipeline_test.py +98 -0
- naas_abi/pipelines/RemoveIndividualPipeline.py +166 -0
- naas_abi/pipelines/RemoveIndividualPipeline_test.py +58 -0
- naas_abi/pipelines/UpdateCommercialOrganizationPipeline.py +198 -0
- naas_abi/pipelines/UpdateDataPropertyPipeline.py +175 -0
- naas_abi/pipelines/UpdateLegalNamePipeline.py +107 -0
- naas_abi/pipelines/UpdateLinkedInPagePipeline.py +179 -0
- naas_abi/pipelines/UpdatePersonPipeline.py +184 -0
- naas_abi/pipelines/UpdateSkillPipeline.py +118 -0
- naas_abi/pipelines/UpdateTickerPipeline.py +104 -0
- naas_abi/pipelines/UpdateWebsitePipeline.py +106 -0
- naas_abi/triggers.py +131 -0
- naas_abi/workflows/AgentRecommendationWorkflow.py +321 -0
- naas_abi/workflows/AgentRecommendationWorkflow_test.py +160 -0
- naas_abi/workflows/ArtificialAnalysisWorkflow.py +337 -0
- naas_abi/workflows/ArtificialAnalysisWorkflow_test.py +57 -0
- naas_abi/workflows/ConvertOntologyGraphToYamlWorkflow.py +210 -0
- naas_abi/workflows/ConvertOntologyGraphToYamlWorkflow_test.py +78 -0
- naas_abi/workflows/CreateClassOntologyYamlWorkflow.py +208 -0
- naas_abi/workflows/CreateClassOntologyYamlWorkflow_test.py +65 -0
- naas_abi/workflows/CreateIndividualOntologyYamlWorkflow.py +183 -0
- naas_abi/workflows/CreateIndividualOntologyYamlWorkflow_test.py +86 -0
- naas_abi/workflows/ExportGraphInstancesToExcelWorkflow.py +450 -0
- naas_abi/workflows/ExportGraphInstancesToExcelWorkflow_test.py +33 -0
- naas_abi/workflows/GetObjectPropertiesFromClassWorkflow.py +385 -0
- naas_abi/workflows/GetObjectPropertiesFromClassWorkflow_test.py +57 -0
- naas_abi/workflows/GetSubjectGraphWorkflow.py +84 -0
- naas_abi/workflows/GetSubjectGraphWorkflow_test.py +71 -0
- naas_abi/workflows/SearchIndividualWorkflow.py +190 -0
- naas_abi/workflows/SearchIndividualWorkflow_test.py +98 -0
- naas_abi-1.0.0.dist-info/METADATA +9 -0
- naas_abi-1.0.0.dist-info/RECORD +62 -0
- naas_abi-1.0.0.dist-info/WHEEL +5 -0
- 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
|