unique_toolkit 2026.34.0.dev7__py3-none-any.whl → 2026.34.0.dev9__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.
@@ -379,7 +379,18 @@ class UniqueApi(BaseSettings):
379
379
  )
380
380
 
381
381
  @staticmethod
382
- def _legacy_base_path(hostname: str | None) -> str:
382
+ def _legacy_base_path(hostname: str | None, port: int | None = None) -> str:
383
+ # ponytail: root path inferred from unique-api hostname / local :8096;
384
+ # ceiling = service rename or non-8096 local bind; upgrade = explicit ApiType.
385
+ if hostname and "unique-api" in hostname:
386
+ return ""
387
+ if (
388
+ hostname
389
+ and port == 8096
390
+ and (hostname in ("localhost", "127.0.0.1") or "localhost" in hostname)
391
+ ):
392
+ return ""
393
+
383
394
  base_path = "/public/chat"
384
395
  if hostname and (
385
396
  "gateway.qa.unique" in hostname or "gateway.unique" in hostname
@@ -409,7 +420,7 @@ class UniqueApi(BaseSettings):
409
420
  case ApiType.INTERNAL:
410
421
  base_path = "public"
411
422
  case ApiType.LEGACY:
412
- base_path = self._legacy_base_path(parsed.hostname)
423
+ base_path = self._legacy_base_path(parsed.hostname, parsed.port)
413
424
 
414
425
  return parsed, base_path
415
426
 
@@ -4,6 +4,8 @@ from unique_toolkit.monitoring.tracing import (
4
4
  TracingSettings,
5
5
  configure_tracing,
6
6
  inject_trace_headers,
7
+ instrument_fastapi_app,
8
+ instrument_requests,
7
9
  )
8
10
 
9
11
  __all__ = [
@@ -12,6 +14,8 @@ __all__ = [
12
14
  "TracingSettings",
13
15
  "configure_tracing",
14
16
  "inject_trace_headers",
17
+ "instrument_fastapi_app",
18
+ "instrument_requests",
15
19
  ]
16
20
 
17
21
  _MONITORING_AVAILABLE = False
@@ -9,7 +9,10 @@ from typing import TYPE_CHECKING, ClassVar, Self, TypeAlias, cast
9
9
  from pydantic import AliasChoices, Field
10
10
  from pydantic_settings import BaseSettings, SettingsConfigDict
11
11
 
12
+ from unique_toolkit.app.find_env_file import find_env_file
13
+
12
14
  if TYPE_CHECKING:
15
+ from fastapi import FastAPI # pyright: ignore[reportMissingImports]
13
16
  from opentelemetry.trace.propagation.tracecontext import (
14
17
  TraceContextTextMapPropagator,
15
18
  )
@@ -25,6 +28,10 @@ _OTEL_EXTRA_MESSAGE = (
25
28
  "OpenTelemetry tracing requires the unique_toolkit[otel] extra. "
26
29
  "Install it with: uv add 'unique-toolkit[otel]'"
27
30
  )
31
+ _FASTAPI_EXTRA_MESSAGE = (
32
+ "FastAPI instrumentation requires the unique_toolkit[fastapi] extra. "
33
+ "Install it with: uv add 'unique-toolkit[fastapi]'"
34
+ )
28
35
 
29
36
 
30
37
  class TraceExporter(StrEnum):
@@ -42,6 +49,8 @@ class TracingSettings(BaseSettings):
42
49
  env_prefix="OTEL_",
43
50
  env_ignore_empty=True,
44
51
  extra="ignore",
52
+ env_file=find_env_file(".env", required=False),
53
+ env_file_encoding="utf-8",
45
54
  )
46
55
 
47
56
  traces_exporter: TraceExporter | None = None
@@ -90,6 +99,11 @@ def _missing_otel_extra() -> ImportError:
90
99
  return ImportError(_OTEL_EXTRA_MESSAGE)
91
100
 
92
101
 
102
+ def _missing_fastapi_extra() -> ImportError:
103
+ """Create a consistent error for an unavailable optional FastAPI dependency."""
104
+ return ImportError(_FASTAPI_EXTRA_MESSAGE)
105
+
106
+
93
107
  def _trace_context_propagator() -> TraceContextTextMapPropagator:
94
108
  """Return a W3C trace-context propagator without forwarding baggage."""
95
109
  try:
@@ -165,9 +179,12 @@ def configure_tracing(
165
179
 
166
180
  try:
167
181
  from opentelemetry import trace
182
+ from opentelemetry.baggage.propagation import W3CBaggagePropagator
168
183
  from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
169
184
  OTLPSpanExporter,
170
185
  )
186
+ from opentelemetry.propagate import set_global_textmap
187
+ from opentelemetry.propagators.composite import CompositePropagator
171
188
  from opentelemetry.sdk.resources import Resource
172
189
  from opentelemetry.sdk.trace import TracerProvider
173
190
  from opentelemetry.sdk.trace.export import (
@@ -176,6 +193,9 @@ def configure_tracing(
176
193
  SimpleSpanProcessor,
177
194
  )
178
195
  from opentelemetry.trace import ProxyTracerProvider
196
+ from opentelemetry.trace.propagation.tracecontext import (
197
+ TraceContextTextMapPropagator,
198
+ )
179
199
  except ImportError as error:
180
200
  raise _missing_otel_extra() from error
181
201
 
@@ -196,6 +216,54 @@ def configure_tracing(
196
216
  if exporter is TraceExporter.CONSOLE:
197
217
  provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
198
218
  else:
199
- provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
219
+ # Sizing pinned to match the Node services' instrumentation.ts.
220
+ provider.add_span_processor(
221
+ BatchSpanProcessor(
222
+ OTLPSpanExporter(),
223
+ max_queue_size=2048,
224
+ max_export_batch_size=512,
225
+ schedule_delay_millis=5000,
226
+ )
227
+ )
200
228
  trace.set_tracer_provider(provider)
229
+ # Trace-context + baggage, the shared subset of the Node services' propagator.
230
+ set_global_textmap(
231
+ CompositePropagator([TraceContextTextMapPropagator(), W3CBaggagePropagator()])
232
+ )
201
233
  return True
234
+
235
+
236
+ def instrument_fastapi_app(app: FastAPI, *, excluded_urls: str | None = None) -> None:
237
+ """Trace inbound requests for an already-created FastAPI app instance.
238
+
239
+ Uses ``FastAPIInstrumentor.instrument_app`` rather than the global
240
+ ``FastAPIInstrumentor().instrument()``: the global form swaps out the
241
+ ``fastapi.FastAPI`` class, so it only takes effect for app objects created
242
+ afterward. Instrumenting the instance has no such ordering requirement.
243
+ """
244
+ try:
245
+ import fastapi # noqa: F401 # pyright: ignore[reportMissingImports, reportUnusedImport]
246
+ except ImportError as error:
247
+ # opentelemetry-instrumentation-fastapi imports fastapi internally, so without
248
+ # this explicit check its own ImportError would be misreported below as a
249
+ # missing otel extra when fastapi itself is what's actually missing.
250
+ raise _missing_fastapi_extra() from error
251
+
252
+ try:
253
+ from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
254
+ except ImportError as error:
255
+ raise _missing_otel_extra() from error
256
+ FastAPIInstrumentor.instrument_app(app, excluded_urls=excluded_urls)
257
+
258
+
259
+ def instrument_requests() -> None:
260
+ """Trace outbound calls made with the ``requests`` library.
261
+
262
+ Patches ``requests`` at module level, so callers that already imported it
263
+ are still covered.
264
+ """
265
+ try:
266
+ from opentelemetry.instrumentation.requests import RequestsInstrumentor
267
+ except ImportError as error:
268
+ raise _missing_otel_extra() from error
269
+ RequestsInstrumentor().instrument()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-toolkit
3
- Version: 2026.34.0.dev7
3
+ Version: 2026.34.0.dev9
4
4
  Summary:
5
5
  Author: Cedric Klinkert, Martin Fadler, Sadique Sheik, Fabian Schläpfer, Pascal Hauri, Ejiroghene Oki-Peter, Rami Azouz, Ricardo Visini, Sanzio Monti
6
6
  Author-email: Cedric Klinkert <cedric.klinkert@unique.ch>, Martin Fadler <martin.fadler@unique.ch>, Sadique Sheik <sadique@unique.ch>, Fabian Schläpfer <fabian@unique.ch>, Pascal Hauri <pascal@unique.ch>, Ejiroghene Oki-Peter <oki-peter.ejiroghene@unique.ai>, Rami Azouz <rami.ext@unique.ch>, Ricardo Visini <ricardo.visini@unique.ch>, Sanzio Monti <sanzio.monti@unique.ai>
@@ -16,7 +16,7 @@ Requires-Dist: sseclient>=0.0.27,<0.0.28
16
16
  Requires-Dist: openai>=1.105.0,<3
17
17
  Requires-Dist: platformdirs>=4.0.0,<5
18
18
  Requires-Dist: pillow>=12.1.1
19
- Requires-Dist: unique-sdk>=2026.34.0.dev3,<2026.34.0rc0
19
+ Requires-Dist: unique-sdk>=2026.34.0.dev4,<2026.34.0rc0
20
20
  Requires-Dist: jambo>=0.1.2,<0.2
21
21
  Requires-Dist: jinja2>=3.1.6,<4
22
22
  Requires-Dist: docxtpl>=0.20.1,<0.21
@@ -44,6 +44,8 @@ Requires-Dist: langchain-core>=1.0.0,<2 ; extra == 'langchain'
44
44
  Requires-Dist: prometheus-client>=0.24.1,<1 ; extra == 'monitoring'
45
45
  Requires-Dist: opentelemetry-sdk>=1.27,<2 ; extra == 'otel'
46
46
  Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27,<2 ; extra == 'otel'
47
+ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.65b0,<1 ; extra == 'otel'
48
+ Requires-Dist: opentelemetry-instrumentation-requests>=0.65b0,<1 ; extra == 'otel'
47
49
  Requires-Python: >=3.12
48
50
  Provides-Extra: fastapi
49
51
  Provides-Extra: langchain
@@ -251,7 +251,7 @@ unique_toolkit/app/init_sdk.py,sha256=5_oDoETr6akwYyBCb0ivTdMNu3SVgPSkrXcDS6ELyY
251
251
  unique_toolkit/app/performance/async_tasks.py,sha256=8Xv5kEmmFDJO7YyyBxv4e7bcNgq-3z_E2_4z1Yn9EqU,1970
252
252
  unique_toolkit/app/performance/async_wrapper.py,sha256=yVVcRDkcdyfjsxro-N29SBvi-7773wnfDplef6-y8xw,1077
253
253
  unique_toolkit/app/schemas.py,sha256=_jHuLDZO9YSP0xf-1AxNVorWYg2NgWfNZ9yXpGt2DHE,18518
254
- unique_toolkit/app/unique_settings.py,sha256=-bYvxwCw_DvhSViHXgc-pYoNboxhHB8VG2L9YNPiDHU,26861
254
+ unique_toolkit/app/unique_settings.py,sha256=dfqqh1Nv2JaqL3fePndmuRAfJcARzImdwSOO1J3KS1E,27318
255
255
  unique_toolkit/app/verification.py,sha256=GxFFwcJMy25fCA_Xe89wKW7bgqOu8PAs5y8QpHF0GSc,3861
256
256
  unique_toolkit/app/webhook.py,sha256=evpxLDnvz7P3D7-TV1XBkl8WcoldXpjOkw3tvFRxHr0,2368
257
257
  unique_toolkit/chat/__init__.py,sha256=-239hRT9x8fJ5rL_qvGAhnxLO_F7gToTVVLo0wZbesU,1722
@@ -402,11 +402,11 @@ unique_toolkit/language_model/service.py,sha256=hDoJBBgG_wWs8WuC_c8tnmK8cNu8o5Eh
402
402
  unique_toolkit/language_model/settings.py,sha256=7szohExI-qmTfUf2Zp5AB5TzrsZy1UekAGyXx_UaPMc,1524
403
403
  unique_toolkit/language_model/stream_transform.py,sha256=iNNGpLY_7G6Bpp9Y4KtbIkQm3WB4yZt75BXcqx0DVkI,2775
404
404
  unique_toolkit/language_model/utils.py,sha256=bPQ4l6_YO71w-zaIPanUUmtbXC1_hCvLK0tAFc3VCRc,1902
405
- unique_toolkit/monitoring/__init__.py,sha256=auxvvOBwYrCD3TqEX0sIupDYx9nbCt2u4XN0Sy55qKI,1376
405
+ unique_toolkit/monitoring/__init__.py,sha256=XHRzPx4LKcbKruRrf_gNv50amhvhy8Go2IkOoHMuLsw,1486
406
406
  unique_toolkit/monitoring/gunicorn.py,sha256=urTFFS5zYHKoxb1TEQHcLqb2byi-aYgFRHGwgYRzYYI,844
407
407
  unique_toolkit/monitoring/middleware.py,sha256=MNsUuKgt74mSnUwttXTB_AxX546Pz2zKoAP9OewVrwQ,4531
408
408
  unique_toolkit/monitoring/registry.py,sha256=De2Bx5QBBRJQY2MM3xtntiJ2JU2FoKkVT7rNMG7ewgA,5874
409
- unique_toolkit/monitoring/tracing.py,sha256=86yUIF8HrHVrkegOBOM7GqpbmVrYP07AZ4HtP-3X9QY,7128
409
+ unique_toolkit/monitoring/tracing.py,sha256=0lmLuQjyyC3iFpNTw8rOyCpEZ6PTvp16nVbzcO9d2sQ,10043
410
410
  unique_toolkit/protocols/support.py,sha256=x5edzbBmKAUuvm3oLFh52gkgQsqR5LU7M0t8RHK0DZk,5161
411
411
  unique_toolkit/services/__init__.py,sha256=b9Wl7Wvz1JmZDbDexIZzBoXZ0wscXuczGAH8LoL-Qak,572
412
412
  unique_toolkit/services/chat_service.py,sha256=6lVMSwvi2ztPebK6meo9BVLOL_Wdje5wPSUBp5dMZIg,99268
@@ -420,6 +420,6 @@ unique_toolkit/short_term_memory/service.py,sha256=2JMBEBn6GoyK-a0xvdcWpPft-3C74
420
420
  unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
421
421
  unique_toolkit/smart_rules/compile.py,sha256=vRe0aMJWuVIK-VsqomgxQImzke7MDmwgSxxJfYCa7zw,1386
422
422
  unique_toolkit/test_utilities/events.py,sha256=FLrhMh0A_mQutAUjoD0FdMcVQ4Gq31r35OVGzRjvLro,6760
423
- unique_toolkit-2026.34.0.dev7.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
424
- unique_toolkit-2026.34.0.dev7.dist-info/METADATA,sha256=23fTKIILLrhEk-wSV7lRMJs6hhguyDHBChVkldd8wTc,7656
425
- unique_toolkit-2026.34.0.dev7.dist-info/RECORD,,
423
+ unique_toolkit-2026.34.0.dev9.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
424
+ unique_toolkit-2026.34.0.dev9.dist-info/METADATA,sha256=u_HrX_80XqfWG54ud1CwTxBiJF2lKD0L9Go050etA4c,7821
425
+ unique_toolkit-2026.34.0.dev9.dist-info/RECORD,,