naas-abi-core 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_core/__init__.py +1 -0
- naas_abi_core/apps/api/api.py +242 -0
- naas_abi_core/apps/api/api_test.py +281 -0
- naas_abi_core/apps/api/openapi_doc.py +307 -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/cli/__init__.py +53 -0
- naas_abi_core/cli/agent.py +30 -0
- naas_abi_core/cli/chat.py +26 -0
- naas_abi_core/cli/config.py +49 -0
- naas_abi_core/cli/init.py +13 -0
- naas_abi_core/cli/module.py +28 -0
- naas_abi_core/cli/new.py +13 -0
- naas_abi_core/cli/secret.py +79 -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 +160 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_GenericLoader.py +49 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService.py +131 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService_test.py +26 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService.py +116 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService.py +171 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService.py +65 -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 +15 -0
- naas_abi_core/models/OpenRouter_test.py +36 -0
- naas_abi_core/module/Module.py +245 -0
- naas_abi_core/module/ModuleAgentLoader.py +49 -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 +1171 -0
- naas_abi_core/services/agent/IntentAgent_test.py +139 -0
- naas_abi_core/services/agent/beta/Embeddings.py +180 -0
- naas_abi_core/services/agent/beta/IntentMapper.py +119 -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 +40 -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 +81 -0
- naas_abi_core/services/secret/adaptors/secondary/NaasSecret_test.py +25 -0
- naas_abi_core/services/secret/adaptors/secondary/dotenv_secret_secondaryadaptor.py +26 -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 +1284 -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/utils/Expose.py +53 -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.disabled.py +679 -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.0.0.dist-info/METADATA +75 -0
- naas_abi_core-1.0.0.dist-info/RECORD +124 -0
- naas_abi_core-1.0.0.dist-info/WHEEL +4 -0
- naas_abi_core-1.0.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Annotated, Dict, Optional
|
|
5
|
+
|
|
6
|
+
from langchain_core.language_models.chat_models import BaseChatModel
|
|
7
|
+
from naas_abi_core.models.OpenRouter import ChatOpenRouter
|
|
8
|
+
from pydantic import Field
|
|
9
|
+
|
|
10
|
+
OPENROUTER_MODEL_MAPPING: Dict[str, str] = {
|
|
11
|
+
"gpt-5": "openai/gpt-5",
|
|
12
|
+
"gpt-5-mini": "openai/gpt-5-mini",
|
|
13
|
+
"gpt-5-nano": "openai/gpt-5-nano",
|
|
14
|
+
"gpt-4.1": "openai/gpt-4.1",
|
|
15
|
+
"gpt-4.1-mini": "openai/gpt-4.1-mini",
|
|
16
|
+
"o3-deep-research": "openai/o3-deep-research",
|
|
17
|
+
"o4-mini-deep-research": "openai/o4-mini-deep-research",
|
|
18
|
+
"o3-mini": "openai/o3-mini",
|
|
19
|
+
"sonar-pro-search": "perplexity/sonar-pro-search",
|
|
20
|
+
"sonar-reasoning-pro": "perplexity/sonar-reasoning-pro",
|
|
21
|
+
"sonar-pro": "perplexity/sonar-pro",
|
|
22
|
+
"sonar-deep-research": "perplexity/sonar-deep-research",
|
|
23
|
+
"sonar-reasoning": "perplexity/sonar-reasoning",
|
|
24
|
+
"sonar": "perplexity/sonar",
|
|
25
|
+
"claude-haiku-4-5-20251001": "anthropic/claude-haiku-4.5",
|
|
26
|
+
"claude-sonnet-4-5-20250929": "anthropic/claude-sonnet-4.5",
|
|
27
|
+
"claude-opus-4-1-20250805": "anthropic/claude-opus-4.1",
|
|
28
|
+
"claude-opus-4-20250514": "anthropic/claude-opus-4",
|
|
29
|
+
"claude-sonnet-4-20250514": "anthropic/claude-sonnet-4",
|
|
30
|
+
"claude-3-7-sonnet-20250219": "anthropic/claude-3.7-sonnet",
|
|
31
|
+
"qwen3:8b": "qwen/qwen3-8b",
|
|
32
|
+
"mistral-large-2411": "mistralai/mistral-large-2411",
|
|
33
|
+
"mistral-medium-2508": "mistralai/mistral-medium-3.1",
|
|
34
|
+
"mistral-small-2506": "mistralai/mistral-small",
|
|
35
|
+
"grok-4": "x-ai/grok-4",
|
|
36
|
+
"gemini-2.5-flash": "google/gemini-2.5-flash",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ModelType(Enum):
|
|
41
|
+
CHAT = "chat"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Model:
|
|
45
|
+
model_id: Annotated[str, Field(description="Unique identifier for the model")]
|
|
46
|
+
provider: Annotated[
|
|
47
|
+
str,
|
|
48
|
+
Field(
|
|
49
|
+
description="The provider of the model (e.g. 'openai', 'anthropic', 'openrouter', 'aws bedrock', etc.)"
|
|
50
|
+
),
|
|
51
|
+
]
|
|
52
|
+
model: Annotated[
|
|
53
|
+
BaseChatModel, Field(description="The base model chat from Langchain")
|
|
54
|
+
]
|
|
55
|
+
name: Annotated[
|
|
56
|
+
Optional[str],
|
|
57
|
+
Field(
|
|
58
|
+
description="Display name of the model (e.g. 'GPT-4.1', 'Claude Sonnet 4.5', 'Grok 4', 'Mistral Large', 'Gemini 2.5 Flash', etc.)"
|
|
59
|
+
),
|
|
60
|
+
]
|
|
61
|
+
owner: Annotated[Optional[str], Field(description="The owner/creator of the model")]
|
|
62
|
+
description: Annotated[
|
|
63
|
+
Optional[str],
|
|
64
|
+
Field(
|
|
65
|
+
description="The description of the model (e.g. 'GPT-4.1 is OpenAI's most advanced model with superior performance across text, code, and reasoning tasks.', 'Claude Sonnet 4.5 is Anthropic's most advanced Sonnet model to date, optimized for real-world agents and coding workflows.', 'Grok 4 is xAI's latest multimodal model with SOTA cost-efficiency and a 2M token context window. It comes in two flavors: non-reasoning and reasoning. Read more about the model on xAI's [news post](http://x.ai/news/grok-4-fast). Reasoning can be enabled using the `reasoning` `enabled` parameter in the API. [Learn more in our docs](https://openrouter.ai/docs/use-cases/reasoning-tokens#controlling-reasoning-tokens)', 'Mistral Large is Mistral's latest large model with superior performance across text, code, and reasoning tasks.', 'Gemini 2.5 Flash is Google's latest multimodal model with superior performance across text, code, and reasoning tasks.', etc.)"
|
|
66
|
+
),
|
|
67
|
+
]
|
|
68
|
+
image: Annotated[Optional[str], Field(description="The image of the model")]
|
|
69
|
+
created_at: Annotated[
|
|
70
|
+
Optional[datetime], Field(description="The date and time the model was created")
|
|
71
|
+
]
|
|
72
|
+
canonical_slug: Annotated[
|
|
73
|
+
Optional[str], Field(description="Canonical slug for the model")
|
|
74
|
+
]
|
|
75
|
+
hugging_face_id: Annotated[
|
|
76
|
+
Optional[str], Field(description="Hugging Face model identifier, if applicable")
|
|
77
|
+
]
|
|
78
|
+
pricing: Annotated[
|
|
79
|
+
Optional[dict], Field(description="Pricing information for the model")
|
|
80
|
+
]
|
|
81
|
+
architecture: Annotated[
|
|
82
|
+
Optional[dict], Field(description="Model architecture information")
|
|
83
|
+
]
|
|
84
|
+
top_provider: Annotated[
|
|
85
|
+
Optional[dict],
|
|
86
|
+
Field(description="Information about the top provider for this model"),
|
|
87
|
+
]
|
|
88
|
+
per_request_limits: Annotated[
|
|
89
|
+
Optional[dict], Field(description="Per-request token limits")
|
|
90
|
+
]
|
|
91
|
+
supported_parameters: Annotated[
|
|
92
|
+
Optional[list], Field(description="List of supported parameters for this model")
|
|
93
|
+
]
|
|
94
|
+
default_parameters: Annotated[
|
|
95
|
+
Optional[dict], Field(description="Default parameters for this model")
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
model_id: str,
|
|
101
|
+
provider: str,
|
|
102
|
+
model: BaseChatModel,
|
|
103
|
+
name: Optional[str] = None,
|
|
104
|
+
owner: Optional[str] = None,
|
|
105
|
+
description: Optional[str] = None,
|
|
106
|
+
image: Optional[str] = None,
|
|
107
|
+
created_at: Optional[datetime] = None,
|
|
108
|
+
canonical_slug: Optional[str] = None,
|
|
109
|
+
hugging_face_id: Optional[str] = None,
|
|
110
|
+
pricing: Optional[dict] = None,
|
|
111
|
+
architecture: Optional[dict] = None,
|
|
112
|
+
top_provider: Optional[dict] = None,
|
|
113
|
+
per_request_limits: Optional[dict] = None,
|
|
114
|
+
supported_parameters: Optional[list] = None,
|
|
115
|
+
default_parameters: Optional[dict] = None,
|
|
116
|
+
):
|
|
117
|
+
self.model_id = model_id
|
|
118
|
+
self.provider = provider
|
|
119
|
+
|
|
120
|
+
# If OPENROUTER_API_KEY is set, use ChatOpenRouter as BaseChatModel
|
|
121
|
+
if (
|
|
122
|
+
os.getenv("OPENROUTER_API_KEY")
|
|
123
|
+
and os.getenv("AI_MODE") == "cloud"
|
|
124
|
+
and isinstance(model, BaseChatModel)
|
|
125
|
+
and not isinstance(model, ChatOpenRouter)
|
|
126
|
+
):
|
|
127
|
+
if model_id in OPENROUTER_MODEL_MAPPING:
|
|
128
|
+
self.model = ChatOpenRouter(
|
|
129
|
+
model_name=OPENROUTER_MODEL_MAPPING[model_id]
|
|
130
|
+
)
|
|
131
|
+
else:
|
|
132
|
+
raise ValueError(f"""Model '{model_id}' from provider '{provider}' not found in OPENROUTER_MODEL_MAPPING.
|
|
133
|
+
Please add it to the mapping in lib/abi/models/Model.py.""")
|
|
134
|
+
else:
|
|
135
|
+
self.model = model
|
|
136
|
+
|
|
137
|
+
self.name = name
|
|
138
|
+
self.owner = owner
|
|
139
|
+
self.description = description
|
|
140
|
+
self.image = image
|
|
141
|
+
self.created_at = created_at
|
|
142
|
+
self.canonical_slug = canonical_slug
|
|
143
|
+
self.hugging_face_id = hugging_face_id
|
|
144
|
+
self.pricing = pricing
|
|
145
|
+
self.architecture = architecture
|
|
146
|
+
self.top_provider = top_provider
|
|
147
|
+
self.per_request_limits = per_request_limits
|
|
148
|
+
self.supported_parameters = supported_parameters
|
|
149
|
+
self.default_parameters = default_parameters
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class ChatModel(Model):
|
|
153
|
+
model: BaseChatModel
|
|
154
|
+
context_window: Annotated[
|
|
155
|
+
Optional[int], Field(description="Maximum context length in tokens")
|
|
156
|
+
]
|
|
157
|
+
model_type: ModelType = ModelType.CHAT
|
|
158
|
+
|
|
159
|
+
def __init__(
|
|
160
|
+
self,
|
|
161
|
+
model_id: str,
|
|
162
|
+
provider: str,
|
|
163
|
+
model: BaseChatModel,
|
|
164
|
+
context_window: Optional[int] = None,
|
|
165
|
+
name: Optional[str] = None,
|
|
166
|
+
owner: Optional[str] = None,
|
|
167
|
+
description: Optional[str] = None,
|
|
168
|
+
image: Optional[str] = None,
|
|
169
|
+
created_at: Optional[datetime] = None,
|
|
170
|
+
canonical_slug: Optional[str] = None,
|
|
171
|
+
hugging_face_id: Optional[str] = None,
|
|
172
|
+
pricing: Optional[dict] = None,
|
|
173
|
+
architecture: Optional[dict] = None,
|
|
174
|
+
top_provider: Optional[dict] = None,
|
|
175
|
+
per_request_limits: Optional[dict] = None,
|
|
176
|
+
supported_parameters: Optional[list] = None,
|
|
177
|
+
default_parameters: Optional[dict] = None,
|
|
178
|
+
):
|
|
179
|
+
super().__init__(
|
|
180
|
+
model_id=model_id,
|
|
181
|
+
provider=provider,
|
|
182
|
+
model=model,
|
|
183
|
+
name=name,
|
|
184
|
+
owner=owner,
|
|
185
|
+
description=description,
|
|
186
|
+
image=image,
|
|
187
|
+
created_at=created_at,
|
|
188
|
+
canonical_slug=canonical_slug,
|
|
189
|
+
hugging_face_id=hugging_face_id,
|
|
190
|
+
pricing=pricing,
|
|
191
|
+
architecture=architecture,
|
|
192
|
+
top_provider=top_provider,
|
|
193
|
+
per_request_limits=per_request_limits,
|
|
194
|
+
supported_parameters=supported_parameters,
|
|
195
|
+
default_parameters=default_parameters,
|
|
196
|
+
)
|
|
197
|
+
self.model_type = ModelType.CHAT
|
|
198
|
+
self.context_window = context_window
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from langchain_openai import ChatOpenAI # using the OpenAI LLM class as wrapper
|
|
3
|
+
from pydantic import SecretStr
|
|
4
|
+
from dotenv import load_dotenv
|
|
5
|
+
load_dotenv()
|
|
6
|
+
|
|
7
|
+
class ChatOpenRouter(ChatOpenAI):
|
|
8
|
+
def __init__(self, model_name: str, **kwargs):
|
|
9
|
+
api_key = SecretStr(os.environ["OPENROUTER_API_KEY"])
|
|
10
|
+
super().__init__(
|
|
11
|
+
model=model_name,
|
|
12
|
+
api_key=api_key,
|
|
13
|
+
base_url="https://openrouter.ai/api/v1",
|
|
14
|
+
**kwargs,
|
|
15
|
+
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from naas_abi_core.models.OpenRouter import ChatOpenRouter
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_openai():
|
|
5
|
+
llm = ChatOpenRouter(model_name="openai/gpt-4.1")
|
|
6
|
+
result = llm.invoke("What is the capital of France?").content
|
|
7
|
+
assert result is not None, result
|
|
8
|
+
assert "Paris" in result, result
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_anthropic():
|
|
12
|
+
llm = ChatOpenRouter(model_name="anthropic/claude-sonnet-4.5")
|
|
13
|
+
result = llm.invoke("What is the capital of France?").content
|
|
14
|
+
assert result is not None, result
|
|
15
|
+
assert "Paris" in result, result
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_xai():
|
|
19
|
+
llm = ChatOpenRouter(model_name="x-ai/grok-4")
|
|
20
|
+
result = llm.invoke("What is the capital of France?").content
|
|
21
|
+
assert result is not None, result
|
|
22
|
+
assert "Paris" in result, result
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_mistral():
|
|
26
|
+
llm = ChatOpenRouter(model_name="mistralai/mistral-large")
|
|
27
|
+
result = llm.invoke("What is the capital of France?").content
|
|
28
|
+
assert result is not None, result
|
|
29
|
+
assert "Paris" in result, result
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_gemini():
|
|
33
|
+
llm = ChatOpenRouter(model_name="google/gemini-2.5-flash")
|
|
34
|
+
result = llm.invoke("What is the capital of France?").content
|
|
35
|
+
assert result is not None, result
|
|
36
|
+
assert "Paris" in result, result
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import glob
|
|
4
|
+
import os
|
|
5
|
+
from typing import Dict, List
|
|
6
|
+
|
|
7
|
+
from naas_abi_core import logger
|
|
8
|
+
from naas_abi_core.engine.engine_configuration.EngineConfiguration import GlobalConfig
|
|
9
|
+
from naas_abi_core.engine.EngineProxy import EngineProxy
|
|
10
|
+
from naas_abi_core.integration.integration import Integration
|
|
11
|
+
from naas_abi_core.module.ModuleAgentLoader import ModuleAgentLoader
|
|
12
|
+
from naas_abi_core.pipeline.pipeline import Pipeline
|
|
13
|
+
from naas_abi_core.services.agent.Agent import Agent
|
|
14
|
+
from naas_abi_core.workflow.workflow import Workflow
|
|
15
|
+
from pydantic import BaseModel, ConfigDict
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ModuleDependencies:
|
|
19
|
+
__modules: List[str]
|
|
20
|
+
__services: List[type]
|
|
21
|
+
|
|
22
|
+
def __init__(self, modules: List[str], services: List[type]):
|
|
23
|
+
self.__modules = modules
|
|
24
|
+
self.__services = services
|
|
25
|
+
|
|
26
|
+
def _get_modules(self) -> List[str]:
|
|
27
|
+
return self.__modules
|
|
28
|
+
|
|
29
|
+
def _set_modules(self, modules: List[str]) -> None:
|
|
30
|
+
self.__modules = modules
|
|
31
|
+
|
|
32
|
+
modules = property(_get_modules, _set_modules)
|
|
33
|
+
|
|
34
|
+
def _get_services(self) -> List[type]:
|
|
35
|
+
return self.__services
|
|
36
|
+
|
|
37
|
+
def _set_services(self, services: List[type]) -> None:
|
|
38
|
+
self.__services = services
|
|
39
|
+
|
|
40
|
+
services = property(_get_services, _set_services)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ModuleConfiguration(BaseModel):
|
|
44
|
+
model_config = ConfigDict(extra="forbid")
|
|
45
|
+
|
|
46
|
+
global_config: GlobalConfig
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class BaseModule:
|
|
50
|
+
"""Base interface class for ABI modules."""
|
|
51
|
+
|
|
52
|
+
_instances: Dict[type, "BaseModule"] = {}
|
|
53
|
+
|
|
54
|
+
_engine: EngineProxy
|
|
55
|
+
_configuration: ModuleConfiguration
|
|
56
|
+
dependencies: ModuleDependencies = ModuleDependencies(modules=[], services=[])
|
|
57
|
+
|
|
58
|
+
__ontologies: List[str] = []
|
|
59
|
+
__agents: List[type[Agent]] = []
|
|
60
|
+
__integrations: List[Integration] = []
|
|
61
|
+
__workflows: List[Workflow] = []
|
|
62
|
+
__pipelines: List[Pipeline] = []
|
|
63
|
+
|
|
64
|
+
def __init__(self, engine: EngineProxy, configuration: ModuleConfiguration):
|
|
65
|
+
assert isinstance(configuration, ModuleConfiguration), (
|
|
66
|
+
"configuration must be an instance of ModuleConfiguration"
|
|
67
|
+
)
|
|
68
|
+
logger.debug(f"Initializing module {self.__module__.split('.')[0]}")
|
|
69
|
+
self._engine = engine
|
|
70
|
+
self._configuration = configuration
|
|
71
|
+
|
|
72
|
+
assert hasattr(self.__class__, "Configuration"), (
|
|
73
|
+
"BaseModule must have a Configuration class"
|
|
74
|
+
)
|
|
75
|
+
assert type(self.__class__.Configuration) is type(ModuleConfiguration), (
|
|
76
|
+
"BaseModule.Configuration must be a subclass of ModuleConfiguration"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
self._instances[self.__class__] = self
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def get_dependencies(cls) -> List[str]:
|
|
83
|
+
"""Return the list of module dependencies."""
|
|
84
|
+
return getattr(cls, "dependencies", [])
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def get_instance(cls) -> "BaseModule":
|
|
88
|
+
if cls not in cls._instances:
|
|
89
|
+
raise ValueError(f"Module {cls} not initialized")
|
|
90
|
+
return cls._instances[cls]
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def engine(self) -> EngineProxy:
|
|
94
|
+
return self._engine
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def configuration(self) -> ModuleConfiguration:
|
|
98
|
+
return self._configuration
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def ontologies(self) -> List[str]:
|
|
102
|
+
return self.__ontologies
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def agents(self) -> List[type[Agent]]:
|
|
106
|
+
return self.__agents
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def integrations(self) -> List[Integration]:
|
|
110
|
+
return self.__integrations
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def workflows(self) -> List[Workflow]:
|
|
114
|
+
return self.__workflows
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def pipelines(self) -> List[Pipeline]:
|
|
118
|
+
return self.__pipelines
|
|
119
|
+
|
|
120
|
+
def on_load(self):
|
|
121
|
+
logger.debug(f"on_load for module {self.__module__.split('.')[0]}")
|
|
122
|
+
self.__load_ontologies()
|
|
123
|
+
|
|
124
|
+
self.__agents = ModuleAgentLoader.load_agents(self.__class__)
|
|
125
|
+
|
|
126
|
+
def on_initialized(self):
|
|
127
|
+
"""
|
|
128
|
+
Called after all modules have been loaded and the engine is fully initialized.
|
|
129
|
+
|
|
130
|
+
Use this method to perform post-initialization steps that require other modules,
|
|
131
|
+
services, or ontologies to be available and loaded.
|
|
132
|
+
"""
|
|
133
|
+
logger.debug(f"on_initialized for module {self.__module__.split('.')[0]}")
|
|
134
|
+
|
|
135
|
+
def on_unloaded(self):
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
def __load_ontologies(self):
|
|
139
|
+
if os.path.exists(os.path.join(self.__module__.split(".")[0], "ontologies")):
|
|
140
|
+
for file in glob.glob(
|
|
141
|
+
os.path.join(self.module_path, "ontologies", "**", "*.ttl"),
|
|
142
|
+
recursive=True,
|
|
143
|
+
):
|
|
144
|
+
self.ontologies.append(file)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# class IModule(ABC):
|
|
148
|
+
# """Base interface class for ABI modules.
|
|
149
|
+
|
|
150
|
+
# This class serves as the base interface for all modules in the src/modules directory.
|
|
151
|
+
|
|
152
|
+
# The IModule class provides a default loading mechanism.
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# The default loading mechanism will:
|
|
156
|
+
# 1. Load all agents from the module's 'agents' directory
|
|
157
|
+
# 2. Initialize module configuration and state
|
|
158
|
+
|
|
159
|
+
# Attributes:
|
|
160
|
+
# agents (List[Agent]): List of agents loaded from the module
|
|
161
|
+
# """
|
|
162
|
+
|
|
163
|
+
# agents: List[Agent]
|
|
164
|
+
# triggers: List[tuple[tuple[Any, Any, Any], OntologyEvent, Callable]]
|
|
165
|
+
# ontologies: List[str]
|
|
166
|
+
|
|
167
|
+
# def __init__(self, module_path: str, module_import_path: str, imported_module: Any):
|
|
168
|
+
# self.module_path = module_path
|
|
169
|
+
# self.module_import_path = module_import_path
|
|
170
|
+
# self.imported_module = imported_module
|
|
171
|
+
# self.triggers = []
|
|
172
|
+
# self.ontologies = []
|
|
173
|
+
# self.agents = []
|
|
174
|
+
|
|
175
|
+
# def check_requirements(self):
|
|
176
|
+
# if hasattr(self.imported_module, "requirements"):
|
|
177
|
+
# if not self.imported_module.requirements():
|
|
178
|
+
# logger.error(f"❌ Module {self.module_import_path} failed to meet requirements.")
|
|
179
|
+
# raise SystemExit(f"Application crashed due to module loading failure: {self.module_import_path}")
|
|
180
|
+
|
|
181
|
+
# def load(self):
|
|
182
|
+
# try:
|
|
183
|
+
# self.check_requirements()
|
|
184
|
+
# # self.__load_agents()
|
|
185
|
+
# self.__load_triggers()
|
|
186
|
+
# self.__load_ontologies()
|
|
187
|
+
# except Exception as e:
|
|
188
|
+
# logger.error(f"❌ Critical error loading module {self.module_import_path}: {e}")
|
|
189
|
+
# raise SystemExit(f"Application crashed due to module loading failure: {self.module_import_path}")
|
|
190
|
+
|
|
191
|
+
# def load_agents(self):
|
|
192
|
+
# try:
|
|
193
|
+
# self.__load_agents()
|
|
194
|
+
# except Exception as e:
|
|
195
|
+
# import traceback
|
|
196
|
+
# logger.error(f"❌ Critical error loading agents for module {self.module_import_path}: {e}")
|
|
197
|
+
# traceback.print_exc()
|
|
198
|
+
# raise SystemExit(f"Application crashed due to agent loading failure: {self.module_import_path}")
|
|
199
|
+
|
|
200
|
+
# def __load_agents(self):
|
|
201
|
+
# # Load agents
|
|
202
|
+
# self.agents = []
|
|
203
|
+
# loaded_agent_names = set()
|
|
204
|
+
|
|
205
|
+
# # Find all agent files recursively
|
|
206
|
+
# for root, _, files in os.walk(self.module_path):
|
|
207
|
+
# for file in files:
|
|
208
|
+
# if file.endswith("Agent.py") and not file.endswith("Agent_test.py"):
|
|
209
|
+
# # Get relative path from module root to agent file
|
|
210
|
+
# rel_path = os.path.relpath(root, self.module_path)
|
|
211
|
+
# # Convert path to import format
|
|
212
|
+
# import_path = rel_path.replace(os.sep, ".")
|
|
213
|
+
# if import_path == ".":
|
|
214
|
+
# agent_path = self.module_import_path + "." + file[:-3]
|
|
215
|
+
# else:
|
|
216
|
+
# agent_path = self.module_import_path + "." + import_path + "." + file[:-3]
|
|
217
|
+
|
|
218
|
+
# module = importlib.import_module(agent_path)
|
|
219
|
+
# if hasattr(module, "create_agent"):
|
|
220
|
+
# agent = module.create_agent()
|
|
221
|
+
# if agent is not None:
|
|
222
|
+
# agent_name = getattr(agent, "name", None)
|
|
223
|
+
# if agent_name and agent_name not in loaded_agent_names:
|
|
224
|
+
# self.agents.append(agent)
|
|
225
|
+
# loaded_agent_names.add(agent_name)
|
|
226
|
+
# else:
|
|
227
|
+
# logger.warning(f"Skipping duplicate agent: {agent_name}")
|
|
228
|
+
|
|
229
|
+
# def __load_triggers(self):
|
|
230
|
+
# if os.path.exists(os.path.join(self.module_path, "triggers.py")):
|
|
231
|
+
# module = importlib.import_module(self.module_import_path + ".triggers")
|
|
232
|
+
# if hasattr(module, "triggers"):
|
|
233
|
+
# self.triggers = module.triggers
|
|
234
|
+
|
|
235
|
+
# def __load_ontologies(self):
|
|
236
|
+
# if os.path.exists(os.path.join(self.module_path, "ontologies")):
|
|
237
|
+
# for file in glob.glob(
|
|
238
|
+
# os.path.join(self.module_path, "ontologies", "**", "*.ttl"),
|
|
239
|
+
# recursive=True,
|
|
240
|
+
# ):
|
|
241
|
+
# self.ontologies.append(file)
|
|
242
|
+
|
|
243
|
+
# def on_initialized(self):
|
|
244
|
+
# if hasattr(self.imported_module, "on_initialized"):
|
|
245
|
+
# self.imported_module.on_initialized()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import os
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from naas_abi_core.module.ModuleUtils import find_class_module_root_path
|
|
6
|
+
from naas_abi_core.services.agent.Agent import Agent
|
|
7
|
+
from naas_abi_core.utils.Logger import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ModuleAgentLoader:
|
|
11
|
+
@classmethod
|
|
12
|
+
def load_agents(cls, class_: type) -> List[type[Agent]]:
|
|
13
|
+
agents: List[type[Agent]] = []
|
|
14
|
+
module_root_path = find_class_module_root_path(class_)
|
|
15
|
+
|
|
16
|
+
agents_path = module_root_path / "agents"
|
|
17
|
+
|
|
18
|
+
logger.debug(f"Loading agents from {agents_path}")
|
|
19
|
+
|
|
20
|
+
if os.path.exists(agents_path):
|
|
21
|
+
for file in os.listdir(agents_path):
|
|
22
|
+
if file.endswith(".py") and not file.endswith("test.py"):
|
|
23
|
+
agent_module_path = (
|
|
24
|
+
f"{class_.__module__}.agents.{file.replace('.py', '')}"
|
|
25
|
+
)
|
|
26
|
+
agent_module = importlib.import_module(agent_module_path)
|
|
27
|
+
for key, value in agent_module.__dict__.items():
|
|
28
|
+
if (
|
|
29
|
+
isinstance(value, type)
|
|
30
|
+
and issubclass(value, Agent)
|
|
31
|
+
and value.__module__.split(".")[0]
|
|
32
|
+
== class_.__module__.split(".")[
|
|
33
|
+
0
|
|
34
|
+
] # This makes sure we only load agents from the same module.
|
|
35
|
+
):
|
|
36
|
+
if not hasattr(key, "New") and hasattr(
|
|
37
|
+
agent_module, "create_agent"
|
|
38
|
+
):
|
|
39
|
+
setattr(
|
|
40
|
+
getattr(agent_module, key),
|
|
41
|
+
"New",
|
|
42
|
+
getattr(agent_module, "create_agent"),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
agents.append(getattr(agent_module, key))
|
|
46
|
+
|
|
47
|
+
logger.debug(f"Agents: {agents}")
|
|
48
|
+
|
|
49
|
+
return agents
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def find_class_module_root_path(class_: type) -> Path:
|
|
6
|
+
class_file_path = inspect.getfile(class_)
|
|
7
|
+
class_path = Path(class_file_path)
|
|
8
|
+
class_directory = str(class_path.parent)
|
|
9
|
+
return Path(class_directory)
|
|
10
|
+
# logger.debug(f"Class directory: {class_.__module__}")
|
|
11
|
+
# logger.debug(f"Class file path: {class_directory}")
|
|
12
|
+
# module_name = class_.__module__.split(".")[0]
|
|
13
|
+
|
|
14
|
+
# # find last occurrence of module_name in class_directory
|
|
15
|
+
# module_root_path = class_directory[
|
|
16
|
+
# : class_directory.rfind(module_name) + len(module_name)
|
|
17
|
+
# ]
|
|
18
|
+
|
|
19
|
+
# logger.debug(f"Module root path: {module_root_path}")
|
|
20
|
+
# return Path(module_root_path)
|