gaard-api 0.1.1__tar.gz → 0.1.3__tar.gz
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.
- {gaard_api-0.1.1/src/gaard_api.egg-info → gaard_api-0.1.3}/PKG-INFO +3 -2
- {gaard_api-0.1.1 → gaard_api-0.1.3}/pyproject.toml +3 -2
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/admin/services.py +113 -30
- gaard_api-0.1.3/src/gaard_api/admin-web/assets/main.js +2215 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/admin-web/assets/styles.css +62 -2
- gaard_api-0.1.3/src/gaard_api/admin-web/package.json +10 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/admin-web/src/main.ts +461 -51
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/api/v1/admin.py +181 -13
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/api/v1/prompts.py +4 -4
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/api/v1/query.py +15 -11
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/api/v1/schema.py +4 -4
- gaard_api-0.1.3/src/gaard_api/api_registry.py +266 -0
- gaard_api-0.1.3/src/gaard_api/extension_services.py +82 -0
- gaard_api-0.1.3/src/gaard_api/extensions.py +50 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/main.py +3 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3/src/gaard_api.egg-info}/PKG-INFO +3 -2
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api.egg-info/SOURCES.txt +5 -3
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api.egg-info/requires.txt +2 -1
- {gaard_api-0.1.1 → gaard_api-0.1.3}/tests/test_admin_api.py +251 -0
- gaard_api-0.1.3/tests/test_api_registry.py +101 -0
- gaard_api-0.1.1/src/gaard_api/admin-web/assets/main.js +0 -2056
- gaard_api-0.1.1/src/gaard_api/dependencies/__init__.py +0 -0
- gaard_api-0.1.1/src/gaard_api/schemas/__init__.py +0 -0
- gaard_api-0.1.1/src/gaard_api/services/__init__.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/MANIFEST.in +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/README.md +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/setup.cfg +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/__init__.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/admin/__init__.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/admin/database.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/admin/defaults.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/admin/models.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/admin/prompt_runtime.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/admin/security.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/admin-web/index.html +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/api/__init__.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/api/v1/__init__.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/cli.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/cli_commands.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/core/__init__.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/core/error_handlers.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/core/schema_cache.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/core/settings.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/example_data/__init__.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/example_data/medical_poc/__init__.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/example_data/medical_poc/schema.sql +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/example_data/medical_poc/seed.sql +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/example_database.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api/server_cli.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api.egg-info/dependency_links.txt +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api.egg-info/entry_points.txt +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/src/gaard_api.egg-info/top_level.txt +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/tests/test_error_handlers.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/tests/test_investigation_error_messages.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/tests/test_prompt_runtime.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/tests/test_server_cli.py +0 -0
- {gaard_api-0.1.1 → gaard_api-0.1.3}/tests/test_settings.py +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gaard-api
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: GAARD backend web services providing admin interface
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
7
|
Requires-Dist: gaard-core==0.1.0
|
|
8
|
-
Requires-Dist: gaard-connectors==0.1.
|
|
8
|
+
Requires-Dist: gaard-connectors==0.1.1
|
|
9
9
|
Requires-Dist: gaard-llm==0.1.0
|
|
10
|
+
Requires-Dist: gaard-plugin-api<0.2.0,>=0.1.0
|
|
10
11
|
Requires-Dist: fastapi>=0.111.0
|
|
11
12
|
Requires-Dist: uvicorn[standard]>=0.30.0
|
|
12
13
|
Requires-Dist: pydantic>=2.7.0
|
|
@@ -4,15 +4,16 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gaard-api"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.3"
|
|
8
8
|
description = "GAARD backend web services providing admin interface"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
11
11
|
|
|
12
12
|
dependencies = [
|
|
13
13
|
"gaard-core==0.1.0",
|
|
14
|
-
"gaard-connectors==0.1.
|
|
14
|
+
"gaard-connectors==0.1.1",
|
|
15
15
|
"gaard-llm==0.1.0",
|
|
16
|
+
"gaard-plugin-api>=0.1.0,<0.2.0",
|
|
16
17
|
"fastapi>=0.111.0",
|
|
17
18
|
"uvicorn[standard]>=0.30.0",
|
|
18
19
|
"pydantic>=2.7.0",
|
|
@@ -5,12 +5,10 @@ from dataclasses import dataclass
|
|
|
5
5
|
from datetime import UTC, datetime, timedelta
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
-
from sqlalchemy import create_engine, text
|
|
9
8
|
from sqlalchemy import delete, desc, select
|
|
10
9
|
from sqlalchemy.exc import SQLAlchemyError
|
|
11
10
|
from sqlalchemy.orm import Session
|
|
12
11
|
|
|
13
|
-
from gaard_connectors.sqlalchemy.introspector import SQLAlchemySchemaIntrospector
|
|
14
12
|
from gaard_core.errors import LlmProviderError
|
|
15
13
|
from gaard_core.llm_output import remove_thinking_blocks
|
|
16
14
|
from gaard_core.query_pipeline.models import OutputClassification, QueryRequest, QueryResponse
|
|
@@ -33,6 +31,7 @@ from gaard_api.admin.models import (
|
|
|
33
31
|
PromptTemplate,
|
|
34
32
|
)
|
|
35
33
|
from gaard_api.core.settings import settings
|
|
34
|
+
from gaard_api.extensions import get_connector_registry
|
|
36
35
|
|
|
37
36
|
|
|
38
37
|
def json_dumps(value: Any) -> str:
|
|
@@ -43,24 +42,16 @@ def json_loads(value: str) -> Any:
|
|
|
43
42
|
return json.loads(value or "{}")
|
|
44
43
|
|
|
45
44
|
|
|
46
|
-
SUPPORTED_DATABASE_TYPES = {
|
|
47
|
-
"sqlite": ("sqlite://", "sqlite"),
|
|
48
|
-
"postgresql": ("postgresql://", "postgresql+psycopg://"),
|
|
49
|
-
"mysql": ("mysql://", "mysql+pymysql://"),
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
45
|
def validate_datasource_url(database_type: str, database_url: str) -> None:
|
|
54
|
-
|
|
46
|
+
get_connector_registry().get(database_type).validate_database_url(database_url)
|
|
55
47
|
|
|
56
|
-
if prefixes is None:
|
|
57
|
-
raise ValueError("Unsupported datasource type.")
|
|
58
48
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
49
|
+
def validate_datasource_configuration(
|
|
50
|
+
database_type: str,
|
|
51
|
+
database_url: str,
|
|
52
|
+
sql_dialect: str,
|
|
53
|
+
) -> None:
|
|
54
|
+
get_connector_registry().validate(database_type, database_url, sql_dialect)
|
|
64
55
|
|
|
65
56
|
|
|
66
57
|
def mask_database_url(database_url: str) -> str:
|
|
@@ -827,6 +818,104 @@ def set_llm_runtime_config(
|
|
|
827
818
|
return get_llm_runtime_config(session)
|
|
828
819
|
|
|
829
820
|
|
|
821
|
+
def build_llm_runtime_config(
|
|
822
|
+
session: Session,
|
|
823
|
+
*,
|
|
824
|
+
provider: str,
|
|
825
|
+
base_url: str,
|
|
826
|
+
api_key: str | None,
|
|
827
|
+
clear_api_key: bool,
|
|
828
|
+
model: str,
|
|
829
|
+
timeout_seconds: int | None,
|
|
830
|
+
extra_body: dict[str, Any],
|
|
831
|
+
) -> LlmRuntimeConfig:
|
|
832
|
+
if provider != "openai-compatible":
|
|
833
|
+
raise ValueError("Only openai-compatible LLM provider is supported.")
|
|
834
|
+
|
|
835
|
+
current_config = get_llm_runtime_config(session)
|
|
836
|
+
normalized_base_url = str(base_url or "").strip()
|
|
837
|
+
normalized_model = str(model or "").strip()
|
|
838
|
+
if not normalized_base_url:
|
|
839
|
+
raise ValueError("LLM Base URL is required.")
|
|
840
|
+
if not normalized_model:
|
|
841
|
+
raise ValueError("LLM model is required.")
|
|
842
|
+
if timeout_seconds is not None and timeout_seconds < 1:
|
|
843
|
+
raise ValueError("LLM timeout seconds must be greater than 0.")
|
|
844
|
+
if not isinstance(extra_body, dict):
|
|
845
|
+
raise ValueError("LLM Extra body JSON must be an object.")
|
|
846
|
+
|
|
847
|
+
next_api_key = current_config.api_key
|
|
848
|
+
if clear_api_key:
|
|
849
|
+
next_api_key = "change-me"
|
|
850
|
+
elif api_key is not None and api_key.strip():
|
|
851
|
+
next_api_key = api_key.strip()
|
|
852
|
+
|
|
853
|
+
return LlmRuntimeConfig(
|
|
854
|
+
provider=provider,
|
|
855
|
+
base_url=normalized_base_url,
|
|
856
|
+
api_key=next_api_key,
|
|
857
|
+
model=normalized_model,
|
|
858
|
+
timeout_seconds=timeout_seconds or current_config.timeout_seconds,
|
|
859
|
+
extra_body=extra_body,
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
def test_llm_runtime_config(
|
|
864
|
+
session: Session,
|
|
865
|
+
*,
|
|
866
|
+
provider: str,
|
|
867
|
+
base_url: str,
|
|
868
|
+
api_key: str | None,
|
|
869
|
+
clear_api_key: bool,
|
|
870
|
+
model: str,
|
|
871
|
+
timeout_seconds: int | None,
|
|
872
|
+
extra_body: dict[str, Any],
|
|
873
|
+
) -> dict[str, Any]:
|
|
874
|
+
config = build_llm_runtime_config(
|
|
875
|
+
session,
|
|
876
|
+
provider=provider,
|
|
877
|
+
base_url=base_url,
|
|
878
|
+
api_key=api_key,
|
|
879
|
+
clear_api_key=clear_api_key,
|
|
880
|
+
model=model,
|
|
881
|
+
timeout_seconds=timeout_seconds,
|
|
882
|
+
extra_body=extra_body,
|
|
883
|
+
)
|
|
884
|
+
if not config.api_key or config.api_key == "change-me":
|
|
885
|
+
raise ValueError("LLM API key is required for the connection test.")
|
|
886
|
+
|
|
887
|
+
client = OpenAICompatibleClient(
|
|
888
|
+
base_url=config.base_url,
|
|
889
|
+
api_key=config.api_key,
|
|
890
|
+
timeout_seconds=config.timeout_seconds,
|
|
891
|
+
)
|
|
892
|
+
|
|
893
|
+
try:
|
|
894
|
+
response = client.create_chat_completion(
|
|
895
|
+
ChatCompletionRequest(
|
|
896
|
+
model=config.model,
|
|
897
|
+
messages=[
|
|
898
|
+
ChatMessage(
|
|
899
|
+
role="user",
|
|
900
|
+
content="GAARD LLM connection test. Reply with OK.",
|
|
901
|
+
)
|
|
902
|
+
],
|
|
903
|
+
temperature=0,
|
|
904
|
+
extra_body=config.extra_body,
|
|
905
|
+
)
|
|
906
|
+
)
|
|
907
|
+
except LlmProviderError as exc:
|
|
908
|
+
raise ValueError(str(exc)) from exc
|
|
909
|
+
|
|
910
|
+
if not response.content.strip():
|
|
911
|
+
raise ValueError("LLM provider returned an empty response.")
|
|
912
|
+
|
|
913
|
+
return {
|
|
914
|
+
"ok": True,
|
|
915
|
+
"model": response.model or config.model,
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
|
|
830
919
|
def get_data_query_audit_retention_days(session: Session) -> int:
|
|
831
920
|
value = get_setting(
|
|
832
921
|
session,
|
|
@@ -1896,18 +1985,7 @@ def set_active_datasource_connector(
|
|
|
1896
1985
|
|
|
1897
1986
|
|
|
1898
1987
|
def test_datasource_connection(connector: DatasourceConnector) -> None:
|
|
1899
|
-
|
|
1900
|
-
{"check_same_thread": False}
|
|
1901
|
-
if connector.database_url.startswith("sqlite")
|
|
1902
|
-
else {}
|
|
1903
|
-
)
|
|
1904
|
-
engine = create_engine(connector.database_url, connect_args=connect_args)
|
|
1905
|
-
|
|
1906
|
-
try:
|
|
1907
|
-
with engine.connect() as connection:
|
|
1908
|
-
connection.execute(text("SELECT 1"))
|
|
1909
|
-
finally:
|
|
1910
|
-
engine.dispose()
|
|
1988
|
+
get_connector_registry().get(connector.database_type).connection_tester(connector.database_url)
|
|
1911
1989
|
|
|
1912
1990
|
|
|
1913
1991
|
def get_datasource_schema_cache(
|
|
@@ -1935,7 +2013,12 @@ def introspect_datasource_connector(
|
|
|
1935
2013
|
connector: DatasourceConnector,
|
|
1936
2014
|
actor: str,
|
|
1937
2015
|
) -> DatasourceSchemaCache:
|
|
1938
|
-
schema =
|
|
2016
|
+
schema = (
|
|
2017
|
+
get_connector_registry()
|
|
2018
|
+
.get(connector.database_type)
|
|
2019
|
+
.introspector_factory(connector.database_url)
|
|
2020
|
+
.introspect()
|
|
2021
|
+
)
|
|
1939
2022
|
existing = get_datasource_schema_cache(session, connector.id)
|
|
1940
2023
|
existing_settings = (
|
|
1941
2024
|
json_loads(existing.table_settings_json) if existing is not None else {}
|