feat: expose Lance metrics via OpenTelemetry in Python and Node (#3609)

Bridges Lance's internal `metrics`-crate instrumentation (object store
request counts, bytes, latency, errors, and throttles) into
OpenTelemetry, in both the Python and Node bindings, with a shared
adapter in the Rust core. This is the LanceDB counterpart to
lance-format/lance#7537.

## Rust core (`rust/lancedb`)
Two new, **off-by-default** features:
- `metrics` — re-exports the [`metrics`](https://docs.rs/metrics) crate
as `lancedb::metrics` and turns on Lance's object-store instrumentation.
Install any `metrics`-compatible recorder to collect them.
- `metrics-otel` — adds `lancedb::metrics_otel`, a pull-based adapter
that installs a process-global recorder aggregating into lock-free
cumulative storage and exposes a snapshot/catalog API
(`register_metrics_recorder`, `metrics_catalog`, `snapshot_metrics`,
`MetricPoint`/`MetricValue`/`MetricKind`/`MetricDescription`). Both
bindings build on this.

## Python
`lancedb.otel.instrument_lancedb_metrics()` registers each metric as an
OpenTelemetry observable instrument on the given (or global)
`MeterProvider`. Available via the `otel` extra (`pip install
lancedb[otel]`), which pulls in only `opentelemetry-api` — the
application supplies and configures the SDK.

## Node
`instrumentLanceDbMetrics()` provides the equivalent wiring against
`@opentelemetry/api`. This is the only public entry point; the
underlying recorder/catalog/snapshot functions stay internal.

Because OpenTelemetry has no asynchronous histogram instrument,
histograms are exported Prometheus-style as `<name>_bucket` (with an
`le` attribute), `<name>_count`, and `<name>_sum`. Only `_sum` carries
the histogram's unit; `_bucket` and `_count` observe cumulative counts
and are unitless. The adapter is enabled by default in the Python and
Node builds, and off by default in the Rust crate.

## Notes
- Requires Lance ≥ `v9.0.0-beta.19`, which ships the object-store
metrics APIs (upstream lance-format/lance#7537, now merged). `main` is
already on beta.19, so this is a single feature commit with no
dependency bump.
- Tests: 8 Rust unit tests, 3 Python tests, 2 Node tests, all covering
the end-to-end object-store-metrics → OpenTelemetry path.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Will Jones
2026-07-09 15:36:03 -07:00
committed by GitHub
parent 22bf091de1
commit 285add40dd
23 changed files with 1658 additions and 21 deletions
+1 -1
View File
@@ -47,6 +47,6 @@ pyo3-build-config = { version = "0.28", features = [
] }
[features]
default = ["remote", "lancedb/aws", "lancedb/gcs", "lancedb/azure", "lancedb/dynamodb", "lancedb/oss", "lancedb/huggingface", "lancedb/cos", "lancedb/goosefs"]
default = ["remote", "lancedb/aws", "lancedb/gcs", "lancedb/azure", "lancedb/dynamodb", "lancedb/oss", "lancedb/huggingface", "lancedb/cos", "lancedb/goosefs", "lancedb/metrics-otel"]
fp16kernels = ["lancedb/fp16kernels"]
remote = ["lancedb/remote"]
+5
View File
@@ -47,6 +47,10 @@ repository = "https://github.com/lancedb/lancedb"
pylance = [
"pylance>=5.0.0b5",
]
# A library only needs the OpenTelemetry API; the application supplies and
# configures the SDK (the actual exporter/reader). See
# https://opentelemetry.io/docs/languages/python/instrumentation/
otel = ["opentelemetry-api"]
tests = [
"aiohttp>=3.9.0",
"boto3>=1.28.57",
@@ -61,6 +65,7 @@ tests = [
"pylance>=5.0.0b5",
"requests>=2.31.0",
"datafusion>=52,<53",
"opentelemetry-sdk>=1.30.0",
]
dev = [
"ruff>=0.3.0",
+19
View File
@@ -30,6 +30,25 @@ IvfHnswPq: type[HnswPq] = HnswPq
IvfHnswSq: type[HnswSq] = HnswSq
IvfHnswFlat: type[HnswFlat] = HnswFlat
class MetricPoint:
name: str
kind: str
attributes: Dict[str, str]
value: Optional[float]
buckets: Optional[List[Tuple[str, int]]]
count: Optional[int]
sum: Optional[float]
class MetricDescription:
name: str
kind: str
unit: Optional[str]
description: str
def register_lancedb_metrics_recorder() -> bool: ...
def lancedb_metrics_catalog() -> List[MetricDescription]: ...
def snapshot_lancedb_metrics() -> List[MetricPoint]: ...
class PyExpr:
"""A type-safe DataFusion expression node (Rust-side handle)."""
+170
View File
@@ -0,0 +1,170 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright The LanceDB Authors
"""Bridge LanceDB's internal metrics into OpenTelemetry.
LanceDB (through Lance core) publishes metrics (currently object store request
counts, bytes, latency, errors, and throttles) through the Rust ``metrics``
facade. This module installs a process-global recorder that aggregates them and
registers OpenTelemetry observable instruments that report the aggregated values
into the user's ``MeterProvider``.
The bridge is generic: every metric LanceDB describes is surfaced automatically,
with no per-metric Python code. Histograms have no asynchronous OpenTelemetry
instrument, so each is exported Prometheus-style as cumulative ``le`` buckets
plus ``_count`` and ``_sum`` observable counters.
"""
from __future__ import annotations
import warnings
from typing import TYPE_CHECKING, Optional
from ._lancedb import (
lancedb_metrics_catalog,
register_lancedb_metrics_recorder,
snapshot_lancedb_metrics,
)
if TYPE_CHECKING:
from opentelemetry.metrics import MeterProvider
_INSTRUMENTED = False
def instrument_lancedb_metrics(
meter_provider: Optional["MeterProvider"] = None,
) -> bool:
"""Register LanceDB metrics as OpenTelemetry observable instruments.
Installs a process-global metrics recorder and creates one observable
instrument per LanceDB metric on the given (or global) ``MeterProvider``. The
user's configured ``MetricReader`` then collects them on its own schedule.
Counters and gauges map directly to observable counters/gauges. Each
histogram is exported as cumulative ``le`` bucket counts (``<name>_bucket``,
with an ``le`` attribute) plus ``<name>_count`` and ``<name>_sum``.
Parameters
----------
meter_provider : opentelemetry.metrics.MeterProvider, optional
The provider to register instruments on. Defaults to the global provider
from ``opentelemetry.metrics.get_meter_provider()``.
Returns
-------
bool
``True`` if the recorder is installed and instruments are registered.
``False`` if a different ``metrics`` recorder is already installed in
this process (``metrics`` permits only one global recorder), in which
case a warning is emitted and no instruments are created.
Notes
-----
Requires the OpenTelemetry API (``pip install lancedb[otel]``) and, to
actually export, an OpenTelemetry SDK (``pip install opentelemetry-sdk``)
configured by the application. Calling this more than once is safe;
instruments are created only on the first successful call.
"""
global _INSTRUMENTED
try:
from opentelemetry.metrics import Observation, get_meter_provider
except ImportError as exc:
raise ImportError(
"instrument_lancedb_metrics requires the OpenTelemetry API/SDK. "
"Install it with `pip install lancedb[otel]` or "
"`pip install opentelemetry-sdk`."
) from exc
if not register_lancedb_metrics_recorder():
warnings.warn(
"Could not install the LanceDB metrics recorder: another `metrics` "
"recorder is already installed in this process. LanceDB metrics will "
"not be exported via OpenTelemetry.",
stacklevel=2,
)
return False
if _INSTRUMENTED:
return True
provider = meter_provider or get_meter_provider()
meter = provider.get_meter("lancedb")
def scalar_callback(metric_name: str):
def callback(_options):
return [
Observation(point.value, point.attributes)
for point in snapshot_lancedb_metrics()
if point.name == metric_name and point.value is not None
]
return callback
def bucket_callback(metric_name: str):
def callback(_options):
observations = []
for point in snapshot_lancedb_metrics():
if point.name != metric_name or point.buckets is None:
continue
for le, cumulative in point.buckets:
attributes = dict(point.attributes)
attributes["le"] = le
observations.append(Observation(cumulative, attributes))
return observations
return callback
def field_callback(metric_name: str, field: str):
def callback(_options):
observations = []
for point in snapshot_lancedb_metrics():
if point.name != metric_name:
continue
value = getattr(point, field)
if value is not None:
observations.append(Observation(value, point.attributes))
return observations
return callback
for desc in lancedb_metrics_catalog():
unit = desc.unit or ""
if desc.kind == "counter":
meter.create_observable_counter(
desc.name,
callbacks=[scalar_callback(desc.name)],
unit=unit,
description=desc.description,
)
elif desc.kind == "gauge":
meter.create_observable_gauge(
desc.name,
callbacks=[scalar_callback(desc.name)],
unit=unit,
description=desc.description,
)
elif desc.kind == "histogram":
# `_bucket` and `_count` observe cumulative sample counts, not the
# histogram's measured quantity, so they are unitless; only `_sum`
# carries the histogram's unit.
meter.create_observable_counter(
f"{desc.name}_bucket",
callbacks=[bucket_callback(desc.name)],
description=f"{desc.description} (cumulative buckets)",
)
meter.create_observable_counter(
f"{desc.name}_count",
callbacks=[field_callback(desc.name, "count")],
description=f"{desc.description} (count)",
)
meter.create_observable_counter(
f"{desc.name}_sum",
callbacks=[field_callback(desc.name, "sum")],
unit=unit,
description=f"{desc.description} (sum)",
)
_INSTRUMENTED = True
return True
+10
View File
@@ -27,6 +27,7 @@ pub mod header;
pub mod index;
pub mod namespace;
pub mod oauth;
pub mod otel;
pub mod permutation;
pub mod query;
pub mod runtime;
@@ -61,6 +62,15 @@ pub fn _lancedb(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyAsyncPermutationBuilder>()?;
m.add_class::<PyPermutationReader>()?;
m.add_class::<PyExpr>()?;
// OpenTelemetry metrics bridge
m.add_class::<otel::PyMetricPoint>()?;
m.add_class::<otel::PyMetricDescription>()?;
m.add_function(wrap_pyfunction!(
otel::register_lancedb_metrics_recorder,
m
)?)?;
m.add_function(wrap_pyfunction!(otel::lancedb_metrics_catalog, m)?)?;
m.add_function(wrap_pyfunction!(otel::snapshot_lancedb_metrics, m)?)?;
m.add_function(wrap_pyfunction!(connect, m)?)?;
m.add_function(wrap_pyfunction!(connect_namespace, m)?)?;
m.add_function(wrap_pyfunction!(connect_namespace_client, m)?)?;
+96
View File
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
//! Python-facing wrappers over [`lancedb::metrics_otel`].
//!
//! The aggregation, catalog, and histogram bucketing all live in the LanceDB
//! core crate; this module only converts the core snapshot types into PyO3
//! classes and exposes the three entry points to Python, where
//! `lancedb/otel.py` bridges them into the user's OpenTelemetry `MeterProvider`.
use std::collections::HashMap;
use lancedb::metrics_otel::{MetricPoint, MetricValue};
use pyo3::prelude::*;
/// One metric data point exposed to Python. For counters and gauges only
/// `value` is set; for histograms `buckets` (cumulative `le` counts), `count`,
/// and `sum` are set.
#[pyclass(name = "MetricPoint", get_all)]
pub struct PyMetricPoint {
name: String,
kind: String,
attributes: HashMap<String, String>,
value: Option<f64>,
buckets: Option<Vec<(String, u64)>>,
count: Option<u64>,
sum: Option<f64>,
}
impl From<MetricPoint> for PyMetricPoint {
fn from(point: MetricPoint) -> Self {
let kind = point.kind.as_str().to_string();
let (value, buckets, count, sum) = match point.value {
MetricValue::Scalar(v) => (Some(v), None, None, None),
MetricValue::Histogram {
buckets,
count,
sum,
} => (None, Some(buckets), Some(count), Some(sum)),
};
Self {
name: point.name,
kind,
attributes: point.attributes,
value,
buckets,
count,
sum,
}
}
}
/// A described metric, used by the Python layer to create instruments up front.
#[pyclass(name = "MetricDescription", get_all)]
pub struct PyMetricDescription {
name: String,
kind: String,
unit: Option<String>,
description: String,
}
/// Install the LanceDB metrics recorder as the process-global `metrics` recorder.
///
/// Returns `True` if the recorder is installed (now or previously). Returns
/// `False` if a *different* recorder is already installed — `metrics` allows
/// only one global recorder per process, so LanceDB cannot coexist with another.
#[pyfunction]
pub fn register_lancedb_metrics_recorder() -> bool {
lancedb::metrics_otel::register_metrics_recorder()
}
/// The catalog of described LanceDB metrics. Empty until the recorder is installed.
#[pyfunction]
pub fn lancedb_metrics_catalog() -> Vec<PyMetricDescription> {
lancedb::metrics_otel::metrics_catalog()
.into_iter()
.map(|desc| PyMetricDescription {
name: desc.name,
kind: desc.kind.as_str().to_string(),
unit: desc.unit,
description: desc.description,
})
.collect()
}
/// A point-in-time snapshot of every recorded metric. Empty until the recorder
/// is installed.
///
/// The read is lock-free but not O(1): it walks every registered series and
/// allocates owned copies of their names and labels. The GIL is released across
/// that work so a periodic collection doesn't stall other Python threads.
#[pyfunction]
pub fn snapshot_lancedb_metrics(py: Python<'_>) -> Vec<PyMetricPoint> {
let points = py.detach(lancedb::metrics_otel::snapshot_metrics);
points.into_iter().map(PyMetricPoint::from).collect()
}
+101
View File
@@ -0,0 +1,101 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright The LanceDB Authors
import lancedb
import pyarrow as pa
import pytest
# The metrics recorder is process-global and installed once, so the whole
# bridge is exercised in a single test to avoid cross-test global-state coupling.
def _metrics_by_name(reader):
data = reader.get_metrics_data()
result = {}
for resource_metrics in data.resource_metrics:
for scope_metrics in resource_metrics.scope_metrics:
for metric in scope_metrics.metrics:
result[metric.name] = metric
return result
def test_instrument_lancedb_metrics_exports_object_store_metrics(tmp_path):
pytest.importorskip("opentelemetry.sdk.metrics")
from lancedb.otel import instrument_lancedb_metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import InMemoryMetricReader
reader = InMemoryMetricReader()
provider = MeterProvider(metric_readers=[reader])
assert instrument_lancedb_metrics(provider)
# The catalog is populated once the recorder is installed.
from lancedb._lancedb import lancedb_metrics_catalog
catalog = {desc.name: desc for desc in lancedb_metrics_catalog()}
# Every metric kind emitted by the object store must be described so it is
# surfaced by the bridge (counter, histogram, and gauge).
assert catalog["lance_object_store_requests_total"].kind == "counter"
assert catalog["lance_object_store_request_duration_seconds"].kind == "histogram"
assert catalog["lance_object_store_in_flight_requests"].kind == "gauge"
assert catalog["lance_object_store_retryable_responses_total"].kind == "counter"
# Generate object store activity on the local filesystem (scheme "file").
db = lancedb.connect(str(tmp_path))
table = db.create_table("t", pa.table({"id": pa.array(range(256))}))
assert table.count_rows() == 256
assert table.to_arrow().num_rows == 256
metrics = _metrics_by_name(reader)
requests = metrics["lance_object_store_requests_total"]
points = list(requests.data.data_points)
assert points, "expected at least one request data point"
# Object store metrics are labelled by `operation` and `base` (the store
# scheme, e.g. "file", by default).
assert all("base" in p.attributes and "operation" in p.attributes for p in points)
assert sum(p.value for p in points) > 0
# Histograms are decomposed into bucket / count / sum observable counters.
bucket = metrics["lance_object_store_request_duration_seconds_bucket"]
bucket_points = list(bucket.data.data_points)
assert bucket_points
assert all("le" in p.attributes for p in bucket_points)
# The implicit +Inf bucket must be present and is the cumulative maximum.
assert any(p.attributes["le"] == "+Inf" for p in bucket_points)
count = metrics["lance_object_store_request_duration_seconds_count"]
assert sum(p.value for p in count.data.data_points) > 0
# The `_sum` instrument must also be wired and report positive latency.
duration_sum = metrics["lance_object_store_request_duration_seconds_sum"]
assert sum(p.value for p in duration_sum.data.data_points) > 0
# Unit handling: only `_sum` keeps the histogram's unit (seconds); `_bucket`
# and `_count` observe cumulative counts and are unitless.
assert duration_sum.unit == "s"
assert bucket.unit == ""
assert count.unit == ""
def test_snapshot_empty_before_install_is_safe():
# snapshot is callable regardless of installation state and never raises.
from lancedb._lancedb import snapshot_lancedb_metrics
assert isinstance(snapshot_lancedb_metrics(), list)
def test_instrument_warns_when_recorder_unavailable(monkeypatch):
# A foreign `metrics` recorder already installed -> register returns False;
# instrument_lancedb_metrics must warn and return False without instrumenting.
pytest.importorskip("opentelemetry.sdk.metrics")
import lancedb.otel as otel
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import InMemoryMetricReader
monkeypatch.setattr(otel, "register_lancedb_metrics_recorder", lambda: False)
reader = InMemoryMetricReader()
provider = MeterProvider(metric_readers=[reader])
with pytest.warns(UserWarning, match="recorder"):
assert otel.instrument_lancedb_metrics(provider) is False
+63 -18
View File
@@ -780,7 +780,7 @@ name = "cuda-bindings"
version = "13.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cuda-pathfinder" },
{ name = "cuda-pathfinder", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/21/8464d133752951c154feafb3b65c297e7d80f301183d220bec4c830f1441/cuda_bindings-13.3.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:120fcc53d57903df529c3486962c56528cba5b7d6c57c99537320ed9922c8b86", size = 6073403, upload-time = "2026-05-29T23:11:36.22Z" },
@@ -815,37 +815,37 @@ wheels = [
[package.optional-dependencies]
cublas = [
{ name = "nvidia-cublas", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "nvidia-cublas", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" },
]
cudart = [
{ name = "nvidia-cuda-runtime", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "nvidia-cuda-runtime", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" },
]
cufft = [
{ name = "nvidia-cufft", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "nvidia-cufft", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" },
]
cufile = [
{ name = "nvidia-cufile", marker = "sys_platform == 'linux'" },
]
cupti = [
{ name = "nvidia-cuda-cupti", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "nvidia-cuda-cupti", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" },
]
curand = [
{ name = "nvidia-curand", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "nvidia-curand", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" },
]
cusolver = [
{ name = "nvidia-cusolver", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "nvidia-cusolver", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" },
]
cusparse = [
{ name = "nvidia-cusparse", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "nvidia-cusparse", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" },
]
nvjitlink = [
{ name = "nvidia-nvjitlink", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "nvidia-nvjitlink", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" },
]
nvrtc = [
{ name = "nvidia-cuda-nvrtc", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "nvidia-cuda-nvrtc", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" },
]
nvtx = [
{ name = "nvidia-nvtx", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "nvidia-nvtx", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" },
]
[[package]]
@@ -1909,6 +1909,9 @@ embeddings = [
{ name = "sentencepiece" },
{ name = "torch" },
]
otel = [
{ name = "opentelemetry-api" },
]
pylance = [
{ name = "pylance" },
]
@@ -1923,6 +1926,7 @@ tests = [
{ name = "boto3" },
{ name = "datafusion" },
{ name = "duckdb" },
{ name = "opentelemetry-sdk" },
{ name = "pandas", version = "2.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.14'" },
{ name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" },
@@ -1963,6 +1967,8 @@ requires-dist = [
{ name = "open-clip-torch", marker = "extra == 'clip'" },
{ name = "open-clip-torch", marker = "extra == 'embeddings'", specifier = ">=2.20.0" },
{ name = "openai", marker = "extra == 'embeddings'", specifier = ">=1.6.1" },
{ name = "opentelemetry-api", marker = "extra == 'otel'" },
{ name = "opentelemetry-sdk", marker = "extra == 'tests'", specifier = ">=1.30.0" },
{ name = "overrides", marker = "python_full_version < '3.12'", specifier = ">=0.7" },
{ name = "packaging", specifier = ">=23.0" },
{ name = "pandas", marker = "extra == 'tests'", specifier = ">=1.4" },
@@ -1994,7 +2000,7 @@ requires-dist = [
{ name = "transformers", marker = "extra == 'siglip'", specifier = ">=4.41.0" },
{ name = "typing-extensions", marker = "python_full_version < '3.11' and extra == 'dev'", specifier = ">=4.0.0" },
]
provides-extras = ["azure", "clip", "dev", "docs", "embeddings", "pylance", "siglip", "tests"]
provides-extras = ["azure", "clip", "dev", "docs", "embeddings", "otel", "pylance", "siglip", "tests"]
[[package]]
name = "lomond"
@@ -2775,7 +2781,7 @@ name = "nvidia-cudnn-cu13"
version = "9.19.0.56"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-cublas" },
{ name = "nvidia-cublas", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/f1/84/26025437c1e6b61a707442184fa0c03d083b661adf3a3eecfd6d21677740/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:6ed29ffaee1176c612daf442e4dd6cfeb6a0caa43ddcbeb59da94953030b1be4", size = 433781201, upload-time = "2026-02-03T20:40:53.805Z" },
@@ -2787,7 +2793,7 @@ name = "nvidia-cufft"
version = "12.0.0.61"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-nvjitlink" },
{ name = "nvidia-nvjitlink", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" },
@@ -2817,9 +2823,9 @@ name = "nvidia-cusolver"
version = "12.0.4.66"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-cublas" },
{ name = "nvidia-cusparse" },
{ name = "nvidia-nvjitlink" },
{ name = "nvidia-cublas", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" },
{ name = "nvidia-cusparse", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" },
{ name = "nvidia-nvjitlink", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" },
@@ -2831,7 +2837,7 @@ name = "nvidia-cusparse"
version = "12.6.3.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-nvjitlink" },
{ name = "nvidia-nvjitlink", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" },
@@ -2934,6 +2940,45 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f6/46/180e14be801a75bc13f234cb1b594b232adeb9c84e60a9ab1832e8333591/openai-2.40.0-py3-none-any.whl", hash = "sha256:2b205637ff214477f9ce9ab035e9f494db0e3fa8f1e599008953735fbf6ff1ff", size = 1350935, upload-time = "2026-06-01T21:48:21.462Z" },
]
[[package]]
name = "opentelemetry-api"
version = "1.43.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ae/cc/e4c9584181f86494df0f6bdec1a4f3280c50db44704dc2a407e994fc87bb/opentelemetry_api-1.43.0.tar.gz", hash = "sha256:107d0d03857ea8fc7c5fcbbbd83f800c281f0d560553d61c1d675fccfd1761c1", size = 73476, upload-time = "2026-06-24T15:19:55.323Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/17/83/6dba32b85f31868400440dc7ad2ca1eab94cbbf3a7b0459ed39f8311a9e2/opentelemetry_api-1.43.0-py3-none-any.whl", hash = "sha256:20acf45e9b21851926835292e4045d290acade1edd2ff3de86d2f069687ba1fd", size = 61912, upload-time = "2026-06-24T15:19:35.434Z" },
]
[[package]]
name = "opentelemetry-sdk"
version = "1.43.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-semantic-conventions" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3e/eb/5041074274ac0956b03637cc039d434569112468e875eddfcc9a0674ce06/opentelemetry_sdk-1.43.0.tar.gz", hash = "sha256:d8187c81c162df9913e4003dd6485f7390d9a24fc17026ec7387b8b8218b08e9", size = 254744, upload-time = "2026-06-24T15:20:08.467Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/49/e3/b17be23af124201c9f52eececd4cc8ddfed1597d37b4ee771895d325805c/opentelemetry_sdk-1.43.0-py3-none-any.whl", hash = "sha256:d1323a547c1ce69d6a069a17a44b7da82bb8b332051ecb074041f87642c86823", size = 178852, upload-time = "2026-06-24T15:19:52.169Z" },
]
[[package]]
name = "opentelemetry-semantic-conventions"
version = "0.64b0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5a/30/5f26df29509eccd86b99b481ac9ffa39da49ba9577cc69071c552ae30447/opentelemetry_semantic_conventions-0.64b0.tar.gz", hash = "sha256:72f76fb2d1582d9d033dd1fcd84532e961e6ff3d90d24ba6fabc72975a83864c", size = 148340, upload-time = "2026-06-24T15:20:09.267Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f2/ca/23ba87a221b574a7c5a99d48849d80bfe8b047624681357e2b002e566187/opentelemetry_semantic_conventions-0.64b0-py3-none-any.whl", hash = "sha256:ea77e85e354b8f604ddbe5f3d9135216f982fa4d77e5859ac30f6d8a50505aa6", size = 203713, upload-time = "2026-06-24T15:19:53.339Z" },
]
[[package]]
name = "overrides"
version = "7.7.0"