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,251 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, List, Optional, Union, cast
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from qdrant_client import QdrantClient
|
|
7
|
+
from qdrant_client.models import (
|
|
8
|
+
Distance,
|
|
9
|
+
FieldCondition,
|
|
10
|
+
Filter,
|
|
11
|
+
MatchValue,
|
|
12
|
+
PointIdsList,
|
|
13
|
+
PointStruct,
|
|
14
|
+
PointVectors,
|
|
15
|
+
UpdateStatus,
|
|
16
|
+
VectorParams,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from ..IVectorStorePort import IVectorStorePort, SearchResult, VectorDocument
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class QdrantAdapter(IVectorStorePort):
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
host: str = "localhost",
|
|
28
|
+
port: int = 6333,
|
|
29
|
+
api_key: Optional[str] = None,
|
|
30
|
+
https: bool = False,
|
|
31
|
+
timeout: int = 30,
|
|
32
|
+
):
|
|
33
|
+
self.host = host
|
|
34
|
+
self.port = port
|
|
35
|
+
self.api_key = api_key
|
|
36
|
+
self.https = https
|
|
37
|
+
self.timeout = timeout
|
|
38
|
+
self.client: Optional[QdrantClient] = None
|
|
39
|
+
|
|
40
|
+
def initialize(self) -> None:
|
|
41
|
+
if self.client is None:
|
|
42
|
+
self.client = QdrantClient(
|
|
43
|
+
host=self.host,
|
|
44
|
+
port=self.port,
|
|
45
|
+
api_key=self.api_key,
|
|
46
|
+
https=self.https,
|
|
47
|
+
timeout=self.timeout,
|
|
48
|
+
)
|
|
49
|
+
logger.info(f"Connected to Qdrant at {self.host}:{self.port}")
|
|
50
|
+
|
|
51
|
+
def _get_distance_metric(self, metric: str) -> Distance:
|
|
52
|
+
metric_map = {
|
|
53
|
+
"cosine": Distance.COSINE,
|
|
54
|
+
"euclidean": Distance.EUCLID,
|
|
55
|
+
"dot": Distance.DOT,
|
|
56
|
+
}
|
|
57
|
+
return metric_map.get(metric.lower(), Distance.COSINE)
|
|
58
|
+
|
|
59
|
+
def create_collection(
|
|
60
|
+
self,
|
|
61
|
+
collection_name: str,
|
|
62
|
+
dimension: int,
|
|
63
|
+
distance_metric: str = "cosine",
|
|
64
|
+
**kwargs,
|
|
65
|
+
) -> None:
|
|
66
|
+
if not self.client:
|
|
67
|
+
raise RuntimeError("Adapter not initialized")
|
|
68
|
+
|
|
69
|
+
self.client.create_collection(
|
|
70
|
+
collection_name=collection_name,
|
|
71
|
+
vectors_config=VectorParams(
|
|
72
|
+
size=dimension, distance=self._get_distance_metric(distance_metric)
|
|
73
|
+
),
|
|
74
|
+
**kwargs,
|
|
75
|
+
)
|
|
76
|
+
logger.info(f"Created collection: {collection_name}")
|
|
77
|
+
|
|
78
|
+
def delete_collection(self, collection_name: str) -> None:
|
|
79
|
+
if not self.client:
|
|
80
|
+
raise RuntimeError("Adapter not initialized")
|
|
81
|
+
|
|
82
|
+
self.client.delete_collection(collection_name=collection_name)
|
|
83
|
+
logger.info(f"Deleted collection: {collection_name}")
|
|
84
|
+
|
|
85
|
+
def list_collections(self) -> List[str]:
|
|
86
|
+
if not self.client:
|
|
87
|
+
raise RuntimeError("Adapter not initialized")
|
|
88
|
+
|
|
89
|
+
collections = self.client.get_collections()
|
|
90
|
+
return [c.name for c in collections.collections]
|
|
91
|
+
|
|
92
|
+
def store_vectors(
|
|
93
|
+
self, collection_name: str, documents: List[VectorDocument]
|
|
94
|
+
) -> None:
|
|
95
|
+
if not self.client:
|
|
96
|
+
raise RuntimeError("Adapter not initialized")
|
|
97
|
+
|
|
98
|
+
points = []
|
|
99
|
+
for doc in documents:
|
|
100
|
+
payload = {}
|
|
101
|
+
if doc.metadata:
|
|
102
|
+
payload.update(doc.metadata)
|
|
103
|
+
if doc.payload:
|
|
104
|
+
payload["payload"] = doc.payload
|
|
105
|
+
|
|
106
|
+
point = PointStruct(
|
|
107
|
+
id=doc.id,
|
|
108
|
+
vector=doc.vector.tolist(),
|
|
109
|
+
payload=payload if payload else None,
|
|
110
|
+
)
|
|
111
|
+
points.append(point)
|
|
112
|
+
|
|
113
|
+
operation_info = self.client.upsert(
|
|
114
|
+
collection_name=collection_name, points=points
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if operation_info.status != UpdateStatus.COMPLETED:
|
|
118
|
+
raise RuntimeError(f"Failed to store vectors: {operation_info}")
|
|
119
|
+
|
|
120
|
+
logger.debug(f"Stored {len(documents)} vectors in {collection_name}")
|
|
121
|
+
|
|
122
|
+
def search(
|
|
123
|
+
self,
|
|
124
|
+
collection_name: str,
|
|
125
|
+
query_vector: np.ndarray,
|
|
126
|
+
k: int = 10,
|
|
127
|
+
filter: Optional[Dict[str, Any]] = None,
|
|
128
|
+
include_vectors: bool = False,
|
|
129
|
+
include_metadata: bool = True,
|
|
130
|
+
) -> List[SearchResult]:
|
|
131
|
+
if not self.client:
|
|
132
|
+
raise RuntimeError("Adapter not initialized")
|
|
133
|
+
|
|
134
|
+
search_filter = None
|
|
135
|
+
if filter:
|
|
136
|
+
conditions = []
|
|
137
|
+
for key, value in filter.items():
|
|
138
|
+
conditions.append(
|
|
139
|
+
FieldCondition(key=key, match=MatchValue(value=value))
|
|
140
|
+
)
|
|
141
|
+
if conditions:
|
|
142
|
+
from typing import cast
|
|
143
|
+
|
|
144
|
+
search_filter = Filter(must=cast(Any, conditions))
|
|
145
|
+
|
|
146
|
+
search_result = self.client.query_points(
|
|
147
|
+
collection_name=collection_name,
|
|
148
|
+
query=query_vector.tolist(),
|
|
149
|
+
limit=k,
|
|
150
|
+
query_filter=search_filter,
|
|
151
|
+
with_vectors=include_vectors,
|
|
152
|
+
with_payload=include_metadata,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
results = []
|
|
156
|
+
for hit in search_result.points:
|
|
157
|
+
result = SearchResult(
|
|
158
|
+
id=str(hit.id),
|
|
159
|
+
score=hit.score,
|
|
160
|
+
vector=np.array(hit.vector) if hit.vector else None,
|
|
161
|
+
metadata=dict(hit.payload)
|
|
162
|
+
if hit.payload and include_metadata
|
|
163
|
+
else None,
|
|
164
|
+
payload=hit.payload.get("payload")
|
|
165
|
+
if hit.payload and "payload" in hit.payload
|
|
166
|
+
else None,
|
|
167
|
+
)
|
|
168
|
+
results.append(result)
|
|
169
|
+
|
|
170
|
+
return results
|
|
171
|
+
|
|
172
|
+
def get_vector(
|
|
173
|
+
self, collection_name: str, vector_id: str, include_vector: bool = True
|
|
174
|
+
) -> Optional[VectorDocument]:
|
|
175
|
+
if not self.client:
|
|
176
|
+
raise RuntimeError("Adapter not initialized")
|
|
177
|
+
|
|
178
|
+
points = self.client.retrieve(
|
|
179
|
+
collection_name=collection_name,
|
|
180
|
+
ids=[vector_id],
|
|
181
|
+
with_vectors=include_vector,
|
|
182
|
+
with_payload=True,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if not points:
|
|
186
|
+
return None
|
|
187
|
+
|
|
188
|
+
point = points[0]
|
|
189
|
+
payload = dict(point.payload) if point.payload else {}
|
|
190
|
+
|
|
191
|
+
vector_array = np.array(point.vector) if point.vector else np.array([])
|
|
192
|
+
return VectorDocument(
|
|
193
|
+
id=str(point.id),
|
|
194
|
+
vector=vector_array,
|
|
195
|
+
metadata={k: v for k, v in payload.items() if k != "payload"},
|
|
196
|
+
payload=payload.get("payload"),
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
def update_vector(
|
|
200
|
+
self,
|
|
201
|
+
collection_name: str,
|
|
202
|
+
vector_id: str,
|
|
203
|
+
vector: Optional[np.ndarray] = None,
|
|
204
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
205
|
+
payload: Optional[Dict[str, Any]] = None,
|
|
206
|
+
) -> None:
|
|
207
|
+
if not self.client:
|
|
208
|
+
raise RuntimeError("Adapter not initialized")
|
|
209
|
+
|
|
210
|
+
if vector is not None:
|
|
211
|
+
self.client.update_vectors(
|
|
212
|
+
collection_name=collection_name,
|
|
213
|
+
points=[PointVectors(id=vector_id, vector=vector.tolist())],
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
if metadata is not None or payload is not None:
|
|
217
|
+
new_payload = {}
|
|
218
|
+
if metadata:
|
|
219
|
+
new_payload.update(metadata)
|
|
220
|
+
if payload:
|
|
221
|
+
new_payload["payload"] = payload
|
|
222
|
+
|
|
223
|
+
self.client.set_payload(
|
|
224
|
+
collection_name=collection_name, payload=new_payload, points=[vector_id]
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def delete_vectors(self, collection_name: str, vector_ids: List) -> None:
|
|
228
|
+
if not self.client:
|
|
229
|
+
raise RuntimeError("Adapter not initialized")
|
|
230
|
+
|
|
231
|
+
point_ids = cast(List[Union[int, str, UUID]], vector_ids)
|
|
232
|
+
|
|
233
|
+
# Cast to satisfy mypy: list[str] is compatible with list[int | str | UUID | PointId]
|
|
234
|
+
self.client.delete(
|
|
235
|
+
collection_name=collection_name,
|
|
236
|
+
points_selector=PointIdsList(points=point_ids),
|
|
237
|
+
)
|
|
238
|
+
logger.debug(f"Deleted {len(vector_ids)} vectors from {collection_name}")
|
|
239
|
+
|
|
240
|
+
def count_vectors(self, collection_name: str) -> int:
|
|
241
|
+
if not self.client:
|
|
242
|
+
raise RuntimeError("Adapter not initialized")
|
|
243
|
+
|
|
244
|
+
collection_info = self.client.get_collection(collection_name=collection_name)
|
|
245
|
+
return collection_info.indexed_vectors_count or 0
|
|
246
|
+
|
|
247
|
+
def close(self) -> None:
|
|
248
|
+
if self.client:
|
|
249
|
+
self.client.close()
|
|
250
|
+
self.client = None
|
|
251
|
+
logger.info("Closed connection to Qdrant")
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import os
|
|
3
|
+
from ..IVectorStorePort_test import GenericVectorStoreAdapterTest
|
|
4
|
+
from .QdrantAdapter import QdrantAdapter
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.skipif(
|
|
8
|
+
os.getenv("QDRANT_HOST") is None,
|
|
9
|
+
reason="Qdrant integration tests require QDRANT_HOST to be set"
|
|
10
|
+
)
|
|
11
|
+
class TestQdrantAdapter(GenericVectorStoreAdapterTest):
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def adapter(self):
|
|
14
|
+
adapter = QdrantAdapter(
|
|
15
|
+
host=os.getenv("QDRANT_HOST", "localhost"),
|
|
16
|
+
port=int(os.getenv("QDRANT_PORT", "6333")),
|
|
17
|
+
api_key=os.getenv("QDRANT_API_KEY"),
|
|
18
|
+
https=os.getenv("QDRANT_HTTPS", "false").lower() == "true"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
adapter.initialize()
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
adapter.delete_collection("test_collection")
|
|
25
|
+
except Exception:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
yield adapter
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
adapter.delete_collection("test_collection")
|
|
32
|
+
except Exception:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
adapter.close()
|
|
36
|
+
|
|
37
|
+
def test_distance_metric_mapping(self):
|
|
38
|
+
adapter = QdrantAdapter()
|
|
39
|
+
|
|
40
|
+
from qdrant_client.models import Distance
|
|
41
|
+
|
|
42
|
+
assert adapter._get_distance_metric("cosine") == Distance.COSINE
|
|
43
|
+
assert adapter._get_distance_metric("euclidean") == Distance.EUCLID
|
|
44
|
+
assert adapter._get_distance_metric("dot") == Distance.DOT
|
|
45
|
+
assert adapter._get_distance_metric("unknown") == Distance.COSINE
|
|
46
|
+
|
|
47
|
+
def test_adapter_not_initialized_error(self):
|
|
48
|
+
adapter = QdrantAdapter()
|
|
49
|
+
|
|
50
|
+
with pytest.raises(RuntimeError, match="Adapter not initialized"):
|
|
51
|
+
adapter.create_collection("test", 128)
|
|
52
|
+
|
|
53
|
+
with pytest.raises(RuntimeError, match="Adapter not initialized"):
|
|
54
|
+
adapter.list_collections()
|
|
55
|
+
|
|
56
|
+
with pytest.raises(RuntimeError, match="Adapter not initialized"):
|
|
57
|
+
adapter.store_vectors("test", [])
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# This is used to test all imports and ensure all libraries are installed and imported correctly.
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_imports():
|
|
5
|
+
from naas_abi_core.services.agent import Agent, IntentAgent
|
|
6
|
+
from naas_abi_core.services.cache import CacheFactory, CachePort, CacheService
|
|
7
|
+
from naas_abi_core.services.cache.adapters.secondary import CacheFSAdapter
|
|
8
|
+
from naas_abi_core.services.object_storage import ObjectStorageService
|
|
9
|
+
from naas_abi_core.services.object_storage.adapters.secondary import (
|
|
10
|
+
ObjectStorageSecondaryAdapterFS,
|
|
11
|
+
ObjectStorageSecondaryAdapterNaas,
|
|
12
|
+
ObjectStorageSecondaryAdapterS3,
|
|
13
|
+
)
|
|
14
|
+
from naas_abi_core.services.secret import Secret, SecretPorts
|
|
15
|
+
from naas_abi_core.services.secret.adaptors.secondary import (
|
|
16
|
+
Base64Secret,
|
|
17
|
+
NaasSecret,
|
|
18
|
+
dotenv_secret_secondaryadaptor,
|
|
19
|
+
)
|
|
20
|
+
from naas_abi_core.services.triple_store import (
|
|
21
|
+
TripleStoreFactory,
|
|
22
|
+
TripleStorePorts,
|
|
23
|
+
TripleStoreService,
|
|
24
|
+
)
|
|
25
|
+
from naas_abi_core.services.triple_store.adaptors.secondary import (
|
|
26
|
+
AWSNeptune,
|
|
27
|
+
Oxigraph,
|
|
28
|
+
)
|
|
29
|
+
from naas_abi_core.services.vector_store import (
|
|
30
|
+
IVectorStorePort,
|
|
31
|
+
VectorStoreFactory,
|
|
32
|
+
VectorStoreService,
|
|
33
|
+
)
|
|
34
|
+
from naas_abi_core.services.vector_store.adapters.QdrantAdapter import QdrantAdapter
|
|
35
|
+
|
|
36
|
+
Agent
|
|
37
|
+
IntentAgent
|
|
38
|
+
CacheService
|
|
39
|
+
CacheFactory
|
|
40
|
+
CachePort
|
|
41
|
+
CacheFSAdapter
|
|
42
|
+
ObjectStorageService
|
|
43
|
+
ObjectStorageSecondaryAdapterFS
|
|
44
|
+
ObjectStorageSecondaryAdapterNaas
|
|
45
|
+
ObjectStorageSecondaryAdapterS3
|
|
46
|
+
ObjectStorageSecondaryAdapterFS
|
|
47
|
+
ObjectStorageSecondaryAdapterNaas
|
|
48
|
+
ObjectStorageSecondaryAdapterS3
|
|
49
|
+
TripleStoreService
|
|
50
|
+
TripleStoreFactory
|
|
51
|
+
TripleStorePorts
|
|
52
|
+
AWSNeptune
|
|
53
|
+
Oxigraph
|
|
54
|
+
ObjectStorageSecondaryAdapterNaas
|
|
55
|
+
ObjectStorageSecondaryAdapterS3
|
|
56
|
+
TripleStoreService
|
|
57
|
+
TripleStoreFactory
|
|
58
|
+
Secret
|
|
59
|
+
SecretPorts
|
|
60
|
+
Base64Secret
|
|
61
|
+
dotenv_secret_secondaryadaptor
|
|
62
|
+
NaasSecret
|
|
63
|
+
VectorStoreFactory
|
|
64
|
+
VectorStoreService
|
|
65
|
+
IVectorStorePort
|
|
66
|
+
QdrantAdapter
|
|
67
|
+
VectorStoreService
|
|
68
|
+
IVectorStorePort
|
|
69
|
+
QdrantAdapter
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from langchain_core.tools import BaseTool
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Expose(ABC):
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def as_tools(self) -> list[BaseTool]:
|
|
16
|
+
"""Returns a list of Tools that can be used by an Agent.
|
|
17
|
+
|
|
18
|
+
This method should be implemented by concrete classes to expose their functionality
|
|
19
|
+
as LangChain StructuredTools that can be used by an Agent.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
list[StructuredTool]: A list of StructuredTools that expose the class's functionality
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
NotImplementedError: If the concrete class does not implement this method
|
|
26
|
+
"""
|
|
27
|
+
raise NotImplementedError()
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def as_api(
|
|
31
|
+
self,
|
|
32
|
+
router: APIRouter,
|
|
33
|
+
route_name: str = "",
|
|
34
|
+
name: str = "",
|
|
35
|
+
description: str = "",
|
|
36
|
+
description_stream: str = "",
|
|
37
|
+
tags: list[str | Enum] | None = [],
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Registers API routes for the class's functionality on the provided FastAPI router.
|
|
40
|
+
|
|
41
|
+
This method should be implemented by concrete classes to expose their functionality
|
|
42
|
+
via HTTP endpoints using FastAPI. The method should register routes on the provided
|
|
43
|
+
router that allow accessing the class's functionality through HTTP requests.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
router (APIRouter): The FastAPI router on which to register the routes
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
None
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
NotImplementedError: If the concrete class does not implement this method
|
|
53
|
+
"""
|
|
54
|
+
raise NotImplementedError()
|
|
55
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
from datetime import date, datetime
|
|
2
|
+
from urllib.parse import quote
|
|
3
|
+
|
|
4
|
+
import pytz
|
|
5
|
+
from rdflib import Graph as rdfgraph
|
|
6
|
+
from rdflib import Literal, Namespace, URIRef
|
|
7
|
+
from rdflib.namespace import OWL, RDF, RDFS, SKOS
|
|
8
|
+
|
|
9
|
+
from naas_abi_core import logger
|
|
10
|
+
|
|
11
|
+
BFO = Namespace("http://purl.obolibrary.org/obo/")
|
|
12
|
+
ABI = Namespace("http://ontology.naas.ai/abi/")
|
|
13
|
+
TEST = Namespace("http://ontology.naas.ai/test/")
|
|
14
|
+
TIME = Namespace("http://www.w3.org/2006/time#")
|
|
15
|
+
XSD = Namespace("http://www.w3.org/2001/XMLSchema#")
|
|
16
|
+
CCO = Namespace("https://www.commoncoreontologies.org/")
|
|
17
|
+
DCTERMS = Namespace("http://purl.org/dc/terms/")
|
|
18
|
+
URI_REGEX = r"http:\/\/ontology\.naas\.ai\/.+\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ABIGraph(rdfgraph):
|
|
22
|
+
def __init__(self, **kwargs):
|
|
23
|
+
super().__init__(**kwargs)
|
|
24
|
+
|
|
25
|
+
self.bind("bfo", BFO)
|
|
26
|
+
self.bind("skos", SKOS)
|
|
27
|
+
self.bind("abi", ABI)
|
|
28
|
+
self.bind("cco", CCO)
|
|
29
|
+
self.bind("xsd", XSD)
|
|
30
|
+
self.bind("time", TIME)
|
|
31
|
+
|
|
32
|
+
def add_data_properties(self, uri: URIRef, lang="en", **data_properties):
|
|
33
|
+
for x in data_properties:
|
|
34
|
+
value = data_properties.get(x)
|
|
35
|
+
if value is not None:
|
|
36
|
+
if type(value) is str:
|
|
37
|
+
self.add((uri, ABI[x], Literal(value.strip(), lang=lang)))
|
|
38
|
+
elif type(value) in [int, float]:
|
|
39
|
+
self.add((uri, ABI[x], Literal(value, datatype=XSD.integer)))
|
|
40
|
+
elif isinstance(value, datetime):
|
|
41
|
+
if value.tzinfo is None:
|
|
42
|
+
value = value.replace(
|
|
43
|
+
tzinfo=pytz.utc
|
|
44
|
+
) # Apply UTC timezone if none
|
|
45
|
+
self.add(
|
|
46
|
+
(
|
|
47
|
+
uri,
|
|
48
|
+
ABI[x],
|
|
49
|
+
Literal(
|
|
50
|
+
value.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
|
51
|
+
datatype=XSD.dateTime,
|
|
52
|
+
),
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
elif isinstance(value, date):
|
|
56
|
+
self.add(
|
|
57
|
+
(
|
|
58
|
+
uri,
|
|
59
|
+
ABI[x],
|
|
60
|
+
Literal(value.strftime("%Y-%m-%d"), datatype=XSD.date),
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
self.add(
|
|
64
|
+
(
|
|
65
|
+
uri,
|
|
66
|
+
DCTERMS.modified,
|
|
67
|
+
Literal(
|
|
68
|
+
str(datetime.now().strftime("%Y-%m-%dT%H:%M:%S%z")),
|
|
69
|
+
datatype=XSD.dateTime,
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Add OWL NamedIndividual to Graph
|
|
75
|
+
def add_individual(
|
|
76
|
+
self,
|
|
77
|
+
uri: URIRef,
|
|
78
|
+
label,
|
|
79
|
+
is_a,
|
|
80
|
+
lang="en",
|
|
81
|
+
skip_if_exists=True,
|
|
82
|
+
**data_properties,
|
|
83
|
+
) -> URIRef:
|
|
84
|
+
if (uri, RDF.type, is_a) in self and skip_if_exists:
|
|
85
|
+
logger.debug(f"🟡 '{label}' ({str(uri)}) already exists in ontology.")
|
|
86
|
+
else:
|
|
87
|
+
# Add NamedIndividual to ontology
|
|
88
|
+
self.add((uri, RDF.type, OWL.NamedIndividual))
|
|
89
|
+
self.add((uri, RDF.type, URIRef(is_a)))
|
|
90
|
+
self.add((uri, RDFS.label, Literal(str(label), lang=lang)))
|
|
91
|
+
self.add(
|
|
92
|
+
(
|
|
93
|
+
uri,
|
|
94
|
+
DCTERMS.created,
|
|
95
|
+
Literal(
|
|
96
|
+
str(datetime.now().strftime("%Y-%m-%dT%H:%M:%S%z")),
|
|
97
|
+
datatype=XSD.dateTime,
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
logger.debug(f"🟢 '{label}' ({str(uri)}) successfully added to ontology.")
|
|
102
|
+
|
|
103
|
+
# Add data properties to NamedIndividual
|
|
104
|
+
self.add_data_properties(uri, lang, **data_properties)
|
|
105
|
+
return uri
|
|
106
|
+
|
|
107
|
+
def add_individual_to_prefix(
|
|
108
|
+
self,
|
|
109
|
+
prefix: Namespace,
|
|
110
|
+
uid: str,
|
|
111
|
+
label: str,
|
|
112
|
+
is_a: URIRef,
|
|
113
|
+
lang="en",
|
|
114
|
+
skip_if_exists=True,
|
|
115
|
+
**data_properties,
|
|
116
|
+
) -> URIRef:
|
|
117
|
+
uid = str(uid).split(":")[-1]
|
|
118
|
+
type_name = str(is_a).split("/")[-1]
|
|
119
|
+
uri = URIRef(quote(f"{str(prefix)}{type_name}#{uid}", safe=":/#"))
|
|
120
|
+
return self.add_individual(
|
|
121
|
+
uri, label, is_a, lang, skip_if_exists, **data_properties
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def add_process(
|
|
125
|
+
self,
|
|
126
|
+
prefix: Namespace,
|
|
127
|
+
uid: str,
|
|
128
|
+
label: str,
|
|
129
|
+
is_a: URIRef,
|
|
130
|
+
lang="en",
|
|
131
|
+
participants=[],
|
|
132
|
+
participants_oprop=BFO.BFO_0000057,
|
|
133
|
+
participants_oprop_inverse=BFO.BFO_0000056,
|
|
134
|
+
realizes=[],
|
|
135
|
+
realizes_oprop=BFO.BFO_0000055,
|
|
136
|
+
realizes_oprop_inverse=BFO.BFO_0000054,
|
|
137
|
+
occurs_in=[],
|
|
138
|
+
occurs_in_oprop=BFO.BFO_0000066,
|
|
139
|
+
occurs_in_oprop_inverse=BFO.BFO_0000183,
|
|
140
|
+
concretizes=[],
|
|
141
|
+
concretizes_oprop=BFO.BFO_0000058,
|
|
142
|
+
concretizes_oprop_inverse=BFO.BFO_0000059,
|
|
143
|
+
temporal_region=None,
|
|
144
|
+
temporal_region_oprop=BFO.BFO_0000199,
|
|
145
|
+
spatiotemporal_region=None,
|
|
146
|
+
spatiotemporal_region_oprop=BFO.BFO_0000200,
|
|
147
|
+
skip_if_exists=True,
|
|
148
|
+
**data_properties,
|
|
149
|
+
):
|
|
150
|
+
# Init
|
|
151
|
+
uri = self.add_individual_to_prefix(
|
|
152
|
+
prefix, uid, label, is_a, lang, skip_if_exists, **data_properties
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Add participants
|
|
156
|
+
for participant in participants:
|
|
157
|
+
self.add((uri, participants_oprop, participant))
|
|
158
|
+
self.add((participant, participants_oprop_inverse, uri))
|
|
159
|
+
|
|
160
|
+
# Add realizable entities
|
|
161
|
+
for realize in realizes:
|
|
162
|
+
self.add((uri, realizes_oprop, realize))
|
|
163
|
+
self.add((realize, realizes_oprop_inverse, uri))
|
|
164
|
+
|
|
165
|
+
# Add occurs_in
|
|
166
|
+
for oi in occurs_in:
|
|
167
|
+
self.add((uri, occurs_in_oprop, oi))
|
|
168
|
+
self.add((oi, occurs_in_oprop_inverse, uri))
|
|
169
|
+
|
|
170
|
+
# Add concretizes
|
|
171
|
+
for concretize in concretizes:
|
|
172
|
+
self.add((uri, concretizes_oprop, concretize))
|
|
173
|
+
self.add((concretize, concretizes_oprop_inverse, uri))
|
|
174
|
+
|
|
175
|
+
# Add occupies_temporal_region
|
|
176
|
+
if temporal_region:
|
|
177
|
+
self.add((uri, temporal_region_oprop, temporal_region))
|
|
178
|
+
|
|
179
|
+
# Add occupies_spatiotemporal_region
|
|
180
|
+
if spatiotemporal_region:
|
|
181
|
+
self.add((uri, spatiotemporal_region_oprop, spatiotemporal_region))
|
|
182
|
+
return uri
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from naas_abi_core import logger
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def extract_json_from_completion(completion_text: str) -> list | dict:
|
|
8
|
+
"""Extract JSON object from completion text that contains markdown formatting.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
completion_text (str): Raw completion text containing JSON in markdown format
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
dict: Parsed JSON object
|
|
15
|
+
"""
|
|
16
|
+
# Find JSON content between ```json and ``` markers
|
|
17
|
+
json_start = completion_text.find("```json\n")
|
|
18
|
+
json_end = completion_text.rfind("```")
|
|
19
|
+
|
|
20
|
+
if json_start == -1 or json_end == -1:
|
|
21
|
+
# If no markdown markers found, try parsing the whole text
|
|
22
|
+
json_str = completion_text
|
|
23
|
+
else:
|
|
24
|
+
# Extract content between markers and clean it
|
|
25
|
+
json_start += len("```json\n")
|
|
26
|
+
json_str = completion_text[json_start:json_end].strip()
|
|
27
|
+
|
|
28
|
+
# Try multiple cleanup approaches
|
|
29
|
+
attempts = [
|
|
30
|
+
lambda x: x, # Original string
|
|
31
|
+
lambda x: x.replace("```", "").strip(), # Remove markdown artifacts
|
|
32
|
+
lambda x: x.replace("\n", "").replace(" ", ""), # Remove all whitespace
|
|
33
|
+
lambda x: x.replace("'", '"'), # Replace single quotes with double quotes
|
|
34
|
+
lambda x: re.sub(
|
|
35
|
+
r"([{,])\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:", r'\1"\2":', x
|
|
36
|
+
), # Quote unquoted keys
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
for attempt in attempts:
|
|
40
|
+
try:
|
|
41
|
+
cleaned_str = attempt(json_str)
|
|
42
|
+
return json.loads(cleaned_str)
|
|
43
|
+
except json.JSONDecodeError as e:
|
|
44
|
+
logger.debug(f"JSON parse attempt failed: {str(e)}")
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
# If all attempts fail, return empty dict
|
|
48
|
+
logger.error("Failed to parse JSON after all cleanup attempts")
|
|
49
|
+
return {}
|