nucliadb-agentic-api 1.0.0.post156__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.
- nucliadb_agentic_api/VERSION +1 -0
- nucliadb_agentic_api/__init__.py +20 -0
- nucliadb_agentic_api/agentic/__init__.py +0 -0
- nucliadb_agentic_api/agentic/ask_handler.py +66 -0
- nucliadb_agentic_api/agentic/ask_result.py +389 -0
- nucliadb_agentic_api/agentic/ask_transform_to_interaction.py +10 -0
- nucliadb_agentic_api/app.py +147 -0
- nucliadb_agentic_api/commands.py +66 -0
- nucliadb_agentic_api/db/__init__.py +0 -0
- nucliadb_agentic_api/db/agentic_configs.py +296 -0
- nucliadb_agentic_api/db/settings.py +11 -0
- nucliadb_agentic_api/db/sources.py +202 -0
- nucliadb_agentic_api/db/transform.py +208 -0
- nucliadb_agentic_api/exceptions.py +10 -0
- nucliadb_agentic_api/logging.py +18 -0
- nucliadb_agentic_api/models.py +321 -0
- nucliadb_agentic_api/py.typed +0 -0
- nucliadb_agentic_api/server/__init__.py +1 -0
- nucliadb_agentic_api/server/server.py +113 -0
- nucliadb_agentic_api/server/session.py +276 -0
- nucliadb_agentic_api/server/settings.py +22 -0
- nucliadb_agentic_api/settings.py +39 -0
- nucliadb_agentic_api/utils.py +284 -0
- nucliadb_agentic_api/v1/__init__.py +19 -0
- nucliadb_agentic_api/v1/agentic_configs.py +117 -0
- nucliadb_agentic_api/v1/ask.py +303 -0
- nucliadb_agentic_api/v1/ask_websocket.py +141 -0
- nucliadb_agentic_api/v1/mcp_content.py +310 -0
- nucliadb_agentic_api/v1/mcp_nucliadb.py +785 -0
- nucliadb_agentic_api/v1/oauth.py +60 -0
- nucliadb_agentic_api/v1/router.py +3 -0
- nucliadb_agentic_api/v1/sources.py +158 -0
- nucliadb_agentic_api/v1/utils.py +12 -0
- nucliadb_agentic_api-1.0.0.post156.dist-info/METADATA +150 -0
- nucliadb_agentic_api-1.0.0.post156.dist-info/RECORD +37 -0
- nucliadb_agentic_api-1.0.0.post156.dist-info/WHEEL +4 -0
- nucliadb_agentic_api-1.0.0.post156.dist-info/entry_points.txt +6 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
from typing import Any, Dict, Tuple
|
|
2
|
+
from uuid import uuid4
|
|
3
|
+
|
|
4
|
+
from hyperforge.driver import DriverConfig
|
|
5
|
+
from hyperforge.models import MemoryConfig, Rules
|
|
6
|
+
from hyperforge.retrieval.config import RetrievalAgentConfig
|
|
7
|
+
from hyperforge.workflows import WorkflowData
|
|
8
|
+
from hyperforge_mcp.config import Transport
|
|
9
|
+
from hyperforge_mcp.config_driver import (
|
|
10
|
+
MCPHTTPDriverConfig,
|
|
11
|
+
MCPHTTPInnerConfig,
|
|
12
|
+
)
|
|
13
|
+
from hyperforge_nucliadb.driver_config import (
|
|
14
|
+
NucliaDBConfig,
|
|
15
|
+
NucliaDBConnection,
|
|
16
|
+
)
|
|
17
|
+
from hyperforge_nucliadb_agentic.ask.model import AskRequest
|
|
18
|
+
from hyperforge_nucliadb_agentic.internal_driver import (
|
|
19
|
+
InternalNucliaDBConfig,
|
|
20
|
+
InternalNucliaDBConnection,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from nucliadb_agentic_api.db.sources import Sources
|
|
24
|
+
from nucliadb_agentic_api.models import AgenticConfigSchema
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def transform_agentic_config(
|
|
28
|
+
agentic_config: AgenticConfigSchema,
|
|
29
|
+
source_manager: Sources,
|
|
30
|
+
account: str,
|
|
31
|
+
internal_nucliadb: bool = True,
|
|
32
|
+
internal_nucliadb_url: str | None = None,
|
|
33
|
+
external_nucliadb_url: str | None = None,
|
|
34
|
+
external_nucliadb_key: str | None = None,
|
|
35
|
+
ask_request: AskRequest | None = None,
|
|
36
|
+
kbid: str = "",
|
|
37
|
+
) -> Tuple[RetrievalAgentConfig, Dict[str, DriverConfig], list[str]]:
|
|
38
|
+
drivers: Dict[str, DriverConfig] = {}
|
|
39
|
+
global_driver = []
|
|
40
|
+
|
|
41
|
+
title = agentic_config.title if agentic_config.title else "Default Agentic Config"
|
|
42
|
+
|
|
43
|
+
if agentic_config.rephrase:
|
|
44
|
+
config: Dict[str, Any] = {
|
|
45
|
+
"title": f"{title} - Retrieval",
|
|
46
|
+
}
|
|
47
|
+
if agentic_config.rephrase.model:
|
|
48
|
+
config["model"] = agentic_config.rephrase.model
|
|
49
|
+
if agentic_config.rephrase.prompt:
|
|
50
|
+
config["rules"] = [agentic_config.rephrase.prompt]
|
|
51
|
+
if agentic_config.rephrase.ask_to:
|
|
52
|
+
# Same KB with different layers
|
|
53
|
+
config["kbid"] = agentic_config.rephrase.ask_to
|
|
54
|
+
if agentic_config.rephrase.history:
|
|
55
|
+
config["history"] = agentic_config.rephrase.history
|
|
56
|
+
|
|
57
|
+
config["module"] = "rephrase"
|
|
58
|
+
preprocess = [config]
|
|
59
|
+
else:
|
|
60
|
+
preprocess = []
|
|
61
|
+
|
|
62
|
+
if agentic_config.smart_agent:
|
|
63
|
+
config = {
|
|
64
|
+
"title": f"{title} - Smart Agent",
|
|
65
|
+
}
|
|
66
|
+
if agentic_config.smart_agent.extra_prompt:
|
|
67
|
+
config["extra_prompt"] = agentic_config.smart_agent.extra_prompt
|
|
68
|
+
if agentic_config.smart_agent.models:
|
|
69
|
+
if agentic_config.smart_agent.models.context_validation:
|
|
70
|
+
config["context_validation_model"] = (
|
|
71
|
+
agentic_config.smart_agent.models.context_validation
|
|
72
|
+
)
|
|
73
|
+
if agentic_config.smart_agent.models.planner:
|
|
74
|
+
config["planner_model"] = agentic_config.smart_agent.models.planner
|
|
75
|
+
if agentic_config.smart_agent.models.executor:
|
|
76
|
+
config["executor_model"] = agentic_config.smart_agent.models.executor
|
|
77
|
+
if agentic_config.smart_agent.history:
|
|
78
|
+
config["history"] = agentic_config.smart_agent.history
|
|
79
|
+
if agentic_config.smart_agent.extra_prompt:
|
|
80
|
+
config["extra_prompt"] = agentic_config.smart_agent.extra_prompt
|
|
81
|
+
registered_agents = []
|
|
82
|
+
for source in agentic_config.smart_agent.sources:
|
|
83
|
+
source_obj = await source_manager.get_source(account, kbid, source)
|
|
84
|
+
source_config: Dict[str, Any] = {
|
|
85
|
+
"title": f"{title} - Smart Agent - {source_obj.type.capitalize()} Source",
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if source_obj.type == "nucliadb":
|
|
89
|
+
# Same KB different
|
|
90
|
+
uid = uuid4().hex
|
|
91
|
+
|
|
92
|
+
source_config["sources"] = [uid]
|
|
93
|
+
source_config["module"] = "nucliadb_agent"
|
|
94
|
+
|
|
95
|
+
ndb_driver_config: DriverConfig
|
|
96
|
+
if internal_nucliadb and internal_nucliadb_url:
|
|
97
|
+
ndb_driver_config = InternalNucliaDBConfig(
|
|
98
|
+
identifier=uid,
|
|
99
|
+
name="NucliaDB",
|
|
100
|
+
provider="nucliadb_internal",
|
|
101
|
+
config=InternalNucliaDBConnection(
|
|
102
|
+
url=internal_nucliadb_url,
|
|
103
|
+
key=None,
|
|
104
|
+
manager="",
|
|
105
|
+
description="",
|
|
106
|
+
kbid=kbid,
|
|
107
|
+
filter_expression=source_obj.filter_expression,
|
|
108
|
+
filters=source_obj.resource_filters
|
|
109
|
+
if source_obj.resource_filters
|
|
110
|
+
else [],
|
|
111
|
+
),
|
|
112
|
+
)
|
|
113
|
+
elif external_nucliadb_url:
|
|
114
|
+
ndb_driver_config = NucliaDBConfig(
|
|
115
|
+
identifier=uid,
|
|
116
|
+
name="NucliaDB",
|
|
117
|
+
provider="nucliadb",
|
|
118
|
+
config=NucliaDBConnection(
|
|
119
|
+
url=external_nucliadb_url,
|
|
120
|
+
key=external_nucliadb_key,
|
|
121
|
+
manager="",
|
|
122
|
+
description="",
|
|
123
|
+
kbid=kbid,
|
|
124
|
+
filter_expression=source_obj.filter_expression,
|
|
125
|
+
filters=source_obj.resource_filters
|
|
126
|
+
if source_obj.resource_filters
|
|
127
|
+
else [],
|
|
128
|
+
),
|
|
129
|
+
)
|
|
130
|
+
else:
|
|
131
|
+
raise ValueError(
|
|
132
|
+
"No NucliaDB URL configured for internal or external access"
|
|
133
|
+
)
|
|
134
|
+
drivers[uid] = ndb_driver_config
|
|
135
|
+
registered_agents.append(source_config)
|
|
136
|
+
elif source_obj.type == "mcp":
|
|
137
|
+
uid = uuid4().hex
|
|
138
|
+
source_config["sources"] = [uid]
|
|
139
|
+
source_config["transport"] = Transport.HTTP
|
|
140
|
+
source_config["module"] = "mcp"
|
|
141
|
+
mcp_driver_config = MCPHTTPDriverConfig(
|
|
142
|
+
identifier=uid,
|
|
143
|
+
name="MCPHTTP",
|
|
144
|
+
provider="mcphttp",
|
|
145
|
+
config=MCPHTTPInnerConfig(
|
|
146
|
+
uri=source_obj.uri, # TODO: pass real URL if needed
|
|
147
|
+
headers=source_obj.headers if source_obj.headers else {},
|
|
148
|
+
),
|
|
149
|
+
) # TODO: pass real config if needed
|
|
150
|
+
drivers[uid] = mcp_driver_config
|
|
151
|
+
registered_agents.append(source_config)
|
|
152
|
+
|
|
153
|
+
elif source_obj.type == "google":
|
|
154
|
+
source_config["module"] = "google"
|
|
155
|
+
global_driver.append("google")
|
|
156
|
+
registered_agents.append(source_config)
|
|
157
|
+
|
|
158
|
+
elif source_obj.type == "perplexity":
|
|
159
|
+
source_config["module"] = "perplexity"
|
|
160
|
+
global_driver.append("perplexity")
|
|
161
|
+
registered_agents.append(source_config)
|
|
162
|
+
|
|
163
|
+
config["registered_agents"] = registered_agents
|
|
164
|
+
config["module"] = "smart"
|
|
165
|
+
context = [config]
|
|
166
|
+
else:
|
|
167
|
+
context = []
|
|
168
|
+
|
|
169
|
+
if agentic_config.summarize:
|
|
170
|
+
config = {
|
|
171
|
+
"title": f"{title} - Summarize Agent",
|
|
172
|
+
}
|
|
173
|
+
if agentic_config.summarize.model:
|
|
174
|
+
config["model"] = agentic_config.summarize.model
|
|
175
|
+
if agentic_config.summarize.user_prompt:
|
|
176
|
+
config["user_prompt"] = agentic_config.summarize.user_prompt
|
|
177
|
+
if agentic_config.summarize.system_prompt:
|
|
178
|
+
config["system_prompt"] = agentic_config.summarize.system_prompt
|
|
179
|
+
if agentic_config.summarize.conversational:
|
|
180
|
+
config["conversational"] = agentic_config.summarize.conversational
|
|
181
|
+
if agentic_config.summarize.history:
|
|
182
|
+
config["history"] = agentic_config.summarize.history
|
|
183
|
+
|
|
184
|
+
config["module"] = "summarize"
|
|
185
|
+
|
|
186
|
+
generation = [config]
|
|
187
|
+
else:
|
|
188
|
+
generation = []
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
RetrievalAgentConfig(
|
|
192
|
+
drivers=[],
|
|
193
|
+
rules=Rules(),
|
|
194
|
+
memory=MemoryConfig(),
|
|
195
|
+
workflow=WorkflowData(
|
|
196
|
+
id="default",
|
|
197
|
+
name="Default Workflow",
|
|
198
|
+
description="Default workflow for agentic config transformation",
|
|
199
|
+
parameters=None,
|
|
200
|
+
),
|
|
201
|
+
preprocess=preprocess, # type: ignore
|
|
202
|
+
context=context, # type: ignore
|
|
203
|
+
generation=generation, # type: ignore
|
|
204
|
+
postprocess=[],
|
|
205
|
+
),
|
|
206
|
+
drivers,
|
|
207
|
+
global_driver,
|
|
208
|
+
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class Conflict(Exception):
|
|
2
|
+
"""Raised when a resource already exists and cannot be created again."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class NotFound(Exception):
|
|
6
|
+
"""Raised when a resource is not found."""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InvalidReference(Exception):
|
|
10
|
+
"""Raised when a referenced resource does not exist."""
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from importlib.metadata import version
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import sentry_sdk
|
|
5
|
+
from sentry_sdk.integrations.excepthook import ExcepthookIntegration
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def set_sentry(zone: str, environment: str, sentry_url: Optional[str] = None):
|
|
9
|
+
if sentry_url:
|
|
10
|
+
sentry_exception = ExcepthookIntegration(always_run=True)
|
|
11
|
+
version_num = version("nucliadb_agentic_api")
|
|
12
|
+
sentry_sdk.init(
|
|
13
|
+
release=version_num,
|
|
14
|
+
environment=environment,
|
|
15
|
+
dsn=sentry_url,
|
|
16
|
+
integrations=[sentry_exception],
|
|
17
|
+
)
|
|
18
|
+
sentry_sdk.set_tag("zone", zone)
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Any, Dict, List, Literal, Optional, Union
|
|
3
|
+
|
|
4
|
+
from hyperforge.driver import DriverConfig
|
|
5
|
+
from hyperforge.models import Rules
|
|
6
|
+
from nucliadb_models import FilterExpression, TextFormat
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
from typing_extensions import Annotated
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class StashRoles(str, Enum):
|
|
12
|
+
# Can do anything at the stash
|
|
13
|
+
OWNER = "SOWNER"
|
|
14
|
+
|
|
15
|
+
# Can access the stash
|
|
16
|
+
MEMBER = "SMEMBER"
|
|
17
|
+
|
|
18
|
+
# Can access the stash
|
|
19
|
+
CONTRIBUTOR = "SCONTRIBUTOR"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AccountRoles(str, Enum):
|
|
23
|
+
OWNER = "AOWNER"
|
|
24
|
+
MEMBER = "AMEMBER"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AgentRole(str, Enum):
|
|
28
|
+
MEMBER = "SESSIONMEMBER"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class NucliaDBRoles(str, Enum):
|
|
32
|
+
MANAGER = "MANAGER"
|
|
33
|
+
READER = "READER"
|
|
34
|
+
WRITER = "WRITER"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class UserType(str, Enum):
|
|
38
|
+
ROOT = "ROOT"
|
|
39
|
+
DEALER = "DEALER"
|
|
40
|
+
USER = "USER"
|
|
41
|
+
READONLY = "READONLY"
|
|
42
|
+
MANAGER = "MANAGER"
|
|
43
|
+
SALES = "SALES"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class AccountTypes(str, Enum):
|
|
47
|
+
TRIAL = "stash-trial"
|
|
48
|
+
STARTER = "stash-starter"
|
|
49
|
+
GROWTH = "stash-growth"
|
|
50
|
+
STARTUP = "stash-startup"
|
|
51
|
+
ENTERPRISE = "stash-enterprise"
|
|
52
|
+
|
|
53
|
+
# will be removed at some point in the near future
|
|
54
|
+
DEVELOPER = "stash-developer"
|
|
55
|
+
BUSINESS = "stash-business"
|
|
56
|
+
|
|
57
|
+
# V3 account types
|
|
58
|
+
V3_STARTER = "v3starter"
|
|
59
|
+
V3_FLY = "v3fly"
|
|
60
|
+
V3_GROWTH = "v3growth"
|
|
61
|
+
V3_PRO = "v3pro"
|
|
62
|
+
V3_ENTERPRISE = "v3enterprise"
|
|
63
|
+
COWORK = "cowork"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class SessionData(BaseModel):
|
|
67
|
+
slug: str
|
|
68
|
+
name: str
|
|
69
|
+
summary: str
|
|
70
|
+
data: str
|
|
71
|
+
format: TextFormat
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
INFO_FIELD_ID = "info"
|
|
75
|
+
|
|
76
|
+
DEFAULT_RESOURCE_LIST_PAGE_SIZE = 20
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class InspectData(BaseModel):
|
|
80
|
+
contexts: List[Any]
|
|
81
|
+
driver: List[DriverConfig]
|
|
82
|
+
postprocess: List[Any]
|
|
83
|
+
preprocess: List[Any]
|
|
84
|
+
rules: Rules
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class AgentID(BaseModel):
|
|
88
|
+
id: str
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class DriverID(BaseModel):
|
|
92
|
+
id: str
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class PromptID(BaseModel):
|
|
96
|
+
id: str
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class InteractionsAuditDownloadRequest(BaseModel):
|
|
100
|
+
session_id: Optional[str] = Field(default=None, description="Filter by session ID")
|
|
101
|
+
year: Optional[int] = Field(
|
|
102
|
+
default=None,
|
|
103
|
+
description="Filter by year (e.g., 2024). If not specified, defaults to the current year.",
|
|
104
|
+
)
|
|
105
|
+
month: Optional[int] = Field(
|
|
106
|
+
default=None,
|
|
107
|
+
description="Filter by month (1-12). If not specified, defaults to the past month.",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class DownloadStatus(BaseModel):
|
|
112
|
+
id: str
|
|
113
|
+
type: str
|
|
114
|
+
status: Literal["pending", "ready"]
|
|
115
|
+
download_url: str | None
|
|
116
|
+
query: dict[str, Any]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class InteractionOperation(int, Enum):
|
|
120
|
+
QUESTION = 0
|
|
121
|
+
QUIT = 1
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class InteractionRequest(BaseModel):
|
|
125
|
+
question: str
|
|
126
|
+
headers: Dict[str, str] = {}
|
|
127
|
+
arguments: Dict[str, str] = {}
|
|
128
|
+
operation: InteractionOperation = InteractionOperation.QUESTION
|
|
129
|
+
streaming: bool = False
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class AgenticRephraseConfiguration(BaseModel):
|
|
133
|
+
ask_to: Optional[str] = None
|
|
134
|
+
prompt: Optional[str] = None
|
|
135
|
+
model: Optional[str] = None
|
|
136
|
+
history: Optional[bool] = None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class AgenticSmartAgentMode(str, Enum):
|
|
140
|
+
REACTIVE = "reactive"
|
|
141
|
+
PLAN_EXECUTE = "plan_execute"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class AgenticSmartAgentModels(BaseModel):
|
|
145
|
+
context_validation: Optional[str] = None
|
|
146
|
+
planner: Optional[str] = None
|
|
147
|
+
executor: Optional[str] = None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class NucliaDBAgenticSource(BaseModel):
|
|
151
|
+
type: Literal["nucliadb"] = "nucliadb"
|
|
152
|
+
source_id: Optional[str] = Field(
|
|
153
|
+
default=None,
|
|
154
|
+
description="ID of an existing source in the sources table",
|
|
155
|
+
)
|
|
156
|
+
description: Optional[str] = None
|
|
157
|
+
filter_expression: Optional[str] = None
|
|
158
|
+
connection: Optional[str] = None
|
|
159
|
+
params: Optional[Dict[str, Any]] = None
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class GoogleAgenticSource(BaseModel):
|
|
163
|
+
type: Literal["google"] = "google"
|
|
164
|
+
source_id: Optional[str] = Field(
|
|
165
|
+
default=None,
|
|
166
|
+
description="ID of an existing source in the sources table",
|
|
167
|
+
)
|
|
168
|
+
description: Optional[str] = None
|
|
169
|
+
params: Optional[Dict[str, Any]] = None
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class PerplexityAgenticSource(BaseModel):
|
|
173
|
+
type: Literal["perplexity"] = "perplexity"
|
|
174
|
+
source_id: Optional[str] = Field(
|
|
175
|
+
default=None,
|
|
176
|
+
description="ID of an existing source in the sources table",
|
|
177
|
+
)
|
|
178
|
+
description: Optional[str] = None
|
|
179
|
+
params: Optional[Dict[str, Any]] = None
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class MCPAgenticSource(BaseModel):
|
|
183
|
+
type: Literal["mcp"] = "mcp"
|
|
184
|
+
source_id: Optional[str] = Field(
|
|
185
|
+
default=None,
|
|
186
|
+
description="ID of an existing source in the sources table",
|
|
187
|
+
)
|
|
188
|
+
description: Optional[str] = None
|
|
189
|
+
uri: str
|
|
190
|
+
headers: Optional[Dict[str, Any]] = None
|
|
191
|
+
tool_choice_model: Optional[str] = None
|
|
192
|
+
valid_headers: Optional[List[str]] = None
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class SyncAgenticSource(BaseModel):
|
|
196
|
+
type: Literal["sync"] = "sync"
|
|
197
|
+
source_id: Optional[str] = Field(
|
|
198
|
+
default=None,
|
|
199
|
+
description="ID of an existing source in the sources table",
|
|
200
|
+
)
|
|
201
|
+
description: Optional[str] = None
|
|
202
|
+
connection: Optional[str] = None
|
|
203
|
+
params: Optional[Dict[str, Any]] = None
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
AgenticSource = Annotated[
|
|
207
|
+
NucliaDBAgenticSource
|
|
208
|
+
| GoogleAgenticSource
|
|
209
|
+
| PerplexityAgenticSource
|
|
210
|
+
| MCPAgenticSource
|
|
211
|
+
| SyncAgenticSource,
|
|
212
|
+
Field(discriminator="type"),
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class AgenticSmartAgentConfiguration(BaseModel):
|
|
217
|
+
mode: AgenticSmartAgentMode = AgenticSmartAgentMode.REACTIVE
|
|
218
|
+
extra_prompt: Optional[str] = None
|
|
219
|
+
models: Optional[AgenticSmartAgentModels] = None
|
|
220
|
+
sources: List[str] = Field(default_factory=list)
|
|
221
|
+
history: Optional[bool] = None
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class AgenticSummarizeConfiguration(BaseModel):
|
|
225
|
+
user_prompt: Optional[str] = None
|
|
226
|
+
system_prompt: Optional[str] = None
|
|
227
|
+
conversational: bool = False
|
|
228
|
+
model: Optional[str] = None
|
|
229
|
+
history: Optional[bool] = None
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class AgenticConfigSchema(BaseModel):
|
|
233
|
+
title: Optional[str] = None
|
|
234
|
+
rephrase: Optional[AgenticRephraseConfiguration] = None
|
|
235
|
+
smart_agent: Optional[AgenticSmartAgentConfiguration] = None
|
|
236
|
+
summarize: Optional[AgenticSummarizeConfiguration] = None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# ---------------------------------------------------------------------------
|
|
240
|
+
# Source models
|
|
241
|
+
# ---------------------------------------------------------------------------
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class GoogleTimeRange(str, Enum):
|
|
245
|
+
PAST_DAY = "past_day"
|
|
246
|
+
PAST_WEEK = "past_week"
|
|
247
|
+
PAST_MONTH = "past_month"
|
|
248
|
+
PAST_YEAR = "past_year"
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class NucliaDBSourceSchema(BaseModel):
|
|
252
|
+
type: Literal["nucliadb"] = "nucliadb"
|
|
253
|
+
description: Optional[str] = None
|
|
254
|
+
|
|
255
|
+
filter_expression: FilterExpression | None = Field(
|
|
256
|
+
default=None,
|
|
257
|
+
title="Filter expression",
|
|
258
|
+
description="Filter expression to narrow NucliaDB search results",
|
|
259
|
+
)
|
|
260
|
+
labels: List[str] | None = Field(
|
|
261
|
+
default=None,
|
|
262
|
+
description="Label filters to restrict which resources are searched",
|
|
263
|
+
)
|
|
264
|
+
resource_filters: List[str] | None = Field(
|
|
265
|
+
default=None,
|
|
266
|
+
title="Resource filters",
|
|
267
|
+
description="Additional resource-level filters passed to the NucliaDB search API",
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class PerplexitySourceSchema(BaseModel):
|
|
272
|
+
type: Literal["perplexity"] = "perplexity"
|
|
273
|
+
description: Optional[str] = None
|
|
274
|
+
|
|
275
|
+
enabled_domains: Optional[List[str]] = Field(
|
|
276
|
+
default=None,
|
|
277
|
+
description="List of domains that Perplexity is allowed to search",
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
class MCPSourceSchema(BaseModel):
|
|
282
|
+
type: Literal["mcp"] = "mcp"
|
|
283
|
+
description: Optional[str] = None
|
|
284
|
+
|
|
285
|
+
uri: str = Field(description="URI of the MCP server endpoint")
|
|
286
|
+
headers: Optional[Dict[str, str]] = Field(
|
|
287
|
+
default=None,
|
|
288
|
+
description="HTTP headers forwarded with every MCP request",
|
|
289
|
+
)
|
|
290
|
+
tool_choice_model: Optional[str] = Field(
|
|
291
|
+
default=None,
|
|
292
|
+
description="Model used for MCP tool selection",
|
|
293
|
+
)
|
|
294
|
+
valid_headers: Optional[List[str]] = Field(
|
|
295
|
+
default=None,
|
|
296
|
+
description="Allowlist of header names that may be forwarded",
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class GoogleSourceSchema(BaseModel):
|
|
301
|
+
type: Literal["google"] = "google"
|
|
302
|
+
description: Optional[str] = None
|
|
303
|
+
time_range: Optional[GoogleTimeRange] = Field(
|
|
304
|
+
default=None,
|
|
305
|
+
description="Restrict Google results to a recent time window",
|
|
306
|
+
)
|
|
307
|
+
exclude_domains: Optional[List[str]] = Field(
|
|
308
|
+
default=None,
|
|
309
|
+
description="Domains to exclude from Google search results",
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
SourceSchema = Annotated[
|
|
314
|
+
Union[
|
|
315
|
+
NucliaDBSourceSchema,
|
|
316
|
+
PerplexitySourceSchema,
|
|
317
|
+
MCPSourceSchema,
|
|
318
|
+
GoogleSourceSchema,
|
|
319
|
+
],
|
|
320
|
+
Field(discriminator="type"),
|
|
321
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SERVICE_NAME = "nucliadb_agentic_api.server"
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from importlib.metadata import version
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
import sentry_sdk
|
|
6
|
+
from hyperforge.broker.redis import RedisBroker
|
|
7
|
+
from hyperforge.feature_flag import get_flag_service
|
|
8
|
+
from hyperforge.server.cache import ValkeyCache
|
|
9
|
+
from hyperforge.server.run import run_metrics_server
|
|
10
|
+
from hyperforge_nucliadb_agentic.ask.predict import start_predict_engine
|
|
11
|
+
from nucliadb_telemetry.logs import setup_logging
|
|
12
|
+
from nucliadb_telemetry.settings import LogFormatType, LogLevel, LogSettings
|
|
13
|
+
from nucliadb_telemetry.utils import setup_telemetry
|
|
14
|
+
from nucliadb_utils.settings import AuditSettings
|
|
15
|
+
from sentry_sdk.integrations.excepthook import ExcepthookIntegration
|
|
16
|
+
|
|
17
|
+
from nucliadb_agentic_api import SERVICE_NAME, logger
|
|
18
|
+
from nucliadb_agentic_api.db.agentic_configs import AgenticConfigs
|
|
19
|
+
from nucliadb_agentic_api.db.settings import DataManagerSettings
|
|
20
|
+
from nucliadb_agentic_api.server.session import NucliaDBAgenticSessionManager
|
|
21
|
+
from nucliadb_agentic_api.server.settings import Settings
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def set_sentry(zone: str, environment: str, sentry_url: str):
|
|
25
|
+
sentry_exception = ExcepthookIntegration(always_run=True)
|
|
26
|
+
sentry_sdk.init(
|
|
27
|
+
release=version("hyperforge"),
|
|
28
|
+
environment=environment,
|
|
29
|
+
dsn=sentry_url,
|
|
30
|
+
integrations=[sentry_exception],
|
|
31
|
+
)
|
|
32
|
+
sentry_sdk.set_tag("zone", zone)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def run_server(
|
|
36
|
+
settings: Settings,
|
|
37
|
+
tracer: Optional[Any],
|
|
38
|
+
data_manager_settings: DataManagerSettings,
|
|
39
|
+
) -> NucliaDBAgenticSessionManager:
|
|
40
|
+
if tracer:
|
|
41
|
+
await setup_telemetry(SERVICE_NAME)
|
|
42
|
+
|
|
43
|
+
await start_predict_engine()
|
|
44
|
+
|
|
45
|
+
audit_settings: AuditSettings = AuditSettings()
|
|
46
|
+
# Connect to Valkey
|
|
47
|
+
broker = RedisBroker.from_url(
|
|
48
|
+
url=settings.valkey_url,
|
|
49
|
+
activate_subject=settings.activate_subject,
|
|
50
|
+
keepalive_ms=int(settings.pubsub_keepalive_seconds * 1000),
|
|
51
|
+
cluster_mode=settings.valkey_cluster_mode,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
agent_manager = await AgenticConfigs.from_settings(
|
|
55
|
+
settings=data_manager_settings,
|
|
56
|
+
)
|
|
57
|
+
await agent_manager.initialize()
|
|
58
|
+
|
|
59
|
+
session = NucliaDBAgenticSessionManager(
|
|
60
|
+
settings=settings,
|
|
61
|
+
broker=broker,
|
|
62
|
+
agent_manager=agent_manager,
|
|
63
|
+
cache=ValkeyCache(broker._client),
|
|
64
|
+
audit_settings=audit_settings,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return session
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def run(): # pragma: no cover
|
|
71
|
+
settings = Settings()
|
|
72
|
+
setup_logging(
|
|
73
|
+
settings=LogSettings(
|
|
74
|
+
log_format_type=LogFormatType.STRUCTURED
|
|
75
|
+
if not settings.debug
|
|
76
|
+
else LogFormatType.PLAIN,
|
|
77
|
+
debug=settings.debug,
|
|
78
|
+
log_level=LogLevel(settings.log_level),
|
|
79
|
+
logger_levels={
|
|
80
|
+
"uvicorn.error": LogLevel.ERROR,
|
|
81
|
+
"nucliadb_telemetry": LogLevel.ERROR,
|
|
82
|
+
"mcp.client.streamable_http": LogLevel.WARNING,
|
|
83
|
+
"mcp.server.lowlevel.server": LogLevel.WARNING,
|
|
84
|
+
"hyperforge.configure": LogLevel.WARNING,
|
|
85
|
+
},
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
tracer = setup_telemetry(SERVICE_NAME)
|
|
89
|
+
if settings.sentry_url is not None:
|
|
90
|
+
set_sentry(
|
|
91
|
+
settings.zone,
|
|
92
|
+
settings.running_environment,
|
|
93
|
+
settings.sentry_url,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
get_flag_service() # precache the flag service
|
|
97
|
+
|
|
98
|
+
data_manager_settings = DataManagerSettings() # type: ignore
|
|
99
|
+
loop = asyncio.get_event_loop()
|
|
100
|
+
|
|
101
|
+
loop.create_task(run_metrics_server(settings.metrics_port))
|
|
102
|
+
|
|
103
|
+
session = loop.run_until_complete(
|
|
104
|
+
run_server(settings, tracer, data_manager_settings)
|
|
105
|
+
)
|
|
106
|
+
loop.run_until_complete(session.initialize())
|
|
107
|
+
logger.info(f"======= Serving on Server to {settings.valkey_url} ======")
|
|
108
|
+
try:
|
|
109
|
+
loop.run_forever()
|
|
110
|
+
finally:
|
|
111
|
+
loop.run_until_complete(session.finalize())
|
|
112
|
+
loop.run_until_complete(loop.shutdown_asyncgens())
|
|
113
|
+
loop.close()
|