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
assets/favicon.ico ADDED
Binary file
assets/logo.png ADDED
Binary file
@@ -0,0 +1 @@
1
+ from naas_abi_core.utils.Logger import logger as logger
@@ -0,0 +1,245 @@
1
+ import os
2
+ import subprocess
3
+ from typing import Annotated, Optional
4
+
5
+ from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request, status
6
+ from fastapi.middleware.cors import CORSMiddleware
7
+ from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
8
+ from fastapi.openapi.models import OAuthFlowPassword
9
+ from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel
10
+ from fastapi.openapi.utils import get_openapi
11
+ from fastapi.responses import HTMLResponse
12
+
13
+ # Authentication
14
+ from fastapi.security import OAuth2PasswordRequestForm
15
+ from fastapi.security.oauth2 import OAuth2
16
+ from fastapi.security.utils import get_authorization_scheme_param
17
+ from fastapi.staticfiles import StaticFiles
18
+ from naas_abi_core import logger
19
+ from importlib.resources import files
20
+
21
+ # Docs
22
+ from naas_abi_core.apps.api.openapi_doc import API_LANDING_HTML, TAGS_METADATA
23
+ from naas_abi_core.engine.Engine import Engine
24
+
25
+ engine = Engine()
26
+ engine.load()
27
+
28
+ # Init API
29
+ TITLE = engine.configuration.api.title
30
+ DESCRIPTION = engine.configuration.api.description
31
+ app = FastAPI(title=TITLE, docs_url=None, redoc_url=None)
32
+
33
+ # Set logo path
34
+ logo_path = engine.configuration.api.logo_path
35
+ logo_name = os.path.basename(logo_path)
36
+
37
+ # Set favicon path
38
+ favicon_path = engine.configuration.api.favicon_path
39
+ favicon_name = os.path.basename(favicon_path)
40
+
41
+ origins = engine.configuration.api.cors_origins
42
+
43
+ logger.debug(f"CORS origins: {origins}")
44
+
45
+ app.add_middleware(
46
+ CORSMiddleware,
47
+ allow_origins=origins,
48
+ allow_credentials=True,
49
+ allow_methods=["*"],
50
+ allow_headers=["*"],
51
+ )
52
+
53
+
54
+ static_dir = os.path.join(os.path.dirname(str(files("naas_abi_core"))), "assets")
55
+ app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
56
+
57
+
58
+
59
+ # Custom OAuth2 class that accepts query parameter
60
+ class OAuth2QueryBearer(OAuth2):
61
+ def __init__(
62
+ self,
63
+ tokenUrl: str,
64
+ scheme_name: Optional[str] = None,
65
+ auto_error: bool = True,
66
+ ):
67
+ flows = OAuthFlowsModel(password=OAuthFlowPassword(tokenUrl=tokenUrl))
68
+ super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
69
+
70
+ async def __call__(self, request: Request) -> Optional[str]:
71
+ authorization = request.headers.get("Authorization")
72
+ # Check header first
73
+ if authorization:
74
+ scheme, header_token = get_authorization_scheme_param(authorization)
75
+ if scheme.lower() == "bearer" and header_token:
76
+ return header_token
77
+
78
+ # Then check query parameter
79
+ query_token = request.query_params.get("token")
80
+ if query_token:
81
+ return query_token
82
+
83
+ # No token found in either place
84
+ if self.auto_error:
85
+ raise HTTPException(
86
+ status_code=status.HTTP_401_UNAUTHORIZED,
87
+ detail="Not authenticated",
88
+ headers={"WWW-Authenticate": "Bearer"},
89
+ )
90
+ return None
91
+
92
+
93
+ # Replace the existing oauth2_scheme with:
94
+ oauth2_scheme = OAuth2QueryBearer(tokenUrl="token")
95
+
96
+
97
+ # Update the token validation dependency
98
+ async def is_token_valid(token: str = Depends(oauth2_scheme)):
99
+ if token != os.environ.get("ABI_API_KEY"):
100
+ raise HTTPException(
101
+ status_code=status.HTTP_401_UNAUTHORIZED,
102
+ detail="Invalid authentication credentials",
103
+ headers={"WWW-Authenticate": "Bearer"},
104
+ )
105
+ return True
106
+
107
+
108
+ @app.post("/token", include_in_schema=False)
109
+ async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
110
+ if form_data.password != "abi":
111
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
112
+
113
+ return {"access_token": "abi", "token_type": "bearer"}
114
+
115
+
116
+ # Create Agents API Router
117
+ agents_router = APIRouter(
118
+ prefix="/agents",
119
+ tags=["Agents"],
120
+ responses={401: {"description": "Unauthorized"}},
121
+ dependencies=[Depends(is_token_valid)], # Apply token verification
122
+ )
123
+
124
+ # Create Pipelines API Router
125
+ pipelines_router = APIRouter(
126
+ prefix="/pipelines",
127
+ tags=["Pipelines"],
128
+ responses={401: {"description": "Unauthorized"}},
129
+ dependencies=[Depends(is_token_valid)], # Apply token verification
130
+ )
131
+
132
+ # Create Pipelines API Router
133
+ workflows_router = APIRouter(
134
+ prefix="/workflows",
135
+ tags=["Workflows"],
136
+ responses={401: {"description": "Unauthorized"}},
137
+ dependencies=[Depends(is_token_valid)], # Apply token verification
138
+ )
139
+
140
+
141
+ def get_git_tag():
142
+ try:
143
+ tag = subprocess.check_output(["git", "describe", "--tags"]).strip().decode()
144
+ except Exception as _:
145
+ # if file VERSION exists, use it
146
+ if os.path.exists("VERSION"):
147
+ with open("VERSION", "r") as f:
148
+ tag = f.read().strip()
149
+ else:
150
+ tag = "v0.0.1"
151
+ return tag
152
+
153
+
154
+ def custom_openapi():
155
+ if app.openapi_schema:
156
+ return app.openapi_schema
157
+ openapi_schema = get_openapi(
158
+ title=TITLE,
159
+ description=DESCRIPTION,
160
+ version=get_git_tag(),
161
+ routes=app.routes,
162
+ tags=TAGS_METADATA,
163
+ )
164
+ openapi_schema["info"]["x-logo"] = {
165
+ "url": f"/static/{logo_name}",
166
+ "altText": "Logo",
167
+ }
168
+ app.openapi_schema = openapi_schema
169
+ return app.openapi_schema
170
+
171
+
172
+ app.openapi = custom_openapi # type: ignore[method-assign]
173
+
174
+
175
+ @app.get("/docs", include_in_schema=False)
176
+ def overridden_swagger():
177
+ return get_swagger_ui_html(
178
+ openapi_url="/openapi.json",
179
+ title=TITLE,
180
+ swagger_favicon_url=f"/static/{favicon_name}",
181
+ )
182
+
183
+
184
+ @app.get("/redoc", include_in_schema=False)
185
+ def overridden_redoc():
186
+ return get_redoc_html(
187
+ openapi_url="/openapi.json",
188
+ title=TITLE,
189
+ redoc_favicon_url=f"/static/{favicon_name}",
190
+ )
191
+
192
+
193
+ @app.get("/", response_class=HTMLResponse, include_in_schema=False)
194
+ def root():
195
+ return API_LANDING_HTML.replace("[TITLE]", TITLE).replace("[LOGO_NAME]", logo_name)
196
+
197
+
198
+ # Add agents to the API
199
+ all_agents: list = []
200
+ for module in engine.modules.values():
201
+ for agent in module.agents:
202
+ if agent is not None:
203
+ all_agents.append(agent.New())
204
+ else:
205
+ logger.warning(f"Skipping {agent.name} agent (missing API key)")
206
+
207
+ # Sort agents by name and add to router
208
+ for agent in sorted(all_agents, key=lambda a: a.name):
209
+ logger.debug(f"Adding agent to API: {agent.name}")
210
+ agent.as_api(agents_router)
211
+
212
+ # Include routers
213
+ app.include_router(agents_router)
214
+ app.include_router(pipelines_router)
215
+ app.include_router(workflows_router)
216
+
217
+
218
+ def api():
219
+ import uvicorn
220
+
221
+ if os.environ.get("ENV") == "dev":
222
+ uvicorn.run(
223
+ "naas_abi_core.apps.api.api:app",
224
+ host="0.0.0.0",
225
+ port=9879,
226
+ reload=os.environ.get("ENV") == "dev",
227
+ reload_dirs=["src", "lib"],
228
+ log_level="debug",
229
+ )
230
+ else:
231
+ uvicorn.run(app, host="0.0.0.0", port=9879)
232
+
233
+
234
+ def test_init():
235
+ logger.info("✅ API initialization completed successfully")
236
+ print("API_INIT_TEST_PASSED")
237
+
238
+
239
+ if __name__ == "__main__":
240
+ import sys
241
+
242
+ if "--test-init" in sys.argv or os.environ.get("TEST_INIT") == "true":
243
+ test_init()
244
+ else:
245
+ api()
@@ -0,0 +1,281 @@
1
+ import os
2
+
3
+ import pytest
4
+ from fastapi.testclient import TestClient
5
+
6
+
7
+ def test_import_fastapi_app():
8
+ """Test that we can import the FastAPI app from naas_abi.api."""
9
+ try:
10
+ from naas_abi_core.apps.api.api import app
11
+
12
+ assert app is not None
13
+ assert hasattr(app, "title")
14
+ print(f"✅ Successfully imported FastAPI app with title: {app.title}")
15
+ except Exception as e:
16
+ pytest.fail(f"Failed to import FastAPI app: {e}")
17
+
18
+
19
+ def test_api_basic_endpoints():
20
+ """Test basic endpoints of the API."""
21
+ try:
22
+ from naas_abi_core.apps.api.api import app
23
+
24
+ client = TestClient(app)
25
+
26
+ # Test root endpoint
27
+ response = client.get("/")
28
+ assert response.status_code == 200
29
+ print("✅ API root endpoint works")
30
+
31
+ # Test docs endpoint
32
+ response = client.get("/docs")
33
+ assert response.status_code == 200
34
+ print("✅ API docs endpoint works")
35
+
36
+ # Test redoc endpoint
37
+ response = client.get("/redoc")
38
+ assert response.status_code == 200
39
+ print("✅ API redoc endpoint works")
40
+
41
+ # Test OpenAPI schema
42
+ response = client.get("/openapi.json")
43
+ assert response.status_code == 200
44
+ schema = response.json()
45
+ assert "openapi" in schema
46
+ assert "info" in schema
47
+ print("✅ API OpenAPI schema works")
48
+
49
+ except Exception as e:
50
+ pytest.fail(f"Failed to test API basic endpoints: {e}")
51
+
52
+
53
+ def test_api_authentication():
54
+ """Test authentication endpoints of the API."""
55
+ try:
56
+ from naas_abi_core.apps.api.api import app
57
+
58
+ client = TestClient(app)
59
+
60
+ # Test token endpoint with valid credentials
61
+ response = client.post("/token", data={"username": "user", "password": "abi"})
62
+ assert response.status_code == 200
63
+ data = response.json()
64
+ assert data["access_token"] == "abi"
65
+ assert data["token_type"] == "bearer"
66
+ print("✅ API authentication with valid credentials works")
67
+
68
+ # Test token endpoint with invalid credentials
69
+ response = client.post("/token", data={"username": "user", "password": "wrong"})
70
+ assert response.status_code == 400
71
+ print("✅ API properly rejects invalid credentials")
72
+
73
+ except Exception as e:
74
+ pytest.fail(f"Failed to test API authentication: {e}")
75
+
76
+
77
+ def test_api_agent_routes():
78
+ """Test that each agent has both /completion and /stream-completion endpoints."""
79
+ try:
80
+ from fastapi import APIRouter
81
+ from naas_abi_core.apps.api.api import app, engine
82
+
83
+ client = TestClient(app)
84
+
85
+ modules = engine.modules.values()
86
+
87
+ # Get OpenAPI schema to check routes
88
+ response = client.get("/openapi.json")
89
+ assert response.status_code == 200
90
+ schema = response.json()
91
+
92
+ paths = schema.get("paths", {})
93
+
94
+ # Look for agent routes
95
+ agent_routes = [path for path in paths.keys() if path.startswith("/agents/")]
96
+
97
+ print(f"📊 Found {len(agent_routes)} agent routes in API:")
98
+ for route in sorted(agent_routes):
99
+ print(f" {route}")
100
+
101
+ # Get loaded modules and agents
102
+ loaded_modules = modules
103
+ total_agents = 0
104
+ agents_with_routes = {}
105
+
106
+ print("\n📦 Loaded modules and their agents:")
107
+ for module in loaded_modules:
108
+ print(f"Module: {module.module_import_path}")
109
+ print(f"Agents: {module.agents}")
110
+ for agent in module.agents:
111
+ # Skip None agents (when API keys are missing)
112
+ if agent is not None:
113
+ total_agents += 1
114
+
115
+ # Create a temporary router to inspect what routes the agent would add
116
+ temp_router = APIRouter()
117
+ agent.as_api(temp_router)
118
+
119
+ # Extract route paths from the temporary router
120
+ agent_route_paths = []
121
+ for route in temp_router.routes:
122
+ if hasattr(route, "path"):
123
+ agent_route_paths.append(route.path)
124
+
125
+ agents_with_routes[agent.name] = agent_route_paths
126
+ print(f" 🤖 Agent: {agent.name} (routes: {agent_route_paths})")
127
+ else:
128
+ print(" ⚠️ Skipped None agent (missing API key)")
129
+
130
+ print("\n📈 Summary:")
131
+ print(f" - Total modules loaded: {len(loaded_modules)}")
132
+ print(f" - Total agents loaded: {total_agents}")
133
+ print(f" - Total agent routes in API: {len(agent_routes)}")
134
+
135
+ # Verify we have some structure
136
+ assert len(paths) > 0, f"No API paths found in the OpenAPI schema: {paths}"
137
+ assert len(loaded_modules) > 0, f"No modules loaded: {loaded_modules}"
138
+
139
+ # Check that each agent has both required endpoints
140
+ if total_agents > 0:
141
+ # # We expect at least 2 routes per agent (completion and stream-completion)
142
+ # assert len(agent_routes) >= 2 * total_agents, \
143
+ # f"Expected at least {2 * total_agents} routes for {total_agents} agents, but found {len(agent_routes)}"
144
+
145
+ # Check each agent has routes ending with /completion and /stream-completion
146
+ agents_missing_routes = []
147
+ agents_with_both_routes = []
148
+
149
+ for agent_name, route_paths in agents_with_routes.items():
150
+ has_completion = False
151
+ has_stream = False
152
+
153
+ # Check in the actual API paths (with /agents prefix)
154
+ for path in paths.keys():
155
+ if path.startswith("/agents/"):
156
+ if path.endswith("/completion"):
157
+ has_completion = True
158
+ elif path.endswith("/stream-completion"):
159
+ has_stream = True
160
+
161
+ if has_completion and has_stream:
162
+ agents_with_both_routes.append(agent_name)
163
+ else:
164
+ missing = []
165
+ if not has_completion:
166
+ missing.append("completion")
167
+ if not has_stream:
168
+ missing.append("stream-completion")
169
+ agents_missing_routes.append((agent_name, missing))
170
+
171
+ print("\n🔍 Route validation:")
172
+ print(
173
+ f" - Agents with both endpoints: {len(agents_with_both_routes)}/{total_agents}"
174
+ )
175
+ print(
176
+ f" - Agents missing endpoints: {len(agents_missing_routes)}/{total_agents}"
177
+ )
178
+
179
+ # assert total_agents == len(agents_with_both_routes) + len(agents_missing_routes), f"Expected {total_agents} agents, but found {len(agents_with_both_routes) + len(agents_missing_routes)}"
180
+
181
+ if agents_with_both_routes:
182
+ print(" ✅ Agents with both endpoints:")
183
+ for agent in agents_with_both_routes[:5]: # Show first 5
184
+ print(f" - {agent}")
185
+
186
+ if agents_missing_routes:
187
+ print(" ❌ Agents missing endpoints:")
188
+ for agent, missing in agents_missing_routes[:5]: # Show first 5
189
+ print(f" - {agent}: missing {', '.join(missing)}")
190
+ pytest.fail(
191
+ f"{len(agents_missing_routes)} agents are missing required endpoints"
192
+ )
193
+
194
+ print(
195
+ "✅ All agents have both /completion and /stream-completion endpoints"
196
+ )
197
+ else:
198
+ print("⚠️ No agents were loaded, so no agent routes expected")
199
+
200
+ except Exception as e:
201
+ pytest.fail(f"Failed to test API agent routes: {e}")
202
+
203
+
204
+ def test_api_protected_routes():
205
+ """Test that protected routes require authentication."""
206
+ try:
207
+ from naas_abi_core.apps.api.api import app
208
+
209
+ client = TestClient(app)
210
+
211
+ # Get available agent routes first
212
+ response = client.get("/openapi.json")
213
+ schema = response.json()
214
+ paths = schema.get("paths", {})
215
+ agent_routes = [
216
+ path
217
+ for path in paths.keys()
218
+ if path.startswith("/agents/") and not path.endswith("/stream-completion")
219
+ ]
220
+
221
+ if agent_routes:
222
+ # Test first agent route without authentication
223
+ test_route = agent_routes[0]
224
+ response = client.get(test_route)
225
+
226
+ # Should be 401 (unauthorized) or 405 (method not allowed for GET on POST endpoint)
227
+ # Many agent routes are POST endpoints, so GET might return 405
228
+ assert response.status_code in [401, 404, 405], (
229
+ f"Expected 401, 404, or 405 but got {response.status_code}"
230
+ )
231
+ print(
232
+ f"✅ Route {test_route} properly requires authentication or correct method"
233
+ )
234
+
235
+ # Test with valid authentication header
236
+ headers = {"Authorization": f"Bearer {os.getenv('ABI_API_KEY', 'abi')}"}
237
+ response = client.get(test_route, headers=headers)
238
+
239
+ # Should still be 405 if it's a POST endpoint, or 422 if it needs request body
240
+ assert response.status_code in [401, 404, 405, 422], (
241
+ f"Route behavior: {response.status_code}"
242
+ )
243
+ print(f"✅ Route {test_route} responds appropriately with auth header")
244
+ else:
245
+ print("⚠️ No agent routes found to test authentication")
246
+
247
+ except Exception as e:
248
+ pytest.fail(f"Failed to test API protected routes: {e}")
249
+
250
+
251
+ def test_api_cors_configuration():
252
+ """Test CORS configuration of the API."""
253
+ try:
254
+ from naas_abi_core.apps.api.api import app
255
+
256
+ client = TestClient(app)
257
+
258
+ # Test CORS headers with origin
259
+ response = client.get("/", headers={"Origin": "http://localhost:9879"})
260
+ assert response.status_code == 200
261
+
262
+ # Check for CORS headers
263
+ headers = response.headers
264
+ cors_headers = [
265
+ h for h in headers.keys() if h.lower().startswith("access-control")
266
+ ]
267
+
268
+ if cors_headers:
269
+ print(f"✅ CORS headers found: {cors_headers}")
270
+ else:
271
+ # CORS headers might not appear on simple requests
272
+ print("⚠️ No CORS headers found (may be normal for simple requests)")
273
+
274
+ print("✅ API CORS configuration tested")
275
+
276
+ except Exception as e:
277
+ pytest.fail(f"Failed to test API CORS: {e}")
278
+
279
+
280
+ if __name__ == "__main__":
281
+ pytest.main([__file__, "-v", "-s"])
@@ -0,0 +1,144 @@
1
+ TAGS_METADATA = [
2
+ {
3
+ "name": "Overview",
4
+ "description": """
5
+ ### Project Overview
6
+ The **ABI** (Artificial Business Intelligence) project is a Python-based backend framework designed to serve as the core infrastructure for building an Organizational AI System.
7
+ This system empowers businesses to integrate, manage, and scale AI-driven operations with a focus on ontology, assistant-driven workflows, and analytics.\n
8
+ Designed for flexibility and scalability, ABI provides a customizable framework suitable for organizations aiming to create intelligent, automated systems tailored to their needs.
9
+
10
+ ### API Overview
11
+ The ABI API allows users and applications to interact with ABI's capabilities for business process automation and intelligence.\n
12
+ This document describes the current version of the ABI API, which provides access to agents, pipelines, workflows, integrations, ontology management and analytics features.
13
+ """,
14
+ },
15
+ {
16
+ "name": "Authentication",
17
+ "description": """
18
+ Authentication uses a Bearer token that can be provided either in the Authorization header (e.g. 'Authorization: Bearer `<token>`') or as a query parameter (e.g. '?token=`<token>`').
19
+ The token must match the `ABI_API_KEY` environment variable.
20
+ Contact your administrator to get the token.
21
+
22
+ *Authentication with Authorization header:*
23
+
24
+ ```python
25
+ import requests
26
+
27
+ url = "https://<your-registry-name>.default.space.naas.ai/agents/abi/completion"
28
+
29
+ headers = {
30
+ "Authorization": f"Bearer {token}"
31
+ }
32
+
33
+ response = requests.post(url, headers=headers)
34
+ print(response.json())
35
+ ```
36
+
37
+ *Authentication with query parameter:*
38
+
39
+ ```python
40
+ import requests
41
+
42
+ url = "https://<your-registry-name>.default.space.naas.ai/agents/abi/completion?token=<token>"
43
+
44
+ response = requests.post(url)
45
+ print(response.json())
46
+ ```
47
+ """,
48
+ },
49
+ {
50
+ "name": "Agents",
51
+ "description": """
52
+ API endpoints for interacting with ABI's agents.
53
+
54
+ ### Core Agents:
55
+ - Abi: Manages and coordinates other agents
56
+ - Ontology: Manages and coordinates other agents
57
+ - Naas: Manages and coordinates other agents
58
+ - Support: Provides help and guidance for using ABI
59
+
60
+ ### Marketplace Agents:
61
+ - Custom agents with deep expertise in specific domains
62
+ - Can be configured and trained for specialized tasks
63
+ - Extensible through custom tools and knowledge bases
64
+
65
+ Each agent can be accessed through dedicated endpoints that allow:
66
+ - Completion requests for generating responses
67
+ - Chat interactions for ongoing conversations
68
+ - Tool execution for specific tasks
69
+ - Configuration updates for customizing behavior
70
+
71
+ Agents leverage various tools including integrations, pipelines and workflows to accomplish tasks. They can be extended with custom tools and knowledge to enhance their capabilities.
72
+
73
+ """,
74
+ },
75
+ {
76
+ "name": "Pipelines",
77
+ "description": """
78
+ API endpoints for interacting with ABI's pipelines.
79
+ """,
80
+ },
81
+ {
82
+ "name": "Workflows",
83
+ "description": """
84
+ API endpoints for interacting with ABI's workflows.
85
+ """,
86
+ },
87
+ ]
88
+
89
+ API_LANDING_HTML = """
90
+ <!DOCTYPE html>
91
+ <html>
92
+ <head>
93
+ <title>[TITLE]</title>
94
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
95
+ <style>
96
+ body {
97
+ font-family: Arial, sans-serif;
98
+ display: flex;
99
+ flex-direction: column;
100
+ align-items: center;
101
+ justify-content: center;
102
+ height: 100vh;
103
+ margin: 0;
104
+ background-color: #000000;
105
+ color: white;
106
+ }
107
+ .logo {
108
+ width: 200px;
109
+ margin-bottom: 20px;
110
+ }
111
+ h1 {
112
+ font-size: 48px;
113
+ margin-bottom: 40px;
114
+ }
115
+ .buttons {
116
+ display: flex;
117
+ gap: 20px;
118
+ }
119
+ a {
120
+ padding: 12px 24px;
121
+ font-size: 18px;
122
+ border: none;
123
+ border-radius: 4px;
124
+ cursor: pointer;
125
+ text-decoration: none;
126
+ color: white;
127
+ background-color: #007bff;
128
+ transition: background-color 0.2s;
129
+ }
130
+ a:hover {
131
+ background-color: #0056b3;
132
+ }
133
+ </style>
134
+ </head>
135
+ <body>
136
+ <img src="/static/[LOGO_NAME]" alt="Logo" class="logo">
137
+ <h1>Welcome to [TITLE]!</h1>
138
+ <p>[TITLE] is a tool that allows you to interact with ABI's capabilities for business process automation and intelligence.</p>
139
+ <div class="buttons">
140
+ <a href="/redoc">Go to Documentation</a>
141
+ </div>
142
+ </body>
143
+ </html>
144
+ """