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.
Files changed (124) hide show
  1. assets/favicon.ico +0 -0
  2. assets/logo.png +0 -0
  3. naas_abi_core/__init__.py +1 -0
  4. naas_abi_core/apps/api/api.py +245 -0
  5. naas_abi_core/apps/api/api_test.py +281 -0
  6. naas_abi_core/apps/api/openapi_doc.py +144 -0
  7. naas_abi_core/apps/mcp/Dockerfile.mcp +35 -0
  8. naas_abi_core/apps/mcp/mcp_server.py +243 -0
  9. naas_abi_core/apps/mcp/mcp_server_test.py +163 -0
  10. naas_abi_core/apps/terminal_agent/main.py +555 -0
  11. naas_abi_core/apps/terminal_agent/terminal_style.py +175 -0
  12. naas_abi_core/engine/Engine.py +87 -0
  13. naas_abi_core/engine/EngineProxy.py +109 -0
  14. naas_abi_core/engine/Engine_test.py +6 -0
  15. naas_abi_core/engine/IEngine.py +91 -0
  16. naas_abi_core/engine/conftest.py +45 -0
  17. naas_abi_core/engine/engine_configuration/EngineConfiguration.py +216 -0
  18. naas_abi_core/engine/engine_configuration/EngineConfiguration_Deploy.py +7 -0
  19. naas_abi_core/engine/engine_configuration/EngineConfiguration_GenericLoader.py +49 -0
  20. naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService.py +159 -0
  21. naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService_test.py +26 -0
  22. naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService.py +138 -0
  23. naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService_test.py +74 -0
  24. naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService.py +224 -0
  25. naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService_test.py +109 -0
  26. naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService.py +76 -0
  27. naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService_test.py +33 -0
  28. naas_abi_core/engine/engine_configuration/EngineConfiguration_test.py +9 -0
  29. naas_abi_core/engine/engine_configuration/utils/PydanticModelValidator.py +15 -0
  30. naas_abi_core/engine/engine_loaders/EngineModuleLoader.py +302 -0
  31. naas_abi_core/engine/engine_loaders/EngineOntologyLoader.py +16 -0
  32. naas_abi_core/engine/engine_loaders/EngineServiceLoader.py +47 -0
  33. naas_abi_core/integration/__init__.py +7 -0
  34. naas_abi_core/integration/integration.py +28 -0
  35. naas_abi_core/models/Model.py +198 -0
  36. naas_abi_core/models/OpenRouter.py +18 -0
  37. naas_abi_core/models/OpenRouter_test.py +36 -0
  38. naas_abi_core/module/Module.py +252 -0
  39. naas_abi_core/module/ModuleAgentLoader.py +50 -0
  40. naas_abi_core/module/ModuleUtils.py +20 -0
  41. naas_abi_core/modules/templatablesparqlquery/README.md +196 -0
  42. naas_abi_core/modules/templatablesparqlquery/__init__.py +39 -0
  43. naas_abi_core/modules/templatablesparqlquery/ontologies/TemplatableSparqlQueryOntology.ttl +116 -0
  44. naas_abi_core/modules/templatablesparqlquery/workflows/GenericWorkflow.py +48 -0
  45. naas_abi_core/modules/templatablesparqlquery/workflows/TemplatableSparqlQueryLoader.py +192 -0
  46. naas_abi_core/pipeline/__init__.py +6 -0
  47. naas_abi_core/pipeline/pipeline.py +70 -0
  48. naas_abi_core/services/__init__.py +0 -0
  49. naas_abi_core/services/agent/Agent.py +1619 -0
  50. naas_abi_core/services/agent/AgentMemory_test.py +28 -0
  51. naas_abi_core/services/agent/Agent_test.py +214 -0
  52. naas_abi_core/services/agent/IntentAgent.py +1179 -0
  53. naas_abi_core/services/agent/IntentAgent_test.py +139 -0
  54. naas_abi_core/services/agent/beta/Embeddings.py +181 -0
  55. naas_abi_core/services/agent/beta/IntentMapper.py +120 -0
  56. naas_abi_core/services/agent/beta/LocalModel.py +88 -0
  57. naas_abi_core/services/agent/beta/VectorStore.py +89 -0
  58. naas_abi_core/services/agent/test_agent_memory.py +278 -0
  59. naas_abi_core/services/agent/test_postgres_integration.py +145 -0
  60. naas_abi_core/services/cache/CacheFactory.py +31 -0
  61. naas_abi_core/services/cache/CachePort.py +63 -0
  62. naas_abi_core/services/cache/CacheService.py +246 -0
  63. naas_abi_core/services/cache/CacheService_test.py +85 -0
  64. naas_abi_core/services/cache/adapters/secondary/CacheFSAdapter.py +39 -0
  65. naas_abi_core/services/object_storage/ObjectStorageFactory.py +57 -0
  66. naas_abi_core/services/object_storage/ObjectStoragePort.py +47 -0
  67. naas_abi_core/services/object_storage/ObjectStorageService.py +41 -0
  68. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterFS.py +52 -0
  69. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterNaas.py +131 -0
  70. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterS3.py +171 -0
  71. naas_abi_core/services/ontology/OntologyPorts.py +36 -0
  72. naas_abi_core/services/ontology/OntologyService.py +17 -0
  73. naas_abi_core/services/ontology/adaptors/secondary/OntologyService_SecondaryAdaptor_NERPort.py +37 -0
  74. naas_abi_core/services/secret/Secret.py +138 -0
  75. naas_abi_core/services/secret/SecretPorts.py +45 -0
  76. naas_abi_core/services/secret/Secret_test.py +65 -0
  77. naas_abi_core/services/secret/adaptors/secondary/Base64Secret.py +57 -0
  78. naas_abi_core/services/secret/adaptors/secondary/Base64Secret_test.py +39 -0
  79. naas_abi_core/services/secret/adaptors/secondary/NaasSecret.py +88 -0
  80. naas_abi_core/services/secret/adaptors/secondary/NaasSecret_test.py +25 -0
  81. naas_abi_core/services/secret/adaptors/secondary/dotenv_secret_secondaryadaptor.py +29 -0
  82. naas_abi_core/services/triple_store/TripleStoreFactory.py +116 -0
  83. naas_abi_core/services/triple_store/TripleStorePorts.py +223 -0
  84. naas_abi_core/services/triple_store/TripleStoreService.py +419 -0
  85. naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune.py +1300 -0
  86. naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune_test.py +284 -0
  87. naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph.py +597 -0
  88. naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph_test.py +1474 -0
  89. naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__Filesystem.py +223 -0
  90. naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__ObjectStorage.py +234 -0
  91. naas_abi_core/services/triple_store/adaptors/secondary/base/TripleStoreService__SecondaryAdaptor__FileBase.py +18 -0
  92. naas_abi_core/services/vector_store/IVectorStorePort.py +101 -0
  93. naas_abi_core/services/vector_store/IVectorStorePort_test.py +189 -0
  94. naas_abi_core/services/vector_store/VectorStoreFactory.py +47 -0
  95. naas_abi_core/services/vector_store/VectorStoreService.py +171 -0
  96. naas_abi_core/services/vector_store/VectorStoreService_test.py +185 -0
  97. naas_abi_core/services/vector_store/__init__.py +13 -0
  98. naas_abi_core/services/vector_store/adapters/QdrantAdapter.py +251 -0
  99. naas_abi_core/services/vector_store/adapters/QdrantAdapter_test.py +57 -0
  100. naas_abi_core/tests/test_services_imports.py +69 -0
  101. naas_abi_core/utils/Expose.py +55 -0
  102. naas_abi_core/utils/Graph.py +182 -0
  103. naas_abi_core/utils/JSON.py +49 -0
  104. naas_abi_core/utils/LazyLoader.py +44 -0
  105. naas_abi_core/utils/Logger.py +12 -0
  106. naas_abi_core/utils/OntologyReasoner.py +141 -0
  107. naas_abi_core/utils/OntologyYaml.py +681 -0
  108. naas_abi_core/utils/SPARQL.py +256 -0
  109. naas_abi_core/utils/Storage.py +33 -0
  110. naas_abi_core/utils/StorageUtils.py +398 -0
  111. naas_abi_core/utils/String.py +52 -0
  112. naas_abi_core/utils/Workers.py +114 -0
  113. naas_abi_core/utils/__init__.py +0 -0
  114. naas_abi_core/utils/onto2py/README.md +0 -0
  115. naas_abi_core/utils/onto2py/__init__.py +10 -0
  116. naas_abi_core/utils/onto2py/__main__.py +29 -0
  117. naas_abi_core/utils/onto2py/onto2py.py +611 -0
  118. naas_abi_core/utils/onto2py/tests/ttl2py_test.py +271 -0
  119. naas_abi_core/workflow/__init__.py +5 -0
  120. naas_abi_core/workflow/workflow.py +48 -0
  121. naas_abi_core-1.4.1.dist-info/METADATA +630 -0
  122. naas_abi_core-1.4.1.dist-info/RECORD +124 -0
  123. naas_abi_core-1.4.1.dist-info/WHEEL +4 -0
  124. 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 {}