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,133 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from naas_abi.pipelines.AIAgentOntologyGenerationPipeline import (
|
|
5
|
+
AIAgentOntologyGenerationConfiguration,
|
|
6
|
+
AIAgentOntologyGenerationParameters,
|
|
7
|
+
AIAgentOntologyGenerationPipeline,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def pipeline() -> AIAgentOntologyGenerationPipeline:
|
|
13
|
+
from naas_abi import services
|
|
14
|
+
|
|
15
|
+
pipeline_configuration = AIAgentOntologyGenerationConfiguration(
|
|
16
|
+
triple_store=services.triple_store,
|
|
17
|
+
datastore_path="storage/datastore/test/ai_agent_ontology_generation",
|
|
18
|
+
source_datastore_path="storage/datastore/test/artificial_analysis_workflow",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
return AIAgentOntologyGenerationPipeline(pipeline_configuration)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.fixture
|
|
25
|
+
def mock_aa_data():
|
|
26
|
+
"""Mock Artificial Analysis data for testing."""
|
|
27
|
+
return {
|
|
28
|
+
"llms": [
|
|
29
|
+
{
|
|
30
|
+
"name": "GPT-4o",
|
|
31
|
+
"slug": "gpt-4o",
|
|
32
|
+
"model_creator": {"name": "OpenAI", "slug": "openai"},
|
|
33
|
+
"pricing": {
|
|
34
|
+
"input_cost": 5.0,
|
|
35
|
+
"output_cost": 15.0,
|
|
36
|
+
"blended_cost": 10.0,
|
|
37
|
+
},
|
|
38
|
+
"performance": {
|
|
39
|
+
"output_speed": 25.5,
|
|
40
|
+
"time_to_first_token": 0.5,
|
|
41
|
+
"time_to_first_answer_token": 1.2,
|
|
42
|
+
},
|
|
43
|
+
"evaluations": {"index": 85, "coding_index": 90, "math_index": 80},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "Claude 3.5 Sonnet",
|
|
47
|
+
"slug": "claude-35-sonnet",
|
|
48
|
+
"model_creator": {"name": "Anthropic", "slug": "anthropic"},
|
|
49
|
+
"pricing": {
|
|
50
|
+
"input_cost": 3.0,
|
|
51
|
+
"output_cost": 15.0,
|
|
52
|
+
"blended_cost": 9.0,
|
|
53
|
+
},
|
|
54
|
+
"performance": {
|
|
55
|
+
"output_speed": 22.1,
|
|
56
|
+
"time_to_first_token": 0.8,
|
|
57
|
+
"time_to_first_answer_token": 1.5,
|
|
58
|
+
},
|
|
59
|
+
"evaluations": {"index": 88, "coding_index": 85, "math_index": 82},
|
|
60
|
+
},
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_pipeline_basic_functionality(
|
|
66
|
+
pipeline: AIAgentOntologyGenerationPipeline, mock_aa_data, tmp_path
|
|
67
|
+
):
|
|
68
|
+
"""Test basic pipeline functionality with mock data."""
|
|
69
|
+
|
|
70
|
+
# Setup test paths
|
|
71
|
+
source_path = tmp_path / "source"
|
|
72
|
+
source_path.mkdir()
|
|
73
|
+
output_path = tmp_path / "output"
|
|
74
|
+
|
|
75
|
+
# Create mock data file
|
|
76
|
+
mock_file = source_path / "20250811T120000_llms_data.json"
|
|
77
|
+
with open(mock_file, "w") as f:
|
|
78
|
+
json.dump(mock_aa_data, f)
|
|
79
|
+
|
|
80
|
+
# Update configuration paths
|
|
81
|
+
config = pipeline.get_configuration()
|
|
82
|
+
config.source_datastore_path = str(source_path)
|
|
83
|
+
config.datastore_path = str(output_path)
|
|
84
|
+
|
|
85
|
+
# Run pipeline
|
|
86
|
+
parameters = AIAgentOntologyGenerationParameters(force_regenerate=True)
|
|
87
|
+
graph = pipeline.run(parameters)
|
|
88
|
+
|
|
89
|
+
assert graph is not None
|
|
90
|
+
|
|
91
|
+
# Verify triples were added
|
|
92
|
+
triples = list(graph)
|
|
93
|
+
assert len(triples) > 0
|
|
94
|
+
|
|
95
|
+
for s, p, o in triples:
|
|
96
|
+
assert s is not None
|
|
97
|
+
assert p is not None
|
|
98
|
+
assert o is not None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_model_mapping_logic(pipeline: AIAgentOntologyGenerationPipeline):
|
|
102
|
+
"""Test the model to agent mapping logic."""
|
|
103
|
+
|
|
104
|
+
# Test GPT model mapping
|
|
105
|
+
gpt_model = {
|
|
106
|
+
"name": "GPT-4o",
|
|
107
|
+
"slug": "gpt-4o",
|
|
108
|
+
"model_creator": {"name": "OpenAI"},
|
|
109
|
+
}
|
|
110
|
+
assert pipeline._determine_ai_agent_module(gpt_model) == "chatgpt"
|
|
111
|
+
|
|
112
|
+
# Test Claude model mapping
|
|
113
|
+
claude_model = {
|
|
114
|
+
"name": "Claude 3.5 Sonnet",
|
|
115
|
+
"slug": "claude-35-sonnet",
|
|
116
|
+
"model_creator": {"name": "Anthropic"},
|
|
117
|
+
}
|
|
118
|
+
assert pipeline._determine_ai_agent_module(claude_model) == "claude"
|
|
119
|
+
|
|
120
|
+
# Test Gemini vs Gemma mapping
|
|
121
|
+
gemini_model = {
|
|
122
|
+
"name": "Gemini 2.0",
|
|
123
|
+
"slug": "gemini-2-0",
|
|
124
|
+
"model_creator": {"name": "Google"},
|
|
125
|
+
}
|
|
126
|
+
assert pipeline._determine_ai_agent_module(gemini_model) == "gemini"
|
|
127
|
+
|
|
128
|
+
gemma_model = {
|
|
129
|
+
"name": "Gemma 3 27B",
|
|
130
|
+
"slug": "gemma-3-27b",
|
|
131
|
+
"model_creator": {"name": "Google"},
|
|
132
|
+
}
|
|
133
|
+
assert pipeline._determine_ai_agent_module(gemma_model) == "gemma"
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Annotated, Optional
|
|
5
|
+
|
|
6
|
+
from fastapi import APIRouter
|
|
7
|
+
from langchain_core.tools import BaseTool, StructuredTool
|
|
8
|
+
from naas_abi.workflows.SearchIndividualWorkflow import (
|
|
9
|
+
SearchIndividualWorkflow,
|
|
10
|
+
SearchIndividualWorkflowConfiguration,
|
|
11
|
+
SearchIndividualWorkflowParameters,
|
|
12
|
+
)
|
|
13
|
+
from naas_abi_core import logger
|
|
14
|
+
from naas_abi_core.pipeline import Pipeline, PipelineConfiguration, PipelineParameters
|
|
15
|
+
from naas_abi_core.services.triple_store.TripleStorePorts import ITripleStoreService
|
|
16
|
+
from pydantic import Field
|
|
17
|
+
from rdflib import DCTERMS, OWL, RDF, RDFS, Graph, Literal, Namespace, URIRef
|
|
18
|
+
|
|
19
|
+
BFO = Namespace("http://purl.obolibrary.org/obo/")
|
|
20
|
+
CCO = Namespace("https://www.commoncoreontologies.org/")
|
|
21
|
+
ABI = Namespace("http://ontology.naas.ai/abi/")
|
|
22
|
+
URI_REGEX = r"http:\/\/ontology\.naas\.ai\/abi\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class AddIndividualPipelineConfiguration(PipelineConfiguration):
|
|
27
|
+
"""Configuration for AddIndividualPipeline.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
triple_store (ITripleStoreService): The ontology store service to use
|
|
31
|
+
search_individual_workflow (SearchIndividualWorkflow): The search individual workflow to use
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
triple_store: ITripleStoreService
|
|
35
|
+
search_individual_configuration: SearchIndividualWorkflowConfiguration
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AddIndividualPipelineParameters(PipelineParameters):
|
|
39
|
+
individual_label: Annotated[
|
|
40
|
+
str,
|
|
41
|
+
Field(
|
|
42
|
+
description="Individual label to add to the ontology.", example="Naas.ai"
|
|
43
|
+
),
|
|
44
|
+
]
|
|
45
|
+
class_uri: Annotated[
|
|
46
|
+
str,
|
|
47
|
+
Field(
|
|
48
|
+
description="Class URI to add the individual to. Use tool `search_class` to search for a class URI in the ontology.",
|
|
49
|
+
example="https://www.commoncoreontologies.org/ont00000443",
|
|
50
|
+
),
|
|
51
|
+
]
|
|
52
|
+
threshold: Annotated[
|
|
53
|
+
Optional[int],
|
|
54
|
+
Field(
|
|
55
|
+
description="Threshold to use for the search individual workflow.",
|
|
56
|
+
ge=0,
|
|
57
|
+
le=100,
|
|
58
|
+
),
|
|
59
|
+
] = 80
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class AddIndividualPipeline(Pipeline):
|
|
63
|
+
"""Pipeline for adding a named individual."""
|
|
64
|
+
|
|
65
|
+
__configuration: AddIndividualPipelineConfiguration
|
|
66
|
+
|
|
67
|
+
def __init__(self, configuration: AddIndividualPipelineConfiguration):
|
|
68
|
+
super().__init__(configuration)
|
|
69
|
+
self.__configuration = configuration
|
|
70
|
+
self.__search_individual_workflow = SearchIndividualWorkflow(
|
|
71
|
+
configuration.search_individual_configuration
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def run(self, parameters: PipelineParameters) -> Graph:
|
|
75
|
+
if not isinstance(parameters, AddIndividualPipelineParameters):
|
|
76
|
+
raise ValueError(
|
|
77
|
+
"Parameters must be of type AddIndividualPipelineParameters"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Search for individual
|
|
81
|
+
search_individual_result = self.__search_individual_workflow.search_individual(
|
|
82
|
+
SearchIndividualWorkflowParameters(
|
|
83
|
+
class_uri=parameters.class_uri,
|
|
84
|
+
search_label=parameters.individual_label,
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
filtered_results = [
|
|
88
|
+
result
|
|
89
|
+
for result in search_individual_result
|
|
90
|
+
if parameters.threshold is not None
|
|
91
|
+
and int(result["score"]) >= parameters.threshold
|
|
92
|
+
]
|
|
93
|
+
if len(filtered_results) > 0:
|
|
94
|
+
individual_uri = filtered_results[0]["individual_uri"]
|
|
95
|
+
logger.debug(
|
|
96
|
+
f"🔍 Found individual '{parameters.individual_label}' in the ontology: {individual_uri} from class: {parameters.class_uri}"
|
|
97
|
+
)
|
|
98
|
+
return self.__configuration.triple_store.get_subject_graph(individual_uri)
|
|
99
|
+
|
|
100
|
+
# Init graph
|
|
101
|
+
graph = Graph()
|
|
102
|
+
graph.bind("bfo", BFO)
|
|
103
|
+
graph.bind("cco", CCO)
|
|
104
|
+
graph.bind("abi", ABI)
|
|
105
|
+
graph.bind("dcterms", DCTERMS)
|
|
106
|
+
|
|
107
|
+
# Add individual
|
|
108
|
+
individual_uri = ABI[str(uuid.uuid4())]
|
|
109
|
+
graph.add((individual_uri, RDF.type, OWL.NamedIndividual))
|
|
110
|
+
graph.add((individual_uri, RDF.type, URIRef(parameters.class_uri)))
|
|
111
|
+
graph.add((individual_uri, RDFS.label, Literal(parameters.individual_label)))
|
|
112
|
+
self.__configuration.triple_store.insert(graph)
|
|
113
|
+
return graph
|
|
114
|
+
|
|
115
|
+
def as_tools(self) -> list[BaseTool]:
|
|
116
|
+
return [
|
|
117
|
+
StructuredTool(
|
|
118
|
+
name="add_individual_to_triple_store",
|
|
119
|
+
description="Add a new individual/instance to triple store.",
|
|
120
|
+
func=lambda **kwargs: self.run(
|
|
121
|
+
AddIndividualPipelineParameters(**kwargs)
|
|
122
|
+
),
|
|
123
|
+
args_schema=AddIndividualPipelineParameters,
|
|
124
|
+
),
|
|
125
|
+
StructuredTool(
|
|
126
|
+
name="add_commercial_organization",
|
|
127
|
+
description="Add a new commercial organization to ontology.",
|
|
128
|
+
func=lambda **kwargs: self.run(
|
|
129
|
+
AddIndividualPipelineParameters(
|
|
130
|
+
class_uri="https://www.commoncoreontologies.org/ont00000443",
|
|
131
|
+
individual_label=kwargs["individual_label"],
|
|
132
|
+
)
|
|
133
|
+
),
|
|
134
|
+
args_schema=AddIndividualPipelineParameters,
|
|
135
|
+
),
|
|
136
|
+
StructuredTool(
|
|
137
|
+
name="add_person",
|
|
138
|
+
description="Add a new person to ontology.",
|
|
139
|
+
func=lambda **kwargs: self.run(
|
|
140
|
+
AddIndividualPipelineParameters(
|
|
141
|
+
class_uri="https://www.commoncoreontologies.org/ont00001262",
|
|
142
|
+
individual_label=kwargs["individual_label"],
|
|
143
|
+
)
|
|
144
|
+
),
|
|
145
|
+
args_schema=AddIndividualPipelineParameters,
|
|
146
|
+
),
|
|
147
|
+
StructuredTool(
|
|
148
|
+
name="add_website",
|
|
149
|
+
description="Add a new website to ontology.",
|
|
150
|
+
func=lambda **kwargs: self.run(
|
|
151
|
+
AddIndividualPipelineParameters(
|
|
152
|
+
class_uri=ABI.Website,
|
|
153
|
+
individual_label=kwargs["individual_label"],
|
|
154
|
+
)
|
|
155
|
+
),
|
|
156
|
+
args_schema=AddIndividualPipelineParameters,
|
|
157
|
+
),
|
|
158
|
+
StructuredTool(
|
|
159
|
+
name="add_skill",
|
|
160
|
+
description="Add a new skill to ontology.",
|
|
161
|
+
func=lambda **kwargs: self.run(
|
|
162
|
+
AddIndividualPipelineParameters(
|
|
163
|
+
class_uri=CCO.ont00000089,
|
|
164
|
+
individual_label=kwargs["individual_label"],
|
|
165
|
+
)
|
|
166
|
+
),
|
|
167
|
+
args_schema=AddIndividualPipelineParameters,
|
|
168
|
+
),
|
|
169
|
+
StructuredTool(
|
|
170
|
+
name="add_legal_name",
|
|
171
|
+
description="Add a new legal name of a commercial organization.",
|
|
172
|
+
func=lambda **kwargs: self.run(
|
|
173
|
+
AddIndividualPipelineParameters(
|
|
174
|
+
class_uri=CCO.ont00001331,
|
|
175
|
+
individual_label=kwargs["individual_label"],
|
|
176
|
+
)
|
|
177
|
+
),
|
|
178
|
+
args_schema=AddIndividualPipelineParameters,
|
|
179
|
+
),
|
|
180
|
+
StructuredTool(
|
|
181
|
+
name="add_ticker_symbol",
|
|
182
|
+
description="Add a new ticker symbol to triple store.",
|
|
183
|
+
func=lambda **kwargs: self.run(
|
|
184
|
+
AddIndividualPipelineParameters(
|
|
185
|
+
class_uri=ABI.Ticker,
|
|
186
|
+
individual_label=kwargs["individual_label"],
|
|
187
|
+
)
|
|
188
|
+
),
|
|
189
|
+
args_schema=AddIndividualPipelineParameters,
|
|
190
|
+
),
|
|
191
|
+
StructuredTool(
|
|
192
|
+
name="add_linkedin_page",
|
|
193
|
+
description="Add a new LinkedIn page represented by a profile or organization to triple store.",
|
|
194
|
+
func=lambda **kwargs: self.run(
|
|
195
|
+
AddIndividualPipelineParameters(
|
|
196
|
+
class_uri=ABI.LinkedInProfilePage,
|
|
197
|
+
individual_label=kwargs["individual_label"],
|
|
198
|
+
)
|
|
199
|
+
),
|
|
200
|
+
args_schema=AddIndividualPipelineParameters,
|
|
201
|
+
),
|
|
202
|
+
]
|
|
203
|
+
|
|
204
|
+
def as_api(
|
|
205
|
+
self,
|
|
206
|
+
router: APIRouter,
|
|
207
|
+
route_name: str = "",
|
|
208
|
+
name: str = "",
|
|
209
|
+
description: str = "",
|
|
210
|
+
description_stream: str = "",
|
|
211
|
+
tags: list[str | Enum] | None = None,
|
|
212
|
+
) -> None:
|
|
213
|
+
if tags is None:
|
|
214
|
+
tags = []
|
|
215
|
+
return None
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from naas_abi import services
|
|
3
|
+
from naas_abi.pipelines.AddIndividualPipeline import (
|
|
4
|
+
AddIndividualPipeline,
|
|
5
|
+
AddIndividualPipelineConfiguration,
|
|
6
|
+
AddIndividualPipelineParameters,
|
|
7
|
+
)
|
|
8
|
+
from naas_abi.workflows.SearchIndividualWorkflow import (
|
|
9
|
+
SearchIndividualWorkflowConfiguration,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def pipeline() -> AddIndividualPipeline:
|
|
15
|
+
search_individual_workflow_configuration = SearchIndividualWorkflowConfiguration(
|
|
16
|
+
triple_store=services.triple_store_service
|
|
17
|
+
)
|
|
18
|
+
pipeline = AddIndividualPipeline(
|
|
19
|
+
configuration=AddIndividualPipelineConfiguration(
|
|
20
|
+
triple_store=services.triple_store_service,
|
|
21
|
+
search_individual_configuration=search_individual_workflow_configuration,
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
return pipeline
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_add_individual_pipeline(pipeline: AddIndividualPipeline):
|
|
28
|
+
from naas_abi.utils.SPARQL import results_to_list
|
|
29
|
+
from rdflib import OWL, RDF, RDFS, Literal, URIRef
|
|
30
|
+
|
|
31
|
+
label = "Naas.ai"
|
|
32
|
+
class_uri = "https://www.commoncoreontologies.org/ont00000443"
|
|
33
|
+
graph = pipeline.run(
|
|
34
|
+
AddIndividualPipelineParameters(individual_label=label, class_uri=class_uri)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
individual_uri = list(graph.triples((None, RDF.type, OWL.NamedIndividual)))[0][0]
|
|
38
|
+
|
|
39
|
+
assert graph is not None, graph.serialize(format="turtle")
|
|
40
|
+
assert len(list(graph.triples((None, RDFS.label, Literal(label))))) == 1, (
|
|
41
|
+
graph.serialize(format="turtle")
|
|
42
|
+
)
|
|
43
|
+
assert len(list(graph.triples((None, RDF.type, URIRef(class_uri))))) == 1, (
|
|
44
|
+
graph.serialize(format="turtle")
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Remove graph
|
|
48
|
+
services.triple_store_service.remove(graph)
|
|
49
|
+
|
|
50
|
+
# Check triples are removed from the triple store
|
|
51
|
+
sparql_query = """
|
|
52
|
+
PREFIX abi: <http://ontology.naas.ai/abi/>
|
|
53
|
+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
54
|
+
PREFIX cco: <https://www.commoncoreontologies.org/>
|
|
55
|
+
PREFIX owl: <http://www.w3.org/2002/07/owl#>
|
|
56
|
+
|
|
57
|
+
SELECT ?s ?p ?o
|
|
58
|
+
WHERE {
|
|
59
|
+
?s ?p ?o .
|
|
60
|
+
FILTER(?s = <{{individual_uri}}>)
|
|
61
|
+
}
|
|
62
|
+
"""
|
|
63
|
+
sparql_query = sparql_query.replace("{{individual_uri}}", str(individual_uri))
|
|
64
|
+
results = services.triple_store_service.query(sparql_query)
|
|
65
|
+
results_list = results_to_list(results)
|
|
66
|
+
assert results_list is None, results_list
|
|
@@ -0,0 +1,197 @@
|
|
|
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_core import logger
|
|
8
|
+
from naas_abi_core.pipeline import Pipeline, PipelineConfiguration, PipelineParameters
|
|
9
|
+
from naas_abi_core.services.triple_store.TripleStorePorts import ITripleStoreService
|
|
10
|
+
from pydantic import Field
|
|
11
|
+
from rdflib import Graph, Namespace
|
|
12
|
+
|
|
13
|
+
BFO = Namespace("http://purl.obolibrary.org/obo/")
|
|
14
|
+
CCO = Namespace("https://www.commoncoreontologies.org/")
|
|
15
|
+
ABI = Namespace("http://ontology.naas.ai/abi/")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class InsertDataSPARQLPipelineConfiguration(PipelineConfiguration):
|
|
20
|
+
"""Configuration for InsertDataSPARQLPipeline.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
triple_store (ITripleStoreService): The triple store service to use
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
triple_store: ITripleStoreService
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class InsertDataSPARQLPipelineParameters(PipelineParameters):
|
|
30
|
+
"""Parameters for InsertDataSPARQLPipeline execution.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
sparql_statement (str): The SPARQL INSERT DATA statement to execute
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
sparql_statement: Annotated[
|
|
37
|
+
str,
|
|
38
|
+
Field(
|
|
39
|
+
description="SPARQL INSERT DATA statement to execute. Must be a valid SPARQL INSERT DATA query."
|
|
40
|
+
),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class InsertDataSPARQLPipeline(Pipeline):
|
|
45
|
+
"""Pipeline for executing SPARQL INSERT DATA statements on the ontology."""
|
|
46
|
+
|
|
47
|
+
__configuration: InsertDataSPARQLPipelineConfiguration
|
|
48
|
+
|
|
49
|
+
def __init__(self, configuration: InsertDataSPARQLPipelineConfiguration):
|
|
50
|
+
super().__init__(configuration)
|
|
51
|
+
self.__configuration = configuration
|
|
52
|
+
|
|
53
|
+
def get_sparql_from_text(
|
|
54
|
+
self, parameters: InsertDataSPARQLPipelineParameters
|
|
55
|
+
) -> str:
|
|
56
|
+
"""Get the SPARQL INSERT DATA statement from the text.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
text (str): The text to extract the SPARQL INSERT DATA statement from
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
str: The SPARQL INSERT DATA statement
|
|
63
|
+
"""
|
|
64
|
+
text = parameters.sparql_statement
|
|
65
|
+
|
|
66
|
+
# Clean the statement from the ```sparql and ```
|
|
67
|
+
text = text.split("```sparql")[-1].split("```")[0].strip()
|
|
68
|
+
if "INSERT DATA" in text:
|
|
69
|
+
logger.info(f"✅ SPARQL INSERT DATA statement found: {text}")
|
|
70
|
+
return text
|
|
71
|
+
return f"No SPARQL INSERT DATA statement found in statement: {parameters.sparql_statement}"
|
|
72
|
+
|
|
73
|
+
def run(self, parameters: PipelineParameters) -> Graph:
|
|
74
|
+
"""Execute the SPARQL INSERT DATA statement.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
parameters (PipelineParameters): Must be InsertDataSPARQLPipelineParameters
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Graph: The graph with the inserted data
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
ValueError: If parameters are not of the correct type
|
|
84
|
+
"""
|
|
85
|
+
if not isinstance(parameters, InsertDataSPARQLPipelineParameters):
|
|
86
|
+
raise ValueError(
|
|
87
|
+
"Parameters must be of type InsertDataSPARQLPipelineParameters"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
logger.info("Executing SPARQL INSERT DATA statement...")
|
|
91
|
+
|
|
92
|
+
# Initialize a new graph with common namespace bindings
|
|
93
|
+
graph = Graph()
|
|
94
|
+
graph.bind("bfo", BFO)
|
|
95
|
+
graph.bind("cco", CCO)
|
|
96
|
+
graph.bind("abi", ABI)
|
|
97
|
+
|
|
98
|
+
# Execute the SPARQL INSERT DATA statement
|
|
99
|
+
try:
|
|
100
|
+
sparql_statement = self.get_sparql_from_text(parameters)
|
|
101
|
+
graph.update(sparql_statement)
|
|
102
|
+
logger.info("✅ SPARQL INSERT DATA is valid.")
|
|
103
|
+
except Exception as e:
|
|
104
|
+
logger.error(f"❌ Failed to execute SPARQL INSERT DATA: {e}")
|
|
105
|
+
return Graph()
|
|
106
|
+
|
|
107
|
+
# Insert the graph into the triple store
|
|
108
|
+
if len(graph) > 0:
|
|
109
|
+
logger.info("✅ Inserting data to triplestore:")
|
|
110
|
+
logger.info(graph.serialize(format="turtle"))
|
|
111
|
+
self.__configuration.triple_store.insert(graph)
|
|
112
|
+
else:
|
|
113
|
+
logger.info("❌ No data to insert")
|
|
114
|
+
return graph
|
|
115
|
+
|
|
116
|
+
def as_tools(self) -> list[BaseTool]:
|
|
117
|
+
"""Returns a list of LangChain tools for this pipeline.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
list[BaseTool]: List containing the pipeline tool
|
|
121
|
+
"""
|
|
122
|
+
return [
|
|
123
|
+
StructuredTool(
|
|
124
|
+
name="insert_data_sparql",
|
|
125
|
+
description="Execute a SPARQL INSERT DATA statement to add triples to the triple store",
|
|
126
|
+
func=lambda **kwargs: self.run(
|
|
127
|
+
InsertDataSPARQLPipelineParameters(**kwargs)
|
|
128
|
+
),
|
|
129
|
+
args_schema=InsertDataSPARQLPipelineParameters,
|
|
130
|
+
),
|
|
131
|
+
StructuredTool(
|
|
132
|
+
name="extract_sparql_from_text",
|
|
133
|
+
description="Extract a SPARQL INSERT DATA statement from the text",
|
|
134
|
+
func=lambda **kwargs: self.get_sparql_from_text(
|
|
135
|
+
InsertDataSPARQLPipelineParameters(**kwargs)
|
|
136
|
+
),
|
|
137
|
+
args_schema=InsertDataSPARQLPipelineParameters,
|
|
138
|
+
),
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
def as_api(
|
|
142
|
+
self,
|
|
143
|
+
router: APIRouter,
|
|
144
|
+
route_name: str = "",
|
|
145
|
+
name: str = "",
|
|
146
|
+
description: str = "",
|
|
147
|
+
description_stream: str = "",
|
|
148
|
+
tags: list[str | Enum] | None = None,
|
|
149
|
+
) -> None:
|
|
150
|
+
"""Register API endpoints for this pipeline.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
router: FastAPI router to register endpoints on
|
|
154
|
+
route_name: Name for the route
|
|
155
|
+
name: Display name for the endpoint
|
|
156
|
+
description: Description for the endpoint
|
|
157
|
+
description_stream: Description for streaming endpoint
|
|
158
|
+
tags: Tags for the endpoint
|
|
159
|
+
"""
|
|
160
|
+
if tags is None:
|
|
161
|
+
tags = []
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
from naas_abi import services
|
|
167
|
+
|
|
168
|
+
sparql_statement = """
|
|
169
|
+
PREFIX abi: <http://ontology.naas.ai/abi/>
|
|
170
|
+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
171
|
+
PREFIX cco: <https://www.commoncoreontologies.org/>
|
|
172
|
+
PREFIX owl: <http://www.w3.org/2002/07/owl#>
|
|
173
|
+
|
|
174
|
+
INSERT DATA {
|
|
175
|
+
abi:john a cco:ont00001262, owl:NamedIndividual ;
|
|
176
|
+
abi:name "John Doe" ;
|
|
177
|
+
abi:age 30 ;
|
|
178
|
+
abi:email "john.doe@example.com" .
|
|
179
|
+
|
|
180
|
+
abi:jane a cco:ont00001262, owl:NamedIndividual ;
|
|
181
|
+
abi:name "Jane Smith" ;
|
|
182
|
+
abi:age 28 ;
|
|
183
|
+
abi:email "jane.smith@example.com" .
|
|
184
|
+
}
|
|
185
|
+
"""
|
|
186
|
+
pipeline = InsertDataSPARQLPipeline(
|
|
187
|
+
InsertDataSPARQLPipelineConfiguration(
|
|
188
|
+
triple_store=services.triple_store_service
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
# result = pipeline.get_sparql_from_text(InsertDataSPARQLPipelineParameters(sparql_statement=sparql_statement))
|
|
192
|
+
# logger.info(result)
|
|
193
|
+
|
|
194
|
+
result = pipeline.run(
|
|
195
|
+
InsertDataSPARQLPipelineParameters(sparql_statement=sparql_statement)
|
|
196
|
+
)
|
|
197
|
+
logger.info(result)
|