nucliadb-agentic-api 1.0.0.post211__tar.gz → 1.0.0.post230__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.
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/PKG-INFO +1 -1
- nucliadb_agentic_api-1.0.0.post230/VERSION +1 -0
- nucliadb_agentic_api-1.0.0.post230/alembic/versions/c4d5e6f7a8b9_add_agentic_config_soft_deletion.py +39 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/pyproject.toml +4 -3
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/pyproject.toml.orig +4 -3
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/agentic/ask_handler.py +3 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/agentic/ask_result.py +5 -2
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/app.py +21 -7
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/commands.py +15 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/db/agentic_configs.py +67 -25
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/db/settings.py +2 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/db/transform.py +90 -87
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/exceptions.py +4 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/models.py +4 -4
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/server/server.py +0 -3
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/server/session.py +6 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/v1/agentic_configs.py +25 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/v1/ask.py +6 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/v1/mcp_nucliadb.py +7 -1
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/v1/sources.py +12 -7
- nucliadb_agentic_api-1.0.0.post211/VERSION +0 -1
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/README.md +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/alembic/README +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/alembic/env.py +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/alembic/script.py.mako +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/alembic/versions/7f3b2f92a8c1_add_agentic_config_table.py +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/alembic/versions/b3c4d5e6f7a8_add_sources_table.py +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/alembic.ini +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/VERSION +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/__init__.py +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/agentic/__init__.py +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/agentic/ask_transform_to_interaction.py +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/db/__init__.py +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/db/sources.py +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/logging.py +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/py.typed +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/server/__init__.py +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/server/settings.py +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/settings.py +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/utils.py +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/v1/__init__.py +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/v1/ask_websocket.py +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/v1/mcp_content.py +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/v1/oauth.py +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/v1/router.py +0 -0
- {nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/src/nucliadb_agentic_api/v1/utils.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.0.0.post230
|
nucliadb_agentic_api-1.0.0.post230/alembic/versions/c4d5e6f7a8b9_add_agentic_config_soft_deletion.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Add agentic config soft deletion
|
|
2
|
+
|
|
3
|
+
Revision ID: c4d5e6f7a8b9
|
|
4
|
+
Revises: b3c4d5e6f7a8
|
|
5
|
+
Create Date: 2026-08-06 00:00:00.000000
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Sequence, Union
|
|
10
|
+
|
|
11
|
+
import sqlalchemy as sa
|
|
12
|
+
|
|
13
|
+
from alembic import op
|
|
14
|
+
|
|
15
|
+
revision: str = "c4d5e6f7a8b9"
|
|
16
|
+
down_revision: Union[str, None] = "b3c4d5e6f7a8"
|
|
17
|
+
branch_labels: Union[str, Sequence[str], None] = None
|
|
18
|
+
depends_on: Union[str, Sequence[str], None] = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def upgrade() -> None:
|
|
22
|
+
op.add_column(
|
|
23
|
+
"agentic_config_table",
|
|
24
|
+
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
|
|
25
|
+
)
|
|
26
|
+
op.create_index(
|
|
27
|
+
op.f("ix_agentic_config_table_deleted_at"),
|
|
28
|
+
"agentic_config_table",
|
|
29
|
+
["deleted_at"],
|
|
30
|
+
unique=False,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def downgrade() -> None:
|
|
35
|
+
op.drop_index(
|
|
36
|
+
op.f("ix_agentic_config_table_deleted_at"),
|
|
37
|
+
table_name="agentic_config_table",
|
|
38
|
+
)
|
|
39
|
+
op.drop_column("agentic_config_table", "deleted_at")
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nucliadb_agentic_api"
|
|
7
|
-
version = "1.0.0.
|
|
7
|
+
version = "1.0.0.post230"
|
|
8
8
|
description = "NucliaDB Agentic API for Hyperforge"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -28,6 +28,7 @@ nucliadb-agentic-api = "nucliadb_agentic_api.commands:run"
|
|
|
28
28
|
nucliadb-agentic-server = "nucliadb_agentic_api.server.server:run"
|
|
29
29
|
nucliadb-agentic-sandbox = "hyperforge.server.sandbox:run"
|
|
30
30
|
nucliadb-agentic-api-extract-openapi = "nucliadb_agentic_api.commands:extract_openapi"
|
|
31
|
+
nucliadb-agentic-api-purge-deleted-configs = "nucliadb_agentic_api.commands:purge_deleted_agentic_configs"
|
|
31
32
|
|
|
32
33
|
[tool.setuptools.packages.find]
|
|
33
34
|
where = ["src/"]
|
|
@@ -65,13 +66,13 @@ server = [
|
|
|
65
66
|
"aiohttp<3.14.0",
|
|
66
67
|
"backoff",
|
|
67
68
|
"nats-py",
|
|
68
|
-
"nuclia>=4.
|
|
69
|
+
"nuclia>=4.14.0",
|
|
69
70
|
"nuclia-models>=0.59.0",
|
|
70
71
|
"nucliadb-protos",
|
|
71
72
|
"nucliadb-sdk>=6.14.0.post6532",
|
|
72
73
|
"nucliadb-telemetry[otel]>=6.14.0.post6534",
|
|
73
74
|
"nucliadb-utils[storages,fastapi]>=6.15.0.post6671",
|
|
74
|
-
"hyperforge>=1.0.0.
|
|
75
|
+
"hyperforge>=1.0.0.post237",
|
|
75
76
|
"hyperforge_conditional>=1.0.0.post27",
|
|
76
77
|
"hyperforge_mcp>=1.0.0.post41",
|
|
77
78
|
"hyperforge_nucliadb>=1.0.0.post46",
|
{nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/pyproject.toml.orig
RENAMED
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nucliadb_agentic_api"
|
|
7
|
-
version = "1.0.0.
|
|
7
|
+
version = "1.0.0.post230"
|
|
8
8
|
authors = [{ name = "AI Data Team", email = "learning@nuclia.com" }]
|
|
9
9
|
description = "NucliaDB Agentic API for Hyperforge"
|
|
10
10
|
readme = "README.md"
|
|
@@ -52,6 +52,7 @@ nucliadb-agentic-api = "nucliadb_agentic_api.commands:run"
|
|
|
52
52
|
nucliadb-agentic-server = "nucliadb_agentic_api.server.server:run"
|
|
53
53
|
nucliadb-agentic-sandbox = "hyperforge.server.sandbox:run"
|
|
54
54
|
nucliadb-agentic-api-extract-openapi = "nucliadb_agentic_api.commands:extract_openapi"
|
|
55
|
+
nucliadb-agentic-api-purge-deleted-configs = "nucliadb_agentic_api.commands:purge_deleted_agentic_configs"
|
|
55
56
|
|
|
56
57
|
[dependency-groups]
|
|
57
58
|
|
|
@@ -60,13 +61,13 @@ server = [
|
|
|
60
61
|
"aiohttp<3.14.0",
|
|
61
62
|
"backoff",
|
|
62
63
|
"nats-py",
|
|
63
|
-
"nuclia>=4.
|
|
64
|
+
"nuclia>=4.14.0",
|
|
64
65
|
"nuclia-models>=0.59.0",
|
|
65
66
|
"nucliadb-protos",
|
|
66
67
|
"nucliadb-sdk>=6.14.0.post6532",
|
|
67
68
|
"nucliadb-telemetry[otel]>=6.14.0.post6534",
|
|
68
69
|
"nucliadb-utils[storages,fastapi]>=6.15.0.post6671",
|
|
69
|
-
"hyperforge>=1.0.0.
|
|
70
|
+
"hyperforge>=1.0.0.post237",
|
|
70
71
|
"hyperforge_conditional>=1.0.0.post27",
|
|
71
72
|
"hyperforge_mcp>=1.0.0.post41",
|
|
72
73
|
"hyperforge_nucliadb>=1.0.0.post46",
|
|
@@ -8,6 +8,7 @@ from hyperforge_nucliadb_agentic.ask.model import (
|
|
|
8
8
|
from hyperforge_nucliadb_agentic.ask.search.ask import (
|
|
9
9
|
handled_ask_exceptions,
|
|
10
10
|
)
|
|
11
|
+
from nuclia.lib.nua_responses import CitationsType
|
|
11
12
|
from nucliadb_models.search import (
|
|
12
13
|
NucliaDBClientType,
|
|
13
14
|
)
|
|
@@ -34,6 +35,8 @@ async def create_agentic_response(
|
|
|
34
35
|
extra_predict_headers: dict[str, str] | None = None,
|
|
35
36
|
) -> Response:
|
|
36
37
|
ask_request.max_tokens = parse_max_tokens(ask_request.max_tokens)
|
|
38
|
+
if ask_request.citations is None:
|
|
39
|
+
ask_request.citations = CitationsType.LLM_FOOTNOTES
|
|
37
40
|
ask_result = AgenticAskResult(
|
|
38
41
|
app=app,
|
|
39
42
|
kbid=kbid,
|
|
@@ -27,8 +27,11 @@ from hyperforge_nucliadb_agentic.ask.model import (
|
|
|
27
27
|
StatusAskResponseItem,
|
|
28
28
|
TokensDetail,
|
|
29
29
|
)
|
|
30
|
-
from hyperforge_nucliadb_agentic.ask.
|
|
31
|
-
|
|
30
|
+
from hyperforge_nucliadb_agentic.ask.search.ask import (
|
|
31
|
+
AnswerStatusCode,
|
|
32
|
+
AskResult,
|
|
33
|
+
RetrievalMatch,
|
|
34
|
+
)
|
|
32
35
|
from hyperforge_nucliadb_agentic.ask.search.metrics import (
|
|
33
36
|
AskMetrics,
|
|
34
37
|
)
|
|
@@ -8,23 +8,21 @@ from hyperforge.api.authentication import RaoAuthenticationBackend
|
|
|
8
8
|
from hyperforge.broker import Broker
|
|
9
9
|
from hyperforge.broker.redis import RedisBroker
|
|
10
10
|
from hyperforge.driver import Driver
|
|
11
|
+
from hyperforge.manager import Manager
|
|
11
12
|
from hyperforge_nucliadb_agentic.ask.audit import (
|
|
12
13
|
AuditMiddleware,
|
|
13
14
|
start_audit_utility,
|
|
14
15
|
stop_audit_utility,
|
|
15
16
|
)
|
|
16
|
-
from hyperforge_nucliadb_agentic.ask.predict import (
|
|
17
|
-
start_predict_engine,
|
|
18
|
-
stop_predict_engine,
|
|
19
|
-
)
|
|
20
17
|
from lru import LRU
|
|
21
18
|
from mcp.server.lowlevel.server import Server as MCPServer
|
|
22
19
|
from mcp.server.streamable_http import (
|
|
23
20
|
StreamableHTTPServerTransport,
|
|
24
21
|
)
|
|
22
|
+
from nuclia.lib.nua import AsyncNuaClient
|
|
25
23
|
from nucliadb_sdk import NucliaDBAsync
|
|
26
24
|
from nucliadb_telemetry.utils import clean_telemetry, setup_telemetry
|
|
27
|
-
from nucliadb_utils.settings import AuditSettings
|
|
25
|
+
from nucliadb_utils.settings import AuditSettings, nuclia_settings
|
|
28
26
|
from prometheus_client import CONTENT_TYPE_LATEST
|
|
29
27
|
from starlette.middleware.authentication import AuthenticationMiddleware
|
|
30
28
|
from starlette.responses import PlainTextResponse
|
|
@@ -61,6 +59,7 @@ class HTTPApplication(FastAPI):
|
|
|
61
59
|
source_manager: Sources
|
|
62
60
|
broker: Broker
|
|
63
61
|
hyperforge_drivers: dict[str, "Driver"]
|
|
62
|
+
predict_manager: Manager
|
|
64
63
|
|
|
65
64
|
def __init__(
|
|
66
65
|
self,
|
|
@@ -105,9 +104,24 @@ class HTTPApplication(FastAPI):
|
|
|
105
104
|
async def startup(self) -> None:
|
|
106
105
|
await setup_telemetry(SERVICE_NAME)
|
|
107
106
|
|
|
108
|
-
await start_predict_engine()
|
|
109
107
|
await start_audit_utility(SERVICE_NAME, self.audit_settings)
|
|
110
108
|
|
|
109
|
+
if nuclia_settings.onprem:
|
|
110
|
+
nua = AsyncNuaClient.onprem(
|
|
111
|
+
nuclia_settings.nuclia_public_url,
|
|
112
|
+
service_account=nuclia_settings.nuclia_service_account,
|
|
113
|
+
zone=nuclia_settings.nuclia_zone,
|
|
114
|
+
local_predict=nuclia_settings.local_predict,
|
|
115
|
+
local_predict_headers=nuclia_settings.local_predict_headers,
|
|
116
|
+
)
|
|
117
|
+
else:
|
|
118
|
+
nua = AsyncNuaClient.internal(nuclia_settings.nuclia_inner_predict_url)
|
|
119
|
+
self.predict_manager = await Manager.from_config(
|
|
120
|
+
drivers=[],
|
|
121
|
+
nua=nua,
|
|
122
|
+
send_rao_origin=False,
|
|
123
|
+
)
|
|
124
|
+
|
|
111
125
|
self.broker = RedisBroker.from_url(
|
|
112
126
|
url=self.settings.valkey_url,
|
|
113
127
|
activate_subject=self.settings.activate_subject,
|
|
@@ -163,5 +177,5 @@ class HTTPApplication(FastAPI):
|
|
|
163
177
|
await self.source_manager.finalize()
|
|
164
178
|
await self.broker.finalize()
|
|
165
179
|
await stop_audit_utility()
|
|
166
|
-
await
|
|
180
|
+
await self.predict_manager.aclose()
|
|
167
181
|
await clean_telemetry(SERVICE_NAME)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
1
3
|
import uvicorn
|
|
2
4
|
from hyperforge import openapi
|
|
3
5
|
from hyperforge.feature_flag import get_flag_service
|
|
@@ -9,6 +11,7 @@ from nucliadb_utils.settings import AuditSettings
|
|
|
9
11
|
|
|
10
12
|
from nucliadb_agentic_api import SERVICE_NAME
|
|
11
13
|
from nucliadb_agentic_api.app import HTTPApplication
|
|
14
|
+
from nucliadb_agentic_api.db.agentic_configs import AgenticConfigs
|
|
12
15
|
from nucliadb_agentic_api.db.settings import DataManagerSettings
|
|
13
16
|
from nucliadb_agentic_api.logging import set_sentry
|
|
14
17
|
from nucliadb_agentic_api.settings import Settings
|
|
@@ -63,3 +66,15 @@ def extract_openapi():
|
|
|
63
66
|
openapi.extract_openapi_command(
|
|
64
67
|
"nucliadb_agentic_api", "NucliaDB Agentic API", router
|
|
65
68
|
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def purge_deleted_agentic_configs():
|
|
72
|
+
async def purge() -> int:
|
|
73
|
+
manager = await AgenticConfigs.from_settings(DataManagerSettings()) # type: ignore
|
|
74
|
+
await manager.initialize()
|
|
75
|
+
try:
|
|
76
|
+
return await manager.hard_delete_expired_configs()
|
|
77
|
+
finally:
|
|
78
|
+
await manager.finalize()
|
|
79
|
+
|
|
80
|
+
asyncio.run(purge())
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import datetime
|
|
2
|
-
import json
|
|
3
2
|
from time import time
|
|
4
3
|
from typing import Dict
|
|
5
4
|
|
|
@@ -46,6 +45,7 @@ agentic_config_table = sa.Table(
|
|
|
46
45
|
sa.Column("agentic_id", sa.String, primary_key=True, nullable=False), # Agentic ID
|
|
47
46
|
sa.Column("created", sa.DateTime, default=sa.func.now()),
|
|
48
47
|
sa.Column("modified", sa.DateTime, onupdate=sa.func.now()),
|
|
48
|
+
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True, index=True),
|
|
49
49
|
sa.Column("title", sa.String, nullable=True),
|
|
50
50
|
sa.Column("config", JSONB, nullable=False),
|
|
51
51
|
)
|
|
@@ -120,6 +120,7 @@ class AgenticConfigs:
|
|
|
120
120
|
agentic_config_table.c.account == account,
|
|
121
121
|
agentic_config_table.c.kbid == kbid,
|
|
122
122
|
agentic_config_table.c.agentic_id == agentic_id,
|
|
123
|
+
agentic_config_table.c.deleted_at.is_(None),
|
|
123
124
|
)
|
|
124
125
|
.returning(agentic_config_table.c.agentic_id)
|
|
125
126
|
)
|
|
@@ -135,14 +136,30 @@ class AgenticConfigs:
|
|
|
135
136
|
if source_ids:
|
|
136
137
|
await self._validate_sources_exist(account, kbid, source_ids)
|
|
137
138
|
|
|
138
|
-
query = sa.select(agentic_config_table.c.
|
|
139
|
+
query = sa.select(agentic_config_table.c.deleted_at).where(
|
|
139
140
|
agentic_config_table.c.account == account,
|
|
140
141
|
agentic_config_table.c.kbid == kbid,
|
|
141
142
|
agentic_config_table.c.agentic_id == agentic_id,
|
|
142
143
|
)
|
|
143
144
|
existing = await self.database.fetch_one(query)
|
|
144
145
|
if existing is not None:
|
|
145
|
-
|
|
146
|
+
if existing["deleted_at"] is None:
|
|
147
|
+
raise exceptions.Conflict("Agentic configuration already exists")
|
|
148
|
+
await self.database.execute(
|
|
149
|
+
sa.update(agentic_config_table)
|
|
150
|
+
.values(
|
|
151
|
+
title=config.title,
|
|
152
|
+
config=_serialize_config(config),
|
|
153
|
+
deleted_at=None,
|
|
154
|
+
)
|
|
155
|
+
.where(
|
|
156
|
+
agentic_config_table.c.account == account,
|
|
157
|
+
agentic_config_table.c.kbid == kbid,
|
|
158
|
+
agentic_config_table.c.agentic_id == agentic_id,
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
CACHE[_cache_key(account, kbid, agentic_id)] = config
|
|
162
|
+
return
|
|
146
163
|
|
|
147
164
|
insert_query = sa.insert(agentic_config_table).values(
|
|
148
165
|
account=account,
|
|
@@ -172,35 +189,58 @@ class AgenticConfigs:
|
|
|
172
189
|
f"Source(s) not found: {', '.join(missing)}"
|
|
173
190
|
)
|
|
174
191
|
|
|
175
|
-
async def
|
|
176
|
-
self, account: str, kbid: str,
|
|
177
|
-
) ->
|
|
178
|
-
"""Delete every agentic config that references source_id in its smart_agent
|
|
179
|
-
sources list. Returns the number of deleted configs.
|
|
180
|
-
|
|
181
|
-
Uses the JSONB containment operator (@>) to find matching rows:
|
|
182
|
-
config->'smart_agent'->'sources' @> '[{"source_id": "<id>"}]'
|
|
183
|
-
"""
|
|
192
|
+
async def delete_agentic_config(
|
|
193
|
+
self, account: str, kbid: str, agentic_id: str
|
|
194
|
+
) -> None:
|
|
184
195
|
query = (
|
|
185
|
-
sa.
|
|
196
|
+
sa.update(agentic_config_table)
|
|
197
|
+
.values(deleted_at=utc_now())
|
|
186
198
|
.where(
|
|
187
199
|
agentic_config_table.c.account == account,
|
|
188
200
|
agentic_config_table.c.kbid == kbid,
|
|
189
|
-
agentic_config_table.c.
|
|
190
|
-
|
|
191
|
-
json.dumps([{"source_id": source_id}]),
|
|
192
|
-
JSONB,
|
|
193
|
-
)
|
|
194
|
-
),
|
|
201
|
+
agentic_config_table.c.agentic_id == agentic_id,
|
|
202
|
+
agentic_config_table.c.deleted_at.is_(None),
|
|
195
203
|
)
|
|
196
204
|
.returning(agentic_config_table.c.agentic_id)
|
|
197
205
|
)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
206
|
+
deleted = await self.database.fetch_one(query)
|
|
207
|
+
if deleted is None:
|
|
208
|
+
raise exceptions.NotFound("Agentic configuration not found")
|
|
209
|
+
CACHE.pop(_cache_key(account, kbid, agentic_id), None)
|
|
210
|
+
|
|
211
|
+
async def configs_referencing_source(
|
|
212
|
+
self, account: str, kbid: str, source_id: str
|
|
213
|
+
) -> list[str]:
|
|
214
|
+
query = sa.select(agentic_config_table.c.agentic_id).where(
|
|
215
|
+
agentic_config_table.c.account == account,
|
|
216
|
+
agentic_config_table.c.kbid == kbid,
|
|
217
|
+
agentic_config_table.c.deleted_at.is_(None),
|
|
218
|
+
sa.func.jsonb_exists(
|
|
219
|
+
agentic_config_table.c.config["smart_agent"]["sources"], source_id
|
|
220
|
+
),
|
|
221
|
+
)
|
|
222
|
+
return [row["agentic_id"] for row in await self.database.fetch_all(query)]
|
|
223
|
+
|
|
224
|
+
async def hard_delete_expired_configs(self) -> int:
|
|
225
|
+
cutoff = utc_now() - datetime.timedelta(
|
|
226
|
+
days=self.settings.agentic_config_deletion_retention_days
|
|
227
|
+
)
|
|
228
|
+
conditions = (
|
|
229
|
+
agentic_config_table.c.deleted_at.is_not(None),
|
|
230
|
+
agentic_config_table.c.deleted_at < cutoff,
|
|
231
|
+
)
|
|
232
|
+
count_query = (
|
|
233
|
+
sa.select(sa.func.count())
|
|
234
|
+
.select_from(agentic_config_table)
|
|
235
|
+
.where(*conditions)
|
|
236
|
+
)
|
|
237
|
+
count = await self.database.fetch_val(count_query)
|
|
238
|
+
if count == 0:
|
|
239
|
+
return 0
|
|
240
|
+
|
|
241
|
+
delete_query = sa.delete(agentic_config_table).where(*conditions)
|
|
242
|
+
await self.database.execute(delete_query)
|
|
243
|
+
return count
|
|
204
244
|
|
|
205
245
|
async def get_agentic_config(
|
|
206
246
|
self, account: str, kbid: str, agentic_id: str
|
|
@@ -211,6 +251,7 @@ class AgenticConfigs:
|
|
|
211
251
|
agentic_config_table.c.account == account,
|
|
212
252
|
agentic_config_table.c.kbid == kbid,
|
|
213
253
|
agentic_config_table.c.agentic_id == agentic_id,
|
|
254
|
+
agentic_config_table.c.deleted_at.is_(None),
|
|
214
255
|
)
|
|
215
256
|
row = await self.database.fetch_one(query)
|
|
216
257
|
if not row:
|
|
@@ -229,6 +270,7 @@ class AgenticConfigs:
|
|
|
229
270
|
query = sa.select(agentic_config_table).where(
|
|
230
271
|
agentic_config_table.c.account == account,
|
|
231
272
|
agentic_config_table.c.kbid == kbid,
|
|
273
|
+
agentic_config_table.c.deleted_at.is_(None),
|
|
232
274
|
)
|
|
233
275
|
rows = await self.database.fetch_all(query)
|
|
234
276
|
return {row["agentic_id"]: _config_from_row(row) for row in rows}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
from pydantic import Field
|
|
1
2
|
from pydantic_settings import BaseSettings
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
class DataManagerSettings(BaseSettings):
|
|
5
6
|
postgresql_dsn: str
|
|
7
|
+
agentic_config_deletion_retention_days: int = Field(default=30, ge=0)
|
|
6
8
|
export_read_chunk_size: int = 1024 * 1024 # 1 MB
|
|
7
9
|
export_read_max_size: int = 10 * 1024 * 1024 # 10 MB
|
|
8
10
|
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Dict, Tuple
|
|
2
2
|
from uuid import uuid4
|
|
3
3
|
|
|
4
|
+
from hyperforge.agent import AgentConfig
|
|
5
|
+
from hyperforge.context.config import ContextAgentConfig
|
|
4
6
|
from hyperforge.driver import DriverConfig
|
|
5
7
|
from hyperforge.models import MemoryConfig, Rules
|
|
6
8
|
from hyperforge.retrieval.config import RetrievalAgentConfig
|
|
7
9
|
from hyperforge.workflows import WorkflowData
|
|
8
|
-
from
|
|
10
|
+
from hyperforge_google.config import GoogleAgentConfig
|
|
11
|
+
from hyperforge_mcp.config import MCPAgentConfig, Transport
|
|
9
12
|
from hyperforge_mcp.config_driver import (
|
|
10
13
|
MCPHTTPDriverConfig,
|
|
11
14
|
MCPHTTPInnerConfig,
|
|
@@ -14,10 +17,15 @@ from hyperforge_nucliadb.driver_config import (
|
|
|
14
17
|
NucliaDBConfig,
|
|
15
18
|
NucliaDBConnection,
|
|
16
19
|
)
|
|
20
|
+
from hyperforge_nucliadb_agentic.config import NucliaDBAgentConfig
|
|
17
21
|
from hyperforge_nucliadb_agentic.internal_driver import (
|
|
18
22
|
InternalNucliaDBConfig,
|
|
19
23
|
InternalNucliaDBConnection,
|
|
20
24
|
)
|
|
25
|
+
from hyperforge_perplexity.config import PerplexityAgentConfig
|
|
26
|
+
from hyperforge_rephrase.config import RephraseAgentConfig
|
|
27
|
+
from hyperforge_smart.config import SmartAgentConfig
|
|
28
|
+
from hyperforge_summarize.config import SummarizeAgentConfig
|
|
21
29
|
|
|
22
30
|
from nucliadb_agentic_api.db.sources import Sources
|
|
23
31
|
from nucliadb_agentic_api.models import AgenticConfigSchema
|
|
@@ -43,60 +51,35 @@ async def transform_agentic_config(
|
|
|
43
51
|
global_driver = []
|
|
44
52
|
|
|
45
53
|
title = agentic_config.title if agentic_config.title else "Default Agentic Config"
|
|
54
|
+
preprocess: list[AgentConfig] = []
|
|
55
|
+
context: list[AgentConfig] = []
|
|
56
|
+
generation: list[AgentConfig] = []
|
|
46
57
|
|
|
47
58
|
if agentic_config.rephrase:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if agentic_config.rephrase.history:
|
|
59
|
-
config["history"] = agentic_config.rephrase.history
|
|
60
|
-
|
|
61
|
-
config["module"] = "rephrase"
|
|
62
|
-
preprocess = [config]
|
|
63
|
-
else:
|
|
64
|
-
preprocess = []
|
|
59
|
+
rephrase = agentic_config.rephrase
|
|
60
|
+
rephrase_config = RephraseAgentConfig(
|
|
61
|
+
title=f"{title} - Retrieval",
|
|
62
|
+
rules=[rephrase.prompt] if rephrase.prompt else None,
|
|
63
|
+
kb=rephrase.ask_to,
|
|
64
|
+
history=rephrase.history if rephrase.history is not None else False,
|
|
65
|
+
)
|
|
66
|
+
if rephrase.model:
|
|
67
|
+
rephrase_config.model = rephrase.model
|
|
68
|
+
preprocess.append(rephrase_config)
|
|
65
69
|
|
|
66
70
|
if agentic_config.smart_agent:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if agentic_config.smart_agent.extra_prompt:
|
|
71
|
-
config["extra_prompt"] = agentic_config.smart_agent.extra_prompt
|
|
72
|
-
if agentic_config.smart_agent.models:
|
|
73
|
-
if agentic_config.smart_agent.models.context_validation:
|
|
74
|
-
config["context_validation_model"] = (
|
|
75
|
-
agentic_config.smart_agent.models.context_validation
|
|
76
|
-
)
|
|
77
|
-
if agentic_config.smart_agent.models.planner:
|
|
78
|
-
config["planner_model"] = agentic_config.smart_agent.models.planner
|
|
79
|
-
if agentic_config.smart_agent.models.executor:
|
|
80
|
-
config["executor_model"] = agentic_config.smart_agent.models.executor
|
|
81
|
-
if agentic_config.smart_agent.history:
|
|
82
|
-
config["history"] = agentic_config.smart_agent.history
|
|
83
|
-
if agentic_config.smart_agent.extra_prompt:
|
|
84
|
-
config["extra_prompt"] = agentic_config.smart_agent.extra_prompt
|
|
85
|
-
registered_agents = []
|
|
86
|
-
for source in agentic_config.smart_agent.sources:
|
|
71
|
+
smart_agent = agentic_config.smart_agent
|
|
72
|
+
registered_agents: list[ContextAgentConfig] = []
|
|
73
|
+
for source in smart_agent.sources:
|
|
87
74
|
source_obj = await source_manager.get_source(account, kbid, source)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
75
|
+
source_title = (
|
|
76
|
+
f"{title} - Smart Agent - {source_obj.type.capitalize()} Source"
|
|
77
|
+
)
|
|
91
78
|
|
|
92
79
|
if source_obj.type == "nucliadb":
|
|
93
80
|
# Same KB different
|
|
94
81
|
uid = uuid4().hex
|
|
95
82
|
|
|
96
|
-
source_config["id"] = f"nucliadb-agent-{source}"
|
|
97
|
-
source_config["sources"] = [uid]
|
|
98
|
-
source_config["module"] = "nucliadb_agent"
|
|
99
|
-
|
|
100
83
|
ndb_driver_config: DriverConfig
|
|
101
84
|
if internal_nucliadb and internal_nucliadb_url:
|
|
102
85
|
ndb_driver_config = InternalNucliaDBConfig(
|
|
@@ -137,15 +120,13 @@ async def transform_agentic_config(
|
|
|
137
120
|
"No NucliaDB URL configured for internal or external access"
|
|
138
121
|
)
|
|
139
122
|
drivers[uid] = ndb_driver_config
|
|
140
|
-
registered_agents.append(
|
|
123
|
+
registered_agents.append(
|
|
124
|
+
NucliaDBAgentConfig(
|
|
125
|
+
id=f"nucliadb-agent-{source}", title=source_title, sources=[uid]
|
|
126
|
+
)
|
|
127
|
+
)
|
|
141
128
|
elif source_obj.type == "mcp":
|
|
142
129
|
uid = uuid4().hex
|
|
143
|
-
# See the nucliadb branch above for why this must be
|
|
144
|
-
# deterministic rather than random.
|
|
145
|
-
source_config["id"] = f"mcp-agent-{source}"
|
|
146
|
-
source_config["sources"] = [uid]
|
|
147
|
-
source_config["transport"] = Transport.HTTP
|
|
148
|
-
source_config["module"] = "mcp"
|
|
149
130
|
mcp_driver_config = MCPHTTPDriverConfig(
|
|
150
131
|
identifier=uid,
|
|
151
132
|
name="MCPHTTP",
|
|
@@ -156,46 +137,68 @@ async def transform_agentic_config(
|
|
|
156
137
|
),
|
|
157
138
|
) # TODO: pass real config if needed
|
|
158
139
|
drivers[uid] = mcp_driver_config
|
|
159
|
-
registered_agents.append(
|
|
140
|
+
registered_agents.append(
|
|
141
|
+
MCPAgentConfig(
|
|
142
|
+
id=f"mcp-agent-{source}",
|
|
143
|
+
title=source_title,
|
|
144
|
+
source=uid,
|
|
145
|
+
transport=Transport.HTTP,
|
|
146
|
+
tool_choice_model=source_obj.tool_choice_model or "chatgpt-4.1",
|
|
147
|
+
valid_headers=source_obj.valid_headers or [],
|
|
148
|
+
)
|
|
149
|
+
)
|
|
160
150
|
|
|
161
151
|
elif source_obj.type == "google":
|
|
162
|
-
source_config["id"] = f"google-agent-{source}"
|
|
163
|
-
source_config["module"] = "google"
|
|
164
152
|
global_driver.append("google")
|
|
165
|
-
registered_agents.append(
|
|
153
|
+
registered_agents.append(
|
|
154
|
+
GoogleAgentConfig(id=f"google-agent-{source}", title=source_title)
|
|
155
|
+
)
|
|
166
156
|
|
|
167
157
|
elif source_obj.type == "perplexity":
|
|
168
|
-
source_config["id"] = f"perplexity-agent-{source}"
|
|
169
|
-
source_config["module"] = "perplexity"
|
|
170
158
|
global_driver.append("perplexity")
|
|
171
|
-
registered_agents.append(
|
|
159
|
+
registered_agents.append(
|
|
160
|
+
PerplexityAgentConfig(
|
|
161
|
+
id=f"perplexity-agent-{source}",
|
|
162
|
+
title=source_title,
|
|
163
|
+
domain=source_obj.enabled_domains or [],
|
|
164
|
+
prompt=None,
|
|
165
|
+
)
|
|
166
|
+
)
|
|
172
167
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
168
|
+
models = smart_agent.models
|
|
169
|
+
smart_config = SmartAgentConfig(
|
|
170
|
+
title=f"{title} - Smart Agent",
|
|
171
|
+
planning_mode=smart_agent.mode.value,
|
|
172
|
+
extra_prompt=smart_agent.extra_prompt,
|
|
173
|
+
history=smart_agent.history if smart_agent.history is not None else False,
|
|
174
|
+
# SmartAgentConfig's before-validator accepts serialized agent configs.
|
|
175
|
+
registered_agents=[agent.model_dump() for agent in registered_agents], # type: ignore[misc]
|
|
176
|
+
)
|
|
177
|
+
# Only set the models if they are provided; otherwise, leave them as None to use hyperforge's default models.
|
|
178
|
+
# TODO: Provide nucliadb agentic api level defaults for models
|
|
179
|
+
if models:
|
|
180
|
+
if models.context_validation:
|
|
181
|
+
smart_config.context_validation_model = models.context_validation
|
|
182
|
+
if models.planner:
|
|
183
|
+
smart_config.planner_model = models.planner
|
|
184
|
+
if models.executor:
|
|
185
|
+
smart_config.executor_model = models.executor
|
|
186
|
+
context.append(smart_config)
|
|
178
187
|
|
|
179
188
|
if agentic_config.summarize:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
config["module"] = "summarize"
|
|
195
|
-
|
|
196
|
-
generation = [config]
|
|
197
|
-
else:
|
|
198
|
-
generation = []
|
|
189
|
+
summarize = agentic_config.summarize
|
|
190
|
+
summarize_config = SummarizeAgentConfig(
|
|
191
|
+
title=f"{title} - Summarize Agent",
|
|
192
|
+
prompt=summarize.user_prompt,
|
|
193
|
+
system_prompt=summarize.system_prompt,
|
|
194
|
+
conversational=summarize.conversational,
|
|
195
|
+
citations=True,
|
|
196
|
+
force_chunk_level_citations=False,
|
|
197
|
+
history=summarize.history if summarize.history is not None else False,
|
|
198
|
+
)
|
|
199
|
+
if summarize.model:
|
|
200
|
+
summarize_config.model = summarize.model
|
|
201
|
+
generation.append(summarize_config)
|
|
199
202
|
|
|
200
203
|
return (
|
|
201
204
|
RetrievalAgentConfig(
|
|
@@ -208,9 +211,9 @@ async def transform_agentic_config(
|
|
|
208
211
|
description="Default workflow for agentic config transformation",
|
|
209
212
|
parameters=None,
|
|
210
213
|
),
|
|
211
|
-
preprocess=preprocess,
|
|
212
|
-
context=context,
|
|
213
|
-
generation=generation,
|
|
214
|
+
preprocess=preprocess,
|
|
215
|
+
context=context,
|
|
216
|
+
generation=generation,
|
|
214
217
|
postprocess=[],
|
|
215
218
|
),
|
|
216
219
|
drivers,
|
|
@@ -133,7 +133,7 @@ class AgenticRephraseConfiguration(BaseModel):
|
|
|
133
133
|
ask_to: Optional[str] = None
|
|
134
134
|
prompt: Optional[str] = None
|
|
135
135
|
model: Optional[str] = None
|
|
136
|
-
history: Optional[bool] =
|
|
136
|
+
history: Optional[bool] = True
|
|
137
137
|
|
|
138
138
|
|
|
139
139
|
class AgenticSmartAgentMode(str, Enum):
|
|
@@ -218,15 +218,15 @@ class AgenticSmartAgentConfiguration(BaseModel):
|
|
|
218
218
|
extra_prompt: Optional[str] = None
|
|
219
219
|
models: Optional[AgenticSmartAgentModels] = None
|
|
220
220
|
sources: List[str] = Field(default_factory=list)
|
|
221
|
-
history: Optional[bool] =
|
|
221
|
+
history: Optional[bool] = True
|
|
222
222
|
|
|
223
223
|
|
|
224
224
|
class AgenticSummarizeConfiguration(BaseModel):
|
|
225
225
|
user_prompt: Optional[str] = None
|
|
226
226
|
system_prompt: Optional[str] = None
|
|
227
|
-
conversational: bool =
|
|
227
|
+
conversational: bool = True
|
|
228
228
|
model: Optional[str] = None
|
|
229
|
-
history: Optional[bool] =
|
|
229
|
+
history: Optional[bool] = True
|
|
230
230
|
|
|
231
231
|
|
|
232
232
|
class AgenticConfigSchema(BaseModel):
|
|
@@ -7,7 +7,6 @@ from hyperforge.broker.redis import RedisBroker
|
|
|
7
7
|
from hyperforge.feature_flag import get_flag_service
|
|
8
8
|
from hyperforge.server.cache import ValkeyCache
|
|
9
9
|
from hyperforge.server.run import run_metrics_server
|
|
10
|
-
from hyperforge_nucliadb_agentic.ask.predict import start_predict_engine
|
|
11
10
|
from nucliadb_telemetry.logs import setup_logging
|
|
12
11
|
from nucliadb_telemetry.settings import LogFormatType, LogLevel, LogSettings
|
|
13
12
|
from nucliadb_telemetry.utils import setup_telemetry
|
|
@@ -40,8 +39,6 @@ async def run_server(
|
|
|
40
39
|
if tracer:
|
|
41
40
|
await setup_telemetry(SERVICE_NAME)
|
|
42
41
|
|
|
43
|
-
await start_predict_engine()
|
|
44
|
-
|
|
45
42
|
audit_settings: AuditSettings = AuditSettings()
|
|
46
43
|
# Connect to Valkey
|
|
47
44
|
broker = RedisBroker.from_url(
|
|
@@ -255,6 +255,12 @@ class NucliaDBAgenticSessionManager(SessionManager):
|
|
|
255
255
|
errors.capture_exception(e)
|
|
256
256
|
error = ARAGException(detail=str(e))
|
|
257
257
|
observation.set_status("error")
|
|
258
|
+
finally:
|
|
259
|
+
try:
|
|
260
|
+
await state.manager.aclose()
|
|
261
|
+
except Exception as e:
|
|
262
|
+
logger.exception("Error closing Manager")
|
|
263
|
+
errors.capture_exception(e)
|
|
258
264
|
|
|
259
265
|
observation.end()
|
|
260
266
|
answer_running.dec()
|
|
@@ -115,3 +115,28 @@ async def patch_agentic_config_endpoint(
|
|
|
115
115
|
except exceptions.InvalidReference as exc:
|
|
116
116
|
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
117
117
|
return Response(status_code=204)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@router.delete(
|
|
121
|
+
"/api/v1/kb/{kbid}/agentic_configs/{agentic_id}",
|
|
122
|
+
status_code=204,
|
|
123
|
+
summary="Delete agentic configuration",
|
|
124
|
+
tags=["Agentic configs"],
|
|
125
|
+
)
|
|
126
|
+
@requires(NucliaDBRoles.OWNER)
|
|
127
|
+
async def delete_agentic_config_endpoint(
|
|
128
|
+
request: Request,
|
|
129
|
+
kbid: str,
|
|
130
|
+
agentic_id: str,
|
|
131
|
+
x_nucliadb_account: str = Header(default="", include_in_schema=False),
|
|
132
|
+
) -> Response:
|
|
133
|
+
app = cast("HTTPApplication", request.app)
|
|
134
|
+
try:
|
|
135
|
+
await app.agent_manager.delete_agentic_config(
|
|
136
|
+
account=x_nucliadb_account,
|
|
137
|
+
kbid=kbid,
|
|
138
|
+
agentic_id=agentic_id,
|
|
139
|
+
)
|
|
140
|
+
except exceptions.NotFound as exc:
|
|
141
|
+
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
142
|
+
return Response(status_code=204)
|
|
@@ -2,6 +2,7 @@ import json
|
|
|
2
2
|
from uuid import UUID
|
|
3
3
|
|
|
4
4
|
from fastapi import Header, Request, Response
|
|
5
|
+
from hyperforge.manager import Manager
|
|
5
6
|
from hyperforge_nucliadb_agentic.ask.exceptions import (
|
|
6
7
|
AnswerJsonSchemaTooLong,
|
|
7
8
|
)
|
|
@@ -108,6 +109,7 @@ async def ask_knowledgebox_endpoint(
|
|
|
108
109
|
)
|
|
109
110
|
|
|
110
111
|
return await create_ask_response(
|
|
112
|
+
predict_manager=request.app.predict_manager,
|
|
111
113
|
kbid=kbid,
|
|
112
114
|
ask_request=item,
|
|
113
115
|
user_id=x_nucliadb_user,
|
|
@@ -169,6 +171,7 @@ async def resource_ask_endpoint_by_uuid(
|
|
|
169
171
|
)
|
|
170
172
|
|
|
171
173
|
return await create_ask_response(
|
|
174
|
+
predict_manager=request.app.predict_manager,
|
|
172
175
|
kbid=kbid,
|
|
173
176
|
ask_request=item,
|
|
174
177
|
user_id=x_nucliadb_user,
|
|
@@ -238,6 +241,7 @@ async def resource_ask_endpoint_by_slug(
|
|
|
238
241
|
)
|
|
239
242
|
|
|
240
243
|
return await create_ask_response(
|
|
244
|
+
predict_manager=request.app.predict_manager,
|
|
241
245
|
kbid=kbid,
|
|
242
246
|
ask_request=item,
|
|
243
247
|
user_id=x_nucliadb_user,
|
|
@@ -251,6 +255,7 @@ async def resource_ask_endpoint_by_slug(
|
|
|
251
255
|
|
|
252
256
|
@handled_ask_exceptions
|
|
253
257
|
async def create_ask_response(
|
|
258
|
+
predict_manager: Manager,
|
|
254
259
|
kbid: str,
|
|
255
260
|
ask_request: AskRequest,
|
|
256
261
|
user_id: str,
|
|
@@ -266,6 +271,7 @@ async def create_ask_response(
|
|
|
266
271
|
search_sdk=rpc.get_sdk("search"),
|
|
267
272
|
reader_sdk=rpc.get_sdk("reader"),
|
|
268
273
|
kbid=kbid,
|
|
274
|
+
predict_manager=predict_manager,
|
|
269
275
|
ask_request=ask_request,
|
|
270
276
|
user_id=user_id,
|
|
271
277
|
client_type=client_type,
|
|
@@ -10,6 +10,7 @@ import anyio
|
|
|
10
10
|
import pydantic_core
|
|
11
11
|
from fastapi import Header
|
|
12
12
|
from hyperforge.api.authentication import requires_one
|
|
13
|
+
from hyperforge.manager import Manager
|
|
13
14
|
from mcp.server.fastmcp.exceptions import ResourceError
|
|
14
15
|
from mcp.server.fastmcp.utilities.types import Image
|
|
15
16
|
from mcp.server.lowlevel.helper_types import ReadResourceContents
|
|
@@ -34,7 +35,7 @@ from starlette.responses import Response
|
|
|
34
35
|
from nucliadb_agentic_api import logger
|
|
35
36
|
|
|
36
37
|
if TYPE_CHECKING:
|
|
37
|
-
from
|
|
38
|
+
from nucliadb_agentic_api.app import HTTPApplication
|
|
38
39
|
|
|
39
40
|
from anyio.abc import TaskStatus
|
|
40
41
|
from hyperforge_nucliadb_agentic.ask.exceptions import (
|
|
@@ -80,6 +81,7 @@ BATCH_GET_DOCUMENTS_MAX = 20
|
|
|
80
81
|
|
|
81
82
|
@dataclass
|
|
82
83
|
class MCPContext:
|
|
84
|
+
predict_manager: Manager
|
|
83
85
|
ndb_reader: NucliaDBAsync
|
|
84
86
|
ndb_search: NucliaDBAsync
|
|
85
87
|
kbid: str
|
|
@@ -249,6 +251,7 @@ async def _tool_search_documents(
|
|
|
249
251
|
search_sdk=context.ndb_search,
|
|
250
252
|
reader_sdk=context.ndb_reader,
|
|
251
253
|
kbid=context.kbid,
|
|
254
|
+
predict_manager=context.predict_manager,
|
|
252
255
|
ask_request=ask_request,
|
|
253
256
|
user_id=context.x_nucliadb_user,
|
|
254
257
|
client_type=context.x_ndb_client,
|
|
@@ -469,6 +472,7 @@ async def list_tools(description: str) -> list[Tool]:
|
|
|
469
472
|
|
|
470
473
|
|
|
471
474
|
async def call_tool(
|
|
475
|
+
predict_manager: Manager,
|
|
472
476
|
x_stf_account: str,
|
|
473
477
|
x_nucliadb_user: str,
|
|
474
478
|
x_ndb_client: NucliaDBClientType,
|
|
@@ -492,6 +496,7 @@ async def call_tool(
|
|
|
492
496
|
)
|
|
493
497
|
raise ResourceError(f"Invalid arguments for '{name}': {error_details}") from e
|
|
494
498
|
context = MCPContext(
|
|
499
|
+
predict_manager=predict_manager,
|
|
495
500
|
ndb_reader=ndb_reader,
|
|
496
501
|
ndb_search=ndb_search,
|
|
497
502
|
kbid=kbid,
|
|
@@ -678,6 +683,7 @@ async def mcp_handler(
|
|
|
678
683
|
list_resources_partial = partial(list_resources, ndb_reader, kbid)
|
|
679
684
|
call_tool_partial = partial(
|
|
680
685
|
call_tool,
|
|
686
|
+
app.predict_manager,
|
|
681
687
|
x_stf_account,
|
|
682
688
|
x_nucliadb_user,
|
|
683
689
|
x_ndb_client,
|
|
@@ -141,6 +141,16 @@ async def delete_source_endpoint(
|
|
|
141
141
|
) -> Response:
|
|
142
142
|
app = cast("HTTPApplication", request.app)
|
|
143
143
|
try:
|
|
144
|
+
configs = await app.agent_manager.configs_referencing_source(
|
|
145
|
+
account=x_nucliadb_account,
|
|
146
|
+
kbid=kbid,
|
|
147
|
+
source_id=source_id,
|
|
148
|
+
)
|
|
149
|
+
if configs:
|
|
150
|
+
raise exceptions.InUse(
|
|
151
|
+
"Source is in use by agentic configuration(s): "
|
|
152
|
+
+ ", ".join(sorted(configs))
|
|
153
|
+
)
|
|
144
154
|
await app.source_manager.delete_source(
|
|
145
155
|
account=x_nucliadb_account,
|
|
146
156
|
kbid=kbid,
|
|
@@ -148,11 +158,6 @@ async def delete_source_endpoint(
|
|
|
148
158
|
)
|
|
149
159
|
except exceptions.NotFound as exc:
|
|
150
160
|
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
await app.agent_manager.delete_configs_referencing_source(
|
|
154
|
-
account=x_nucliadb_account,
|
|
155
|
-
kbid=kbid,
|
|
156
|
-
source_id=source_id,
|
|
157
|
-
)
|
|
161
|
+
except exceptions.InUse as exc:
|
|
162
|
+
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
|
158
163
|
return Response(status_code=204)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.0.0.post211
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nucliadb_agentic_api-1.0.0.post211 → nucliadb_agentic_api-1.0.0.post230}/alembic/script.py.mako
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|