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.
Files changed (37) hide show
  1. nucliadb_agentic_api/VERSION +1 -0
  2. nucliadb_agentic_api/__init__.py +20 -0
  3. nucliadb_agentic_api/agentic/__init__.py +0 -0
  4. nucliadb_agentic_api/agentic/ask_handler.py +66 -0
  5. nucliadb_agentic_api/agentic/ask_result.py +389 -0
  6. nucliadb_agentic_api/agentic/ask_transform_to_interaction.py +10 -0
  7. nucliadb_agentic_api/app.py +147 -0
  8. nucliadb_agentic_api/commands.py +66 -0
  9. nucliadb_agentic_api/db/__init__.py +0 -0
  10. nucliadb_agentic_api/db/agentic_configs.py +296 -0
  11. nucliadb_agentic_api/db/settings.py +11 -0
  12. nucliadb_agentic_api/db/sources.py +202 -0
  13. nucliadb_agentic_api/db/transform.py +208 -0
  14. nucliadb_agentic_api/exceptions.py +10 -0
  15. nucliadb_agentic_api/logging.py +18 -0
  16. nucliadb_agentic_api/models.py +321 -0
  17. nucliadb_agentic_api/py.typed +0 -0
  18. nucliadb_agentic_api/server/__init__.py +1 -0
  19. nucliadb_agentic_api/server/server.py +113 -0
  20. nucliadb_agentic_api/server/session.py +276 -0
  21. nucliadb_agentic_api/server/settings.py +22 -0
  22. nucliadb_agentic_api/settings.py +39 -0
  23. nucliadb_agentic_api/utils.py +284 -0
  24. nucliadb_agentic_api/v1/__init__.py +19 -0
  25. nucliadb_agentic_api/v1/agentic_configs.py +117 -0
  26. nucliadb_agentic_api/v1/ask.py +303 -0
  27. nucliadb_agentic_api/v1/ask_websocket.py +141 -0
  28. nucliadb_agentic_api/v1/mcp_content.py +310 -0
  29. nucliadb_agentic_api/v1/mcp_nucliadb.py +785 -0
  30. nucliadb_agentic_api/v1/oauth.py +60 -0
  31. nucliadb_agentic_api/v1/router.py +3 -0
  32. nucliadb_agentic_api/v1/sources.py +158 -0
  33. nucliadb_agentic_api/v1/utils.py +12 -0
  34. nucliadb_agentic_api-1.0.0.post156.dist-info/METADATA +150 -0
  35. nucliadb_agentic_api-1.0.0.post156.dist-info/RECORD +37 -0
  36. nucliadb_agentic_api-1.0.0.post156.dist-info/WHEEL +4 -0
  37. 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()