hyperforge 1.0.0.post19__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 (90) hide show
  1. hyperforge/__init__.py +16 -0
  2. hyperforge/agent.py +81 -0
  3. hyperforge/api/__init__.py +20 -0
  4. hyperforge/api/app.py +155 -0
  5. hyperforge/api/authentication.py +271 -0
  6. hyperforge/api/commands.py +33 -0
  7. hyperforge/api/internal/__init__.py +4 -0
  8. hyperforge/api/internal/inspect.py +30 -0
  9. hyperforge/api/internal/router.py +3 -0
  10. hyperforge/api/logging.py +18 -0
  11. hyperforge/api/models.py +129 -0
  12. hyperforge/api/session.py +197 -0
  13. hyperforge/api/settings.py +38 -0
  14. hyperforge/api/utils.py +354 -0
  15. hyperforge/api/v1/__init__.py +23 -0
  16. hyperforge/api/v1/agents.py +531 -0
  17. hyperforge/api/v1/interaction.py +430 -0
  18. hyperforge/api/v1/mcp_content.py +311 -0
  19. hyperforge/api/v1/mcp_interaction.py +322 -0
  20. hyperforge/api/v1/oauth.py +60 -0
  21. hyperforge/api/v1/prompt.py +129 -0
  22. hyperforge/api/v1/router.py +3 -0
  23. hyperforge/api/v1/schema.py +56 -0
  24. hyperforge/api/v1/session.py +182 -0
  25. hyperforge/api/v1/utils.py +12 -0
  26. hyperforge/api/v1/workflows.py +643 -0
  27. hyperforge/arag.py +28 -0
  28. hyperforge/broker/__init__.py +52 -0
  29. hyperforge/broker/local.py +116 -0
  30. hyperforge/broker/redis.py +161 -0
  31. hyperforge/configure.py +571 -0
  32. hyperforge/context/__init__.py +0 -0
  33. hyperforge/context/agent.py +377 -0
  34. hyperforge/context/config.py +103 -0
  35. hyperforge/database.py +3 -0
  36. hyperforge/db/__init__.py +6 -0
  37. hyperforge/db/agents.py +1521 -0
  38. hyperforge/db/encryption.py +91 -0
  39. hyperforge/db/exceptions.py +26 -0
  40. hyperforge/db/settings.py +16 -0
  41. hyperforge/db/workflow_cleanup.py +69 -0
  42. hyperforge/definition.py +13 -0
  43. hyperforge/driver.py +31 -0
  44. hyperforge/dummy.py +28 -0
  45. hyperforge/engine.py +189 -0
  46. hyperforge/exceptions.py +14 -0
  47. hyperforge/feature_flag.py +105 -0
  48. hyperforge/fixtures.py +602 -0
  49. hyperforge/interaction.py +116 -0
  50. hyperforge/llm.py +75 -0
  51. hyperforge/manager.py +432 -0
  52. hyperforge/memory/__init__.py +5 -0
  53. hyperforge/memory/memory.py +974 -0
  54. hyperforge/minimal_fixtures.py +75 -0
  55. hyperforge/models.py +336 -0
  56. hyperforge/nua.py +336 -0
  57. hyperforge/openapi.py +63 -0
  58. hyperforge/prompts.py +188 -0
  59. hyperforge/pubsub.py +90 -0
  60. hyperforge/py.typed +0 -0
  61. hyperforge/redis_utils.py +82 -0
  62. hyperforge/retrieval/__init__.py +0 -0
  63. hyperforge/retrieval/agent.py +169 -0
  64. hyperforge/retrieval/config.py +94 -0
  65. hyperforge/server/__init__.py +5 -0
  66. hyperforge/server/cache.py +131 -0
  67. hyperforge/server/run.py +109 -0
  68. hyperforge/server/sandbox.py +60 -0
  69. hyperforge/server/session.py +421 -0
  70. hyperforge/server/settings.py +47 -0
  71. hyperforge/server/utils.py +57 -0
  72. hyperforge/server/web.py +31 -0
  73. hyperforge/settings.py +18 -0
  74. hyperforge/standalone/__init__.py +5 -0
  75. hyperforge/standalone/agent.py +189 -0
  76. hyperforge/standalone/app.py +264 -0
  77. hyperforge/standalone/config.py +137 -0
  78. hyperforge/standalone/const.py +1 -0
  79. hyperforge/standalone/run.py +60 -0
  80. hyperforge/standalone/settings.py +133 -0
  81. hyperforge/standalone/ui_router.py +241 -0
  82. hyperforge/trace.py +42 -0
  83. hyperforge/utils/__init__.py +112 -0
  84. hyperforge/utils/http.py +48 -0
  85. hyperforge/workflows.py +44 -0
  86. hyperforge-1.0.0.post19.dist-info/METADATA +95 -0
  87. hyperforge-1.0.0.post19.dist-info/RECORD +90 -0
  88. hyperforge-1.0.0.post19.dist-info/WHEEL +5 -0
  89. hyperforge-1.0.0.post19.dist-info/entry_points.txt +8 -0
  90. hyperforge-1.0.0.post19.dist-info/top_level.txt +1 -0
