naas-abi-core 1.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- assets/favicon.ico +0 -0
- assets/logo.png +0 -0
- naas_abi_core/__init__.py +1 -0
- naas_abi_core/apps/api/api.py +245 -0
- naas_abi_core/apps/api/api_test.py +281 -0
- naas_abi_core/apps/api/openapi_doc.py +144 -0
- naas_abi_core/apps/mcp/Dockerfile.mcp +35 -0
- naas_abi_core/apps/mcp/mcp_server.py +243 -0
- naas_abi_core/apps/mcp/mcp_server_test.py +163 -0
- naas_abi_core/apps/terminal_agent/main.py +555 -0
- naas_abi_core/apps/terminal_agent/terminal_style.py +175 -0
- naas_abi_core/engine/Engine.py +87 -0
- naas_abi_core/engine/EngineProxy.py +109 -0
- naas_abi_core/engine/Engine_test.py +6 -0
- naas_abi_core/engine/IEngine.py +91 -0
- naas_abi_core/engine/conftest.py +45 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration.py +216 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_Deploy.py +7 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_GenericLoader.py +49 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService.py +159 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService_test.py +26 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService.py +138 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService_test.py +74 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService.py +224 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService_test.py +109 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService.py +76 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService_test.py +33 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_test.py +9 -0
- naas_abi_core/engine/engine_configuration/utils/PydanticModelValidator.py +15 -0
- naas_abi_core/engine/engine_loaders/EngineModuleLoader.py +302 -0
- naas_abi_core/engine/engine_loaders/EngineOntologyLoader.py +16 -0
- naas_abi_core/engine/engine_loaders/EngineServiceLoader.py +47 -0
- naas_abi_core/integration/__init__.py +7 -0
- naas_abi_core/integration/integration.py +28 -0
- naas_abi_core/models/Model.py +198 -0
- naas_abi_core/models/OpenRouter.py +18 -0
- naas_abi_core/models/OpenRouter_test.py +36 -0
- naas_abi_core/module/Module.py +252 -0
- naas_abi_core/module/ModuleAgentLoader.py +50 -0
- naas_abi_core/module/ModuleUtils.py +20 -0
- naas_abi_core/modules/templatablesparqlquery/README.md +196 -0
- naas_abi_core/modules/templatablesparqlquery/__init__.py +39 -0
- naas_abi_core/modules/templatablesparqlquery/ontologies/TemplatableSparqlQueryOntology.ttl +116 -0
- naas_abi_core/modules/templatablesparqlquery/workflows/GenericWorkflow.py +48 -0
- naas_abi_core/modules/templatablesparqlquery/workflows/TemplatableSparqlQueryLoader.py +192 -0
- naas_abi_core/pipeline/__init__.py +6 -0
- naas_abi_core/pipeline/pipeline.py +70 -0
- naas_abi_core/services/__init__.py +0 -0
- naas_abi_core/services/agent/Agent.py +1619 -0
- naas_abi_core/services/agent/AgentMemory_test.py +28 -0
- naas_abi_core/services/agent/Agent_test.py +214 -0
- naas_abi_core/services/agent/IntentAgent.py +1179 -0
- naas_abi_core/services/agent/IntentAgent_test.py +139 -0
- naas_abi_core/services/agent/beta/Embeddings.py +181 -0
- naas_abi_core/services/agent/beta/IntentMapper.py +120 -0
- naas_abi_core/services/agent/beta/LocalModel.py +88 -0
- naas_abi_core/services/agent/beta/VectorStore.py +89 -0
- naas_abi_core/services/agent/test_agent_memory.py +278 -0
- naas_abi_core/services/agent/test_postgres_integration.py +145 -0
- naas_abi_core/services/cache/CacheFactory.py +31 -0
- naas_abi_core/services/cache/CachePort.py +63 -0
- naas_abi_core/services/cache/CacheService.py +246 -0
- naas_abi_core/services/cache/CacheService_test.py +85 -0
- naas_abi_core/services/cache/adapters/secondary/CacheFSAdapter.py +39 -0
- naas_abi_core/services/object_storage/ObjectStorageFactory.py +57 -0
- naas_abi_core/services/object_storage/ObjectStoragePort.py +47 -0
- naas_abi_core/services/object_storage/ObjectStorageService.py +41 -0
- naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterFS.py +52 -0
- naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterNaas.py +131 -0
- naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterS3.py +171 -0
- naas_abi_core/services/ontology/OntologyPorts.py +36 -0
- naas_abi_core/services/ontology/OntologyService.py +17 -0
- naas_abi_core/services/ontology/adaptors/secondary/OntologyService_SecondaryAdaptor_NERPort.py +37 -0
- naas_abi_core/services/secret/Secret.py +138 -0
- naas_abi_core/services/secret/SecretPorts.py +45 -0
- naas_abi_core/services/secret/Secret_test.py +65 -0
- naas_abi_core/services/secret/adaptors/secondary/Base64Secret.py +57 -0
- naas_abi_core/services/secret/adaptors/secondary/Base64Secret_test.py +39 -0
- naas_abi_core/services/secret/adaptors/secondary/NaasSecret.py +88 -0
- naas_abi_core/services/secret/adaptors/secondary/NaasSecret_test.py +25 -0
- naas_abi_core/services/secret/adaptors/secondary/dotenv_secret_secondaryadaptor.py +29 -0
- naas_abi_core/services/triple_store/TripleStoreFactory.py +116 -0
- naas_abi_core/services/triple_store/TripleStorePorts.py +223 -0
- naas_abi_core/services/triple_store/TripleStoreService.py +419 -0
- naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune.py +1300 -0
- naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune_test.py +284 -0
- naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph.py +597 -0
- naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph_test.py +1474 -0
- naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__Filesystem.py +223 -0
- naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__ObjectStorage.py +234 -0
- naas_abi_core/services/triple_store/adaptors/secondary/base/TripleStoreService__SecondaryAdaptor__FileBase.py +18 -0
- naas_abi_core/services/vector_store/IVectorStorePort.py +101 -0
- naas_abi_core/services/vector_store/IVectorStorePort_test.py +189 -0
- naas_abi_core/services/vector_store/VectorStoreFactory.py +47 -0
- naas_abi_core/services/vector_store/VectorStoreService.py +171 -0
- naas_abi_core/services/vector_store/VectorStoreService_test.py +185 -0
- naas_abi_core/services/vector_store/__init__.py +13 -0
- naas_abi_core/services/vector_store/adapters/QdrantAdapter.py +251 -0
- naas_abi_core/services/vector_store/adapters/QdrantAdapter_test.py +57 -0
- naas_abi_core/tests/test_services_imports.py +69 -0
- naas_abi_core/utils/Expose.py +55 -0
- naas_abi_core/utils/Graph.py +182 -0
- naas_abi_core/utils/JSON.py +49 -0
- naas_abi_core/utils/LazyLoader.py +44 -0
- naas_abi_core/utils/Logger.py +12 -0
- naas_abi_core/utils/OntologyReasoner.py +141 -0
- naas_abi_core/utils/OntologyYaml.py +681 -0
- naas_abi_core/utils/SPARQL.py +256 -0
- naas_abi_core/utils/Storage.py +33 -0
- naas_abi_core/utils/StorageUtils.py +398 -0
- naas_abi_core/utils/String.py +52 -0
- naas_abi_core/utils/Workers.py +114 -0
- naas_abi_core/utils/__init__.py +0 -0
- naas_abi_core/utils/onto2py/README.md +0 -0
- naas_abi_core/utils/onto2py/__init__.py +10 -0
- naas_abi_core/utils/onto2py/__main__.py +29 -0
- naas_abi_core/utils/onto2py/onto2py.py +611 -0
- naas_abi_core/utils/onto2py/tests/ttl2py_test.py +271 -0
- naas_abi_core/workflow/__init__.py +5 -0
- naas_abi_core/workflow/workflow.py +48 -0
- naas_abi_core-1.4.1.dist-info/METADATA +630 -0
- naas_abi_core-1.4.1.dist-info/RECORD +124 -0
- naas_abi_core-1.4.1.dist-info/WHEEL +4 -0
- naas_abi_core-1.4.1.dist-info/entry_points.txt +2 -0
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
|
+
"""
|