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.
Files changed (124) hide show
  1. naas_abi_core/__init__.py +1 -0
  2. naas_abi_core/apps/api/api.py +242 -0
  3. naas_abi_core/apps/api/api_test.py +281 -0
  4. naas_abi_core/apps/api/openapi_doc.py +307 -0
  5. naas_abi_core/apps/mcp/mcp_server.py +243 -0
  6. naas_abi_core/apps/mcp/mcp_server_test.py +163 -0
  7. naas_abi_core/apps/terminal_agent/main.py +555 -0
  8. naas_abi_core/apps/terminal_agent/terminal_style.py +175 -0
  9. naas_abi_core/cli/__init__.py +53 -0
  10. naas_abi_core/cli/agent.py +30 -0
  11. naas_abi_core/cli/chat.py +26 -0
  12. naas_abi_core/cli/config.py +49 -0
  13. naas_abi_core/cli/init.py +13 -0
  14. naas_abi_core/cli/module.py +28 -0
  15. naas_abi_core/cli/new.py +13 -0
  16. naas_abi_core/cli/secret.py +79 -0
  17. naas_abi_core/engine/Engine.py +87 -0
  18. naas_abi_core/engine/EngineProxy.py +109 -0
  19. naas_abi_core/engine/Engine_test.py +6 -0
  20. naas_abi_core/engine/IEngine.py +91 -0
  21. naas_abi_core/engine/conftest.py +45 -0
  22. naas_abi_core/engine/engine_configuration/EngineConfiguration.py +160 -0
  23. naas_abi_core/engine/engine_configuration/EngineConfiguration_GenericLoader.py +49 -0
  24. naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService.py +131 -0
  25. naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService_test.py +26 -0
  26. naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService.py +116 -0
  27. naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService.py +171 -0
  28. naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService.py +65 -0
  29. naas_abi_core/engine/engine_configuration/EngineConfiguration_test.py +9 -0
  30. naas_abi_core/engine/engine_configuration/utils/PydanticModelValidator.py +15 -0
  31. naas_abi_core/engine/engine_loaders/EngineModuleLoader.py +302 -0
  32. naas_abi_core/engine/engine_loaders/EngineOntologyLoader.py +16 -0
  33. naas_abi_core/engine/engine_loaders/EngineServiceLoader.py +47 -0
  34. naas_abi_core/integration/__init__.py +7 -0
  35. naas_abi_core/integration/integration.py +28 -0
  36. naas_abi_core/models/Model.py +198 -0
  37. naas_abi_core/models/OpenRouter.py +15 -0
  38. naas_abi_core/models/OpenRouter_test.py +36 -0
  39. naas_abi_core/module/Module.py +245 -0
  40. naas_abi_core/module/ModuleAgentLoader.py +49 -0
  41. naas_abi_core/module/ModuleUtils.py +20 -0
  42. naas_abi_core/modules/templatablesparqlquery/README.md +196 -0
  43. naas_abi_core/modules/templatablesparqlquery/__init__.py +39 -0
  44. naas_abi_core/modules/templatablesparqlquery/ontologies/TemplatableSparqlQueryOntology.ttl +116 -0
  45. naas_abi_core/modules/templatablesparqlquery/workflows/GenericWorkflow.py +48 -0
  46. naas_abi_core/modules/templatablesparqlquery/workflows/TemplatableSparqlQueryLoader.py +192 -0
  47. naas_abi_core/pipeline/__init__.py +6 -0
  48. naas_abi_core/pipeline/pipeline.py +70 -0
  49. naas_abi_core/services/__init__.py +0 -0
  50. naas_abi_core/services/agent/Agent.py +1619 -0
  51. naas_abi_core/services/agent/AgentMemory_test.py +28 -0
  52. naas_abi_core/services/agent/Agent_test.py +214 -0
  53. naas_abi_core/services/agent/IntentAgent.py +1171 -0
  54. naas_abi_core/services/agent/IntentAgent_test.py +139 -0
  55. naas_abi_core/services/agent/beta/Embeddings.py +180 -0
  56. naas_abi_core/services/agent/beta/IntentMapper.py +119 -0
  57. naas_abi_core/services/agent/beta/LocalModel.py +88 -0
  58. naas_abi_core/services/agent/beta/VectorStore.py +89 -0
  59. naas_abi_core/services/agent/test_agent_memory.py +278 -0
  60. naas_abi_core/services/agent/test_postgres_integration.py +145 -0
  61. naas_abi_core/services/cache/CacheFactory.py +31 -0
  62. naas_abi_core/services/cache/CachePort.py +63 -0
  63. naas_abi_core/services/cache/CacheService.py +246 -0
  64. naas_abi_core/services/cache/CacheService_test.py +85 -0
  65. naas_abi_core/services/cache/adapters/secondary/CacheFSAdapter.py +39 -0
  66. naas_abi_core/services/object_storage/ObjectStorageFactory.py +57 -0
  67. naas_abi_core/services/object_storage/ObjectStoragePort.py +47 -0
  68. naas_abi_core/services/object_storage/ObjectStorageService.py +41 -0
  69. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterFS.py +52 -0
  70. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterNaas.py +131 -0
  71. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterS3.py +171 -0
  72. naas_abi_core/services/ontology/OntologyPorts.py +36 -0
  73. naas_abi_core/services/ontology/OntologyService.py +17 -0
  74. naas_abi_core/services/ontology/adaptors/secondary/OntologyService_SecondaryAdaptor_NERPort.py +37 -0
  75. naas_abi_core/services/secret/Secret.py +138 -0
  76. naas_abi_core/services/secret/SecretPorts.py +40 -0
  77. naas_abi_core/services/secret/Secret_test.py +65 -0
  78. naas_abi_core/services/secret/adaptors/secondary/Base64Secret.py +57 -0
  79. naas_abi_core/services/secret/adaptors/secondary/Base64Secret_test.py +39 -0
  80. naas_abi_core/services/secret/adaptors/secondary/NaasSecret.py +81 -0
  81. naas_abi_core/services/secret/adaptors/secondary/NaasSecret_test.py +25 -0
  82. naas_abi_core/services/secret/adaptors/secondary/dotenv_secret_secondaryadaptor.py +26 -0
  83. naas_abi_core/services/triple_store/TripleStoreFactory.py +116 -0
  84. naas_abi_core/services/triple_store/TripleStorePorts.py +223 -0
  85. naas_abi_core/services/triple_store/TripleStoreService.py +419 -0
  86. naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune.py +1284 -0
  87. naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune_test.py +284 -0
  88. naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph.py +597 -0
  89. naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph_test.py +1474 -0
  90. naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__Filesystem.py +223 -0
  91. naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__ObjectStorage.py +234 -0
  92. naas_abi_core/services/triple_store/adaptors/secondary/base/TripleStoreService__SecondaryAdaptor__FileBase.py +18 -0
  93. naas_abi_core/services/vector_store/IVectorStorePort.py +101 -0
  94. naas_abi_core/services/vector_store/IVectorStorePort_test.py +189 -0
  95. naas_abi_core/services/vector_store/VectorStoreFactory.py +47 -0
  96. naas_abi_core/services/vector_store/VectorStoreService.py +171 -0
  97. naas_abi_core/services/vector_store/VectorStoreService_test.py +185 -0
  98. naas_abi_core/services/vector_store/__init__.py +13 -0
  99. naas_abi_core/services/vector_store/adapters/QdrantAdapter.py +251 -0
  100. naas_abi_core/services/vector_store/adapters/QdrantAdapter_test.py +57 -0
  101. naas_abi_core/utils/Expose.py +53 -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.disabled.py +679 -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.0.0.dist-info/METADATA +75 -0
  122. naas_abi_core-1.0.0.dist-info/RECORD +124 -0
  123. naas_abi_core-1.0.0.dist-info/WHEEL +4 -0
  124. 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)