@@ -0,0 +1,421 @@
1
+ import asyncio
2
+ import os
3
+ from asyncio import Task
4
+ from functools import partial
5
+ from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Union
6
+
7
+ if TYPE_CHECKING:
8
+ from hyperforge.standalone.agent import StaticAgentManager
9
+
10
+ import nucliadb_telemetry.context
11
+ import nucliadb_telemetry.metrics
12
+ import prometheus_client
13
+ from aiohttp.web import Server
14
+ from lru import LRU
15
+ from nucliadb_telemetry import errors
16
+ from nucliadb_telemetry.utils import get_telemetry
17
+ from opentelemetry import trace
18
+
19
+ from hyperforge.broker import Broker
20
+ from hyperforge.configure import load_all_configurations, scan
21
+ from hyperforge.db.agents import AgentManager
22
+ from hyperforge.engine import State, get_state
23
+ from hyperforge.interaction import (
24
+ AnswerOperation,
25
+ AragAnswer,
26
+ ARAGException,
27
+ Feedback,
28
+ OAuthAuthenticateURL,
29
+ )
30
+ from hyperforge.memory.memory import QuestionMemory
31
+ from hyperforge.pubsub import (
32
+ AgentAnswer,
33
+ AgentDone,
34
+ AgentMessage,
35
+ AgentPing,
36
+ AgentToUserRequest,
37
+ OAuthRequest,
38
+ StartInteraction,
39
+ UserToAgentInteraction,
40
+ )
41
+ from hyperforge.server import SERVICE_NAME, logger
42
+ from hyperforge.server.cache import Cache
43
+ from hyperforge.server.settings import Settings
44
+ from hyperforge.server.utils import get_memory
45
+ from hyperforge.server.web import start_health_check
46
+
47
+ HOSTNAME = os.environ.get("HOSTNAME", "arag-server").encode()
48
+
49
+ answer_observer = nucliadb_telemetry.metrics.Observer("arag_answer")
50
+ activation_observer = nucliadb_telemetry.metrics.Observer("arag_activation")
51
+ answer_running = prometheus_client.Gauge(
52
+ "arag_running_answers_count", "Number of answering processess currently running"
53
+ )
54
+
55
+
56
+ def tracer():
57
+ provider = get_telemetry(SERVICE_NAME)
58
+ if provider:
59
+ return provider.get_tracer(__name__)
60
+ else:
61
+ return trace.NoOpTracer()
62
+
63
+
64
+ class SessionManager:
65
+ server: Optional[Server] = None
66
+ tasks: List[Task]
67
+ hooks: Optional[Dict[str, List[Callable]]] = None
68
+
69
+ def __init__(
70
+ self,
71
+ settings: Settings,
72
+ broker: Broker,
73
+ agent_manager: Union[AgentManager, "StaticAgentManager"],
74
+ cache: Cache,
75
+ ):
76
+ self.settings = settings
77
+ self.agent_manager = agent_manager
78
+ self.broker = broker
79
+ self.memory: LRU = LRU(800)
80
+ self.activation_task: asyncio.Task | None = None
81
+ self.tasks = []
82
+ self.cache = cache
83
+
84
+ async def activation_listener(self):
85
+ import opentelemetry.propagate as otel_propagate
86
+ from opentelemetry.context.context import Context
87
+
88
+ async for msg, trace_headers in self.broker.subscribe_activations():
89
+ try:
90
+ context = (
91
+ otel_propagate.extract(trace_headers)
92
+ if trace_headers
93
+ else Context()
94
+ )
95
+ with tracer().start_as_current_span("Activate agent", context):
96
+ await self.activate(msg)
97
+ except (asyncio.CancelledError, KeyboardInterrupt):
98
+ logger.info("Activation listener cancelled, exiting...")
99
+ break
100
+ except Exception:
101
+ logger.exception("Error processing activation message")
102
+ errors.capture_exception()
103
+
104
+ async def initialize(self, health_check: bool = True) -> None:
105
+
106
+ for load_module in self.settings.load_modules:
107
+ try:
108
+ scan(load_module)
109
+ load_all_configurations(load_module)
110
+ except ImportError:
111
+ logger.error(f"Module {load_module} could not be loaded")
112
+
113
+ self.activation_task = asyncio.create_task(self.activation_listener())
114
+ if health_check:
115
+ self.server = await start_health_check()
116
+
117
+ async def finalize(self):
118
+
119
+ for task in self.tasks:
120
+ if not task.done():
121
+ task.cancel()
122
+ if self.activation_task and not self.activation_task.done():
123
+ self.activation_task.cancel()
124
+ if self.server is not None:
125
+ await self.server.shutdown()
126
+ self.server = None
127
+ await self.broker.finalize()
128
+
129
+ await self.agent_manager.finalize()
130
+
131
+ def _remove_task(self, task: asyncio.Task):
132
+ if task in self.tasks:
133
+ self.tasks.remove(task)
134
+
135
+ async def activate(self, message: StartInteraction):
136
+ topic = None
137
+ logger.info("Activation message received: %s", message)
138
+ observation = activation_observer()
139
+ observation.start()
140
+ try:
141
+ nucliadb_telemetry.context.add_context(
142
+ {
143
+ "agent_id": message.agent_id,
144
+ "session_id": message.session,
145
+ "question_id": message.question_id,
146
+ }
147
+ )
148
+
149
+ topic = self.question_topic(
150
+ message.account,
151
+ message.agent_id,
152
+ message.session,
153
+ message.question_id,
154
+ message.workflow_id,
155
+ )
156
+
157
+ # Get or load session
158
+ config = await self.agent_manager.get_agent_config(
159
+ account=message.account,
160
+ agent_id=message.agent_id,
161
+ internal_nucliadb_url=self.settings.internal_nucliadb_url,
162
+ workflow_id=message.workflow_id,
163
+ )
164
+
165
+ state = await get_state(
166
+ agent_id=message.agent_id,
167
+ config=config,
168
+ internal_nua_api=self.settings.internal_nua_api,
169
+ internal_nua=self.settings.internal_nua,
170
+ local_openai=self.settings.local_openai,
171
+ external_nua_api_key=self.settings.external_nua_api_key,
172
+ account=message.account,
173
+ kbid=None if self.settings.standalone else message.agent_id,
174
+ )
175
+
176
+ if message.session not in self.memory:
177
+ memory = await get_memory(
178
+ settings=self.settings,
179
+ session=message.session,
180
+ cache=self.cache,
181
+ config=config.memory,
182
+ agent=message.agent_id,
183
+ workflow_id=message.workflow_id,
184
+ )
185
+ self.memory[message.session] = memory
186
+ else:
187
+ memory = self.memory[message.session]
188
+
189
+ memory.rules = config.rules.rules
190
+
191
+ question = memory.start_question(
192
+ message.question,
193
+ question_id=message.question_id,
194
+ headers=message.headers,
195
+ arguments=message.arguments,
196
+ streaming=message.streaming,
197
+ )
198
+
199
+ task = asyncio.create_task(
200
+ self.answer(
201
+ message.account,
202
+ message.agent_id,
203
+ message.workflow_id,
204
+ topic,
205
+ state,
206
+ question,
207
+ )
208
+ )
209
+ task.add_done_callback(self._remove_task)
210
+ self.tasks.append(task)
211
+
212
+ except Exception as e:
213
+ logger.exception("Activation exception")
214
+ errors.capture_exception(e)
215
+ observation.set_status("error")
216
+ if topic:
217
+ await self.callback(
218
+ topic,
219
+ AragAnswer(
220
+ exception=ARAGException(detail="Unable to start agent"),
221
+ operation=AnswerOperation.ERROR,
222
+ ),
223
+ )
224
+ await self.send_message(topic, AgentDone())
225
+
226
+ observation.end()
227
+
228
+ def question_topic(
229
+ self,
230
+ account: str,
231
+ agent_id: str,
232
+ session: str,
233
+ question: str,
234
+ workflow_id: str = "default",
235
+ ):
236
+ return self.settings.answers_subject.format(
237
+ account=account,
238
+ agent_id=agent_id,
239
+ session=session,
240
+ question=question,
241
+ workflow_id=workflow_id,
242
+ )
243
+
244
+ async def send_message(
245
+ self,
246
+ topic: str,
247
+ message: AgentMessage,
248
+ ) -> None:
249
+ try:
250
+ await self.broker.publish(topic, message)
251
+
252
+ except Exception as e:
253
+ logger.exception("Error publishing answer to %s", topic)
254
+ errors.capture_exception(e)
255
+
256
+ async def oauth(self, topic: str, oauth: OAuthAuthenticateURL):
257
+ await self.send_message(
258
+ topic,
259
+ OAuthRequest(oauth=oauth),
260
+ )
261
+
262
+ async def get_oauth_callback(
263
+ self,
264
+ account_id: str,
265
+ agent_id: str,
266
+ session_id: str,
267
+ workflow_id: str,
268
+ question_uuid: str,
269
+ oauth_uuid: str,
270
+ timeout_ms: int = 300000,
271
+ ) -> str | None:
272
+ subject = self.settings.oauth_subject.format(
273
+ account=account_id,
274
+ agent_id=agent_id,
275
+ session=session_id,
276
+ question=question_uuid,
277
+ oauth_uuid=oauth_uuid,
278
+ workflow_id=workflow_id,
279
+ )
280
+ # Cap the XREAD block time so it doesn't exceed the overall question
281
+ # timeout. We leave a 10 s margin so the caller can still handle the
282
+ # None return before the outer asyncio.timeout fires.
283
+ margin_ms = 10_000
284
+ max_block_ms = self.settings.question_timeout_seconds * 1000 - margin_ms
285
+ effective_timeout_ms = min(timeout_ms, max(max_block_ms, 0))
286
+
287
+ logger.info(
288
+ "Waiting for OAuth callback %s (timeout=%dms)",
289
+ oauth_uuid,
290
+ effective_timeout_ms,
291
+ )
292
+ try:
293
+ payload = await self.broker.receive_reply(subject, effective_timeout_ms)
294
+ if payload is None:
295
+ return None
296
+
297
+ logger.info("OAuth callback %s received successfully", oauth_uuid)
298
+ return payload
299
+ except Exception as e:
300
+ logger.exception("Error receiving OAuth callback %s", oauth_uuid)
301
+ errors.capture_exception(e)
302
+ return None
303
+
304
+ async def feedback(self, topic: str, feedback: Feedback):
305
+ await self.send_message(
306
+ topic,
307
+ AgentToUserRequest(feedback=feedback),
308
+ )
309
+
310
+ try:
311
+ payload = await self.broker.receive_reply(
312
+ feedback.feedback_id, feedback.timeout_ms
313
+ )
314
+ if payload is None:
315
+ return None
316
+ return UserToAgentInteraction.model_validate_json(payload)
317
+ except Exception as e:
318
+ logger.exception("Error receiving feedback %s", topic)
319
+ errors.capture_exception(e)
320
+ return None
321
+
322
+ async def callback(self, topic: str, message: AragAnswer):
323
+ await self.send_message(topic, AgentAnswer(answer=message))
324
+
325
+ async def keep_alive(self, topic: str):
326
+ while True:
327
+ await asyncio.sleep(self.broker.keepalive_seconds / 2)
328
+ await self.send_message(topic, AgentPing())
329
+
330
+ async def answer(
331
+ self,
332
+ account_id: str,
333
+ agent_id: str,
334
+ workflow_id: str,
335
+ topic: str,
336
+ state: State,
337
+ question_memory: QuestionMemory,
338
+ ):
339
+ error = None
340
+
341
+ keepalive = asyncio.create_task(self.keep_alive(topic))
342
+ observation = answer_observer()
343
+ observation.start()
344
+ answer_running.inc()
345
+
346
+ try:
347
+ callback = partial(self.callback, topic)
348
+ question_memory.set_callback_fn(callback)
349
+
350
+ feedback = partial(self.feedback, topic)
351
+ question_memory.set_feedback_fn(feedback)
352
+
353
+ oauth = partial(self.oauth, topic)
354
+ question_memory.set_oauth_fn(oauth)
355
+
356
+ oauth_callback = partial(
357
+ self.get_oauth_callback,
358
+ account_id,
359
+ agent_id,
360
+ question_memory.session.id,
361
+ workflow_id,
362
+ )
363
+ question_memory.set_oauth_callback_fn(oauth_callback)
364
+
365
+ await self.callback(
366
+ topic,
367
+ AragAnswer(operation=AnswerOperation.START),
368
+ )
369
+
370
+ async with asyncio.timeout(self.settings.question_timeout_seconds):
371
+ await state.agent(question_memory, state.manager)
372
+
373
+ except Exception as e:
374
+ logger.exception("Answering exception")
375
+ errors.capture_exception(e)
376
+ error = ARAGException(detail=str(e))
377
+ observation.set_status("error")
378
+
379
+ observation.end()
380
+ answer_running.dec()
381
+ keepalive.cancel()
382
+
383
+ await self.callback(
384
+ topic,
385
+ AragAnswer(
386
+ exception=error,
387
+ answer=question_memory.final_answer,
388
+ answer_citations=question_memory.final_answer_citations,
389
+ answer_urls=question_memory.final_answer_urls,
390
+ operation=AnswerOperation.ERROR
391
+ if error is not None
392
+ else AnswerOperation.ANSWER,
393
+ data_visualizations=question_memory.data_visualizations
394
+ if question_memory.data_visualizations
395
+ else None,
396
+ ),
397
+ )
398
+ await self.send_message(
399
+ topic,
400
+ AgentDone(),
401
+ )
402
+
403
+ try:
404
+ await question_memory.save()
405
+ self.process_event(
406
+ "memory_saved",
407
+ {"account_id": account_id, "question_memory": question_memory},
408
+ )
409
+ except Exception as e:
410
+ # Log memory errors but don't report them to the user
411
+ logger.exception("Error saving memory")
412
+ errors.capture_exception(e)
413
+
414
+ def process_event(self, event_name: str, data: dict):
415
+ if self.hooks is not None and event_name in self.hooks:
416
+ for hook in self.hooks[event_name]:
417
+ try:
418
+ hook(**data)
419
+ except Exception as e:
420
+ logger.exception("Error in hook for event %s", event_name)
421
+ errors.capture_exception(e)
@@ -0,0 +1,47 @@
1
+ from typing import Optional
2
+
3
+ from pydantic_settings import BaseSettings
4
+
5
+
6
+ class Settings(BaseSettings):
7
+ metrics_port: int = 8090
8
+
9
+ debug: bool = False
10
+ log_level: str = "WARNING"
11
+
12
+ session_timeout: int = 120
13
+ # Maximum time to answer a question
14
+ question_timeout_seconds: int = 300
15
+
16
+ valkey_url: str = "redis://arag-valkey-cluster"
17
+ valkey_cluster_mode: bool = False
18
+ activate_subject: str = "arag.activate"
19
+ answers_subject: str = (
20
+ "arag.{account}.{agent_id}.{workflow_id}.{session}.{question}.answer"
21
+ )
22
+ oauth_subject: str = "arag.{account}.{agent_id}.{workflow_id}.{session}.{question}.oauth.{oauth_uuid}"
23
+ pubsub_keepalive_seconds: float = 20
24
+
25
+ internal_nua_api: str = "http://predict.learning.svc.cluster.local:8080"
26
+ internal_nua: bool = False
27
+ local_openai: Optional[str] = None
28
+
29
+ external_nua_api_key: Optional[str] = None
30
+
31
+ internal_nucliadb: bool = False
32
+ internal_nucliadb_url: Optional[str] = None
33
+
34
+ external_nucliadb_key: Optional[str] = None
35
+ external_nucliadb_url: Optional[str] = None
36
+
37
+ sentry_url: Optional[str] = None
38
+ running_environment: str = "stage"
39
+ zone: str = "stashify"
40
+ load_modules: list[str] = []
41
+
42
+ # Set to True when running as a standalone server (i.e. not inside the
43
+ # full learning cluster). In standalone mode the agent_id is a human-
44
+ # readable slug from the config file, not a real KB UUID, so we resolve
45
+ # the kbid for internal NUA calls from the account_id request header
46
+ # instead.
47
+ standalone: bool = False
@@ -0,0 +1,57 @@
1
+ from hyperforge.memory.memory import (
2
+ BaseSessionMemory,
3
+ EphemeralSessionMemory,
4
+ MemoryConfig,
5
+ NoMemorySessionMemory,
6
+ SessionMemory,
7
+ )
8
+ from hyperforge.server.cache import Cache
9
+ from hyperforge.server.settings import Settings
10
+
11
+
12
+ async def get_memory(
13
+ settings: Settings,
14
+ session: str,
15
+ cache: Cache,
16
+ config: MemoryConfig,
17
+ agent: str,
18
+ workflow_id: str,
19
+ ) -> BaseSessionMemory:
20
+ memory: BaseSessionMemory
21
+
22
+ if (
23
+ config.nucliadb is not None
24
+ and config.nucliadb.internal
25
+ and settings.internal_nucliadb_url
26
+ ):
27
+ config.nucliadb.url = settings.internal_nucliadb_url
28
+ config.nucliadb.key = None
29
+ elif (
30
+ config.nucliadb is not None
31
+ and config.nucliadb.internal
32
+ and settings.internal_nucliadb_url is None
33
+ ):
34
+ raise Exception("Internal NucliaDB URL not configured")
35
+
36
+ if session == "ephemeral":
37
+ memory = NoMemorySessionMemory(
38
+ config=MemoryConfig(nucliadb=None),
39
+ agent_id=agent,
40
+ workflow_id=workflow_id,
41
+ cache=cache,
42
+ )
43
+ elif config.nucliadb is None:
44
+ memory = EphemeralSessionMemory(
45
+ config=config, agent_id=agent, workflow_id=workflow_id, cache=cache
46
+ )
47
+ else:
48
+ memory = SessionMemory(
49
+ config=config,
50
+ agent_id=agent,
51
+ workflow_id=workflow_id,
52
+ cache=cache,
53
+ )
54
+
55
+ memory.init(session=session)
56
+
57
+ return memory
@@ -0,0 +1,31 @@
1
+ import prometheus_client # type: ignore
2
+ from aiohttp import web
3
+
4
+ from hyperforge.server import logger
5
+
6
+
7
+ async def http_handler(request: web.Request):
8
+ if request.path == "/metrics":
9
+ output = prometheus_client.exposition.generate_latest()
10
+ return web.Response(text=output.decode("utf8"))
11
+ elif request.path in ("/health/alive", "/health/ready"):
12
+ # implement health check here
13
+ return web.Response(text="OK")
14
+ else:
15
+ return web.Response(text="OK", status=404)
16
+
17
+
18
+ async def start_web_server() -> web.Server:
19
+ server = web.Server(http_handler) # type: ignore
20
+ runner = web.ServerRunner(server)
21
+ await runner.setup()
22
+ site = web.TCPSite(runner, "0.0.0.0", 8000)
23
+ await site.start()
24
+
25
+ logger.info("======= Serving on http://0.0.0.0:8000/ ======")
26
+ return server
27
+
28
+
29
+ async def start_health_check():
30
+ server = await start_web_server()
31
+ return server
hyperforge/settings.py ADDED
@@ -0,0 +1,18 @@
1
+ from pydantic import model_validator
2
+ from pydantic_settings import BaseSettings
3
+
4
+ _REDIRECT_PATH = "/api/auth/agent/{agent_id}/workflow/{workflow_id}/session/{session_id}/oauth/{oauth_uuid}/callback"
5
+
6
+
7
+ class OAuthSettings(BaseSettings):
8
+ nuclia_public_url: str = "https://{zone}.nuclia.com"
9
+ nuclia_zone: str = "arag"
10
+ rao_redirect_url: str = ""
11
+
12
+ @model_validator(mode="after")
13
+ def _resolve_urls(self) -> "OAuthSettings":
14
+ self.nuclia_public_url = self.nuclia_public_url.format(zone=self.nuclia_zone)
15
+ if not self.rao_redirect_url:
16
+ self.rao_redirect_url = self.nuclia_public_url.rstrip("/") + _REDIRECT_PATH
17
+
18
+ return self
@@ -0,0 +1,5 @@
1
+ import logging
2
+
3
+ SERVICE_NAME = "nuclia-arag-standalone"
4
+
5
+ logger = logging.getLogger(__name__)