diff --git a/.cz.toml b/.cz.toml
index 18b34bc743..a3060950d8 100644
--- a/.cz.toml
+++ b/.cz.toml
@@ -10,6 +10,8 @@ version_files = [
"packages/opentelemetry-instrumentation-mcp/opentelemetry/instrumentation/mcp/version.py",
"packages/opentelemetry-instrumentation-groq/pyproject.toml:^version",
"packages/opentelemetry-instrumentation-groq/opentelemetry/instrumentation/groq/version.py",
+ "packages/opentelemetry-instrumentation-deepseek/pyproject.toml:^version",
+ "packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/version.py",
"packages/opentelemetry-instrumentation-agno/pyproject.toml:^version",
"packages/opentelemetry-instrumentation-agno/opentelemetry/instrumentation/agno/version.py",
"packages/opentelemetry-instrumentation-alephalpha/pyproject.toml:^version",
diff --git a/packages/opentelemetry-instrumentation-deepseek/.python-version b/packages/opentelemetry-instrumentation-deepseek/.python-version
new file mode 100644
index 0000000000..c8cfe39591
--- /dev/null
+++ b/packages/opentelemetry-instrumentation-deepseek/.python-version
@@ -0,0 +1 @@
+3.10
diff --git a/packages/opentelemetry-instrumentation-deepseek/README.md b/packages/opentelemetry-instrumentation-deepseek/README.md
new file mode 100644
index 0000000000..5a8ca803ed
--- /dev/null
+++ b/packages/opentelemetry-instrumentation-deepseek/README.md
@@ -0,0 +1,53 @@
+# OpenTelemetry DeepSeek Instrumentation
+
+
+
+
+
+This library allows tracing prompts and completions sent to [DeepSeek](https://platform.deepseek.com/) using the official [OpenAI SDK](https://github.com/openai/openai-python), since DeepSeek's API is OpenAI-compatible.
+
+## Installation
+
+```bash
+pip install opentelemetry-instrumentation-deepseek
+```
+
+## Example usage
+
+```python
+from opentelemetry.instrumentation.deepseek import DeepSeekInstrumentor
+
+DeepSeekInstrumentor().instrument()
+```
+
+This instrumentor patches `openai.resources.chat.completions.Completions.create` and `AsyncCompletions.create`, but only creates spans for requests made through a client configured with a DeepSeek `base_url`. Regular OpenAI clients are left untouched, so this instrumentation can be safely enabled alongside [opentelemetry-instrumentation-openai](https://pypi.org/project/opentelemetry-instrumentation-openai/).
+
+```python
+from openai import OpenAI
+
+client = OpenAI(
+ api_key="",
+ base_url="https://api.deepseek.com",
+)
+
+response = client.chat.completions.create(
+ model="deepseek-chat",
+ messages=[{"role": "user", "content": "Hello!"}],
+)
+```
+
+## DeepSeek-R1 reasoning content
+
+The `deepseek-reasoner` model (DeepSeek-R1) returns its chain-of-thought in a `reasoning_content` field on the response message, in addition to the final `content`. This instrumentation captures `reasoning_content` as the `gen_ai.deepseek.reasoning_content` span attribute, for both streaming and non-streaming responses.
+
+## Privacy
+
+**By default, this instrumentation logs prompts, completions, and embeddings to span attributes**. This gives you a clear visibility into how your LLM application is working, and can make it easy to debug and evaluate the quality of the outputs.
+
+However, you may want to disable this logging for privacy reasons, as they may contain highly sensitive data from your users. You may also simply want to reduce the size of your traces.
+
+To disable logging, set the `TRACELOOP_TRACE_CONTENT` environment variable to `false`.
+
+```bash
+TRACELOOP_TRACE_CONTENT=false
+```
diff --git a/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/__init__.py b/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/__init__.py
new file mode 100644
index 0000000000..3426a0db63
--- /dev/null
+++ b/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/__init__.py
@@ -0,0 +1,623 @@
+"""OpenTelemetry DeepSeek instrumentation"""
+
+import logging
+import os
+import time
+import warnings
+from typing import Callable, Collection, Optional, Union
+
+from opentelemetry import context as context_api
+from opentelemetry._logs import Logger, get_logger
+from opentelemetry.instrumentation.deepseek.config import Config
+from opentelemetry.instrumentation.deepseek.event_emitter import (
+ emit_choice_events,
+ emit_message_events,
+ emit_streaming_response_events,
+)
+from opentelemetry.instrumentation.deepseek.span_utils import (
+ set_input_attributes,
+ set_model_input_attributes,
+ set_model_response_attributes,
+ set_model_streaming_response_attributes,
+ set_response_attributes,
+ set_streaming_response_attributes,
+)
+from opentelemetry.instrumentation.deepseek.utils import (
+ error_metrics_attributes,
+ shared_metrics_attributes,
+ should_emit_events,
+)
+from opentelemetry.instrumentation.deepseek.version import __version__
+from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
+from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY, unwrap
+from opentelemetry.metrics import Counter, Histogram, Meter, get_meter
+from opentelemetry.semconv._incubating.attributes import (
+ gen_ai_attributes as GenAIAttributes,
+)
+from opentelemetry.semconv_ai import (
+ SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY,
+ Meters,
+)
+from opentelemetry.trace import Span, SpanKind, Tracer, get_tracer
+from opentelemetry.trace.status import Status, StatusCode
+from opentelemetry.semconv.attributes.error_attributes import ERROR_TYPE
+from wrapt import wrap_function_wrapper
+
+from openai._streaming import AsyncStream, Stream
+from openai.types.completion_usage import CompletionUsage
+
+logger = logging.getLogger(__name__)
+
+_instruments = ("openai >= 1.0.0",)
+
+_DEEPSEEK = GenAIAttributes.GenAiProviderNameValues.DEEPSEEK.value
+_CHAT = GenAIAttributes.GenAiOperationNameValues.CHAT.value
+
+WRAPPED_METHODS = [
+ {
+ "package": "openai.resources.chat.completions",
+ "object": "Completions",
+ "method": "create",
+ },
+]
+WRAPPED_AMETHODS = [
+ {
+ "package": "openai.resources.chat.completions",
+ "object": "AsyncCompletions",
+ "method": "create",
+ },
+]
+
+
+def is_streaming_response(response):
+ return isinstance(response, Stream) or isinstance(response, AsyncStream)
+
+
+def _is_deepseek_client(instance) -> bool:
+ """DeepSeek is accessed via the OpenAI SDK pointed at a custom base_url.
+
+ Only instrument calls made through a client configured for the DeepSeek API,
+ so this instrumentation can be safely enabled alongside the OpenAI instrumentation.
+ """
+ client = getattr(instance, "_client", None)
+ base_url = getattr(client, "base_url", "") if client else ""
+ return "deepseek" in str(base_url).lower()
+
+
+def _with_chat_telemetry_wrapper(func):
+ """Helper for providing tracer for wrapper functions. Includes metric collectors."""
+
+ def _with_chat_telemetry(
+ tracer,
+ token_histogram,
+ choice_counter,
+ duration_histogram,
+ event_logger,
+ to_wrap,
+ ):
+ def wrapper(wrapped, instance, args, kwargs):
+ return func(
+ tracer,
+ token_histogram,
+ choice_counter,
+ duration_histogram,
+ event_logger,
+ to_wrap,
+ wrapped,
+ instance,
+ args,
+ kwargs,
+ )
+
+ return wrapper
+
+ return _with_chat_telemetry
+
+
+def _create_metrics(meter: Meter):
+ token_histogram = meter.create_histogram(
+ name=Meters.LLM_TOKEN_USAGE,
+ unit="token",
+ description="Measures number of input and output tokens used",
+ )
+
+ choice_counter = meter.create_counter(
+ name=Meters.LLM_GENERATION_CHOICES,
+ unit="choice",
+ description="Number of choices returned by chat completions call",
+ )
+
+ duration_histogram = meter.create_histogram(
+ name=Meters.LLM_OPERATION_DURATION,
+ unit="s",
+ description="GenAI operation duration",
+ )
+
+ return token_histogram, choice_counter, duration_histogram
+
+
+def _process_streaming_chunk(chunk):
+ """Extract content, reasoning_content, tool_calls_delta, finish_reasons and usage from a streaming chunk."""
+ if not chunk.choices:
+ return None, "", [], [], None
+
+ content = ""
+ reasoning_content = ""
+ tool_calls_delta = []
+ finish_reasons = []
+ for choice in chunk.choices:
+ delta = choice.delta
+ if delta.content:
+ content += delta.content
+ # DeepSeek-R1 (deepseek-reasoner) streams chain-of-thought via `delta.reasoning_content`.
+ delta_reasoning_content = getattr(delta, "reasoning_content", None)
+ if delta_reasoning_content:
+ reasoning_content += delta_reasoning_content
+ if delta.tool_calls:
+ tool_calls_delta.extend(delta.tool_calls)
+ if choice.finish_reason:
+ finish_reasons.append(choice.finish_reason)
+
+ # Usage is only present (on the final chunk) when stream_options={"include_usage": True}.
+ usage = chunk.usage if getattr(chunk, "usage", None) else None
+
+ return content, reasoning_content, tool_calls_delta, finish_reasons, usage
+
+
+def _accumulate_tool_calls(accumulated: dict, tool_calls_delta: list) -> None:
+ """Merge a list of streaming tool_call delta objects into the accumulator dict.
+
+ The accumulator maps tool call index → {id, function: {name, arguments}}.
+ Arguments arrive as JSON fragments and are concatenated across chunks.
+ """
+ for tc in tool_calls_delta:
+ idx = tc.index or 0
+ tc_id = tc.id or ""
+ fn = tc.function
+ fn_name = (fn.name or "") if fn else ""
+ fn_args = (fn.arguments or "") if fn else ""
+
+ if idx not in accumulated:
+ accumulated[idx] = {"id": tc_id, "function": {"name": fn_name, "arguments": ""}}
+ else:
+ if tc_id:
+ accumulated[idx]["id"] = tc_id
+ if fn_name:
+ accumulated[idx]["function"]["name"] = fn_name
+ accumulated[idx]["function"]["arguments"] += fn_args
+
+
+def _handle_streaming_response(
+ span: Span,
+ accumulated_content: str,
+ accumulated_reasoning: str,
+ tool_calls: dict,
+ finish_reasons: list[str],
+ usage: Union[CompletionUsage, None],
+ event_logger: Union[Logger, None],
+) -> None:
+ # finish_reasons is a list; use first entry for message-level finish_reason
+ finish_reason = finish_reasons[0] if finish_reasons else None
+ set_model_streaming_response_attributes(span, usage, finish_reasons)
+ if should_emit_events() and event_logger:
+ emit_streaming_response_events(accumulated_content, finish_reason, event_logger, tool_calls=tool_calls)
+ else:
+ set_streaming_response_attributes(
+ span,
+ accumulated_content,
+ finish_reason,
+ tool_calls=tool_calls,
+ accumulated_reasoning=accumulated_reasoning,
+ )
+
+
+def _create_stream_processor(response, span, event_logger):
+ """Create a generator that processes a stream while collecting telemetry."""
+ accumulated_content = ""
+ accumulated_reasoning = ""
+ accumulated_tool_calls: dict = {}
+ accumulated_finish_reasons: list = []
+ usage = None
+
+ try:
+ for chunk in response:
+ content, reasoning_content, tool_calls_delta, chunk_finish_reasons, chunk_usage = (
+ _process_streaming_chunk(chunk)
+ )
+ if content:
+ accumulated_content += content
+ if reasoning_content:
+ accumulated_reasoning += reasoning_content
+ if tool_calls_delta:
+ _accumulate_tool_calls(accumulated_tool_calls, tool_calls_delta)
+ accumulated_finish_reasons.extend(chunk_finish_reasons)
+ if chunk_usage:
+ usage = chunk_usage
+ yield chunk
+ except Exception as e:
+ span.set_attribute(ERROR_TYPE, e.__class__.__name__)
+ span.record_exception(e)
+ span.set_status(Status(StatusCode.ERROR, str(e)))
+ raise
+ else:
+ tool_calls = [accumulated_tool_calls[i] for i in sorted(accumulated_tool_calls)] or None
+ _handle_streaming_response(
+ span,
+ accumulated_content,
+ accumulated_reasoning,
+ tool_calls,
+ accumulated_finish_reasons,
+ usage,
+ event_logger,
+ )
+ if span.is_recording():
+ span.set_status(Status(StatusCode.OK))
+ finally:
+ span.end()
+
+
+async def _create_async_stream_processor(response, span, event_logger):
+ """Create an async generator that processes a stream while collecting telemetry."""
+ accumulated_content = ""
+ accumulated_reasoning = ""
+ accumulated_tool_calls: dict = {}
+ accumulated_finish_reasons: list = []
+ usage = None
+
+ try:
+ async for chunk in response:
+ content, reasoning_content, tool_calls_delta, chunk_finish_reasons, chunk_usage = (
+ _process_streaming_chunk(chunk)
+ )
+ if content:
+ accumulated_content += content
+ if reasoning_content:
+ accumulated_reasoning += reasoning_content
+ if tool_calls_delta:
+ _accumulate_tool_calls(accumulated_tool_calls, tool_calls_delta)
+ accumulated_finish_reasons.extend(chunk_finish_reasons)
+ if chunk_usage:
+ usage = chunk_usage
+ yield chunk
+ except Exception as e:
+ span.set_attribute(ERROR_TYPE, e.__class__.__name__)
+ span.record_exception(e)
+ span.set_status(Status(StatusCode.ERROR, str(e)))
+ raise
+ else:
+ tool_calls = [accumulated_tool_calls[i] for i in sorted(accumulated_tool_calls)] or None
+ _handle_streaming_response(
+ span,
+ accumulated_content,
+ accumulated_reasoning,
+ tool_calls,
+ accumulated_finish_reasons,
+ usage,
+ event_logger,
+ )
+ if span.is_recording():
+ span.set_status(Status(StatusCode.OK))
+ finally:
+ span.end()
+
+
+def _handle_input(span, kwargs, event_logger):
+ set_model_input_attributes(span, kwargs)
+ if should_emit_events() and event_logger:
+ emit_message_events(kwargs, event_logger)
+ else:
+ set_input_attributes(span, kwargs)
+
+
+def _handle_response(span, response, token_histogram, event_logger):
+ set_model_response_attributes(span, response, token_histogram)
+ if should_emit_events() and event_logger:
+ emit_choice_events(response, event_logger)
+ else:
+ set_response_attributes(span, response)
+
+
+@_with_chat_telemetry_wrapper
+def _wrap(
+ tracer: Tracer,
+ token_histogram: Histogram,
+ choice_counter: Counter,
+ duration_histogram: Histogram,
+ event_logger: Union[Logger, None],
+ to_wrap,
+ wrapped,
+ instance,
+ args,
+ kwargs,
+):
+ """Instruments and calls every function defined in TO_WRAP."""
+ if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY) or context_api.get_value(
+ SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY
+ ):
+ return wrapped(*args, **kwargs)
+
+ if not _is_deepseek_client(instance):
+ return wrapped(*args, **kwargs)
+
+ llm_model = kwargs.get("model", "")
+ span = tracer.start_span(
+ f"{_CHAT} {llm_model}",
+ kind=SpanKind.CLIENT,
+ attributes={
+ GenAIAttributes.GEN_AI_PROVIDER_NAME: _DEEPSEEK,
+ GenAIAttributes.GEN_AI_OPERATION_NAME: _CHAT,
+ GenAIAttributes.GEN_AI_REQUEST_MODEL: llm_model,
+ },
+ )
+
+ _handle_input(span, kwargs, event_logger)
+
+ start_time = time.time()
+ try:
+ response = wrapped(*args, **kwargs)
+ except Exception as e:
+ end_time = time.time()
+ attributes = error_metrics_attributes(e)
+
+ if duration_histogram:
+ duration = end_time - start_time
+ duration_histogram.record(duration, attributes=attributes)
+
+ span.set_attribute(ERROR_TYPE, e.__class__.__name__)
+ span.record_exception(e)
+ span.set_status(Status(StatusCode.ERROR, str(e)))
+ span.end()
+ raise
+
+ end_time = time.time()
+
+ if is_streaming_response(response):
+ try:
+ return _create_stream_processor(response, span, event_logger)
+ except Exception as ex:
+ logger.warning(
+ "Failed to process streaming response for deepseek span, error: %s",
+ str(ex),
+ )
+ span.set_status(Status(StatusCode.ERROR))
+ span.end()
+ raise
+ elif response:
+ try:
+ metric_attributes = shared_metrics_attributes(response)
+
+ if duration_histogram:
+ duration = time.time() - start_time
+ duration_histogram.record(
+ duration,
+ attributes=metric_attributes,
+ )
+
+ _handle_response(span, response, token_histogram, event_logger)
+
+ except Exception as ex:
+ logger.warning(
+ "Failed to set response attributes for deepseek span, error: %s",
+ str(ex),
+ )
+
+ if span.is_recording():
+ span.set_status(Status(StatusCode.OK))
+ span.end()
+ return response
+
+
+@_with_chat_telemetry_wrapper
+async def _awrap(
+ tracer,
+ token_histogram: Histogram,
+ choice_counter: Counter,
+ duration_histogram: Histogram,
+ event_logger: Union[Logger, None],
+ to_wrap,
+ wrapped,
+ instance,
+ args,
+ kwargs,
+):
+ """Instruments and calls every function defined in TO_WRAP."""
+ if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY) or context_api.get_value(
+ SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY
+ ):
+ return await wrapped(*args, **kwargs)
+
+ if not _is_deepseek_client(instance):
+ return await wrapped(*args, **kwargs)
+
+ llm_model = kwargs.get("model", "")
+ span = tracer.start_span(
+ f"{_CHAT} {llm_model}",
+ kind=SpanKind.CLIENT,
+ attributes={
+ GenAIAttributes.GEN_AI_PROVIDER_NAME: _DEEPSEEK,
+ GenAIAttributes.GEN_AI_OPERATION_NAME: _CHAT,
+ GenAIAttributes.GEN_AI_REQUEST_MODEL: llm_model,
+ },
+ )
+
+ _handle_input(span, kwargs, event_logger)
+
+ start_time = time.time()
+
+ try:
+ response = await wrapped(*args, **kwargs)
+ except Exception as e:
+ end_time = time.time()
+ attributes = error_metrics_attributes(e)
+
+ if duration_histogram:
+ duration = end_time - start_time
+ duration_histogram.record(duration, attributes=attributes)
+
+ span.set_attribute(ERROR_TYPE, e.__class__.__name__)
+ span.record_exception(e)
+ span.set_status(Status(StatusCode.ERROR, str(e)))
+ span.end()
+ raise
+
+ end_time = time.time()
+
+ if is_streaming_response(response):
+ try:
+ return _create_async_stream_processor(response, span, event_logger)
+ except Exception as ex:
+ logger.warning(
+ "Failed to process streaming response for deepseek span, error: %s",
+ str(ex),
+ )
+ span.set_status(Status(StatusCode.ERROR))
+ span.end()
+ raise
+ elif response:
+ try:
+ metric_attributes = shared_metrics_attributes(response)
+
+ if duration_histogram:
+ duration = time.time() - start_time
+ duration_histogram.record(
+ duration,
+ attributes=metric_attributes,
+ )
+
+ _handle_response(span, response, token_histogram, event_logger)
+
+ except Exception as ex:
+ logger.warning(
+ "Failed to set response attributes for deepseek span, error: %s",
+ str(ex),
+ )
+
+ if span.is_recording():
+ span.set_status(Status(StatusCode.OK))
+ span.end()
+ return response
+
+
+def is_metrics_enabled() -> bool:
+ return (os.getenv("TRACELOOP_METRICS_ENABLED") or "true").lower() == "true"
+
+
+class DeepSeekInstrumentor(BaseInstrumentor):
+ """An instrumentor for DeepSeek's chat completions API, accessed via the OpenAI SDK."""
+
+ def __init__(
+ self,
+ exception_logger=None,
+ use_attributes: Optional[bool] = None,
+ get_common_metrics_attributes: Callable[[], dict] = lambda: {},
+ use_legacy_attributes: Optional[bool] = None,
+ ):
+ super().__init__()
+ if use_attributes is not None and use_legacy_attributes is not None:
+ raise TypeError(
+ "Cannot pass both `use_attributes` and `use_legacy_attributes`; "
+ "`use_legacy_attributes` is deprecated, use `use_attributes` instead."
+ )
+ if use_legacy_attributes is not None:
+ warnings.warn(
+ "`use_legacy_attributes` is deprecated and will be removed in a "
+ "future release; use `use_attributes` instead. The current OTel "
+ "GenAI spec emits prompts/completions as span attributes "
+ "(`gen_ai.input.messages` / `gen_ai.output.messages`), which is "
+ "what `use_attributes=True` (the default) does. "
+ "`use_attributes=False` opts into the events path instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ use_attributes = use_legacy_attributes
+ if use_attributes is None:
+ use_attributes = True
+ Config.exception_logger = exception_logger
+ Config.get_common_metrics_attributes = get_common_metrics_attributes
+ Config.use_legacy_attributes = use_attributes
+
+ def instrumentation_dependencies(self) -> Collection[str]:
+ return _instruments
+
+ def _instrument(self, **kwargs):
+ tracer_provider = kwargs.get("tracer_provider")
+ tracer = get_tracer(__name__, __version__, tracer_provider)
+
+ # meter and counters are inited here
+ meter_provider = kwargs.get("meter_provider")
+ meter = get_meter(__name__, __version__, meter_provider)
+
+ if is_metrics_enabled():
+ (
+ token_histogram,
+ choice_counter,
+ duration_histogram,
+ ) = _create_metrics(meter)
+ else:
+ (
+ token_histogram,
+ choice_counter,
+ duration_histogram,
+ ) = (None, None, None)
+
+ event_logger = None
+ if not Config.use_legacy_attributes:
+ logger_provider = kwargs.get("logger_provider")
+ event_logger = get_logger(__name__, __version__, logger_provider=logger_provider)
+
+ for wrapped_method in WRAPPED_METHODS:
+ wrap_package = wrapped_method.get("package")
+ wrap_object = wrapped_method.get("object")
+ wrap_method = wrapped_method.get("method")
+
+ try:
+ wrap_function_wrapper(
+ wrap_package,
+ f"{wrap_object}.{wrap_method}",
+ _wrap(
+ tracer,
+ token_histogram,
+ choice_counter,
+ duration_histogram,
+ event_logger,
+ wrapped_method,
+ ),
+ )
+ except ModuleNotFoundError:
+ pass # that's ok, we don't want to fail if some methods do not exist
+
+ for wrapped_method in WRAPPED_AMETHODS:
+ wrap_package = wrapped_method.get("package")
+ wrap_object = wrapped_method.get("object")
+ wrap_method = wrapped_method.get("method")
+ try:
+ wrap_function_wrapper(
+ wrap_package,
+ f"{wrap_object}.{wrap_method}",
+ _awrap(
+ tracer,
+ token_histogram,
+ choice_counter,
+ duration_histogram,
+ event_logger,
+ wrapped_method,
+ ),
+ )
+ except ModuleNotFoundError:
+ pass # that's ok, we don't want to fail if some methods do not exist
+
+ def _uninstrument(self, **kwargs):
+ for wrapped_method in WRAPPED_METHODS:
+ wrap_package = wrapped_method.get("package")
+ wrap_object = wrapped_method.get("object")
+ unwrap(
+ f"{wrap_package}.{wrap_object}",
+ wrapped_method.get("method"),
+ )
+ for wrapped_method in WRAPPED_AMETHODS:
+ wrap_package = wrapped_method.get("package")
+ wrap_object = wrapped_method.get("object")
+ unwrap(
+ f"{wrap_package}.{wrap_object}",
+ wrapped_method.get("method"),
+ )
diff --git a/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/config.py b/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/config.py
new file mode 100644
index 0000000000..f78d2f0d60
--- /dev/null
+++ b/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/config.py
@@ -0,0 +1,7 @@
+from typing import Callable
+
+
+class Config:
+ exception_logger = None
+ get_common_metrics_attributes: Callable[[], dict] = lambda: {}
+ use_legacy_attributes = True
diff --git a/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/event_emitter.py b/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/event_emitter.py
new file mode 100644
index 0000000000..79853cb0f2
--- /dev/null
+++ b/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/event_emitter.py
@@ -0,0 +1,147 @@
+from dataclasses import asdict
+from enum import Enum
+from typing import Union
+
+from opentelemetry._logs import Logger, LogRecord
+from opentelemetry.instrumentation.deepseek.event_models import ChoiceEvent, MessageEvent
+from opentelemetry.instrumentation.deepseek.span_utils import _map_deepseek_finish_reason
+from opentelemetry.instrumentation.deepseek.utils import (
+ dont_throw,
+ should_emit_events,
+ should_send_prompts,
+)
+from opentelemetry.semconv._incubating.attributes import (
+ gen_ai_attributes as GenAIAttributes,
+)
+
+from openai.types.chat.chat_completion import ChatCompletion
+
+
+class Roles(Enum):
+ USER = "user"
+ ASSISTANT = "assistant"
+ SYSTEM = "system"
+ TOOL = "tool"
+
+
+VALID_MESSAGE_ROLES = {role.value for role in Roles}
+"""The valid roles for naming the message event."""
+
+EVENT_ATTRIBUTES = {GenAIAttributes.GEN_AI_PROVIDER_NAME: GenAIAttributes.GenAiProviderNameValues.DEEPSEEK.value}
+"""The attributes to be used for the event."""
+
+
+@dont_throw
+def emit_message_events(kwargs: dict, event_logger):
+ for message in kwargs.get("messages", []):
+ emit_event(
+ MessageEvent(
+ content=message.get("content"),
+ role=message.get("role", "unknown"),
+ tool_calls=message.get("tool_calls"),
+ ),
+ event_logger=event_logger,
+ )
+
+
+@dont_throw
+def emit_choice_events(response: ChatCompletion, event_logger):
+ for choice in response.choices:
+ emit_event(
+ ChoiceEvent(
+ index=choice.index,
+ message={
+ "content": choice.message.content,
+ "role": choice.message.role or "unknown",
+ },
+ finish_reason=_map_deepseek_finish_reason(choice.finish_reason),
+ tool_calls=choice.message.tool_calls or None,
+ ),
+ event_logger=event_logger,
+ )
+
+
+@dont_throw
+def emit_streaming_response_events(
+ accumulated_content: str,
+ finish_reason: Union[str, None],
+ event_logger,
+ tool_calls=None,
+):
+ """Emit events for streaming response."""
+ emit_event(
+ ChoiceEvent(
+ index=0,
+ message={"content": accumulated_content, "role": "assistant"},
+ finish_reason=_map_deepseek_finish_reason(finish_reason),
+ tool_calls=tool_calls,
+ ),
+ event_logger,
+ )
+
+
+def emit_event(event: Union[MessageEvent, ChoiceEvent], event_logger: Union[Logger, None]) -> None:
+ """
+ Emit an event to the OpenTelemetry SDK.
+
+ Args:
+ event: The event to emit.
+ """
+ if not should_emit_events() or event_logger is None:
+ return
+
+ if isinstance(event, MessageEvent):
+ _emit_message_event(event, event_logger)
+ elif isinstance(event, ChoiceEvent):
+ _emit_choice_event(event, event_logger)
+ else:
+ raise TypeError("Unsupported event type")
+
+
+def _emit_message_event(event: MessageEvent, event_logger: Logger) -> None:
+ body = asdict(event)
+
+ if event.role in VALID_MESSAGE_ROLES:
+ name = "gen_ai.{}.message".format(event.role)
+ # According to the semantic conventions, the role is conditionally required if available
+ # and not equal to the "role" in the message name. So, remove the role from the body if
+ # it is the same as the in the event name.
+ body.pop("role", None)
+ else:
+ name = "gen_ai.user.message"
+
+ # According to the semantic conventions, only the assistant role has tool call
+ if event.role != Roles.ASSISTANT.value and event.tool_calls is not None:
+ del body["tool_calls"]
+ elif event.tool_calls is None:
+ del body["tool_calls"]
+
+ if not should_send_prompts():
+ del body["content"]
+ if body.get("tool_calls") is not None:
+ for tool_call in body["tool_calls"]:
+ tool_call["function"].pop("arguments", None)
+
+ log_record = LogRecord(body=body, attributes=EVENT_ATTRIBUTES, event_name=name)
+ event_logger.emit(log_record)
+
+
+def _emit_choice_event(event: ChoiceEvent, event_logger: Logger) -> None:
+ body = asdict(event)
+ if event.message["role"] == Roles.ASSISTANT.value:
+ # According to the semantic conventions, the role is conditionally required if available
+ # and not equal to "assistant", so remove the role from the body if it is "assistant".
+ body["message"].pop("role", None)
+
+ if event.tool_calls is None:
+ del body["tool_calls"]
+
+ if not should_send_prompts():
+ body["message"].pop("content", None)
+ body["message"].pop("role", None)
+ if body.get("tool_calls") is not None:
+ for tool_call in body["tool_calls"]:
+ tool_call["function"].pop("arguments", None)
+
+ log_record = LogRecord(body=body, attributes=EVENT_ATTRIBUTES, event_name="gen_ai.choice")
+ event_logger.emit(log_record)
diff --git a/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/event_models.py b/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/event_models.py
new file mode 100644
index 0000000000..b4f68e58f7
--- /dev/null
+++ b/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/event_models.py
@@ -0,0 +1,41 @@
+from dataclasses import dataclass
+from typing import Any, List, Literal, Optional, TypedDict
+
+
+class _FunctionToolCall(TypedDict):
+ function_name: str
+ arguments: Optional[dict[str, Any]]
+
+
+class ToolCall(TypedDict):
+ """Represents a tool call in the AI model."""
+
+ id: str
+ function: _FunctionToolCall
+ type: Literal["function"]
+
+
+class CompletionMessage(TypedDict):
+ """Represents a message in the AI model."""
+
+ content: Any
+ role: str
+
+
+@dataclass
+class MessageEvent:
+ """Represents an input event for the AI model."""
+
+ content: Any
+ role: str = "user"
+ tool_calls: Optional[List[ToolCall]] = None
+
+
+@dataclass
+class ChoiceEvent:
+ """Represents a completion event for the AI model."""
+
+ index: int
+ message: CompletionMessage
+ finish_reason: str = ""
+ tool_calls: Optional[List[ToolCall]] = None
diff --git a/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/span_utils.py b/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/span_utils.py
new file mode 100644
index 0000000000..ee3a9a0cbb
--- /dev/null
+++ b/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/span_utils.py
@@ -0,0 +1,311 @@
+import json
+
+from opentelemetry.instrumentation.deepseek.utils import (
+ dont_throw,
+ model_as_dict,
+ set_span_attribute,
+ should_send_prompts,
+)
+from opentelemetry.semconv._incubating.attributes.gen_ai_attributes import (
+ GEN_AI_RESPONSE_ID,
+)
+from opentelemetry.semconv._incubating.attributes import (
+ gen_ai_attributes as GenAIAttributes,
+)
+from opentelemetry.semconv_ai import (
+ SpanAttributes,
+)
+
+_DEEPSEEK_PROVIDER = GenAIAttributes.GenAiProviderNameValues.DEEPSEEK.value
+_CHAT_OPERATION = GenAIAttributes.GenAiOperationNameValues.CHAT.value
+
+GEN_AI_DEEPSEEK_REASONING_CONTENT = "gen_ai.deepseek.reasoning_content"
+
+# DeepSeek's API is OpenAI-compatible and returns finish_reason as an OpenAI-compatible string.
+# Map to OTel standard values; unknown / None → "".
+# Note: DeepSeek "tool_calls" (plural, OpenAI-compatible) → OTel "tool_call" (singular).
+_FINISH_REASON_MAP = {
+ "stop": "stop",
+ "length": "length",
+ "content_filter": "content_filter",
+ "tool_calls": "tool_call",
+}
+
+
+def _map_deepseek_finish_reason(finish_reason):
+ if not finish_reason:
+ return ""
+ return _FINISH_REASON_MAP.get(str(finish_reason), str(finish_reason))
+
+
+def _collect_finish_reasons_from_response(response):
+ if response is None:
+ return []
+ choices = getattr(response, "choices", None) or []
+ return [_map_deepseek_finish_reason(getattr(c, "finish_reason", None)) for c in choices]
+
+
+def _content_to_parts(content):
+ """Convert OpenAI-compatible message content to OTel parts array."""
+ if content is None:
+ return []
+ if isinstance(content, str):
+ return [{"type": "text", "content": content}] if content else []
+ # List of content blocks (multimodal)
+ parts = []
+ for block in content:
+ if not isinstance(block, dict):
+ continue
+ block_type = block.get("type")
+ if block_type == "text":
+ parts.append({"type": "text", "content": block.get("text", "")})
+ elif block_type == "image_url":
+ url = (block.get("image_url") or {}).get("url", "")
+ if url.startswith("data:"):
+ try:
+ header, data = url.split(",", 1)
+ mime_type = header.split(":")[1].split(";")[0]
+ parts.append({"type": "blob", "modality": "image", "mime_type": mime_type, "content": data})
+ except Exception:
+ parts.append({"type": "uri", "modality": "image", "uri": url})
+ else:
+ parts.append({"type": "uri", "modality": "image", "uri": url})
+ else:
+ parts.append({"type": block_type or "unknown", **{k: v for k, v in block.items() if k != "type"}})
+ return parts
+
+
+def _tool_calls_to_parts(tool_calls):
+ """Convert OpenAI tool_calls list to OTel tool_call parts.
+
+ Handles both dict representations (user kwargs) and object representations
+ (e.g. Pydantic models returned by the OpenAI SDK).
+ """
+ parts = []
+ for tc in tool_calls or []:
+ if isinstance(tc, dict):
+ tc_id = tc.get("id") or ""
+ fn = tc.get("function") or {}
+ fn_name = fn.get("name") or ""
+ args_raw = fn.get("arguments")
+ elif hasattr(tc, "function"):
+ tc_id = getattr(tc, "id", "") or ""
+ fn = tc.function
+ fn_name = getattr(fn, "name", "") or ""
+ args_raw = getattr(fn, "arguments", None)
+ else:
+ continue
+ if isinstance(args_raw, str):
+ try:
+ args = json.loads(args_raw)
+ except (json.JSONDecodeError, TypeError):
+ args = args_raw
+ elif isinstance(args_raw, dict):
+ args = args_raw
+ else:
+ args = None
+ part = {"type": "tool_call", "name": fn_name}
+ if tc_id:
+ part["id"] = tc_id
+ if args is not None:
+ part["arguments"] = args
+ parts.append(part)
+ return parts
+
+
+@dont_throw
+def set_input_attributes(span, kwargs):
+ if not span.is_recording() or not should_send_prompts():
+ return
+
+ messages = []
+ for msg in kwargs.get("messages") or []:
+ role = msg.get("role", "user")
+
+ if role == "tool":
+ parts = [
+ {
+ "type": "tool_call_response",
+ "id": msg.get("tool_call_id") or "",
+ "response": msg.get("content") or "",
+ }
+ ]
+ else:
+ parts = _content_to_parts(msg.get("content"))
+ tool_calls = msg.get("tool_calls")
+ if tool_calls:
+ parts.extend(_tool_calls_to_parts(tool_calls))
+
+ messages.append({"role": role, "parts": parts})
+
+ if messages:
+ set_span_attribute(span, GenAIAttributes.GEN_AI_INPUT_MESSAGES, json.dumps(messages))
+
+
+@dont_throw
+def set_model_input_attributes(span, kwargs):
+ if not span.is_recording():
+ return
+
+ set_span_attribute(span, GenAIAttributes.GEN_AI_REQUEST_MODEL, kwargs.get("model"))
+ set_span_attribute(span, GenAIAttributes.GEN_AI_REQUEST_MAX_TOKENS, kwargs.get("max_tokens"))
+ set_span_attribute(span, GenAIAttributes.GEN_AI_REQUEST_TEMPERATURE, kwargs.get("temperature"))
+ set_span_attribute(span, GenAIAttributes.GEN_AI_REQUEST_TOP_P, kwargs.get("top_p"))
+ set_span_attribute(
+ span,
+ GenAIAttributes.GEN_AI_REQUEST_FREQUENCY_PENALTY,
+ kwargs.get("frequency_penalty"),
+ )
+ set_span_attribute(
+ span,
+ GenAIAttributes.GEN_AI_REQUEST_PRESENCE_PENALTY,
+ kwargs.get("presence_penalty"),
+ )
+ set_span_attribute(span, SpanAttributes.GEN_AI_IS_STREAMING, kwargs.get("stream") or False)
+
+ if should_send_prompts():
+ tools = kwargs.get("tools")
+ if tools:
+ try:
+ set_span_attribute(span, GenAIAttributes.GEN_AI_TOOL_DEFINITIONS, json.dumps(tools))
+ except Exception:
+ pass
+
+
+@dont_throw
+def set_streaming_response_attributes(
+ span,
+ accumulated_content,
+ finish_reason=None,
+ tool_calls=None,
+ accumulated_reasoning=None,
+):
+ """Set gen_ai.output.messages span attribute for accumulated streaming response."""
+ if not span.is_recording() or not should_send_prompts():
+ return
+
+ mapped_reason = _map_deepseek_finish_reason(finish_reason)
+ parts = [{"type": "text", "content": accumulated_content}] if accumulated_content else []
+ if tool_calls:
+ parts.extend(_tool_calls_to_parts(tool_calls))
+ message = {"role": "assistant", "parts": parts, "finish_reason": mapped_reason}
+ set_span_attribute(span, GenAIAttributes.GEN_AI_OUTPUT_MESSAGES, json.dumps([message]))
+
+ if accumulated_reasoning:
+ set_span_attribute(span, GEN_AI_DEEPSEEK_REASONING_CONTENT, accumulated_reasoning)
+
+
+@dont_throw
+def set_model_streaming_response_attributes(span, usage, finish_reasons=None):
+ if not span.is_recording():
+ return
+
+ if usage:
+ set_span_attribute(span, GenAIAttributes.GEN_AI_USAGE_OUTPUT_TOKENS, usage.completion_tokens)
+ set_span_attribute(span, GenAIAttributes.GEN_AI_USAGE_INPUT_TOKENS, usage.prompt_tokens)
+ set_span_attribute(span, SpanAttributes.GEN_AI_USAGE_TOTAL_TOKENS, usage.total_tokens)
+
+ if finish_reasons:
+ mapped = [_map_deepseek_finish_reason(fr) for fr in finish_reasons]
+ mapped = [m for m in mapped if m]
+ if mapped:
+ set_span_attribute(span, GenAIAttributes.GEN_AI_RESPONSE_FINISH_REASONS, mapped)
+
+
+@dont_throw
+def set_model_response_attributes(span, response, token_histogram):
+ if not span.is_recording():
+ return
+
+ reasons = [r for r in _collect_finish_reasons_from_response(response) if r]
+ if reasons:
+ set_span_attribute(span, GenAIAttributes.GEN_AI_RESPONSE_FINISH_REASONS, reasons)
+
+ response = model_as_dict(response)
+ set_span_attribute(span, GenAIAttributes.GEN_AI_RESPONSE_MODEL, response.get("model"))
+ set_span_attribute(span, GEN_AI_RESPONSE_ID, response.get("id"))
+
+ usage = response.get("usage") or {}
+ prompt_tokens = usage.get("prompt_tokens")
+ completion_tokens = usage.get("completion_tokens")
+ if usage:
+ set_span_attribute(span, SpanAttributes.GEN_AI_USAGE_TOTAL_TOKENS, usage.get("total_tokens"))
+ set_span_attribute(span, GenAIAttributes.GEN_AI_USAGE_OUTPUT_TOKENS, completion_tokens)
+ set_span_attribute(span, GenAIAttributes.GEN_AI_USAGE_INPUT_TOKENS, prompt_tokens)
+
+ if isinstance(prompt_tokens, int) and prompt_tokens >= 0 and token_histogram is not None:
+ token_histogram.record(
+ prompt_tokens,
+ attributes={
+ GenAIAttributes.GEN_AI_PROVIDER_NAME: _DEEPSEEK_PROVIDER,
+ GenAIAttributes.GEN_AI_OPERATION_NAME: _CHAT_OPERATION,
+ GenAIAttributes.GEN_AI_REQUEST_MODEL: response.get("model"),
+ GenAIAttributes.GEN_AI_TOKEN_TYPE: "input",
+ GenAIAttributes.GEN_AI_RESPONSE_MODEL: response.get("model"),
+ },
+ )
+
+ if isinstance(completion_tokens, int) and completion_tokens >= 0 and token_histogram is not None:
+ token_histogram.record(
+ completion_tokens,
+ attributes={
+ GenAIAttributes.GEN_AI_PROVIDER_NAME: _DEEPSEEK_PROVIDER,
+ GenAIAttributes.GEN_AI_OPERATION_NAME: _CHAT_OPERATION,
+ GenAIAttributes.GEN_AI_REQUEST_MODEL: response.get("model"),
+ GenAIAttributes.GEN_AI_TOKEN_TYPE: "output",
+ GenAIAttributes.GEN_AI_RESPONSE_MODEL: response.get("model"),
+ },
+ )
+
+
+@dont_throw
+def set_response_attributes(span, response):
+ if not span.is_recording() or not should_send_prompts():
+ return
+
+ choices = model_as_dict(response).get("choices") or []
+ messages = []
+ reasoning_contents = []
+
+ for choice in choices:
+ message = choice.get("message") or {}
+ finish_reason = _map_deepseek_finish_reason(choice.get("finish_reason"))
+ role = message.get("role") or "assistant"
+
+ parts = _content_to_parts(message.get("content"))
+
+ # tool_calls (modern OpenAI format)
+ tool_calls = message.get("tool_calls")
+ if tool_calls:
+ parts.extend(_tool_calls_to_parts(tool_calls))
+
+ # function_call (legacy OpenAI format)
+ function_call = message.get("function_call")
+ if function_call:
+ args_raw = function_call.get("arguments")
+ if isinstance(args_raw, str):
+ try:
+ args = json.loads(args_raw)
+ except (json.JSONDecodeError, TypeError):
+ args = args_raw
+ elif isinstance(args_raw, dict):
+ args = args_raw
+ else:
+ args = None
+ part = {"type": "tool_call", "name": function_call.get("name") or ""}
+ if args is not None:
+ part["arguments"] = args
+ parts.append(part)
+
+ messages.append({"role": role, "parts": parts, "finish_reason": finish_reason})
+
+ # DeepSeek-R1 (deepseek-reasoner) returns chain-of-thought in `reasoning_content`.
+ reasoning_content = message.get("reasoning_content")
+ if reasoning_content:
+ reasoning_contents.append(reasoning_content)
+
+ if messages:
+ set_span_attribute(span, GenAIAttributes.GEN_AI_OUTPUT_MESSAGES, json.dumps(messages))
+
+ if reasoning_contents:
+ set_span_attribute(span, GEN_AI_DEEPSEEK_REASONING_CONTENT, "\n---\n".join(reasoning_contents))
diff --git a/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/utils.py b/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/utils.py
new file mode 100644
index 0000000000..a62b44417b
--- /dev/null
+++ b/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/utils.py
@@ -0,0 +1,91 @@
+import logging
+import os
+import traceback
+from importlib.metadata import version
+
+from opentelemetry import context as context_api
+from opentelemetry.instrumentation.deepseek.config import Config
+from opentelemetry.semconv._incubating.attributes import (
+ gen_ai_attributes as GenAIAttributes,
+)
+
+_PYDANTIC_VERSION = version("pydantic")
+
+TRACELOOP_TRACE_CONTENT = "TRACELOOP_TRACE_CONTENT"
+
+
+def set_span_attribute(span, name, value):
+ if value is not None and value != "":
+ span.set_attribute(name, value)
+
+
+def should_send_prompts():
+ return (os.getenv(TRACELOOP_TRACE_CONTENT) or "true").lower() == "true" or context_api.get_value(
+ "override_enable_content_tracing"
+ )
+
+
+def dont_throw(func):
+ """
+ A decorator that wraps the passed in function and logs exceptions instead of throwing them.
+
+ @param func: The function to wrap
+ @return: The wrapper function
+ """
+ # Obtain a logger specific to the function's module
+ logger = logging.getLogger(func.__module__)
+
+ def wrapper(*args, **kwargs):
+ try:
+ return func(*args, **kwargs)
+ except Exception as e:
+ logger.debug(
+ "OpenLLMetry failed to trace in %s, error: %s",
+ func.__name__,
+ traceback.format_exc(),
+ )
+ if Config.exception_logger:
+ Config.exception_logger(e)
+
+ return wrapper
+
+
+@dont_throw
+def shared_metrics_attributes(response):
+ response_dict = model_as_dict(response)
+
+ common_attributes = Config.get_common_metrics_attributes()
+
+ return {
+ **common_attributes,
+ GenAIAttributes.GEN_AI_PROVIDER_NAME: GenAIAttributes.GenAiProviderNameValues.DEEPSEEK.value,
+ GenAIAttributes.GEN_AI_RESPONSE_MODEL: response_dict.get("model"),
+ }
+
+
+@dont_throw
+def error_metrics_attributes(exception):
+ return {
+ GenAIAttributes.GEN_AI_PROVIDER_NAME: GenAIAttributes.GenAiProviderNameValues.DEEPSEEK.value,
+ "error.type": exception.__class__.__name__,
+ }
+
+
+def model_as_dict(model):
+ if _PYDANTIC_VERSION < "2.0.0":
+ return model.dict()
+ if hasattr(model, "model_dump"):
+ return model.model_dump()
+ elif hasattr(model, "parse"): # Raw API response
+ return model_as_dict(model.parse())
+ else:
+ return model
+
+
+def should_emit_events() -> bool:
+ """
+ Checks if the instrumentation isn't using the legacy attributes
+ and if the event logger is not None.
+ """
+
+ return not Config.use_legacy_attributes
diff --git a/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/version.py b/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/version.py
new file mode 100644
index 0000000000..a076e5d90e
--- /dev/null
+++ b/packages/opentelemetry-instrumentation-deepseek/opentelemetry/instrumentation/deepseek/version.py
@@ -0,0 +1 @@
+__version__ = "0.61.0"
diff --git a/packages/opentelemetry-instrumentation-deepseek/poetry.toml b/packages/opentelemetry-instrumentation-deepseek/poetry.toml
new file mode 100644
index 0000000000..ab1033bd37
--- /dev/null
+++ b/packages/opentelemetry-instrumentation-deepseek/poetry.toml
@@ -0,0 +1,2 @@
+[virtualenvs]
+in-project = true
diff --git a/packages/opentelemetry-instrumentation-deepseek/project.json b/packages/opentelemetry-instrumentation-deepseek/project.json
new file mode 100644
index 0000000000..94d0372162
--- /dev/null
+++ b/packages/opentelemetry-instrumentation-deepseek/project.json
@@ -0,0 +1,77 @@
+{
+ "name": "opentelemetry-instrumentation-deepseek",
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
+ "projectType": "library",
+ "sourceRoot": "packages/opentelemetry-instrumentation-deepseek/opentelemetry_instrumentation_deepseek",
+ "targets": {
+ "lock": {
+ "executor": "nx:run-commands",
+ "options": {
+ "command": "uv lock",
+ "cwd": "packages/opentelemetry-instrumentation-deepseek"
+ }
+ },
+ "add": {
+ "executor": "@nxlv/python:add",
+ "options": {}
+ },
+ "update": {
+ "executor": "@nxlv/python:update",
+ "options": {}
+ },
+ "remove": {
+ "executor": "@nxlv/python:remove",
+ "options": {}
+ },
+ "build": {
+ "executor": "@nxlv/python:build",
+ "outputs": [
+ "{projectRoot}/dist"
+ ],
+ "options": {
+ "outputPath": "packages/opentelemetry-instrumentation-deepseek/dist",
+ "publish": false,
+ "lockedVersions": true,
+ "bundleLocalDependencies": true
+ }
+ },
+ "install": {
+ "executor": "nx:run-commands",
+ "options": {
+ "command": "uv sync --all-groups",
+ "cwd": "packages/opentelemetry-instrumentation-deepseek"
+ }
+ },
+ "lint": {
+ "executor": "nx:run-commands",
+ "options": {
+ "command": "uv run ruff check .",
+ "cwd": "packages/opentelemetry-instrumentation-deepseek"
+ }
+ },
+ "test": {
+ "executor": "nx:run-commands",
+ "outputs": [
+ "{workspaceRoot}/reports/packages/opentelemetry-instrumentation-deepseek/unittests",
+ "{workspaceRoot}/coverage/packages/opentelemetry-instrumentation-deepseek"
+ ],
+ "options": {
+ "command": "uv run pytest tests/",
+ "cwd": "packages/opentelemetry-instrumentation-deepseek"
+ }
+ },
+ "build-release": {
+ "executor": "nx:run-commands",
+ "options": {
+ "commands": [
+ "chmod +x ../../scripts/build-release.sh",
+ "../../scripts/build-release.sh"
+ ],
+ "cwd": "packages/opentelemetry-instrumentation-deepseek"
+ }
+ }
+ },
+ "tags": [
+ "instrumentation"
+ ]
+}
diff --git a/packages/opentelemetry-instrumentation-deepseek/pyproject.toml b/packages/opentelemetry-instrumentation-deepseek/pyproject.toml
new file mode 100644
index 0000000000..f4888d46c0
--- /dev/null
+++ b/packages/opentelemetry-instrumentation-deepseek/pyproject.toml
@@ -0,0 +1,75 @@
+[project]
+name = "opentelemetry-instrumentation-deepseek"
+version = "0.61.0"
+description = "OpenTelemetry DeepSeek instrumentation"
+authors = [
+ { name = "Gal Kleinman", email = "gal@traceloop.com" },
+ { name = "Nir Gazit", email = "nir@traceloop.com" },
+]
+license = "Apache-2.0"
+readme = "README.md"
+requires-python = ">=3.10,<4"
+dependencies = [
+ "opentelemetry-api>=1.38.0,<2",
+ "opentelemetry-instrumentation>=0.59b0",
+ "opentelemetry-semantic-conventions-ai>=0.5.1,<0.6.0",
+ "opentelemetry-semantic-conventions>=0.59b0",
+]
+
+[project.urls]
+Repository = "https://github.com/traceloop/openllmetry/tree/main/packages/opentelemetry-instrumentation-deepseek"
+
+[project.optional-dependencies]
+instruments = ["openai>=1.0.0"]
+
+[project.entry-points."opentelemetry_instrumentor"]
+deepseek = "opentelemetry.instrumentation.deepseek:DeepSeekInstrumentor"
+
+[dependency-groups]
+dev = [
+ "autopep8>=2.2.0,<3",
+ "pytest-sugar==1.0.0",
+ "pytest>=8.2.2,<9",
+ "ruff>=0.4.0",
+]
+test = [
+ "openai>=1.0.0",
+ "opentelemetry-sdk>=1.38.0,<2",
+ "pytest-asyncio>=0.23.7,<0.24.0",
+ "pytest-recording>=0.13.1,<0.14.0",
+ "pytest-sugar==1.0.0",
+ "pytest>=8.2.2,<9",
+ "vcrpy>=8.0.0,<9",
+]
+
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[tool.hatch.build.targets.wheel]
+packages = ["opentelemetry"]
+
+[tool.coverage.run]
+branch = true
+source = ["opentelemetry/instrumentation/deepseek"]
+
+[tool.coverage.report]
+exclude_lines = ["if TYPE_CHECKING:"]
+show_missing = true
+
+[tool.ruff]
+line-length = 120
+exclude = [
+ ".git",
+ "__pycache__",
+ "build",
+ "dist",
+ ".venv",
+ ".pytest_cache",
+]
+
+[tool.ruff.lint]
+select = ["E", "F", "W"]
+
+[tool.uv]
+constraint-dependencies = ["urllib3>=2.6.3", "pip>=25.3"]
diff --git a/packages/opentelemetry-instrumentation-deepseek/tests/__init__.py b/packages/opentelemetry-instrumentation-deepseek/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/opentelemetry-instrumentation-deepseek/tests/data/.gitkeep b/packages/opentelemetry-instrumentation-deepseek/tests/data/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/opentelemetry-instrumentation-deepseek/tests/traces/conftest.py b/packages/opentelemetry-instrumentation-deepseek/tests/traces/conftest.py
new file mode 100644
index 0000000000..5a4cfbf42e
--- /dev/null
+++ b/packages/opentelemetry-instrumentation-deepseek/tests/traces/conftest.py
@@ -0,0 +1,138 @@
+"""Unit tests configuration module."""
+
+import os
+
+import pytest
+from openai import AsyncOpenAI, OpenAI
+from opentelemetry.instrumentation.deepseek import DeepSeekInstrumentor
+from opentelemetry.instrumentation.deepseek.utils import TRACELOOP_TRACE_CONTENT
+from opentelemetry.sdk._logs import LoggerProvider
+from opentelemetry.sdk._logs.export import (
+ InMemoryLogExporter,
+ SimpleLogRecordProcessor,
+)
+from opentelemetry.sdk.metrics import Counter, Histogram, MeterProvider
+from opentelemetry.sdk.metrics.export import (
+ AggregationTemporality,
+ InMemoryMetricReader,
+)
+from opentelemetry.sdk.resources import Resource
+from opentelemetry.sdk.trace import TracerProvider
+from opentelemetry.sdk.trace.export import SimpleSpanProcessor
+from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
+
+
+@pytest.fixture(scope="function", name="span_exporter")
+def fixture_span_exporter():
+ exporter = InMemorySpanExporter()
+ yield exporter
+
+
+@pytest.fixture(scope="function", name="tracer_provider")
+def fixture_tracer_provider(span_exporter):
+ provider = TracerProvider()
+ provider.add_span_processor(SimpleSpanProcessor(span_exporter))
+ return provider
+
+
+@pytest.fixture(scope="function", name="log_exporter")
+def fixture_log_exporter():
+ exporter = InMemoryLogExporter()
+ yield exporter
+
+
+@pytest.fixture(scope="function", name="logger_provider")
+def fixture_logger_provider(log_exporter):
+ provider = LoggerProvider()
+ provider.add_log_record_processor(SimpleLogRecordProcessor(log_exporter))
+ return provider
+
+
+@pytest.fixture(scope="function", name="reader")
+def fixture_reader():
+ reader = InMemoryMetricReader({Counter: AggregationTemporality.DELTA, Histogram: AggregationTemporality.DELTA})
+ return reader
+
+
+@pytest.fixture(scope="function", name="meter_provider")
+def fixture_meter_provider(reader):
+ resource = Resource.create()
+ meter_provider = MeterProvider(metric_readers=[reader], resource=resource)
+
+ return meter_provider
+
+
+@pytest.fixture
+def deepseek_client():
+ return OpenAI(
+ api_key=os.environ.get("DEEPSEEK_API_KEY"),
+ base_url="https://api.deepseek.com",
+ )
+
+
+@pytest.fixture
+def async_deepseek_client():
+ return AsyncOpenAI(
+ api_key=os.environ.get("DEEPSEEK_API_KEY"),
+ base_url="https://api.deepseek.com",
+ )
+
+
+@pytest.fixture(scope="function")
+def instrument_legacy(reader, tracer_provider, meter_provider):
+ instrumentor = DeepSeekInstrumentor()
+ instrumentor.instrument(
+ tracer_provider=tracer_provider,
+ meter_provider=meter_provider,
+ )
+
+ yield instrumentor
+
+ instrumentor.uninstrument()
+
+
+@pytest.fixture(scope="function")
+def instrument_with_content(reader, tracer_provider, logger_provider, meter_provider):
+ os.environ.update({TRACELOOP_TRACE_CONTENT: "True"})
+
+ instrumentor = DeepSeekInstrumentor(
+ use_legacy_attributes=False,
+ )
+ instrumentor.instrument(
+ tracer_provider=tracer_provider,
+ logger_provider=logger_provider,
+ meter_provider=meter_provider,
+ )
+
+ yield instrumentor
+
+ os.environ.pop(TRACELOOP_TRACE_CONTENT, None)
+ instrumentor.uninstrument()
+
+
+@pytest.fixture(scope="function")
+def instrument_with_no_content(reader, tracer_provider, logger_provider, meter_provider):
+ os.environ.update({TRACELOOP_TRACE_CONTENT: "False"})
+
+ instrumentor = DeepSeekInstrumentor(use_legacy_attributes=False)
+ instrumentor.instrument(
+ tracer_provider=tracer_provider,
+ logger_provider=logger_provider,
+ meter_provider=meter_provider,
+ )
+
+ yield instrumentor
+
+ os.environ.pop(TRACELOOP_TRACE_CONTENT, None)
+ instrumentor.uninstrument()
+
+
+@pytest.fixture(autouse=True)
+def environment():
+ if not os.environ.get("DEEPSEEK_API_KEY"):
+ os.environ["DEEPSEEK_API_KEY"] = "api-key"
+
+
+@pytest.fixture(scope="module")
+def vcr_config():
+ return {"filter_headers": ["authorization", "api-key"]}
diff --git a/packages/opentelemetry-instrumentation-deepseek/tests/traces/test_event_emitter.py b/packages/opentelemetry-instrumentation-deepseek/tests/traces/test_event_emitter.py
new file mode 100644
index 0000000000..edf854d66b
--- /dev/null
+++ b/packages/opentelemetry-instrumentation-deepseek/tests/traces/test_event_emitter.py
@@ -0,0 +1,198 @@
+"""
+Unit tests for event_emitter helpers.
+
+Covers:
+ emit_event, _emit_message_event, _emit_choice_event.
+All tests are pure unit tests — no network calls, no cassettes.
+"""
+
+import os
+from unittest.mock import MagicMock, patch
+
+import pytest
+
+from opentelemetry.instrumentation.deepseek.event_emitter import (
+ _emit_choice_event,
+ _emit_message_event,
+ emit_event,
+ emit_message_events,
+)
+from opentelemetry.instrumentation.deepseek.event_models import ChoiceEvent, MessageEvent
+from opentelemetry.instrumentation.deepseek.utils import TRACELOOP_TRACE_CONTENT
+
+
+# ---------------------------------------------------------------------------
+# Helpers
+# ---------------------------------------------------------------------------
+
+
+def _logger():
+ logger = MagicMock()
+ logger.emit = MagicMock()
+ return logger
+
+
+def _emitted_body(logger):
+ """Return the body of the first emitted LogRecord."""
+ return logger.emit.call_args[0][0].body
+
+
+# ---------------------------------------------------------------------------
+# emit_event
+# ---------------------------------------------------------------------------
+
+
+class TestEmitEvent:
+ def test_none_logger_returns_without_emitting(self):
+ # Should not raise; nothing emitted
+ emit_event(MessageEvent(content="hello", role="user"), event_logger=None)
+
+ def test_events_disabled_returns_without_emitting(self):
+ logger = _logger()
+ with patch(
+ "opentelemetry.instrumentation.deepseek.event_emitter.should_emit_events",
+ return_value=False,
+ ):
+ emit_event(MessageEvent(content="hello", role="user"), event_logger=logger)
+ logger.emit.assert_not_called()
+
+ def test_unsupported_event_type_raises_type_error(self):
+ logger = _logger()
+ with patch(
+ "opentelemetry.instrumentation.deepseek.event_emitter.should_emit_events",
+ return_value=True,
+ ):
+ with pytest.raises(TypeError, match="Unsupported event type"):
+ emit_event("not_an_event_object", event_logger=logger)
+
+
+# ---------------------------------------------------------------------------
+# emit_message_events
+# ---------------------------------------------------------------------------
+
+
+class TestEmitMessageEvents:
+ def test_tool_calls_forwarded_from_assistant_message(self):
+ """emit_message_events must pass tool_calls from kwargs to MessageEvent."""
+ logger = _logger()
+ tool_calls = [{"id": "c1", "type": "function", "function": {"name": "f", "arguments": "{}"}}]
+ kwargs = {
+ "messages": [{"role": "assistant", "content": None, "tool_calls": tool_calls}]
+ }
+ with patch(
+ "opentelemetry.instrumentation.deepseek.event_emitter.should_emit_events",
+ return_value=True,
+ ):
+ emit_message_events(kwargs, logger)
+ body = logger.emit.call_args[0][0].body
+ assert "tool_calls" in body
+ assert body["tool_calls"][0]["id"] == "c1"
+
+ def test_messages_without_tool_calls_do_not_include_tool_calls_key(self):
+ logger = _logger()
+ kwargs = {"messages": [{"role": "user", "content": "Hello"}]}
+ with patch(
+ "opentelemetry.instrumentation.deepseek.event_emitter.should_emit_events",
+ return_value=True,
+ ):
+ emit_message_events(kwargs, logger)
+ body = logger.emit.call_args[0][0].body
+ assert "tool_calls" not in body
+
+
+# ---------------------------------------------------------------------------
+# _emit_message_event
+# ---------------------------------------------------------------------------
+
+
+class TestEmitMessageEvent:
+ def test_invalid_role_uses_fallback_event_name(self):
+ logger = _logger()
+ event = MessageEvent(content="hi", role="some_unknown_role")
+ _emit_message_event(event, logger)
+ log_record = logger.emit.call_args[0][0]
+ assert log_record.event_name == "gen_ai.user.message"
+
+ def test_non_assistant_role_with_tool_calls_removes_tool_calls(self):
+ logger = _logger()
+ tool_calls = [{"id": "c1", "type": "function", "function": {"name": "f", "arguments": "{}"}}]
+ event = MessageEvent(content="some", role="user", tool_calls=tool_calls)
+ _emit_message_event(event, logger)
+ body = _emitted_body(logger)
+ assert "tool_calls" not in body
+
+ def test_assistant_role_with_tool_calls_keeps_tool_calls(self):
+ logger = _logger()
+ tool_calls = [{"id": "c1", "type": "function", "function": {"name": "f", "arguments": "{}"}}]
+ event = MessageEvent(content="", role="assistant", tool_calls=tool_calls)
+ _emit_message_event(event, logger)
+ body = _emitted_body(logger)
+ assert "tool_calls" in body
+
+ def test_no_prompts_removes_content_and_tool_call_arguments(self):
+ logger = _logger()
+ tool_calls = [{"id": "c1", "type": "function", "function": {"name": "f", "arguments": '{"x": 1}'}}]
+ event = MessageEvent(content="some text", role="assistant", tool_calls=tool_calls)
+ with patch.dict(os.environ, {TRACELOOP_TRACE_CONTENT: "False"}):
+ _emit_message_event(event, logger)
+ body = _emitted_body(logger)
+ assert "content" not in body
+ assert "arguments" not in body["tool_calls"][0]["function"]
+
+
+# ---------------------------------------------------------------------------
+# _emit_choice_event
+# ---------------------------------------------------------------------------
+
+
+class TestEmitChoiceEvent:
+ def test_non_assistant_role_keeps_role_in_message(self):
+ logger = _logger()
+ event = ChoiceEvent(
+ index=0,
+ message={"role": "system", "content": "oops"},
+ finish_reason="stop",
+ )
+ _emit_choice_event(event, logger)
+ body = _emitted_body(logger)
+ assert body["message"].get("role") == "system"
+
+ def test_not_none_tool_calls_kept_in_body(self):
+ logger = _logger()
+ tool_calls = [{"id": "c1", "type": "function", "function": {"name": "f", "arguments": "{}"}}]
+ event = ChoiceEvent(
+ index=0,
+ message={"role": "assistant", "content": None},
+ finish_reason="tool_call",
+ tool_calls=tool_calls,
+ )
+ _emit_choice_event(event, logger)
+ body = _emitted_body(logger)
+ assert "tool_calls" in body
+
+ def test_no_prompts_removes_content_and_role_from_message(self):
+ logger = _logger()
+ event = ChoiceEvent(
+ index=0,
+ message={"role": "assistant", "content": "hello"},
+ finish_reason="stop",
+ )
+ with patch.dict(os.environ, {TRACELOOP_TRACE_CONTENT: "False"}):
+ _emit_choice_event(event, logger)
+ body = _emitted_body(logger)
+ assert "content" not in body["message"]
+ assert "role" not in body["message"]
+
+ def test_no_prompts_removes_tool_call_arguments(self):
+ logger = _logger()
+ tool_calls = [{"id": "c1", "type": "function", "function": {"name": "f", "arguments": '{"x": 1}'}}]
+ event = ChoiceEvent(
+ index=0,
+ message={"role": "assistant", "content": None},
+ finish_reason="tool_call",
+ tool_calls=tool_calls,
+ )
+ with patch.dict(os.environ, {TRACELOOP_TRACE_CONTENT: "False"}):
+ _emit_choice_event(event, logger)
+ body = _emitted_body(logger)
+ assert "arguments" not in body["tool_calls"][0]["function"]
diff --git a/packages/opentelemetry-instrumentation-deepseek/tests/traces/test_finish_reasons.py b/packages/opentelemetry-instrumentation-deepseek/tests/traces/test_finish_reasons.py
new file mode 100644
index 0000000000..5ce836ea75
--- /dev/null
+++ b/packages/opentelemetry-instrumentation-deepseek/tests/traces/test_finish_reasons.py
@@ -0,0 +1,84 @@
+"""
+Unit tests for DeepSeek finish reason mapping and collection.
+
+These tests define the expected behavior for:
+- _map_deepseek_finish_reason() — maps DeepSeek's (OpenAI-compatible) finish_reason to OTel string
+- _collect_finish_reasons_from_response() — extracts finish reasons from all choices
+
+All tests here are pure unit tests (no cassettes, no network calls).
+"""
+
+from unittest.mock import MagicMock
+
+from opentelemetry.instrumentation.deepseek.span_utils import (
+ _collect_finish_reasons_from_response,
+ _map_deepseek_finish_reason,
+)
+
+
+# ---------------------------------------------------------------------------
+# _map_deepseek_finish_reason
+# ---------------------------------------------------------------------------
+
+
+class TestMapDeepSeekFinishReason:
+ """DeepSeek's API is OpenAI-compatible and returns finish_reason as a plain string."""
+
+ def test_tool_calls(self):
+ # DeepSeek returns "tool_calls" (plural, OpenAI-compatible) → OTel expects "tool_call" (singular)
+ assert _map_deepseek_finish_reason("tool_calls") == "tool_call"
+
+ def test_none_returns_empty_string(self):
+ assert _map_deepseek_finish_reason(None) == ""
+
+ def test_empty_string_returns_empty_string(self):
+ assert _map_deepseek_finish_reason("") == ""
+
+ def test_unknown_value_is_preserved(self):
+ assert _map_deepseek_finish_reason("something_unexpected") == "something_unexpected"
+
+
+# ---------------------------------------------------------------------------
+# _collect_finish_reasons_from_response
+# ---------------------------------------------------------------------------
+
+
+def _make_response(finish_reasons: list):
+ """Build a mock OpenAI-compatible ChatCompletion with given finish_reasons per choice."""
+ response = MagicMock()
+ choices = []
+ for fr in finish_reasons:
+ choice = MagicMock()
+ choice.finish_reason = fr
+ choices.append(choice)
+ response.choices = choices
+ return response
+
+
+class TestCollectFinishReasonsFromResponse:
+ def test_single_stop(self):
+ response = _make_response(["stop"])
+ assert _collect_finish_reasons_from_response(response) == ["stop"]
+
+ def test_single_length(self):
+ response = _make_response(["length"])
+ assert _collect_finish_reasons_from_response(response) == ["length"]
+
+ def test_multiple_choices(self):
+ response = _make_response(["stop", "length"])
+ assert _collect_finish_reasons_from_response(response) == ["stop", "length"]
+
+ def test_none_finish_reason_maps_to_empty(self):
+ response = _make_response([None])
+ assert _collect_finish_reasons_from_response(response) == [""]
+
+ def test_mixed_known_and_none(self):
+ response = _make_response(["stop", None])
+ assert _collect_finish_reasons_from_response(response) == ["stop", ""]
+
+ def test_empty_choices_returns_empty_list(self):
+ response = _make_response([])
+ assert _collect_finish_reasons_from_response(response) == []
+
+ def test_none_response_returns_empty_list(self):
+ assert _collect_finish_reasons_from_response(None) == []
diff --git a/packages/opentelemetry-instrumentation-deepseek/tests/traces/test_init.py b/packages/opentelemetry-instrumentation-deepseek/tests/traces/test_init.py
new file mode 100644
index 0000000000..11b2722c73
--- /dev/null
+++ b/packages/opentelemetry-instrumentation-deepseek/tests/traces/test_init.py
@@ -0,0 +1,644 @@
+"""
+Unit tests for __init__.py helpers and instrumentation paths.
+
+Covers:
+ _is_deepseek_client, _process_streaming_chunk, _create_stream_processor,
+ _create_async_stream_processor, _wrap, _awrap,
+ DeepSeekInstrumentor._instrument edge cases.
+No cassettes needed — all paths tested with mocks.
+"""
+
+import os
+from unittest.mock import AsyncMock, MagicMock, patch
+
+import pytest
+
+from opentelemetry import context as context_api
+from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
+from opentelemetry.semconv_ai import SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY
+
+from opentelemetry.instrumentation.deepseek import (
+ DeepSeekInstrumentor,
+ _accumulate_tool_calls,
+ _awrap,
+ _create_async_stream_processor,
+ _create_stream_processor,
+ _is_deepseek_client,
+ _process_streaming_chunk,
+ _wrap,
+)
+
+
+# ---------------------------------------------------------------------------
+# Helpers
+# ---------------------------------------------------------------------------
+
+
+def _span(recording: bool = True) -> MagicMock:
+ s = MagicMock()
+ s.is_recording.return_value = recording
+ return s
+
+
+def _deepseek_instance() -> MagicMock:
+ """A mock OpenAI SDK client instance pointed at the DeepSeek API."""
+ instance = MagicMock()
+ instance._client.base_url = "https://api.deepseek.com/v1/"
+ return instance
+
+
+def _openai_instance() -> MagicMock:
+ """A mock OpenAI SDK client instance pointed at the regular OpenAI API."""
+ instance = MagicMock()
+ instance._client.base_url = "https://api.openai.com/v1/"
+ return instance
+
+
+def _make_sync_wrapper(
+ *,
+ tracer=None,
+ token_histogram=None,
+ choice_counter=None,
+ duration_histogram=None,
+ event_logger=None,
+):
+ tracer = tracer or MagicMock()
+ span = _span()
+ tracer.start_span.return_value = span
+ return (
+ span,
+ _wrap(tracer, token_histogram, choice_counter, duration_histogram, event_logger, {}),
+ )
+
+
+def _make_async_wrapper(
+ *,
+ tracer=None,
+ token_histogram=None,
+ choice_counter=None,
+ duration_histogram=None,
+ event_logger=None,
+):
+ tracer = tracer or MagicMock()
+ span = _span()
+ tracer.start_span.return_value = span
+ return (
+ span,
+ _awrap(tracer, token_histogram, choice_counter, duration_histogram, event_logger, {}),
+ )
+
+
+# ---------------------------------------------------------------------------
+# _is_deepseek_client
+# ---------------------------------------------------------------------------
+
+
+class TestIsDeepSeekClient:
+ def test_deepseek_base_url_returns_true(self):
+ assert _is_deepseek_client(_deepseek_instance()) is True
+
+ def test_openai_base_url_returns_false(self):
+ assert _is_deepseek_client(_openai_instance()) is False
+
+ def test_none_instance_returns_false(self):
+ assert _is_deepseek_client(None) is False
+
+
+# ---------------------------------------------------------------------------
+# _process_streaming_chunk
+# ---------------------------------------------------------------------------
+
+
+class TestProcessStreamingChunk:
+ def test_empty_choices_returns_none_quintuple(self):
+ chunk = MagicMock()
+ chunk.choices = []
+ assert _process_streaming_chunk(chunk) == (None, "", [], [], None)
+
+ def test_multiple_choices_accumulates_content(self):
+ chunk = MagicMock()
+ chunk.usage = None
+ choice0 = MagicMock()
+ choice0.delta.content = "Hello"
+ choice0.delta.reasoning_content = None
+ choice0.delta.tool_calls = None
+ choice0.finish_reason = None
+ choice1 = MagicMock()
+ choice1.delta.content = " World"
+ choice1.delta.reasoning_content = None
+ choice1.delta.tool_calls = None
+ choice1.finish_reason = "stop"
+ chunk.choices = [choice0, choice1]
+ content, reasoning_content, tool_calls_delta, finish_reasons, usage = _process_streaming_chunk(chunk)
+ assert content == "Hello World"
+ assert reasoning_content == ""
+ assert tool_calls_delta == []
+ assert finish_reasons == ["stop"]
+ assert usage is None
+
+ def test_reasoning_content_accumulated_for_deepseek_reasoner(self):
+ """DeepSeek-R1 (deepseek-reasoner) streams chain-of-thought via delta.reasoning_content."""
+ chunk = MagicMock()
+ chunk.usage = None
+ choice0 = MagicMock()
+ choice0.delta.content = None
+ choice0.delta.reasoning_content = "Let me "
+ choice0.delta.tool_calls = None
+ choice0.finish_reason = None
+ choice1 = MagicMock()
+ choice1.delta.content = "42"
+ choice1.delta.reasoning_content = "think..."
+ choice1.delta.tool_calls = None
+ choice1.finish_reason = "stop"
+ chunk.choices = [choice0, choice1]
+ content, reasoning_content, tool_calls_delta, finish_reasons, usage = _process_streaming_chunk(chunk)
+ assert content == "42"
+ assert reasoning_content == "Let me think..."
+ assert finish_reasons == ["stop"]
+
+ def test_tool_calls_delta_extracted(self):
+ chunk = MagicMock()
+ chunk.usage = None
+ tc = MagicMock()
+ tc.index = 0
+ tc.id = "call_123"
+ tc.function.name = "get_weather"
+ tc.function.arguments = '{"loc'
+ choice = MagicMock()
+ choice.delta.content = None
+ choice.delta.reasoning_content = None
+ choice.delta.tool_calls = [tc]
+ choice.finish_reason = None
+ chunk.choices = [choice]
+ content, reasoning_content, tool_calls_delta, finish_reasons, usage = _process_streaming_chunk(chunk)
+ assert content == ""
+ assert reasoning_content == ""
+ assert len(tool_calls_delta) == 1
+ assert tool_calls_delta[0].id == "call_123"
+
+ def test_usage_extracted_from_final_chunk(self):
+ chunk = MagicMock()
+ usage = MagicMock()
+ chunk.usage = usage
+ choice = MagicMock()
+ choice.delta.content = "done"
+ choice.delta.reasoning_content = None
+ choice.delta.tool_calls = None
+ choice.finish_reason = "stop"
+ chunk.choices = [choice]
+ _, _, _, _, extracted_usage = _process_streaming_chunk(chunk)
+ assert extracted_usage is usage
+
+ def test_no_usage_on_intermediate_chunk(self):
+ chunk = MagicMock()
+ chunk.usage = None
+ choice = MagicMock()
+ choice.delta.content = "partial"
+ choice.delta.reasoning_content = None
+ choice.delta.tool_calls = None
+ choice.finish_reason = None
+ chunk.choices = [choice]
+ _, _, _, _, usage = _process_streaming_chunk(chunk)
+ assert usage is None
+
+
+# ---------------------------------------------------------------------------
+# _accumulate_tool_calls
+# ---------------------------------------------------------------------------
+
+
+class TestAccumulateToolCalls:
+ def _make_delta(self, index, tc_id=None, name=None, arguments=""):
+ tc = MagicMock()
+ tc.index = index
+ tc.id = tc_id
+ fn = MagicMock()
+ fn.name = name
+ fn.arguments = arguments
+ tc.function = fn
+ return tc
+
+ def test_single_chunk_creates_entry(self):
+ acc = {}
+ tc = self._make_delta(0, tc_id="call_1", name="ping", arguments='{"x"')
+ _accumulate_tool_calls(acc, [tc])
+ assert acc[0]["id"] == "call_1"
+ assert acc[0]["function"]["name"] == "ping"
+ assert acc[0]["function"]["arguments"] == '{"x"'
+
+ def test_fragments_are_concatenated(self):
+ acc = {}
+ _accumulate_tool_calls(acc, [self._make_delta(0, tc_id="call_1", name="fn", arguments='{"a"')])
+ _accumulate_tool_calls(acc, [self._make_delta(0, arguments=': 1}')])
+ assert acc[0]["function"]["arguments"] == '{"a": 1}'
+
+ def test_multiple_tool_calls_tracked_by_index(self):
+ acc = {}
+ _accumulate_tool_calls(acc, [
+ self._make_delta(0, tc_id="c0", name="fn0", arguments=""),
+ self._make_delta(1, tc_id="c1", name="fn1", arguments=""),
+ ])
+ assert 0 in acc and 1 in acc
+ assert acc[0]["id"] == "c0"
+ assert acc[1]["id"] == "c1"
+
+
+# ---------------------------------------------------------------------------
+# _create_stream_processor (sync)
+# ---------------------------------------------------------------------------
+
+
+class TestCreateStreamProcessor:
+ def test_span_not_recording_skips_set_status(self):
+ span = _span(recording=False)
+ chunk = MagicMock()
+ chunk.choices = [] # _process_streaming_chunk returns (None, "", [], [], None)
+
+ # Consume the generator to trigger cleanup
+ list(_create_stream_processor(iter([chunk]), span, None))
+
+ span.set_status.assert_not_called()
+ span.end.assert_called_once()
+
+ def test_accumulated_reasoning_passed_to_streaming_attributes(self):
+ """Reasoning content is accumulated across chunks and forwarded for the final span attribute."""
+ span = _span()
+
+ chunk1 = MagicMock()
+ chunk1.usage = None
+ chunk1.choices = [MagicMock()]
+ chunk1.choices[0].delta.content = None
+ chunk1.choices[0].delta.reasoning_content = "Thinking "
+ chunk1.choices[0].delta.tool_calls = None
+ chunk1.choices[0].finish_reason = None
+
+ chunk2 = MagicMock()
+ chunk2.usage = None
+ chunk2.choices = [MagicMock()]
+ chunk2.choices[0].delta.content = "Answer"
+ chunk2.choices[0].delta.reasoning_content = "more..."
+ chunk2.choices[0].delta.tool_calls = None
+ chunk2.choices[0].finish_reason = "stop"
+
+ with patch("opentelemetry.instrumentation.deepseek.set_streaming_response_attributes") as mock_set:
+ list(_create_stream_processor(iter([chunk1, chunk2]), span, None))
+
+ mock_set.assert_called_once()
+ _, kwargs = mock_set.call_args
+ assert kwargs.get("accumulated_reasoning") == "Thinking more..."
+
+
+# ---------------------------------------------------------------------------
+# _create_async_stream_processor
+# ---------------------------------------------------------------------------
+
+
+class TestCreateAsyncStreamProcessor:
+ @pytest.mark.asyncio
+ async def test_processes_chunks_and_ends_span(self):
+ span = _span()
+
+ chunk = MagicMock()
+ chunk.usage = None
+ chunk.choices = [MagicMock()]
+ chunk.choices[0].delta.content = "hi"
+ chunk.choices[0].delta.reasoning_content = None
+ chunk.choices[0].delta.tool_calls = None
+ chunk.choices[0].finish_reason = "stop"
+
+ async def _response():
+ yield chunk
+
+ chunks = [c async for c in _create_async_stream_processor(_response(), span, None)]
+ assert len(chunks) == 1
+ span.end.assert_called_once()
+
+ @pytest.mark.asyncio
+ async def test_span_not_recording_skips_set_status(self):
+ span = _span(recording=False)
+
+ chunk = MagicMock()
+ chunk.choices = []
+
+ async def _response():
+ yield chunk
+
+ [c async for c in _create_async_stream_processor(_response(), span, None)]
+ span.set_status.assert_not_called()
+ span.end.assert_called_once()
+
+
+# ---------------------------------------------------------------------------
+# _wrap (sync)
+# ---------------------------------------------------------------------------
+
+
+class TestWrap:
+ def test_suppression_key_skips_span(self):
+ tracer = MagicMock()
+ wrapped = MagicMock(return_value="result")
+ wrapper = _wrap(tracer, None, None, None, None, {})
+
+ token = context_api.attach(context_api.set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
+ try:
+ result = wrapper(wrapped, _deepseek_instance(), [], {"model": "m"})
+ finally:
+ context_api.detach(token)
+
+ assert result == "result"
+ tracer.start_span.assert_not_called()
+
+ def test_non_deepseek_client_passes_through_without_span(self):
+ """Calls made through a regular OpenAI client must not be instrumented."""
+ tracer = MagicMock()
+ wrapped = MagicMock(return_value="result")
+ wrapper = _wrap(tracer, None, None, None, None, {})
+
+ result = wrapper(wrapped, _openai_instance(), [], {"model": "gpt-4"})
+
+ assert result == "result"
+ tracer.start_span.assert_not_called()
+ wrapped.assert_called_once()
+
+ def test_none_instance_passes_through_without_span(self):
+ tracer = MagicMock()
+ wrapped = MagicMock(return_value="result")
+ wrapper = _wrap(tracer, None, None, None, None, {})
+
+ result = wrapper(wrapped, None, [], {"model": "m"})
+
+ assert result == "result"
+ tracer.start_span.assert_not_called()
+
+ def test_api_exception_records_duration_and_reraises(self):
+ span, wrapper = _make_sync_wrapper(duration_histogram=MagicMock())
+ error = ValueError("API down")
+ wrapped = MagicMock(side_effect=error)
+
+ with pytest.raises(ValueError, match="API down"):
+ wrapper(wrapped, _deepseek_instance(), [], {"model": "m"})
+
+ span.end.assert_called_once() # span must be ended even on exception
+ span.set_status.assert_called_once()
+
+ def test_api_exception_records_duration_histogram(self):
+ tracer = MagicMock()
+ span = _span()
+ tracer.start_span.return_value = span
+ duration_histogram = MagicMock()
+ wrapped = MagicMock(side_effect=RuntimeError("fail"))
+
+ wrapper = _wrap(tracer, None, None, duration_histogram, None, {})
+ with pytest.raises(RuntimeError):
+ wrapper(wrapped, _deepseek_instance(), [], {"model": "m"})
+
+ duration_histogram.record.assert_called_once()
+
+ def test_falsy_response_ends_span_without_setting_status(self):
+ span, wrapper = _make_sync_wrapper()
+ wrapped = MagicMock(return_value=None)
+
+ result = wrapper(wrapped, _deepseek_instance(), [], {"model": "m"})
+
+ assert result is None
+ span.end.assert_called_once()
+ span.set_status.assert_not_called()
+
+ def test_handle_response_exception_is_swallowed(self):
+ span, wrapper = _make_sync_wrapper()
+ response = MagicMock()
+ wrapped = MagicMock(return_value=response)
+
+ with patch("opentelemetry.instrumentation.deepseek._handle_response", side_effect=Exception("oops")):
+ with patch("opentelemetry.instrumentation.deepseek.shared_metrics_attributes", return_value={}):
+ result = wrapper(wrapped, _deepseek_instance(), [], {"model": "m"})
+
+ assert result is response
+ span.end.assert_called_once()
+
+ def test_no_duration_histogram_skips_duration_record(self):
+ span, wrapper = _make_sync_wrapper(duration_histogram=None)
+ response = MagicMock()
+ wrapped = MagicMock(return_value=response)
+
+ with patch("opentelemetry.instrumentation.deepseek._handle_response"):
+ with patch("opentelemetry.instrumentation.deepseek.shared_metrics_attributes", return_value={}):
+ result = wrapper(wrapped, _deepseek_instance(), [], {"model": "m"})
+
+ assert result is response
+ span.end.assert_called_once()
+
+ def test_stream_processor_exception_sets_error_status(self):
+ from openai._streaming import Stream
+
+ tracer = MagicMock()
+ span = _span()
+ tracer.start_span.return_value = span
+ response = MagicMock(spec=Stream)
+ wrapped = MagicMock(return_value=response)
+
+ wrapper = _wrap(tracer, None, None, None, None, {})
+ with patch(
+ "opentelemetry.instrumentation.deepseek._create_stream_processor",
+ side_effect=RuntimeError("stream error"),
+ ):
+ with pytest.raises(RuntimeError, match="stream error"):
+ wrapper(wrapped, _deepseek_instance(), [], {"model": "m"})
+
+ span.end.assert_called_once()
+
+ def test_span_not_recording_after_response_skips_set_status(self):
+ """Covers span.is_recording() False at the final OK status check."""
+ tracer = MagicMock()
+ span = MagicMock()
+ # Calls: set_model_input_attributes, set_input_attributes, final check
+ span.is_recording.side_effect = [True, True, False]
+ tracer.start_span.return_value = span
+
+ response = MagicMock()
+ wrapped = MagicMock(return_value=response)
+
+ with patch("opentelemetry.instrumentation.deepseek._handle_response"):
+ with patch("opentelemetry.instrumentation.deepseek.shared_metrics_attributes", return_value={}):
+ wrapper = _wrap(tracer, None, None, None, None, {})
+ wrapper(wrapped, _deepseek_instance(), [], {"model": "m"})
+
+ span.set_status.assert_not_called()
+ span.end.assert_called_once()
+
+
+# ---------------------------------------------------------------------------
+# _awrap (async)
+# ---------------------------------------------------------------------------
+
+
+class TestAwrap:
+ @pytest.mark.asyncio
+ async def test_suppression_key_skips_span(self):
+ tracer = MagicMock()
+ wrapped = AsyncMock(return_value="async_result")
+ wrapper = _awrap(tracer, None, None, None, None, {})
+
+ token = context_api.attach(context_api.set_value(SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY, True))
+ try:
+ result = await wrapper(wrapped, _deepseek_instance(), [], {"model": "m"})
+ finally:
+ context_api.detach(token)
+
+ assert result == "async_result"
+ tracer.start_span.assert_not_called()
+
+ @pytest.mark.asyncio
+ async def test_otel_suppression_key_skips_span(self):
+ tracer = MagicMock()
+ wrapped = AsyncMock(return_value="async_result")
+ wrapper = _awrap(tracer, None, None, None, None, {})
+
+ token = context_api.attach(context_api.set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
+ try:
+ result = await wrapper(wrapped, _deepseek_instance(), [], {"model": "m"})
+ finally:
+ context_api.detach(token)
+
+ assert result == "async_result"
+ tracer.start_span.assert_not_called()
+
+ @pytest.mark.asyncio
+ async def test_non_deepseek_client_passes_through_without_span(self):
+ """Calls made through a regular OpenAI client must not be instrumented."""
+ tracer = MagicMock()
+ wrapped = AsyncMock(return_value="async_result")
+ wrapper = _awrap(tracer, None, None, None, None, {})
+
+ result = await wrapper(wrapped, _openai_instance(), [], {"model": "gpt-4"})
+
+ assert result == "async_result"
+ tracer.start_span.assert_not_called()
+ wrapped.assert_awaited_once()
+
+ @pytest.mark.asyncio
+ async def test_api_exception_records_duration_and_reraises(self):
+ tracer = MagicMock()
+ span = _span()
+ tracer.start_span.return_value = span
+ duration_histogram = MagicMock()
+
+ wrapped = AsyncMock(side_effect=RuntimeError("async fail"))
+ wrapper = _awrap(tracer, None, None, duration_histogram, None, {})
+
+ with pytest.raises(RuntimeError, match="async fail"):
+ await wrapper(wrapped, _deepseek_instance(), [], {"model": "m"})
+
+ duration_histogram.record.assert_called_once()
+
+ @pytest.mark.asyncio
+ async def test_falsy_response_ends_span(self):
+ span, wrapper = _make_async_wrapper()
+ wrapped = AsyncMock(return_value=None)
+
+ result = await wrapper(wrapped, _deepseek_instance(), [], {"model": "m"})
+
+ assert result is None
+ span.end.assert_called_once()
+ span.set_status.assert_not_called()
+
+ @pytest.mark.asyncio
+ async def test_response_with_duration_histogram(self):
+ tracer = MagicMock()
+ span = _span()
+ tracer.start_span.return_value = span
+ duration_histogram = MagicMock()
+
+ response = MagicMock()
+ wrapped = AsyncMock(return_value=response)
+
+ wrapper = _awrap(tracer, None, None, duration_histogram, None, {})
+ with patch("opentelemetry.instrumentation.deepseek._handle_response"):
+ with patch("opentelemetry.instrumentation.deepseek.shared_metrics_attributes", return_value={}):
+ result = await wrapper(wrapped, _deepseek_instance(), [], {"model": "m"})
+
+ assert result is response
+ duration_histogram.record.assert_called_once()
+ span.end.assert_called_once()
+
+ @pytest.mark.asyncio
+ async def test_response_without_duration_histogram(self):
+ span, wrapper = _make_async_wrapper(duration_histogram=None)
+ response = MagicMock()
+ wrapped = AsyncMock(return_value=response)
+
+ with patch("opentelemetry.instrumentation.deepseek._handle_response"):
+ with patch("opentelemetry.instrumentation.deepseek.shared_metrics_attributes", return_value={}):
+ result = await wrapper(wrapped, _deepseek_instance(), [], {"model": "m"})
+
+ assert result is response
+ span.end.assert_called_once()
+
+ @pytest.mark.asyncio
+ async def test_async_streaming_exception_sets_error_status(self):
+ """Covers the async streaming path raising and setting ERROR status."""
+ from openai._streaming import AsyncStream
+
+ tracer = MagicMock()
+ span = _span()
+ tracer.start_span.return_value = span
+ response = MagicMock(spec=AsyncStream)
+ wrapped = AsyncMock(return_value=response)
+
+ wrapper = _awrap(tracer, None, None, None, None, {})
+ with patch(
+ "opentelemetry.instrumentation.deepseek._create_async_stream_processor",
+ side_effect=RuntimeError("async stream error"),
+ ):
+ with pytest.raises(RuntimeError, match="async stream error"):
+ await wrapper(wrapped, _deepseek_instance(), [], {"model": "m"})
+
+ span.end.assert_called_once()
+
+ @pytest.mark.asyncio
+ async def test_span_not_recording_after_response_skips_set_status(self):
+ """Covers span.is_recording() False at the final OK status check."""
+ tracer = MagicMock()
+ span = MagicMock()
+ span.is_recording.side_effect = [True, True, False]
+ tracer.start_span.return_value = span
+
+ response = MagicMock()
+ wrapped = AsyncMock(return_value=response)
+
+ with patch("opentelemetry.instrumentation.deepseek._handle_response"):
+ with patch("opentelemetry.instrumentation.deepseek.shared_metrics_attributes", return_value={}):
+ wrapper = _awrap(tracer, None, None, None, None, {})
+ await wrapper(wrapped, _deepseek_instance(), [], {"model": "m"})
+
+ span.set_status.assert_not_called()
+ span.end.assert_called_once()
+
+
+# ---------------------------------------------------------------------------
+# DeepSeekInstrumentor._instrument edge cases
+# ---------------------------------------------------------------------------
+
+
+class TestDeepSeekInstrumentor:
+ def test_metrics_disabled_sets_histograms_to_none(self, tracer_provider, meter_provider):
+ with patch.dict(os.environ, {"TRACELOOP_METRICS_ENABLED": "false"}):
+ instrumentor = DeepSeekInstrumentor()
+ instrumentor.instrument(
+ tracer_provider=tracer_provider,
+ meter_provider=meter_provider,
+ )
+ instrumentor.uninstrument()
+
+ def test_module_not_found_for_sync_wrap_is_swallowed(self, tracer_provider, meter_provider):
+ instrumentor = DeepSeekInstrumentor()
+ with patch(
+ "opentelemetry.instrumentation.deepseek.wrap_function_wrapper",
+ side_effect=ModuleNotFoundError("openai not installed"),
+ ):
+ # Should not raise
+ instrumentor.instrument(
+ tracer_provider=tracer_provider,
+ meter_provider=meter_provider,
+ )
+ instrumentor.uninstrument()
diff --git a/packages/opentelemetry-instrumentation-deepseek/tests/traces/test_span_utils.py b/packages/opentelemetry-instrumentation-deepseek/tests/traces/test_span_utils.py
new file mode 100644
index 0000000000..556aa14743
--- /dev/null
+++ b/packages/opentelemetry-instrumentation-deepseek/tests/traces/test_span_utils.py
@@ -0,0 +1,652 @@
+"""
+Unit tests for span_utils helpers.
+
+Covers:
+ _content_to_parts, _tool_calls_to_parts, and every set_* span-attribute function,
+ including the DeepSeek-R1 `reasoning_content` -> gen_ai.deepseek.reasoning_content attribute.
+All tests use mock spans — no network calls, no cassettes.
+"""
+
+import json
+from unittest.mock import MagicMock
+
+import pytest
+
+from opentelemetry.semconv._incubating.attributes import gen_ai_attributes as GenAIAttributes
+
+from opentelemetry.instrumentation.deepseek.span_utils import (
+ GEN_AI_DEEPSEEK_REASONING_CONTENT,
+ _content_to_parts,
+ _tool_calls_to_parts,
+ set_input_attributes,
+ set_model_input_attributes,
+ set_model_response_attributes,
+ set_model_streaming_response_attributes,
+ set_response_attributes,
+ set_streaming_response_attributes,
+)
+from opentelemetry.instrumentation.deepseek.utils import TRACELOOP_TRACE_CONTENT
+
+
+# ---------------------------------------------------------------------------
+# Helpers
+# ---------------------------------------------------------------------------
+
+
+def _span(recording: bool = True) -> MagicMock:
+ s = MagicMock()
+ s.is_recording.return_value = recording
+ return s
+
+
+def _attr(span: MagicMock, name: str):
+ """Return the value passed to span.set_attribute(name, …)."""
+ for call in span.set_attribute.call_args_list:
+ if call[0][0] == name:
+ return call[0][1]
+ return None
+
+
+# ---------------------------------------------------------------------------
+# _content_to_parts
+# ---------------------------------------------------------------------------
+
+
+class TestContentToParts:
+ def test_none_returns_empty(self):
+ assert _content_to_parts(None) == []
+
+ def test_empty_string_returns_empty(self):
+ assert _content_to_parts("") == []
+
+ def test_plain_string_returns_text_part(self):
+ assert _content_to_parts("hello") == [{"type": "text", "content": "hello"}]
+
+ def test_list_with_text_block(self):
+ content = [{"type": "text", "text": "hello world"}]
+ assert _content_to_parts(content) == [{"type": "text", "content": "hello world"}]
+
+ def test_list_with_image_url_block(self):
+ content = [{"type": "image_url", "image_url": {"url": "https://example.com/img.jpg"}}]
+ assert _content_to_parts(content) == [
+ {"type": "uri", "modality": "image", "uri": "https://example.com/img.jpg"}
+ ]
+
+ def test_list_with_mixed_content(self):
+ content = [
+ {"type": "text", "text": "Describe:"},
+ {"type": "image_url", "image_url": {"url": "https://example.com/cat.jpg"}},
+ ]
+ result = _content_to_parts(content)
+ assert len(result) == 2
+ assert result[0] == {"type": "text", "content": "Describe:"}
+ assert result[1] == {"type": "uri", "modality": "image", "uri": "https://example.com/cat.jpg"}
+
+ def test_list_with_non_dict_items_skipped(self):
+ content = ["not a dict", {"type": "text", "text": "hello"}]
+ assert _content_to_parts(content) == [{"type": "text", "content": "hello"}]
+
+ def test_list_with_unknown_block_type_preserved_as_generic(self):
+ content = [{"type": "audio", "data": "..."}]
+ assert _content_to_parts(content) == [{"type": "audio", "data": "..."}]
+
+ def test_list_with_data_url_image(self):
+ content = [{"type": "image_url", "image_url": {"url": "data:image/png;base64,ABC123"}}]
+ assert _content_to_parts(content) == [
+ {"type": "blob", "modality": "image", "mime_type": "image/png", "content": "ABC123"}
+ ]
+
+
+# ---------------------------------------------------------------------------
+# _tool_calls_to_parts
+# ---------------------------------------------------------------------------
+
+
+class TestToolCallsToParts:
+ def test_none_returns_empty(self):
+ assert _tool_calls_to_parts(None) == []
+
+ def test_empty_list_returns_empty(self):
+ assert _tool_calls_to_parts([]) == []
+
+ def test_string_arguments_parsed_as_json(self):
+ tool_calls = [
+ {
+ "id": "call_123",
+ "type": "function",
+ "function": {"name": "get_weather", "arguments": '{"location": "SF"}'},
+ }
+ ]
+ result = _tool_calls_to_parts(tool_calls)
+ assert result == [
+ {
+ "type": "tool_call",
+ "id": "call_123",
+ "name": "get_weather",
+ "arguments": {"location": "SF"},
+ }
+ ]
+
+ def test_dict_arguments_used_as_is(self):
+ tool_calls = [
+ {
+ "id": "call_456",
+ "type": "function",
+ "function": {"name": "add", "arguments": {"a": 1, "b": 2}},
+ }
+ ]
+ result = _tool_calls_to_parts(tool_calls)
+ assert result[0]["arguments"] == {"a": 1, "b": 2}
+
+ def test_invalid_json_string_returns_raw(self):
+ tool_calls = [
+ {
+ "id": "call_789",
+ "type": "function",
+ "function": {"name": "foo", "arguments": "not valid json {{"},
+ }
+ ]
+ result = _tool_calls_to_parts(tool_calls)
+ assert result[0]["arguments"] == "not valid json {{"
+
+ def test_no_arguments_omits_arguments_key(self):
+ tool_calls = [
+ {
+ "id": "call_000",
+ "type": "function",
+ "function": {"name": "ping"},
+ }
+ ]
+ result = _tool_calls_to_parts(tool_calls)
+ assert "arguments" not in result[0]
+
+ def test_non_dict_items_skipped(self):
+ tool_calls = [
+ "not a dict",
+ {"id": "call_ok", "type": "function", "function": {"name": "foo", "arguments": "{}"}},
+ ]
+ result = _tool_calls_to_parts(tool_calls)
+ assert len(result) == 1
+ assert result[0]["name"] == "foo"
+
+ def test_pydantic_style_object_handled(self):
+ """The OpenAI SDK may return tool_calls as Pydantic objects, not dicts."""
+ fn = MagicMock()
+ fn.name = "get_weather"
+ fn.arguments = '{"city": "Paris"}'
+
+ tc = MagicMock()
+ tc.id = "call_pydantic"
+ tc.function = fn
+
+ result = _tool_calls_to_parts([tc])
+ assert len(result) == 1
+ assert result[0]["id"] == "call_pydantic"
+ assert result[0]["name"] == "get_weather"
+ assert result[0]["arguments"] == {"city": "Paris"}
+
+ def test_multiple_tool_calls(self):
+ tool_calls = [
+ {"id": "call_1", "type": "function", "function": {"name": "func1", "arguments": '{"x": 1}'}},
+ {"id": "call_2", "type": "function", "function": {"name": "func2", "arguments": '{"y": 2}'}},
+ ]
+ result = _tool_calls_to_parts(tool_calls)
+ assert len(result) == 2
+ assert result[0]["name"] == "func1"
+ assert result[1]["name"] == "func2"
+
+
+# ---------------------------------------------------------------------------
+# set_input_attributes
+# ---------------------------------------------------------------------------
+
+
+class TestSetInputAttributes:
+ def test_non_recording_span_returns_early(self):
+ span = _span(recording=False)
+ set_input_attributes(span, {"messages": [{"role": "user", "content": "hello"}]})
+ span.set_attribute.assert_not_called()
+
+ def test_empty_messages_does_not_set_attribute(self):
+ span = _span()
+ set_input_attributes(span, {"messages": []})
+ span.set_attribute.assert_not_called()
+
+ def test_tool_role_creates_tool_call_response_part(self):
+ span = _span()
+ kwargs = {
+ "messages": [
+ {
+ "role": "tool",
+ "tool_call_id": "call_123",
+ "content": "20°C in SF",
+ }
+ ]
+ }
+ set_input_attributes(span, kwargs)
+ value = _attr(span, GenAIAttributes.GEN_AI_INPUT_MESSAGES)
+ assert value is not None
+ messages = json.loads(value)
+ assert messages[0]["role"] == "tool"
+ assert messages[0]["parts"] == [
+ {
+ "type": "tool_call_response",
+ "id": "call_123",
+ "response": "20°C in SF",
+ }
+ ]
+
+ def test_message_with_tool_calls_adds_tool_call_parts(self):
+ span = _span()
+ kwargs = {
+ "messages": [
+ {
+ "role": "assistant",
+ "content": None,
+ "tool_calls": [
+ {
+ "id": "call_456",
+ "type": "function",
+ "function": {"name": "get_weather", "arguments": '{"location": "Paris"}'},
+ }
+ ],
+ }
+ ]
+ }
+ set_input_attributes(span, kwargs)
+ value = _attr(span, GenAIAttributes.GEN_AI_INPUT_MESSAGES)
+ assert value is not None
+ messages = json.loads(value)
+ parts = messages[0]["parts"]
+ assert any(p["type"] == "tool_call" and p["name"] == "get_weather" for p in parts)
+
+
+# ---------------------------------------------------------------------------
+# set_model_input_attributes
+# ---------------------------------------------------------------------------
+
+
+class TestSetModelInputAttributes:
+ def test_non_recording_span_returns_early(self):
+ span = _span(recording=False)
+ set_model_input_attributes(span, {"model": "deepseek-chat"})
+ span.set_attribute.assert_not_called()
+
+ def test_with_tools_sets_tool_definitions(self):
+ span = _span()
+ tools = [{"type": "function", "function": {"name": "ping", "description": "Ping"}}]
+ set_model_input_attributes(span, {"model": "deepseek-chat", "tools": tools})
+ value = _attr(span, GenAIAttributes.GEN_AI_TOOL_DEFINITIONS)
+ assert value is not None
+ assert json.loads(value) == tools
+
+ def test_unserializable_tools_silently_ignored(self):
+ span = _span()
+ # object() is not JSON-serializable — should not raise
+ set_model_input_attributes(span, {"model": "deepseek-chat", "tools": [object()]})
+ # GEN_AI_TOOL_DEFINITIONS must NOT be set (exception swallowed)
+ assert _attr(span, GenAIAttributes.GEN_AI_TOOL_DEFINITIONS) is None
+
+ def test_tools_not_set_when_send_prompts_disabled(self):
+ """Tool definitions are Opt-In — must NOT be recorded when content tracing is off."""
+ span = _span()
+ tools = [{"type": "function", "function": {"name": "ping"}}]
+ with pytest.MonkeyPatch().context() as mp:
+ mp.setenv(TRACELOOP_TRACE_CONTENT, "False")
+ set_model_input_attributes(span, {"model": "deepseek-chat", "tools": tools})
+ assert _attr(span, GenAIAttributes.GEN_AI_TOOL_DEFINITIONS) is None
+
+
+# ---------------------------------------------------------------------------
+# set_streaming_response_attributes
+# ---------------------------------------------------------------------------
+
+
+class TestSetStreamingResponseAttributes:
+ def test_non_recording_span_returns_early(self):
+ span = _span(recording=False)
+ set_streaming_response_attributes(span, "some content")
+ span.set_attribute.assert_not_called()
+
+ def test_empty_content_produces_empty_parts(self):
+ span = _span()
+ set_streaming_response_attributes(span, "", finish_reason="stop")
+ value = _attr(span, GenAIAttributes.GEN_AI_OUTPUT_MESSAGES)
+ messages = json.loads(value)
+ assert messages[0]["parts"] == []
+ assert messages[0]["finish_reason"] == "stop"
+
+ def test_with_content_produces_text_part(self):
+ span = _span()
+ set_streaming_response_attributes(span, "Hello!", finish_reason="stop")
+ value = _attr(span, GenAIAttributes.GEN_AI_OUTPUT_MESSAGES)
+ messages = json.loads(value)
+ assert messages[0]["parts"] == [{"type": "text", "content": "Hello!"}]
+ assert messages[0]["role"] == "assistant"
+
+ def test_with_tool_calls_adds_tool_call_parts(self):
+ span = _span()
+ tool_calls = [{"id": "call_1", "function": {"name": "get_weather", "arguments": '{"city": "Paris"}'}}]
+ set_streaming_response_attributes(span, "", finish_reason="tool_calls", tool_calls=tool_calls)
+ value = _attr(span, GenAIAttributes.GEN_AI_OUTPUT_MESSAGES)
+ messages = json.loads(value)
+ parts = messages[0]["parts"]
+ assert any(p["type"] == "tool_call" and p["name"] == "get_weather" for p in parts)
+ assert messages[0]["finish_reason"] == "tool_call"
+
+ def test_accumulated_reasoning_sets_reasoning_content_attribute(self):
+ """DeepSeek-R1 streaming: accumulated reasoning_content -> gen_ai.deepseek.reasoning_content."""
+ span = _span()
+ set_streaming_response_attributes(
+ span,
+ "The answer is 42.",
+ finish_reason="stop",
+ accumulated_reasoning="Let me think step by step...",
+ )
+ assert _attr(span, GEN_AI_DEEPSEEK_REASONING_CONTENT) == "Let me think step by step..."
+
+ def test_no_reasoning_does_not_set_reasoning_content_attribute(self):
+ span = _span()
+ set_streaming_response_attributes(span, "Hi there!", finish_reason="stop")
+ attr_names = {c[0][0] for c in span.set_attribute.call_args_list}
+ assert GEN_AI_DEEPSEEK_REASONING_CONTENT not in attr_names
+
+
+# ---------------------------------------------------------------------------
+# set_model_streaming_response_attributes
+# ---------------------------------------------------------------------------
+
+
+class TestSetModelStreamingResponseAttributes:
+ def test_non_recording_span_returns_early(self):
+ span = _span(recording=False)
+ usage = MagicMock()
+ set_model_streaming_response_attributes(span, usage)
+ span.set_attribute.assert_not_called()
+
+ def test_with_usage_sets_token_counts(self):
+ span = _span()
+ usage = MagicMock()
+ usage.completion_tokens = 25
+ usage.prompt_tokens = 55
+ usage.total_tokens = 80
+ set_model_streaming_response_attributes(span, usage)
+ assert _attr(span, GenAIAttributes.GEN_AI_USAGE_OUTPUT_TOKENS) == 25
+ assert _attr(span, GenAIAttributes.GEN_AI_USAGE_INPUT_TOKENS) == 55
+
+ def test_none_usage_skips_token_counts(self):
+ span = _span()
+ set_model_streaming_response_attributes(span, None)
+ set_keys = [c[0][0] for c in span.set_attribute.call_args_list]
+ assert GenAIAttributes.GEN_AI_USAGE_INPUT_TOKENS not in set_keys
+
+ def test_with_finish_reason_sets_mapped_reason(self):
+ span = _span()
+ set_model_streaming_response_attributes(span, None, finish_reasons=["stop"])
+ assert _attr(span, GenAIAttributes.GEN_AI_RESPONSE_FINISH_REASONS) == ["stop"]
+
+ def test_none_finish_reason_skips_finish_reasons(self):
+ span = _span()
+ set_model_streaming_response_attributes(span, None, finish_reasons=None)
+ set_keys = [c[0][0] for c in span.set_attribute.call_args_list]
+ assert GenAIAttributes.GEN_AI_RESPONSE_FINISH_REASONS not in set_keys
+
+ def test_unknown_finish_reason_is_preserved(self):
+ span = _span()
+ set_model_streaming_response_attributes(span, None, finish_reasons=["unknown_reason_xyz"])
+ assert _attr(span, GenAIAttributes.GEN_AI_RESPONSE_FINISH_REASONS) == ["unknown_reason_xyz"]
+
+
+# ---------------------------------------------------------------------------
+# set_model_response_attributes
+# ---------------------------------------------------------------------------
+
+
+class TestSetModelResponseAttributes:
+ """
+ Uses plain dicts as response so model_as_dict() returns them unchanged.
+ _collect_finish_reasons_from_response() uses getattr(response, "choices", None)
+ which returns None for dicts → reasons = [] (covers the 'if reasons:' False branch).
+ """
+
+ def _response(self, prompt_tokens=18, completion_tokens=5):
+ return {
+ "id": "chatcmpl-test",
+ "model": "deepseek-chat",
+ "usage": {
+ "prompt_tokens": prompt_tokens,
+ "completion_tokens": completion_tokens,
+ "total_tokens": prompt_tokens + completion_tokens,
+ },
+ }
+
+ def test_non_recording_span_returns_early(self):
+ span = _span(recording=False)
+ histogram = MagicMock()
+ set_model_response_attributes(span, self._response(), histogram)
+ span.set_attribute.assert_not_called()
+ histogram.record.assert_not_called()
+
+ def test_response_without_usage_skips_histogram(self):
+ span = _span()
+ histogram = MagicMock()
+ response = {"id": "chatcmpl-test", "model": "deepseek-chat"}
+ set_model_response_attributes(span, response, histogram)
+ histogram.record.assert_not_called()
+
+ def test_none_histogram_does_not_raise(self):
+ span = _span()
+ set_model_response_attributes(span, self._response(), None)
+ # Tokens are still set on span, just not recorded in histogram
+ assert _attr(span, GenAIAttributes.GEN_AI_USAGE_INPUT_TOKENS) == 18
+
+ def test_histogram_records_input_and_output_tokens(self):
+ span = _span()
+ histogram = MagicMock()
+ set_model_response_attributes(span, self._response(prompt_tokens=10, completion_tokens=5), histogram)
+ assert histogram.record.call_count == 2
+ first = histogram.record.call_args_list[0]
+ assert first[0][0] == 10
+ assert first[1]["attributes"]["gen_ai.token.type"] == "input"
+ second = histogram.record.call_args_list[1]
+ assert second[0][0] == 5
+ assert second[1]["attributes"]["gen_ai.token.type"] == "output"
+
+
+# ---------------------------------------------------------------------------
+# set_response_attributes
+# ---------------------------------------------------------------------------
+
+
+class TestSetResponseAttributes:
+ def test_non_recording_span_returns_early(self):
+ span = _span(recording=False)
+ response = {"choices": [{"message": {"role": "assistant", "content": "hi"}, "finish_reason": "stop"}]}
+ set_response_attributes(span, response)
+ span.set_attribute.assert_not_called()
+
+ def test_empty_choices_does_not_set_attribute(self):
+ span = _span()
+ set_response_attributes(span, {"choices": []})
+ span.set_attribute.assert_not_called()
+
+ def test_with_tool_calls_in_response(self):
+ span = _span()
+ response = {
+ "choices": [
+ {
+ "index": 0,
+ "finish_reason": "tool_calls",
+ "message": {
+ "role": "assistant",
+ "content": None,
+ "tool_calls": [
+ {
+ "id": "call_123",
+ "type": "function",
+ "function": {"name": "get_weather", "arguments": '{"location": "SF"}'},
+ }
+ ],
+ },
+ }
+ ]
+ }
+ set_response_attributes(span, response)
+ value = _attr(span, GenAIAttributes.GEN_AI_OUTPUT_MESSAGES)
+ messages = json.loads(value)
+ assert messages[0]["finish_reason"] == "tool_call"
+ parts = messages[0]["parts"]
+ assert any(p["type"] == "tool_call" and p["name"] == "get_weather" for p in parts)
+
+ def test_with_content_filter(self):
+ span = _span()
+ response = {
+ "choices": [
+ {
+ "index": 0,
+ "finish_reason": "content_filter",
+ "message": {"role": "assistant", "content": "..."},
+ }
+ ]
+ }
+ set_response_attributes(span, response)
+ value = _attr(span, GenAIAttributes.GEN_AI_OUTPUT_MESSAGES)
+ messages = json.loads(value)
+ assert messages[0]["finish_reason"] == "content_filter"
+ assert messages[0]["parts"] == [{"type": "text", "content": "..."}]
+
+ def test_with_legacy_function_call(self):
+ span = _span()
+ response = {
+ "choices": [
+ {
+ "index": 0,
+ "finish_reason": "stop",
+ "message": {
+ "role": "assistant",
+ "content": None,
+ "function_call": {
+ "name": "get_weather",
+ "arguments": '{"location": "London"}',
+ },
+ },
+ }
+ ]
+ }
+ set_response_attributes(span, response)
+ value = _attr(span, GenAIAttributes.GEN_AI_OUTPUT_MESSAGES)
+ messages = json.loads(value)
+ parts = messages[0]["parts"]
+ assert any(p["type"] == "tool_call" and p["name"] == "get_weather" for p in parts)
+
+ def test_legacy_function_call_with_invalid_json_arguments(self):
+ span = _span()
+ response = {
+ "choices": [
+ {
+ "index": 0,
+ "finish_reason": "stop",
+ "message": {
+ "role": "assistant",
+ "content": None,
+ "function_call": {"name": "foo", "arguments": "not valid json {{"},
+ },
+ }
+ ]
+ }
+ set_response_attributes(span, response)
+ value = _attr(span, GenAIAttributes.GEN_AI_OUTPUT_MESSAGES)
+ messages = json.loads(value)
+ part = next(p for p in messages[0]["parts"] if p["type"] == "tool_call")
+ assert part["arguments"] == "not valid json {{"
+
+ def test_legacy_function_call_with_dict_arguments(self):
+ span = _span()
+ response = {
+ "choices": [
+ {
+ "index": 0,
+ "finish_reason": "stop",
+ "message": {
+ "role": "assistant",
+ "content": None,
+ "function_call": {"name": "bar", "arguments": {"x": 42}},
+ },
+ }
+ ]
+ }
+ set_response_attributes(span, response)
+ value = _attr(span, GenAIAttributes.GEN_AI_OUTPUT_MESSAGES)
+ messages = json.loads(value)
+ part = next(p for p in messages[0]["parts"] if p["type"] == "tool_call")
+ assert part["arguments"] == {"x": 42}
+
+ def test_no_prompts_does_not_set_attribute(self):
+ span = _span()
+ response = {"choices": [{"message": {"role": "assistant", "content": "hi"}, "finish_reason": "stop"}]}
+ with pytest.MonkeyPatch().context() as mp:
+ mp.setenv(TRACELOOP_TRACE_CONTENT, "False")
+ set_response_attributes(span, response)
+ span.set_attribute.assert_not_called()
+
+ def test_reasoning_content_present_for_deepseek_reasoner(self):
+ """deepseek-reasoner (R1) responses include reasoning_content -> span attribute is set."""
+ span = _span()
+ response = {
+ "choices": [
+ {
+ "index": 0,
+ "finish_reason": "stop",
+ "message": {
+ "role": "assistant",
+ "content": "The answer is 42.",
+ "reasoning_content": "Let me think step by step...",
+ },
+ }
+ ]
+ }
+ set_response_attributes(span, response)
+ assert _attr(span, GEN_AI_DEEPSEEK_REASONING_CONTENT) == "Let me think step by step..."
+
+ def test_reasoning_content_absent_for_deepseek_chat(self):
+ """deepseek-chat responses have no reasoning_content -> attribute is not set."""
+ span = _span()
+ response = {
+ "choices": [
+ {
+ "index": 0,
+ "finish_reason": "stop",
+ "message": {"role": "assistant", "content": "Hi there!"},
+ }
+ ]
+ }
+ set_response_attributes(span, response)
+ attr_names = {c[0][0] for c in span.set_attribute.call_args_list}
+ assert GEN_AI_DEEPSEEK_REASONING_CONTENT not in attr_names
+
+ def test_reasoning_content_joined_across_multiple_choices(self):
+ span = _span()
+ response = {
+ "choices": [
+ {
+ "index": 0,
+ "finish_reason": "stop",
+ "message": {
+ "role": "assistant",
+ "content": "Answer A",
+ "reasoning_content": "Reasoning A",
+ },
+ },
+ {
+ "index": 1,
+ "finish_reason": "stop",
+ "message": {
+ "role": "assistant",
+ "content": "Answer B",
+ "reasoning_content": "Reasoning B",
+ },
+ },
+ ]
+ }
+ set_response_attributes(span, response)
+ assert _attr(span, GEN_AI_DEEPSEEK_REASONING_CONTENT) == "Reasoning A\n---\nReasoning B"
diff --git a/packages/opentelemetry-instrumentation-deepseek/tests/traces/test_utils.py b/packages/opentelemetry-instrumentation-deepseek/tests/traces/test_utils.py
new file mode 100644
index 0000000000..835881b3f6
--- /dev/null
+++ b/packages/opentelemetry-instrumentation-deepseek/tests/traces/test_utils.py
@@ -0,0 +1,99 @@
+"""
+Unit tests for utils helpers.
+
+Covers:
+ dont_throw exception handler, error_metrics_attributes, model_as_dict edge cases.
+All tests are pure unit tests — no network calls, no cassettes.
+"""
+
+from unittest.mock import MagicMock, patch
+
+from opentelemetry.instrumentation.deepseek.config import Config
+from opentelemetry.instrumentation.deepseek.utils import (
+ dont_throw,
+ error_metrics_attributes,
+ model_as_dict,
+)
+from opentelemetry.semconv._incubating.attributes import gen_ai_attributes as GenAIAttributes
+
+
+# ---------------------------------------------------------------------------
+# dont_throw
+# ---------------------------------------------------------------------------
+
+
+class TestDontThrow:
+ def test_exception_logger_called_when_set(self):
+ callback = MagicMock()
+ original = Config.exception_logger
+ Config.exception_logger = callback
+
+ @dont_throw
+ def failing():
+ raise ValueError("boom")
+
+ try:
+ failing() # must not raise
+ finally:
+ Config.exception_logger = original
+
+ callback.assert_called_once()
+ assert isinstance(callback.call_args[0][0], ValueError)
+
+ def test_exception_not_propagated_without_logger(self):
+ @dont_throw
+ def failing():
+ raise RuntimeError("silent")
+
+ # Should return None silently
+ result = failing()
+ assert result is None
+
+
+# ---------------------------------------------------------------------------
+# error_metrics_attributes
+# ---------------------------------------------------------------------------
+
+
+class TestErrorMetricsAttributes:
+ def test_returns_provider_name_and_error_type(self):
+ result = error_metrics_attributes(ValueError("oops"))
+ assert result[GenAIAttributes.GEN_AI_PROVIDER_NAME] == "deepseek"
+ assert result["error.type"] == "ValueError"
+
+
+# ---------------------------------------------------------------------------
+# model_as_dict
+# ---------------------------------------------------------------------------
+
+
+class TestModelAsDict:
+ def test_pydantic_v2_model_uses_model_dump(self):
+ model = MagicMock()
+ model.model_dump.return_value = {"key": "value"}
+ # hasattr returns True for MagicMock by default
+ result = model_as_dict(model)
+ assert result == {"key": "value"}
+ model.model_dump.assert_called_once()
+
+ def test_pydantic_v1_model_uses_dict(self):
+ model = MagicMock(spec=["dict"]) # only has .dict(), no .model_dump
+ model.dict.return_value = {"key": "v1"}
+ with patch("opentelemetry.instrumentation.deepseek.utils._PYDANTIC_VERSION", "1.9.0"):
+ result = model_as_dict(model)
+ assert result == {"key": "v1"}
+
+ def test_raw_api_response_with_parse_method(self):
+ # Simulate a raw API response that has .parse() but no .model_dump
+ inner = {"parsed": True}
+
+ class RawResponse:
+ def parse(self):
+ return inner
+
+ result = model_as_dict(RawResponse())
+ assert result == {"parsed": True}
+
+ def test_plain_dict_returned_as_is(self):
+ data = {"a": 1, "b": 2}
+ assert model_as_dict(data) is data
diff --git a/packages/opentelemetry-instrumentation-deepseek/uv.lock b/packages/opentelemetry-instrumentation-deepseek/uv.lock
new file mode 100644
index 0000000000..6dd8e8ad43
--- /dev/null
+++ b/packages/opentelemetry-instrumentation-deepseek/uv.lock
@@ -0,0 +1,905 @@
+version = 1
+revision = 3
+requires-python = ">=3.10, <4"
+
+[manifest]
+constraints = [
+ { name = "pip", specifier = ">=25.3" },
+ { name = "urllib3", specifier = ">=2.6.3" },
+]
+
+[[package]]
+name = "annotated-types"
+version = "0.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
+]
+
+[[package]]
+name = "anyio"
+version = "4.13.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
+ { name = "idna" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
+]
+
+[[package]]
+name = "autopep8"
+version = "2.3.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pycodestyle" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/50/d8/30873d2b7b57dee9263e53d142da044c4600a46f2d28374b3e38b023df16/autopep8-2.3.2.tar.gz", hash = "sha256:89440a4f969197b69a995e4ce0661b031f455a9f776d2c5ba3dbd83466931758", size = 92210, upload-time = "2025-01-14T14:46:18.454Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9e/43/53afb8ba17218f19b77c7834128566c5bbb100a0ad9ba2e8e89d089d7079/autopep8-2.3.2-py2.py3-none-any.whl", hash = "sha256:ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128", size = 45807, upload-time = "2025-01-14T14:46:15.466Z" },
+]
+
+[[package]]
+name = "certifi"
+version = "2026.5.20"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" },
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
+]
+
+[[package]]
+name = "distro"
+version = "1.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
+]
+
+[[package]]
+name = "h11"
+version = "0.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
+]
+
+[[package]]
+name = "httpcore"
+version = "1.0.9"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "h11" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
+]
+
+[[package]]
+name = "httpx"
+version = "0.28.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "certifi" },
+ { name = "httpcore" },
+ { name = "idna" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
+]
+
+[[package]]
+name = "idna"
+version = "3.18"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
+]
+
+[[package]]
+name = "jiter"
+version = "0.15.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/66/b5/55f06bb281d92fb3cc86d14e1def2bd908bb77693183e7cb1f5a3c388b0c/jiter-0.15.0.tar.gz", hash = "sha256:4251acc80e2b7c9b7b8823456ea0fceeb0734dac2df7636d3c711b38476b5a76", size = 166640, upload-time = "2026-05-19T10:09:48.361Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1d/da/76a2c7e510ba15fe323d9509c223ab272da79ea59f54488f4a78da6426db/jiter-0.15.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:edebcf7d1f601199084bb6e844d7dc67e03e04f6ac786b0332d616635c4ff7a4", size = 310849, upload-time = "2026-05-19T10:06:51.944Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/8e/827be942883a4dc0862c48626ff41af3320b1902d136a0bf4b9041f2c567/jiter-0.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f924585cdacf631cd382b657966847bb537bf9ed0a6f9b991da5f05a631480f", size = 314991, upload-time = "2026-05-19T10:06:53.522Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/38/be2832be361ba1b9517c76f46d30b64e985be1dd43c974f4c3a4b1844436/jiter-0.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abbf258599526ad0326fe51e252e24f2bd6f24f1852681b4b78feda3808f1d18", size = 340843, upload-time = "2026-05-19T10:06:55.071Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/d8/90f01fb83c0c7ba509303ec93e32a308fbfa167d264860b01c0fd0dbbd06/jiter-0.15.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c468136b8bd6bb18c8786e4236a1fa27362f24cb23450ba0cb204ab379b8e6f", size = 365116, upload-time = "2026-05-19T10:06:56.893Z" },
+ { url = "https://files.pythonhosted.org/packages/91/38/94593d34f8c67a0b6f6cbc027f016ffa9780b3a858a7a86f6fd7a15bcc1e/jiter-0.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05906b93d72f03339e6bb7cf8dc10ebda64a0266126eed6beba79e20abcf5fd4", size = 457970, upload-time = "2026-05-19T10:06:58.707Z" },
+ { url = "https://files.pythonhosted.org/packages/df/04/d79962dd49d00c97e2a9b4cacea1947904d02135936960351f9a96d4c1a6/jiter-0.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30ce785d2adb8e32c3f7741442370a74834ec4c01f3c48f0750227a0b4ef27d6", size = 375744, upload-time = "2026-05-19T10:07:00.471Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/2e/5d37abe2be0e819c21e2338bebd410e481763ce526a9138c8c3652fa0123/jiter-0.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fd73e3da91a0a722d67165e849ce2cdc10de0e0d48738c142be8c6c5f310f4c", size = 349609, upload-time = "2026-05-19T10:07:01.829Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/90/98768ad2ed90c1fda15d64157de2dfbf73c1c074d4b1bfaca915480bc7cf/jiter-0.15.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:ceb8fc27d38793f9c97149be8302720c5b22e5c195a37bf2c45dc36c4600a512", size = 354366, upload-time = "2026-05-19T10:07:03.587Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/c4/fbfb806209f1fe4b7dccdfb07bc62bb044300734a945b06fd64db446ef6a/jiter-0.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d726e3ceeb337191324b49de298142f27c3ad10886341555d1d5315b5f252c6a", size = 393519, upload-time = "2026-05-19T10:07:05.08Z" },
+ { url = "https://files.pythonhosted.org/packages/37/1c/b9c257cd70cb453b6d10f3ebf0402cdb11669ab455389096f09839670290/jiter-0.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2c8aea7781d2a372227871de4e1a1332aa96f5a89fd76c5e835dafdbad102887", size = 519952, upload-time = "2026-05-19T10:07:06.589Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/1a/aa85027db7ab15829c12feebbc33b404f53fc399bd559d85fd0d6365ff0d/jiter-0.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cf4bd113a69c0a740e27cb962ce10630c36d2b8f59d759a651b955ee9d18a823", size = 550770, upload-time = "2026-05-19T10:07:08.228Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/54/8c3f65c8a5687925e84708f19d63f7f37d28e2b86a48d951702ad94424d8/jiter-0.15.0-cp310-cp310-win32.whl", hash = "sha256:d92a5cd21fdb083931d546c207aa29633787c5dc5b02daab2d32b843f88a2c53", size = 209303, upload-time = "2026-05-19T10:07:10.006Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/72/0528a1eb9f42dd2d8228a0711458628f35924d131f623eaebc35fd23d3d4/jiter-0.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:e58585a58209d72691ce2d62a9147445f5a87beb0bde97fde284c96ae392a3d1", size = 200404, upload-time = "2026-05-19T10:07:11.426Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/13/daa722f5765c393576f466378f9dfd29d77c9bed939e0688f96afa3601ea/jiter-0.15.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0f862193b8696249d22ec433e85fd2ab0ad9596bc3e45e6c0bc55e8aeba97be2", size = 310899, upload-time = "2026-05-19T10:07:12.89Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/82/2d2551829b082f4b6d82b9f939b031fb808a10aab1ec0664f82e150bb9a2/jiter-0.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1303d4d68a9b051ea90502402063ecf3807da00ad2affa19ca1ae3b90b3c5f67", size = 314963, upload-time = "2026-05-19T10:07:14.539Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/0a/8b1a51466f7fe9f31dbe4bc7e0ca848674f9825e0f737b929b97e8c60aa7/jiter-0.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:392b8ab019e5502d08aff85c6272209c24bc2cbe706ea82a56368f524236614a", size = 341730, upload-time = "2026-05-19T10:07:15.869Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/2a/e71dea19822e2e404e83992a08c1d6b9b617bb944f28c9c2fbd85d02c91e/jiter-0.15.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:773b6eb282ce11ee19f05f6b2d4404fa308e5bbd353b0b80a0262caad6db2cd7", size = 366214, upload-time = "2026-05-19T10:07:17.259Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/59/97e1fa539d124a509a00ab7f669289d1c1d236ecabf12948a18f16c91082/jiter-0.15.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2c0c44d569ce0f2850f5c926f8caeb5f245fbc84475aeb36efccc2103e6dbd", size = 459527, upload-time = "2026-05-19T10:07:18.741Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/7a/4a68d331aef8cf2e2393c14a3aacb635c62aa86071b0229899fb5baaa907/jiter-0.15.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:032396229564bca02440396bd327710719f724f5e7b7e9f7a8eb3faa4a2c2281", size = 375451, upload-time = "2026-05-19T10:07:20.208Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/7e/1c445c2b6f0e30a274dc8082e0c3c7825411cce80d726bccd697c98cc8d3/jiter-0.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3d37768fce7f88dd2a8c6091f2325dea27d30d30d5c6e7a1c0f0af77723b708", size = 349428, upload-time = "2026-05-19T10:07:22.372Z" },
+ { url = "https://files.pythonhosted.org/packages/00/94/e20d38984fc17a636371bffd2ae0f698124fdc8e75ef969cd2da6ba7cea7/jiter-0.15.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:2c9cb907439d20bd0c7d7565ca01ee52234203208433749bae5b516907526928", size = 355405, upload-time = "2026-05-19T10:07:23.916Z" },
+ { url = "https://files.pythonhosted.org/packages/94/fa/4d09f814779d0ea80a28ed8e4c6662ec9a4a8ecef0ac52190ebac6262d14/jiter-0.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9100ddbec09741cc66feb0fc6773f8bdbd0e3c345689368f260082ff85dcc0cd", size = 393688, upload-time = "2026-05-19T10:07:25.854Z" },
+ { url = "https://files.pythonhosted.org/packages/54/9d/8eb5d4fb8bf7e93a75964a5da71a75c67c864baf7fa3f98598187b3c7e57/jiter-0.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ae1b0d82ac2d987f9ea512b1c9adfcc71a28de3dea3a6039b54d76cffda9901e", size = 520853, upload-time = "2026-05-19T10:07:27.303Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/2c/5e07874e59e623a943a0acf1552a80d05b70f31b402287a8fc6d7ec634c7/jiter-0.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8020c99ec13a7db2b6f96cbe82ef4721c88b426a4892f27478044af0284615ef", size = 551016, upload-time = "2026-05-19T10:07:28.846Z" },
+ { url = "https://files.pythonhosted.org/packages/22/ed/d2d34422143474cadc15b60d482b1c35683dbc5c63c24346ddd0df09bcaf/jiter-0.15.0-cp311-cp311-win32.whl", hash = "sha256:42bfb257930800cf43e7c62c832402c704ab60797c992faf88d20e903eac8f32", size = 209518, upload-time = "2026-05-19T10:07:30.431Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/7d/52778b930e5cc3e52a37d950b1c10494244308b4329b25a0ff0d88303a81/jiter-0.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:860a74063284a2ae9bfedd694f299cc2c68e2696c5f3d440cc9d18bb81b9dd04", size = 200565, upload-time = "2026-05-19T10:07:32.125Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/4f/d9b4067feb69b3fa6eb0488e1b59e2ad5b463fe39f59e527eab2aca00bb0/jiter-0.15.0-cp311-cp311-win_arm64.whl", hash = "sha256:37a10c377ce3a4a85f4a67f28b7afe093154cde77eaf248a72e856aa08b4d865", size = 195488, upload-time = "2026-05-19T10:07:33.846Z" },
+ { url = "https://files.pythonhosted.org/packages/44/53/4f6bddbcde3c71e56d0aa1337ec95950f3d27dd4153e25aadf0feac71751/jiter-0.15.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0e90a1c315a0226ec822d973817967f9223b7701546c8c2a7913e7ab0926294d", size = 308793, upload-time = "2026-05-19T10:07:35.25Z" },
+ { url = "https://files.pythonhosted.org/packages/01/84/c01099b59a285a1ebba64ae93f62bfa036675340fd1b0045ae65890a0442/jiter-0.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8c9004af7c8d67cce7f1aae1026fb55607f4aa600710d08ede3a3ce4aeefe7e0", size = 309570, upload-time = "2026-05-19T10:07:36.919Z" },
+ { url = "https://files.pythonhosted.org/packages/58/64/8fb7f9d45bb98190355454cd04dad8d8f27223d6bd52f83af07f637168a6/jiter-0.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c210f8b35dc6f30aafd4b4365ca89b9d1189f21ab49b8e68fa6322a847aef138", size = 336783, upload-time = "2026-05-19T10:07:38.694Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/b6/f5739011d009b3a30f6a53c5240979030ba29ae46a8c67e3a15759f7c37d/jiter-0.15.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f30bae8bc1c2d613e28e5af3e8cceb09b742f1c8a8a5f839fb67afaffc03b61", size = 363555, upload-time = "2026-05-19T10:07:40.832Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/12/98a9d9f766665e8a3b6252454e17cb0c464606a28cf2fa09399b003345fa/jiter-0.15.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c60e71b6d10cfc284c9bf36bd885e8d44c46f688ce50aa91b5edd90181dea687", size = 452255, upload-time = "2026-05-19T10:07:42.62Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/d5/60f972840f79c5e7544fce567c56f1e4e50468f996baba3e78d823dd62a6/jiter-0.15.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ab068bce62a45aa3e7367eceaffb5dde60b7eb853be8dece45132e3d0ff4879", size = 373559, upload-time = "2026-05-19T10:07:44.201Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/cf/d46ef1234ba335aabc2f013210db8e0821a22f5e644a2e9449df199ecc23/jiter-0.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa248c9eb220197d363f688818dac2fd4b2f0cd7d843ca7105d652034823427d", size = 346055, upload-time = "2026-05-19T10:07:46.005Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/63/4d2749d8d54d230bad9b3a6b0d00cc28c6ff6b2fdffc26a8ccf76cc5a974/jiter-0.15.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2a77aadd57cac1682e4401a72724d2796d89a4ba129b1a5812aa94ee480826eb", size = 351406, upload-time = "2026-05-19T10:07:47.855Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/b9/9965b990035d8773328e0a8c8b457a87bf2b19f6c4126d9d99296be5d16a/jiter-0.15.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2ae901f3a55bfafdde31d289590fa25e3245735a2b1e8c7cc15871710a002871", size = 389357, upload-time = "2026-05-19T10:07:49.665Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/55/9ddf903deda1413e87fed792f416b7123daee5b8efbad6a202a7421c36a5/jiter-0.15.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f0b271b462769543716f92d3a4f90527df6ef5ed05ee95ec4137f513e21e1b77", size = 517263, upload-time = "2026-05-19T10:07:51.537Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/76/a0c40ad064d3a20a4fde231e35d56e9a01ce82164278180e82d5daf85469/jiter-0.15.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2fb6a5d26af81fc0f00f9360a891e05cf755e149bba391c4d563adc54812973d", size = 548646, upload-time = "2026-05-19T10:07:53.196Z" },
+ { url = "https://files.pythonhosted.org/packages/23/4f/eca9b954942916ba2f453891b8593ab444cd872396fe66a3936616f236f3/jiter-0.15.0-cp312-cp312-win32.whl", hash = "sha256:c2f6bb8b5216ab9e7873bc08b5d7bef2b8abbb578a3069bf1cd14a45d71d771d", size = 206427, upload-time = "2026-05-19T10:07:55.307Z" },
+ { url = "https://files.pythonhosted.org/packages/95/bf/8ead82a87495149542748e828d153fd232a512a22c83b02c4815c1a9c7d8/jiter-0.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:40b2c7e92c44a84d748d21706c68dc6ff8161d80b59c99d774721a0d2317d7c7", size = 197300, upload-time = "2026-05-19T10:07:56.651Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/e4/9b8a78fb2d894471bc344e37f1949bdd784bd914d031dba0ba3a40c71dd7/jiter-0.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:cc0bc345cf2df9d1c00ac443f50d543c1ccfa8b0422cb85b1ab70d681c0b255b", size = 192702, upload-time = "2026-05-19T10:07:58.307Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/f4/f708c900ecee41b2025ef8413d5351e5649eb2125c506f6720cc69b06f5c/jiter-0.15.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1c11465f97e2abf45a014b83b730222f8f1c5335e802c7055a67d50de6f1f4e3", size = 307829, upload-time = "2026-05-19T10:07:59.704Z" },
+ { url = "https://files.pythonhosted.org/packages/86/59/db537c0949e83668c38481d426b9f2fd5ab758c4ee53a811dd0a510626a0/jiter-0.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e7b1776f0797956c509e123d0952d10d293a9492dea9f288ab9570ec01d1a5", size = 308445, upload-time = "2026-05-19T10:08:01.184Z" },
+ { url = "https://files.pythonhosted.org/packages/37/38/ea0e13b18c30ef951da0d47d39e7fa9edb82a93a62990ffbd7cea9b622d4/jiter-0.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:351a341c2105aa430b7047e30f1bf7975f6313b00165d3fc07be2edaf741f279", size = 336181, upload-time = "2026-05-19T10:08:02.688Z" },
+ { url = "https://files.pythonhosted.org/packages/58/fc/2303901b16c4ba05865588990a420c0b4156270b44379c20931544a1d962/jiter-0.15.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ab395feec8d249ec4044e228e98a7033f043426a265df439dc3698823f0a4e4", size = 362985, upload-time = "2026-05-19T10:08:04.394Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/6f/11bace093c52e7d4d26c8e606ccd7ae8c972189622469ec0d9e28161e28b/jiter-0.15.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2a438005b6f22d0273413484d6094d7c2c5d10ec1b3a3bf128e0d1d3ba53258", size = 453292, upload-time = "2026-05-19T10:08:05.967Z" },
+ { url = "https://files.pythonhosted.org/packages/22/db/987f2f086ca4d7a6582eb4ccd513f9b26b42d9e4243a087609a3137a8fc7/jiter-0.15.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f18f85e4218d1b40f000f42a92239a7a61a902cd42c65e6c360dbd17dcb20894", size = 373501, upload-time = "2026-05-19T10:08:07.857Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/7c/89fbcabb2739b7a5b8dc959a1b6c5761f6484f5fed3486854b3c789bb1de/jiter-0.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1aa62e277fc1cbd80e6deacae6f4d983b41b3d7728e0645c5d741a6149bba45", size = 344683, upload-time = "2026-05-19T10:08:09.431Z" },
+ { url = "https://files.pythonhosted.org/packages/30/6f/6cca7692e7dddfec6d8d76c54dc97f2af2a41df4ac0674b999df1f09a5f3/jiter-0.15.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:6550fa135c7deb8ead6af49ed7ff648532ea8334a1447fe34a36315ef79c5c29", size = 350892, upload-time = "2026-05-19T10:08:11.352Z" },
+ { url = "https://files.pythonhosted.org/packages/39/14/0338d6190cb8e6d22e677ab1d4eabd4117f67cca70c54cd04b82ff64e068/jiter-0.15.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:066f8f33f18b2419cd8213b2436fa7fbc9c499f315971cfa3ce1f9820c001b1b", size = 388723, upload-time = "2026-05-19T10:08:12.912Z" },
+ { url = "https://files.pythonhosted.org/packages/90/31/cc19f4a1bdb6afb09ce6a2f2615aa8d44d994eba0d8e6105ed1af920e736/jiter-0.15.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:75e8a04e91432dde9f1838373cf93d23726c79d3e908d319acf0e796f85592e7", size = 516648, upload-time = "2026-05-19T10:08:14.808Z" },
+ { url = "https://files.pythonhosted.org/packages/49/9f/833c541512cd091b63c10c0381973dfe11bc7a503a818c16384417e0c81e/jiter-0.15.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a97261f1fccb8e50ecd2890a96e46efdc3f57c80a197324c6777827231eca712", size = 547382, upload-time = "2026-05-19T10:08:16.927Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/11/e7b70e91f90bc4477e8eee9e8a5f7cf3cb41b4525d6394dc98a714eb8f7f/jiter-0.15.0-cp313-cp313-win32.whl", hash = "sha256:c77496cb10bd7549690fbbab3e5ec05857b83e49276f4a9423a766ddd2afcd4c", size = 205845, upload-time = "2026-05-19T10:08:18.401Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/23/5c20d9ad6f02c493e4023e5d2d09e1c1f15fe2753c9102c544aff068a88e/jiter-0.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b15741f501469009ae0ae90b7147958a664a7dede40aa7ff174a8a4645f546d0", size = 196842, upload-time = "2026-05-19T10:08:20.131Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/11/1eb400ef248e8c925fd883fbe325daf5e42cd1b0d308539dd332bd4f7ffc/jiter-0.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d6a60072b44c3c2b797a7ddcbcbbf2b34ea3cfd4721580fbfd2a09d9d9b84ba", size = 192212, upload-time = "2026-05-19T10:08:21.807Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/60/2fd8d7c79da8acf9b7b277c7616847773779356b92acfc9bb158452174da/jiter-0.15.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ef1fd24d9413f6209e00d3d5a453e67acfe004a25cc6c8e8484faed4311ab9e8", size = 315065, upload-time = "2026-05-19T10:08:23.218Z" },
+ { url = "https://files.pythonhosted.org/packages/46/f4/008fb7d65e8ac2abf00811651a661e025c4ba80bbc6f378450384ddd3aed/jiter-0.15.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:144f8e72cb53dab146347b91cceac01f5481237f2b93b4a339a1ee8f8878b67c", size = 339444, upload-time = "2026-05-19T10:08:24.701Z" },
+ { url = "https://files.pythonhosted.org/packages/00/55/90b0c7b9c6896c0f2a591dd36d36b71d22e09674bfef178fa03ba3f81499/jiter-0.15.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553fcac2ef2cb990877f9fc0833b8b629a3e6a5670b6b5fd58219b41a653ddc4", size = 347779, upload-time = "2026-05-19T10:08:26.408Z" },
+ { url = "https://files.pythonhosted.org/packages/51/6b/69666cec5000fd57734c118437394516c749ae8dbeea9fb66d6fef9c4775/jiter-0.15.0-cp313-cp313t-win_amd64.whl", hash = "sha256:774f93f65031856bf14ad9f59bdcab8b8cad501e5ceabd51ba3525f76937a25b", size = 200395, upload-time = "2026-05-19T10:08:28.055Z" },
+ { url = "https://files.pythonhosted.org/packages/39/04/a6aa62cd27e8149b0d28df5561f10f6cceaf7935a9ccf3f1c5a05f9a0cd8/jiter-0.15.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f1e1754960f38ec40613a07e5e372df67acb3b890fb383b6fb3de3e49ddbf3c7", size = 190516, upload-time = "2026-05-19T10:08:29.35Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/d2/079f350ebf7859d081de30aa890f9e3be68516f754f3ba32366ffff4dcee/jiter-0.15.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:ac0d9ddea4350974be7a221fc25895f251a8fee748c889bdced2141c0fec1a49", size = 308884, upload-time = "2026-05-19T10:08:31.667Z" },
+ { url = "https://files.pythonhosted.org/packages/04/4e/a2c30a7f69b48c03b20935d647479106fe932f6e63f75faf53937197e05d/jiter-0.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:01a8222cf05ab1128e239421156c207949808acaaea2bdfd33130ae666786e86", size = 310028, upload-time = "2026-05-19T10:08:33.304Z" },
+ { url = "https://files.pythonhosted.org/packages/40/90/2e7cdfd3cf8ca967be38c48f5cf474d79f089efaf559a40f15984a77ae69/jiter-0.15.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:182226cbc930c9fab81bc2e41a4da672f89539906dadb05e75670ac07b94f71f", size = 337485, upload-time = "2026-05-19T10:08:35.259Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/11/15a1aa28b120b8ee5b4f1fb894c125046225f09847738bd64233d3b84883/jiter-0.15.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:71683c38c825452999b5717fcae07ea708e8c93003e808be4319c1b02e3d176e", size = 364223, upload-time = "2026-05-19T10:08:36.694Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/25/f442e8af5f3d0dcf47b39e83a0efd9ee45ea946aa6d04625dc3181eae3b6/jiter-0.15.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30f2218e6a9e5c18bc10fe6d41ac189c442c88eacf11bad9f28ef95a9bef00e6", size = 456387, upload-time = "2026-05-19T10:08:38.143Z" },
+ { url = "https://files.pythonhosted.org/packages/da/f4/37f2d2c9f64f49af7da652ed7532bb5a2372e588e6927c3fdd76f911db65/jiter-0.15.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5157de9f76eb4bc5ea74a1219366a25f945ad305641d74e04f59c54087091aa9", size = 374461, upload-time = "2026-05-19T10:08:39.869Z" },
+ { url = "https://files.pythonhosted.org/packages/60/28/edcfbbbf0cb15436f36664a8908a0df47ab9006298d4cd937dc08ea932d6/jiter-0.15.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c5db5527c221249a876160663ab891ace358c17f7b9c93ec1478b7f0550e5c", size = 345924, upload-time = "2026-05-19T10:08:41.668Z" },
+ { url = "https://files.pythonhosted.org/packages/47/13/89fba6398dab7f202b7278c4b4aac122399d2c0183971c4a57a3b7088df5/jiter-0.15.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:3e4540b8e74e4268811ac05db226a6a128ff572e7e0ce3f1163b693cadb184cd", size = 352283, upload-time = "2026-05-19T10:08:43.091Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/da/0f6af8cef2c565a1ab44d970f268c43ccaa72707386ea6388e6fe2b6cd26/jiter-0.15.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:62ebd14e47e9aed9df4472afcb2663668ce4d74891cd54f86bf6e44029d6dc89", size = 389985, upload-time = "2026-05-19T10:08:44.915Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/ec/b9cb7d6d29e24ee14910266157d2a279d7a8f60ee0df7fa840882976ba64/jiter-0.15.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0be6f5ad41a809f303f416d17cec92a7a725902fb9b4f3de3d19362ac0ef8554", size = 517695, upload-time = "2026-05-19T10:08:46.486Z" },
+ { url = "https://files.pythonhosted.org/packages/64/5e/6d1bda880723aae0ad86b4b763f044362448efe31e3e819635d41cb03451/jiter-0.15.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:813dfbb17d65328bf86e5f0905dd277ba2265d3ca20556e86c0c7035b7182e5a", size = 548868, upload-time = "2026-05-19T10:08:48.026Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/72/7de501cf38dcacaf35098796f3a50e0f2e338baba18a58946c618544b809/jiter-0.15.0-cp314-cp314-win32.whl", hash = "sha256:50e51156192722a9c58db112837d3f8ef96fb3c5ecc14e95f409134b08b158ec", size = 206380, upload-time = "2026-05-19T10:08:49.738Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/a9/e19addf4b0c1bdce52c6da12351e6bc42c340c45e7c09e2158e46d293ccc/jiter-0.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:30ce1a5d16b5641dc935d50ef775af6a0871e3d14ab05d6fc54dff371b78e558", size = 197687, upload-time = "2026-05-19T10:08:51.088Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/c9/776b1db01db25fc6c1d58d1979a37b0a9fe787e5f5b1d062d2eaacb77923/jiter-0.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:510c8b3c17a0ed9ac69850c0438dada3c9b82d9c4d589fcb62002a5a9cf3a866", size = 192571, upload-time = "2026-05-19T10:08:52.451Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/f6/45bb4670bacf300fd2c7abadbfb3af376e5f1b6ae75fd9bc069891d15870/jiter-0.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7553333dd0930c104a5a0db8df72bf7219fe663d731383b576bb6ed6351c984d", size = 317151, upload-time = "2026-05-19T10:08:53.867Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/68/ed635ad5acd7b73e454283083bbb7c8205ad10e88b0d9d7d793b09fe8226/jiter-0.15.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2143ab06181d2b029eedcb6af3cebe95f11bbac62441781860f98ee9330a6a6", size = 341243, upload-time = "2026-05-19T10:08:55.383Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/db/3ff4176b817b8ea33879e71e13d8bc2b0d481a7ed3fe9e080f333d415c16/jiter-0.15.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6eac374c5c975709b69c10f09afd199df74150172156ad10c8d4fd785b7da995", size = 363629, upload-time = "2026-05-19T10:08:56.928Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/24/5f8270e0ba9c883582f96f722f8a0b58015c7ce1f8c6d4571cf394e99b6b/jiter-0.15.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3b3b775e33d3bfaec9899edc526ae97b0da0bf9d071a46124ba419149a414f8", size = 456198, upload-time = "2026-05-19T10:08:58.618Z" },
+ { url = "https://files.pythonhosted.org/packages/45/5b/76fc02b0b5c54c3d18c60653156e2f76fde1816f9b4722db68d6ee2c897e/jiter-0.15.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3071db3346334beae1360b46da4606da57bf3528c167b3c38533afaf9f2c5", size = 373710, upload-time = "2026-05-19T10:09:00.151Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/52/4310821b0ea9277994d3e1f49fc6a4b34e4800caebacb2c0af81da59a454/jiter-0.15.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6694a173ecabc12eb60efbc0b474464ead1951ff65cd8b1e72100715c64512b", size = 349901, upload-time = "2026-05-19T10:09:01.621Z" },
+ { url = "https://files.pythonhosted.org/packages/93/fe/67648c35b3594fba8854ac64cc8a826d8bcd18324bbdb53d77697c60b6ef/jiter-0.15.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:a254e10b593624d230c365b6d616b22ca0ad65e63a16e6631c2b3466022e6ba8", size = 352438, upload-time = "2026-05-19T10:09:03.216Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/28/0a1879d07ad6b3e025a2750027363452ced93c2d16d1c9d4b153ffd51c91/jiter-0.15.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d8d2955167274e15d79a7a020afdd9b39c990eb80b2d89fca695d92dcfdd38ec", size = 388152, upload-time = "2026-05-19T10:09:04.741Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/78/46c6f6b56ba85c90021f4afd72ed42f691f8f84daacb5fe27277070e3858/jiter-0.15.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:acf4ee4d1fc55917239fe72972fb292dd773055d05eb040d36f4326e02cc2c0e", size = 517707, upload-time = "2026-05-19T10:09:06.231Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/cb/720662d4c88fcad606e826fef5424365527ba43ce4868a479aed8f8c507e/jiter-0.15.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:e7196e56f1cd69af1dbb07dff02dcfb260a50b45a82d409d92a06fedb32473b5", size = 548241, upload-time = "2026-05-19T10:09:08.093Z" },
+ { url = "https://files.pythonhosted.org/packages/60/e3/935b8034fd143f21125c87d51404a9e0e1449186a494405721ff5d1d695e/jiter-0.15.0-cp314-cp314t-win32.whl", hash = "sha256:7f6163c0f10b055245f814dcc59f4818da60dfe72f3e72ab89fc24b6bd5e9c52", size = 207950, upload-time = "2026-05-19T10:09:09.616Z" },
+ { url = "https://files.pythonhosted.org/packages/93/59/984fd9ece895953dad3e0880a650e766f5a2da2c5514f0eafdaaabbeb5f9/jiter-0.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:980c256edb05b78a111b99c4de3b1d32e31634b867fd1fc2cf726e7b7bba9854", size = 200055, upload-time = "2026-05-19T10:09:11.367Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/a4/cf8d779feb133a27a2e3bc833bccb9e13aa332cdf820497ebf72c10ce8c3/jiter-0.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:66b1880df2d01e206e8339769d1c7c1753bcb653efd6289e203f6f24ebada0c0", size = 191244, upload-time = "2026-05-19T10:09:12.74Z" },
+ { url = "https://files.pythonhosted.org/packages/65/43/1fc62172aa98b50a7de9a25554060db510f85c89cfbed0dfe13e1907a139/jiter-0.15.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:411fa4dfa5a7ae3d11491027ffb9beadec3996010a986862db70d91abba1c750", size = 305585, upload-time = "2026-05-19T10:09:35.995Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/c4/dd58fcd9e2df83666e5c1c1347bef58ce919cd8efc3ffa38aeea62ce493b/jiter-0.15.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:2b0074e2f56eb2dacca1689760fd2852a068f85a0547a157b82cb4cafeb6768b", size = 306936, upload-time = "2026-05-19T10:09:37.435Z" },
+ { url = "https://files.pythonhosted.org/packages/39/86/b695e16f1180c07f43ea98e73ecd21cf63fa2e1b0c1103739013784d11ae/jiter-0.15.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:913d02d29c9606643418d9ccfc3b72492ab25a6bf7889934e09a3490f8d3438b", size = 342453, upload-time = "2026-05-19T10:09:39.294Z" },
+ { url = "https://files.pythonhosted.org/packages/34/56/55d76614af37fe3f22a3347d1e410d2a15da581997cb2da499a625000bb5/jiter-0.15.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b15d3ec9b0449c40e85319bdb4caa8b77ab526e74f5532ed94bec15e2f66822c", size = 345606, upload-time = "2026-05-19T10:09:40.727Z" },
+ { url = "https://files.pythonhosted.org/packages/73/38/505941b2b092fd5bbbd60a52a880db1173f1690ae6751bed3af1c9ddcb4e/jiter-0.15.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:631f13a3d04e97d4e083993b10f4b99530e3a10d953e2eb5e196b7dc7f812ce0", size = 303769, upload-time = "2026-05-19T10:09:42.203Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/95/a06692b29e77473f286e1ec1f426d3ca44d7b5843be8ad21d7a5f3fcdcc0/jiter-0.15.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:b6c0ffae686c39bf3737be60793783267628783ea42545632c10b291105aee45", size = 305128, upload-time = "2026-05-19T10:09:43.657Z" },
+ { url = "https://files.pythonhosted.org/packages/23/85/7270d7ad41d6061a25b950c6bf91d638bd9aacb113200a8c8d57a055fd67/jiter-0.15.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d54fb5b31dea401a41af3f8a7d2512e9b6a6a005491e6166c7e4ffab9639a9c", size = 340459, upload-time = "2026-05-19T10:09:45.452Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/8d/302cb2057b7513327b4d575cff6b1d066ee6431a5357fc3f8867cd684406/jiter-0.15.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d5d6090cdc1b7c9e780dfb04949a990adb1e301a2fc0bbcee7de4638d33f9a", size = 344469, upload-time = "2026-05-19T10:09:46.864Z" },
+]
+
+[[package]]
+name = "openai"
+version = "2.41.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "distro" },
+ { name = "httpx" },
+ { name = "jiter" },
+ { name = "pydantic" },
+ { name = "sniffio" },
+ { name = "tqdm" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3c/a6/5815fe2e2aca74b36c650d1bd43b69827cee568073d0d2d9b6fc5aaac80c/openai-2.41.0.tar.gz", hash = "sha256:db5c362acd6604b84f076abbefa66826ea4b46ecba2954ed866e6a149a1352c0", size = 783525, upload-time = "2026-06-03T22:39:40.719Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/be/51/d82bb424e8aa372190c5233253a2ceb399a778747d18b42cff487411e663/openai-2.41.0-py3-none-any.whl", hash = "sha256:20cc7952e8501c7e5773dd2ef7be437bae9cb549044902e1041a83a54516e375", size = 1353378, upload-time = "2026-06-03T22:39:38.964Z" },
+]
+
+[[package]]
+name = "opentelemetry-api"
+version = "1.42.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b4/1c/125e1c936c0873796771b7f04f6c93b9f1bf5d424cea90fda94a99f61da8/opentelemetry_api-1.42.1.tar.gz", hash = "sha256:56c63bea9f77b62856be8c47600474acad853b2924b99b1687c4cb6297166716", size = 72296, upload-time = "2026-05-21T16:32:49.335Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a3/ca/9520cc1f3dfbbd03ac5903bbf55833e257bc64b1cf30fa8b0d6df374d821/opentelemetry_api-1.42.1-py3-none-any.whl", hash = "sha256:51a69edacadbc03a8950ace1c4c21099cacc538820ac2c9e36277e78cebba714", size = 61311, upload-time = "2026-05-21T16:32:28.822Z" },
+]
+
+[[package]]
+name = "opentelemetry-instrumentation"
+version = "0.63b1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-semantic-conventions" },
+ { name = "packaging" },
+ { name = "wrapt" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/da/6d/4de72d97ff54db1ed270c7a59c9b904b917c0ac7af429c086c388b824ddb/opentelemetry_instrumentation-0.63b1.tar.gz", hash = "sha256:32368d6ae52c8de20aa790a6ad86b10a76f09956092337ae37d675773990e541", size = 41081, upload-time = "2026-05-21T16:36:14.206Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/35/a1/9314e621c143e4d82a5bf7a43c2ff7a745d31023506336857607c8c543cc/opentelemetry_instrumentation-0.63b1-py3-none-any.whl", hash = "sha256:f1986716d52cc316ea5f60189098726a9071d8ecc0eee96c9ed110be08bade9c", size = 35577, upload-time = "2026-05-21T16:34:56.818Z" },
+]
+
+[[package]]
+name = "opentelemetry-instrumentation-deepseek"
+version = "0.61.0"
+source = { editable = "." }
+dependencies = [
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-instrumentation" },
+ { name = "opentelemetry-semantic-conventions" },
+ { name = "opentelemetry-semantic-conventions-ai" },
+]
+
+[package.optional-dependencies]
+instruments = [
+ { name = "openai" },
+]
+
+[package.dev-dependencies]
+dev = [
+ { name = "autopep8" },
+ { name = "pytest" },
+ { name = "pytest-sugar" },
+ { name = "ruff" },
+]
+test = [
+ { name = "openai" },
+ { name = "opentelemetry-sdk" },
+ { name = "pytest" },
+ { name = "pytest-asyncio" },
+ { name = "pytest-recording" },
+ { name = "pytest-sugar" },
+ { name = "vcrpy" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "openai", marker = "extra == 'instruments'", specifier = ">=1.0.0" },
+ { name = "opentelemetry-api", specifier = ">=1.38.0,<2" },
+ { name = "opentelemetry-instrumentation", specifier = ">=0.59b0" },
+ { name = "opentelemetry-semantic-conventions", specifier = ">=0.59b0" },
+ { name = "opentelemetry-semantic-conventions-ai", specifier = ">=0.5.1,<0.6.0" },
+]
+provides-extras = ["instruments"]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "autopep8", specifier = ">=2.2.0,<3" },
+ { name = "pytest", specifier = ">=8.2.2,<9" },
+ { name = "pytest-sugar", specifier = "==1.0.0" },
+ { name = "ruff", specifier = ">=0.4.0" },
+]
+test = [
+ { name = "openai", specifier = ">=1.0.0" },
+ { name = "opentelemetry-sdk", specifier = ">=1.38.0,<2" },
+ { name = "pytest", specifier = ">=8.2.2,<9" },
+ { name = "pytest-asyncio", specifier = ">=0.23.7,<0.24.0" },
+ { name = "pytest-recording", specifier = ">=0.13.1,<0.14.0" },
+ { name = "pytest-sugar", specifier = "==1.0.0" },
+ { name = "vcrpy", specifier = ">=8.0.0,<9" },
+]
+
+[[package]]
+name = "opentelemetry-sdk"
+version = "1.42.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-semantic-conventions" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/40/f7/b390bd9bfd703bf98a68fea1f27786c6872331fd617164a54b8a59bdc008/opentelemetry_sdk-1.42.1.tar.gz", hash = "sha256:8c834e8f8c9ba4171d4ec843d0cb8a67e4c7394d3f9e9297e582cbd9456ddbf7", size = 239262, upload-time = "2026-05-21T16:33:04.641Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8f/6b/4287766cfbde577ae2272e8884abac325aeaac0d64f41c61d5b8cc595105/opentelemetry_sdk-1.42.1-py3-none-any.whl", hash = "sha256:083cd4bbfaa5aa7b5a9e552430d9951219967cfb27aa61feb13a77aba1fc839d", size = 170907, upload-time = "2026-05-21T16:32:45.894Z" },
+]
+
+[[package]]
+name = "opentelemetry-semantic-conventions"
+version = "0.63b1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "opentelemetry-api" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/93/99/4d7dd6df64795951413ce6e815f8cf1eb191daf7196ae86574589643d5f3/opentelemetry_semantic_conventions-0.63b1.tar.gz", hash = "sha256:3daf963611334b365e98a57438183eb012d3bfb40b2d931a9af613476b8701a9", size = 148340, upload-time = "2026-05-21T16:33:05.455Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/7a/7fe66f5f3682b1dd47d88cc4e11f1c6c0966b737de2d16671146e23c39a5/opentelemetry_semantic_conventions-0.63b1-py3-none-any.whl", hash = "sha256:dfe5ef4dee82586b746f522b818ceb298d00b3d59f660042bd79404bff8d0682", size = 203713, upload-time = "2026-05-21T16:32:47.016Z" },
+]
+
+[[package]]
+name = "opentelemetry-semantic-conventions-ai"
+version = "0.5.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "opentelemetry-sdk" },
+ { name = "opentelemetry-semantic-conventions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/24/02/10aeacc37a38a3a8fa16ff67bec1ae3bf882539f6f9efb0f70acf802ca2d/opentelemetry_semantic_conventions_ai-0.5.1.tar.gz", hash = "sha256:153906200d8c1d2f8e09bd78dbef526916023de85ac3dab35912bfafb69ff04c", size = 26533, upload-time = "2026-03-26T14:20:38.73Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/55/22/41fb05f1dc5fda2c468e05a41814c20859016c85117b66c8a257cae814f6/opentelemetry_semantic_conventions_ai-0.5.1-py3-none-any.whl", hash = "sha256:25aeb22bd261543b4898a73824026d96770e5351209c7d07a0b1314762b1f6e4", size = 11250, upload-time = "2026-03-26T14:20:37.108Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "26.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" },
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
+]
+
+[[package]]
+name = "pycodestyle"
+version = "2.14.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/11/e0/abfd2a0d2efe47670df87f3e3a0e2edda42f055053c85361f19c0e2c1ca8/pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", size = 39472, upload-time = "2025-06-20T18:49:48.75Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" },
+]
+
+[[package]]
+name = "pydantic"
+version = "2.13.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "annotated-types" },
+ { name = "pydantic-core" },
+ { name = "typing-extensions" },
+ { name = "typing-inspection" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" },
+]
+
+[[package]]
+name = "pydantic-core"
+version = "2.46.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" },
+ { url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" },
+ { url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" },
+ { url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" },
+ { url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" },
+ { url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" },
+ { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" },
+ { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" },
+ { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" },
+ { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" },
+ { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" },
+ { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" },
+ { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" },
+ { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" },
+ { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" },
+ { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" },
+ { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" },
+ { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" },
+ { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" },
+ { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" },
+ { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" },
+ { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" },
+ { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" },
+ { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" },
+ { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" },
+ { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" },
+ { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" },
+ { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" },
+ { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" },
+ { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" },
+ { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" },
+ { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" },
+ { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" },
+ { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" },
+ { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" },
+ { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" },
+ { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" },
+ { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" },
+ { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" },
+ { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" },
+ { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" },
+ { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" },
+ { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" },
+ { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.20.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "8.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
+ { name = "iniconfig" },
+ { name = "packaging" },
+ { name = "pluggy" },
+ { name = "pygments" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
+]
+
+[[package]]
+name = "pytest-asyncio"
+version = "0.23.8"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pytest" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/de/b4/0b378b7bf26a8ae161c3890c0b48a91a04106c5713ce81b4b080ea2f4f18/pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3", size = 46920, upload-time = "2024-07-17T17:39:34.617Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ee/82/62e2d63639ecb0fbe8a7ee59ef0bc69a4669ec50f6d3459f74ad4e4189a2/pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2", size = 17663, upload-time = "2024-07-17T17:39:32.478Z" },
+]
+
+[[package]]
+name = "pytest-recording"
+version = "0.13.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pytest" },
+ { name = "vcrpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/32/9c/f4027c5f1693847b06d11caf4b4f6bb09f22c1581ada4663877ec166b8c6/pytest_recording-0.13.4.tar.gz", hash = "sha256:568d64b2a85992eec4ae0a419c855d5fd96782c5fb016784d86f18053792768c", size = 26576, upload-time = "2025-05-08T10:41:11.231Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/42/c2/ce34735972cc42d912173e79f200fe66530225190c06655c5632a9d88f1e/pytest_recording-0.13.4-py3-none-any.whl", hash = "sha256:ad49a434b51b1c4f78e85b1e6b74fdcc2a0a581ca16e52c798c6ace971f7f439", size = 13723, upload-time = "2025-05-08T10:41:09.684Z" },
+]
+
+[[package]]
+name = "pytest-sugar"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "packaging" },
+ { name = "pytest" },
+ { name = "termcolor" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f5/ac/5754f5edd6d508bc6493bc37d74b928f102a5fff82d9a80347e180998f08/pytest-sugar-1.0.0.tar.gz", hash = "sha256:6422e83258f5b0c04ce7c632176c7732cab5fdb909cb39cca5c9139f81276c0a", size = 14992, upload-time = "2024-02-01T18:30:36.735Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/92/fb/889f1b69da2f13691de09a111c16c4766a433382d44aa0ecf221deded44a/pytest_sugar-1.0.0-py3-none-any.whl", hash = "sha256:70ebcd8fc5795dc457ff8b69d266a4e2e8a74ae0c3edc749381c64b5246c8dfd", size = 10171, upload-time = "2024-02-01T18:30:29.395Z" },
+]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" },
+ { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" },
+ { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" },
+ { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" },
+ { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" },
+ { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" },
+ { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" },
+ { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
+ { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
+ { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
+ { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
+ { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
+ { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
+ { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
+ { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
+ { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
+ { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
+ { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
+ { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
+ { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
+ { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
+ { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
+ { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
+ { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
+]
+
+[[package]]
+name = "ruff"
+version = "0.15.16"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/5f7ec371001337d8fa61701c186ff8b613ecac1651848c5950f4c4d5f2e9/ruff-0.15.16.tar.gz", hash = "sha256:d05e78d38c78caf020b03789e25106c93017db5a0cb6e2819885018c61343b78", size = 4714267, upload-time = "2026-06-04T16:33:09.974Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0c/42/53ef1c3953f157956db9bf7861e3bc50b9b887ce93300aa48cdba8336fe6/ruff-0.15.16-py3-none-linux_armv6l.whl", hash = "sha256:6ac3c0b3969cc6cf6b158c4e2f8f682acb58e7d700d8a44b65ecdc72d66ab0b2", size = 10709025, upload-time = "2026-06-04T16:32:51.935Z" },
+ { url = "https://files.pythonhosted.org/packages/93/9a/a79159346f19134a956607754e57d8d128f7a4c00f4ad2f7514d224c172c/ruff-0.15.16-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:197c207ed75ffba54a0dec23db4aa939a27a3053073e085e0042433cbdc58e4a", size = 11063550, upload-time = "2026-06-04T16:32:42.24Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/72/3ce2ac000a5299ec238e01f51397b3b653c93b077d9b1bfe8715bb895f20/ruff-0.15.16-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3a39fec45ab316cc23e7558f23fea4a70403ddb5648ea9a4a3854a16973d0071", size = 10421345, upload-time = "2026-06-04T16:32:37.251Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/c2/cc7fad3ec9169373f5b6a18f1917b91080feec40c3f9658334a1d28e2f03/ruff-0.15.16-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba93191d79003116b95128c9d306e045200fdbd0bccb782b110f3cd1d4abc5cf", size = 10757217, upload-time = "2026-06-04T16:32:54.722Z" },
+ { url = "https://files.pythonhosted.org/packages/69/d2/3474009eaa0a65b31fa7152a2fad5e2f050c640ceb1e6b02ee6922e94c82/ruff-0.15.16-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6ee4b90520630120ef032aa5cc10db483852dff950e78b1d717e2993a61ac8d", size = 10507035, upload-time = "2026-06-04T16:33:05.343Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/81/b7ae6ccbd11f0c8dc3d5d67fc4be9b57ff57ca86ba56152021378e1277f2/ruff-0.15.16-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e4215bc938bc3c8215c1472c1aa437e310fee20cd427335fec9d7e609563628", size = 11255291, upload-time = "2026-06-04T16:32:49.49Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/e1/46e526f1a7cc90857ce6ddf25fbb77eb6568651ac38d71b033af07076dd5/ruff-0.15.16-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c8d26be963b090f10e29abc8b3e74a2a321f6fa34e02424e30b5af89350ecbb", size = 12124922, upload-time = "2026-06-04T16:33:07.821Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/da/5c791b088b596b24d0deb967fa28ae02ad751a140c0b9ea81c5ab915d6c0/ruff-0.15.16-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f198cf4123602a2280ed46c307bcbafe41758d6fee5b456b6b6058ca1514b3b4", size = 11332186, upload-time = "2026-06-04T16:33:02.971Z" },
+ { url = "https://files.pythonhosted.org/packages/72/11/5da87abe20047c8962361473923ebb2f62b595250126aadfad8c20649c1e/ruff-0.15.16-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb27515fa6240fb586ae82b901a59e67d24acff86f2190b433dc542fe0435aeb", size = 11373541, upload-time = "2026-06-04T16:32:47.007Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/2a/8554754c23a854ae3fd6b507e36ad61ddb121e298c6d5d617dec94ed0f14/ruff-0.15.16-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a267c46ba1593fc26b8eecbea050b39d40c0b6bb7781ee11c90a02cd10032951", size = 11353014, upload-time = "2026-06-04T16:32:34.795Z" },
+ { url = "https://files.pythonhosted.org/packages/62/25/62ea41529ec89f742ea3fed9cb1059c72877ec7cf9b9e99ac9cf3294d1d9/ruff-0.15.16-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:528c68f39a91498a8d50e91ff5985df3d105782bab49cc378e73ac26bff083e8", size = 10737467, upload-time = "2026-06-04T16:32:26.348Z" },
+ { url = "https://files.pythonhosted.org/packages/90/17/334d3ad9de4d40f9dd58fdd09e35ce64553bb501e2f19a839e2fb6be14fc/ruff-0.15.16-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7ed55c58950df60589a9a7a5d2f8fa5f54ebd287163be805adfe6ee95a9de123", size = 10521910, upload-time = "2026-06-04T16:32:32.54Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/bd/3ac7c6ae77a885c1004b3dda2446ea401768d24f851c14b4ad4b24f6639c/ruff-0.15.16-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d482feaf51512b50f9790ceb417a56a61dd1e9d9bf967662b9ed27c01b34f53a", size = 10979190, upload-time = "2026-06-04T16:32:57.492Z" },
+ { url = "https://files.pythonhosted.org/packages/33/d7/609546e6a413c3f216fbf2a50c928f97c80939154f6a0503114094a86191/ruff-0.15.16-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e15bc8c94513dae2a40cc9ef07c94fdd4ecc9e29dabebeebe170f952322c9e3", size = 11477014, upload-time = "2026-06-04T16:32:44.687Z" },
+ { url = "https://files.pythonhosted.org/packages/74/0d/f2cd247ad32633a5c36e97141a2c21b11c6279f7957bc2ff360b1e08fddd/ruff-0.15.16-py3-none-win32.whl", hash = "sha256:580378f7bd4aa25f72e74aa54948a9622f142b1e509521dd10902e886681cc1e", size = 10735541, upload-time = "2026-06-04T16:32:30.145Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/9e/02e845ef151b1dee585e55c4739f8e1734ae1d9f1221dff65761c162208b/ruff-0.15.16-py3-none-win_amd64.whl", hash = "sha256:408256017284eddf98fff77b29aa4fb30f586042d535b2d9befc6512f400aaec", size = 11843403, upload-time = "2026-06-04T16:32:39.76Z" },
+ { url = "https://files.pythonhosted.org/packages/15/19/016553f86f207450aebebc2b2b5088d086b901cc8186c02ac4284db3bd88/ruff-0.15.16-py3-none-win_arm64.whl", hash = "sha256:8cd61783afb39638a7133ef0d2dfb1e91277593962f81b5a8423eb0b888a6121", size = 11134555, upload-time = "2026-06-04T16:33:00.136Z" },
+]
+
+[[package]]
+name = "sniffio"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
+]
+
+[[package]]
+name = "termcolor"
+version = "3.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/46/79/cf31d7a93a8fdc6aa0fbb665be84426a8c5a557d9240b6239e9e11e35fc5/termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5", size = 14434, upload-time = "2025-12-29T12:55:21.882Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/33/d1/8bb87d21e9aeb323cc03034f5eaf2c8f69841e40e4853c2627edf8111ed3/termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5", size = 7734, upload-time = "2025-12-29T12:55:20.718Z" },
+]
+
+[[package]]
+name = "tomli"
+version = "2.4.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" },
+ { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" },
+ { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" },
+ { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" },
+ { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" },
+ { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" },
+ { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" },
+ { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" },
+ { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" },
+ { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" },
+ { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" },
+ { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" },
+ { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" },
+ { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" },
+ { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" },
+ { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" },
+ { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" },
+ { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" },
+ { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" },
+ { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" },
+ { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" },
+ { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" },
+ { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" },
+ { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" },
+ { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" },
+ { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" },
+]
+
+[[package]]
+name = "tqdm"
+version = "4.68.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/85/05/0d5260f1f1ca784f4a4a0def9cbe6affe587f5b4025328d446c3d67765f4/tqdm-4.68.2.tar.gz", hash = "sha256:89c230e8dbc67c7615c142487111222f878c77427ea09549960f62389e258add", size = 171923, upload-time = "2026-06-09T13:26:42.539Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/eb/75/1a0392bcc21c44dcdf87b3cf2d137e7829be2c083a1e38d44efca3d57a16/tqdm-4.68.2-py3-none-any.whl", hash = "sha256:d4240441fb5353290b87d6a85968c9decc131a99b8c7faa28269d829de669ede", size = 78578, upload-time = "2026-06-09T13:26:40.731Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
+]
+
+[[package]]
+name = "typing-inspection"
+version = "0.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
+]
+
+[[package]]
+name = "vcrpy"
+version = "8.1.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyyaml" },
+ { name = "wrapt" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b3/07/bcfd5ebd7cb308026ab78a353e091bd699593358be49197d39d004e5ad83/vcrpy-8.1.1.tar.gz", hash = "sha256:58e3053e33b423f3594031cb758c3f4d1df931307f1e67928e30cf352df7709f", size = 85770, upload-time = "2026-01-04T19:22:03.886Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3a/d7/f79b05a5d728f8786876a7d75dfb0c5cae27e428081b2d60152fb52f155f/vcrpy-8.1.1-py3-none-any.whl", hash = "sha256:2d16f31ad56493efb6165182dd99767207031b0da3f68b18f975545ede8ac4b9", size = 42445, upload-time = "2026-01-04T19:22:02.532Z" },
+]
+
+[[package]]
+name = "wrapt"
+version = "2.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2d/9f/06263fcd8ad6c405f05a3905fd7a84dd3176eb5ad46e44bccc0cd16348bb/wrapt-2.2.1.tar.gz", hash = "sha256:6744f504375775d7609c82c8d3d94af1c9a6f05586984536905908ba905277b9", size = 127620, upload-time = "2026-05-22T14:49:43.056Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b4/8b/84bc1ea68b620fe0e2696a8cff07e82f4b962d952ab14efee8955997bb70/wrapt-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0f68f478004475d97906686e702ddbddeaf717c0b68ad2794384308f2dc713ae", size = 80093, upload-time = "2026-05-22T14:47:27.074Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/8f/64ec81194a0bc708d9720174c998c8a32116e82b5b32c04e20a7fe01176c/wrapt-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e422b2d647a65d6b080cad5accd09055d3809bdff00c76fba8dca00ca935572a", size = 81183, upload-time = "2026-05-22T14:47:29.062Z" },
+ { url = "https://files.pythonhosted.org/packages/94/c2/3d186944aae923631d1def58f4c4ff8f0b6309906afc0b6978de3e69b3e0/wrapt-2.2.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:036dfb40128819a751c6f451c6b9c10172c49e4c401aebcdb8ecf2aec1683598", size = 152494, upload-time = "2026-05-22T14:47:30.583Z" },
+ { url = "https://files.pythonhosted.org/packages/01/d1/6b3d0ea995b867d2862aad5619bd5e17de09a9d64a821f46832dcd272d40/wrapt-2.2.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09ac16c081bebfd15d8e4dfa5bdc805990bbd52249ecff22530da7a129d6120b", size = 154310, upload-time = "2026-05-22T14:47:32.175Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/4b/37ecb90a8c3753e580327fb40731a984b754e3df65d2ef932bf359fe4adc/wrapt-2.2.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07be671fa8875971222b0ba9059ed8b4dc738631122feba17c93aa36b4213e9a", size = 149002, upload-time = "2026-05-22T14:47:34.021Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/d0/918884d9dfa84d0d135b42a51c00910f5c5447fe7a5e211a8e16ac324dd4/wrapt-2.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:93fc2bf40cd7f4a0256010dce073d44eeb4a351b9bca94d0477ce2b6e62532b3", size = 153185, upload-time = "2026-05-22T14:47:35.722Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/00/382299d8ced610b29b59b099a89eda821e8c489aa152b7183748ac83f32a/wrapt-2.2.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ba519b2d765df9871a25879e6f7fa78948ea59a2a31f9c1a257e34b651994afc", size = 148040, upload-time = "2026-05-22T14:47:37.052Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/46/62a79b79e35bbebb1207ca5d15b81192f37f20cc5659cf4e3ce955b7fcc8/wrapt-2.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9011395be8db1827d106c6449b4bb6dd17e331ff6ec521f227e4588f1c78e46f", size = 151773, upload-time = "2026-05-22T14:47:38.713Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/db/95c152151d206d4b430516c89725306e92484072f38e65492afde63f6d19/wrapt-2.2.1-cp310-cp310-win32.whl", hash = "sha256:a8f7176b83664af44567e9cc06e0d3827823fcc1a5e52307ebb8ac3aa95860b9", size = 77393, upload-time = "2026-05-22T14:47:40.061Z" },
+ { url = "https://files.pythonhosted.org/packages/13/d3/882d50452c6fbd13f24fe5d2644b97cdad2565a7e1522cbb6312de8a52cf/wrapt-2.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:d7f513d3185e6fec82d0c3518f2e6365d8b4e49f5f45f29640d5162d56a23b54", size = 80350, upload-time = "2026-05-22T14:47:41.194Z" },
+ { url = "https://files.pythonhosted.org/packages/58/0f/148376523b4e370692286a9ba14d5715cf3c5b86da3bd3630926367b6b73/wrapt-2.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:44255c84bc57554fed822e83e70036b51afa9edb56fc7ca56c54410ece7898c9", size = 79149, upload-time = "2026-05-22T14:47:42.835Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/ac/4370bde262c0e633e6c4f0e56d55095710024cf9a5cecc20c59a10de483c/wrapt-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd57607acc85678925940bd5df0385ff8332083a32fa8d7a43f8767f4997263c", size = 80321, upload-time = "2026-05-22T14:47:43.996Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/79/b8ff3a61e71babf58a8cf4c0d63358e8bad383e15bf7f35e62d2f6b6e4a4/wrapt-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ae574d65c9fa8e86f64f6a7c2668f9fcd507b183e0e577619f504b883cb0a6c", size = 81216, upload-time = "2026-05-22T14:47:45.243Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/fd/c0cac1f77c9c4f6fe58a920ca632ce379bb8be928720e11e8d73de28a5e9/wrapt-2.2.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9a04c28c10ba7fd12842b109d2edb0678872a2fe65277ca4ff06a0d61edee245", size = 159208, upload-time = "2026-05-22T14:47:47.176Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/4f/744132a7b2fbefa6b81118ec5942eca5fc2e9a129f9055a0c5e46885a549/wrapt-2.2.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e2f02472a1cbbf3884b365714a810b5947134a95ad6952b554cb8cce9d492b0", size = 160322, upload-time = "2026-05-22T14:47:49.04Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/95/b7cd9a22a06cf93e6482904ee6afc956248983553593fd1009296d1b3b31/wrapt-2.2.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac2745950b2bff80219c15ebf2fa9d8427eba7e249739f97e55c9d169e47e9e1", size = 153243, upload-time = "2026-05-22T14:47:50.386Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/4a/eb79423192015f46f0db2872e7e04a3dde8d359b83411e8959e7c9287eaa/wrapt-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67a97e5b6c457f0cd3cfc19ebb2d84463e60c3ece754cc831e4281a3ca29bb18", size = 159231, upload-time = "2026-05-22T14:47:51.753Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/dc/435015b58ce33c6fc4104158fa91ddb0e809ab03a5751fb7465d1d461456/wrapt-2.2.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:c803a3d331796255af51ba2c79ed0ac8275865b516c09e61f248d1e7aff31ce9", size = 152351, upload-time = "2026-05-22T14:47:53.214Z" },
+ { url = "https://files.pythonhosted.org/packages/77/ac/5d203f98df8fd136b95c5227139aea02d34505e18baf812d0c005df61963/wrapt-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9b984d1eb252145d6302c1dbd5e87fc6d404d45531447c84eadec04bf1fcb027", size = 158347, upload-time = "2026-05-22T14:47:54.982Z" },
+ { url = "https://files.pythonhosted.org/packages/52/2f/a92427dbdc74e54c1674abbed27e61b2cb5e7a94441b8c1270c70671d928/wrapt-2.2.1-cp311-cp311-win32.whl", hash = "sha256:8a983a603a18c8708f024f7f6991b2e66159219abbf894634c5056243c55f3cd", size = 77562, upload-time = "2026-05-22T14:47:56.275Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/56/987b9c13b3e1c1a3c6de71284076f996b79caec90e75a87c044a40c23db9/wrapt-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:9c210a6994b21aa9b29e81c8d11560e8fdab54c117e9cff37870d0a27bde1343", size = 80616, upload-time = "2026-05-22T14:47:57.854Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/25/d01f560888d99d94a959c85533de349ce68d71ace3f2591d6ea8f632cfed/wrapt-2.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:401229e9d63ca09f9b8891ecf83798d26c11bbb445d11ed9f1836b6d4585b38a", size = 79025, upload-time = "2026-05-22T14:47:59.089Z" },
+ { url = "https://files.pythonhosted.org/packages/89/0c/bfae7b9401583b6d05938cd16dedc43857d96da2f8a3d50d78cc515bf6ff/wrapt-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3ffad790d9d11d8ecf9f17c4bb671a5b4089e4d8b575c46c5129597f41f836b0", size = 81021, upload-time = "2026-05-22T14:48:00.313Z" },
+ { url = "https://files.pythonhosted.org/packages/26/58/80f6a6599f933f4caecc1cb3ee88a04faf81e8b9bddbd6109c688dd63e0f/wrapt-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:628f5220c7a904d5fc78f7075c8d7871433eb6d035c94728a22fdf85f193d2a8", size = 81692, upload-time = "2026-05-22T14:48:01.49Z" },
+ { url = "https://files.pythonhosted.org/packages/17/93/fb357cc7847c58a8ae790be718903afa81a28d23e642c843dc4129e8a0b2/wrapt-2.2.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:61acce4257a9883669703c525447c5b4c392edf0f987ae77ec32668440158f0e", size = 169364, upload-time = "2026-05-22T14:48:02.791Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/0b/76b601ee309a8bd556af0eecb184394c20b3c49aa9c8e085aa1ffacc2568/wrapt-2.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727ab4244622cd6ad2390f322642090c877d2e83a608d2653a7643ae5368d926", size = 171079, upload-time = "2026-05-22T14:48:04.22Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/87/ee3f32d5658e3e26d3e0e457922b47a36dd3bfbdfee7f97bb3e802344a66/wrapt-2.2.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03df9ebed4c73ab93fa8c07e3d41d818dfca1852b15731a3de59457b27814624", size = 160205, upload-time = "2026-05-22T14:48:05.553Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/d0/ae2fd64277a67f5d7bffcf2d05eea1e476263fb2a072baf0b0129ab85984/wrapt-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0d9ff006f420b2ec8296aa56ade43ea7da3e997e85769f0aafc5e0661aacb710", size = 168922, upload-time = "2026-05-22T14:48:07.132Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/f3/2d541a060c5bbafb9400bca4917e4d78bfd1f239f404782c86831a8f6b29/wrapt-2.2.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:844c858fc3bb7eacc0ba8efa904935d16aac6a4470948ad1e7e55c9f5a2a665f", size = 158388, upload-time = "2026-05-22T14:48:08.629Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/68/8d92c8800c57e93cb116ae9e9d6cbafc34fade5ee9f9107b6f203fb4dc35/wrapt-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87bacdaf225117a342a20d9c03438d701c02112f6e3f351ce9b7f32354f14797", size = 167682, upload-time = "2026-05-22T14:48:10.042Z" },
+ { url = "https://files.pythonhosted.org/packages/30/72/83ea3790ea352439442349388e29ff07b76e0686265f9088bbb505d1608d/wrapt-2.2.1-cp312-cp312-win32.whl", hash = "sha256:2f8c90c8afde51969487be4e1343ae049b268854877d415c2510baf833775052", size = 77857, upload-time = "2026-05-22T14:48:11.782Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/cb/99450668dd3502d62a54a1c8aa56e44f34cb8c1261b381cfe2e7926c3b75/wrapt-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ce32763ac31ce94fe9aada947e479b1975012bff166da409b4b9e4e376cf7e5", size = 80825, upload-time = "2026-05-22T14:48:13.046Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/3a/87512881be64e743f9ee4c66f4cbe8e884974bef2a5989af71f999653ac7/wrapt-2.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d1b4d0e0c2119587a31f5c029abd547e0c81d93b89d394566fe1588659eb579", size = 79087, upload-time = "2026-05-22T14:48:14.323Z" },
+ { url = "https://files.pythonhosted.org/packages/88/d1/a1b08f8f4fac8cbb156fa51cf64ee2c7f7f74f9875ba3cf70b3c58368694/wrapt-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d2beb1c7cab10603aecdc42f8edd6ff013f9a32e4543474e38e6b77ce9975aeb", size = 80831, upload-time = "2026-05-22T14:48:15.598Z" },
+ { url = "https://files.pythonhosted.org/packages/54/ce/57890814991446a845e09b3445ce8b694f27eb0577004f2c2a36a9772ed4/wrapt-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0cb7e4dd71f4c32e5e84843cd3c4cd65dda034314004bbe1d7f99af2426ab80", size = 81375, upload-time = "2026-05-22T14:48:17.071Z" },
+ { url = "https://files.pythonhosted.org/packages/38/65/08d7a6c76ac4493bdb668205ee9c1de1bd5daca61717c3e9aa49b4c01499/wrapt-2.2.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95821352042722cd9f1108874579a47989d0a7e12a37d87d2fc4af20fd99ab8a", size = 167417, upload-time = "2026-05-22T14:48:18.303Z" },
+ { url = "https://files.pythonhosted.org/packages/62/ce/f1ccbee7a1bfe5cdc6b3da6bab4b45713d628b9294da32a39f563d648140/wrapt-2.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abd621552ede77c4c69be7fac44ba911225b0c812b6ba604e5964cf98085b474", size = 166948, upload-time = "2026-05-22T14:48:19.768Z" },
+ { url = "https://files.pythonhosted.org/packages/86/2a/f85d48d1cd4869aee6704028d257d740a47c1c467b457ce396b4b5b55d07/wrapt-2.2.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e3677c7146ce694874941ba82b57092cc4875445aadf29d72807351023105143", size = 158148, upload-time = "2026-05-22T14:48:21.96Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/5c/93939ad11d4a12358ab1aab219a2ef5efa5612e0db6b9fc65af8af1a891b/wrapt-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9a5934eaea872e17936b5f45501eba5ab0bce9a74122e172b663d7c28c459c4a", size = 165905, upload-time = "2026-05-22T14:48:23.373Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/22/b8c2aa89862ff58605934d7abf4b70e6a5a1c33df96656f49035ccdf1c8a/wrapt-2.2.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f5b9daf6b629fce418e0cc3dd0436eac045188fa35deadb7a7f3941d5b8203f9", size = 156712, upload-time = "2026-05-22T14:48:24.767Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/78/bf00a7b02239c12bb02ddcc3c0b971bfcc36e578c5a44f1ccfef5b458545/wrapt-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f53ac9f3ef573326d009ed809beff4efcac6451931c2b8132586da4b9e53ff31", size = 166560, upload-time = "2026-05-22T14:48:26.83Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/93/6390ca9c5b787683cef588d04f57c8d41b9a2323b5597a65f18638c90ef2/wrapt-2.2.1-cp313-cp313-win32.whl", hash = "sha256:1ffa9cfd4bdb581539951b14ae661ff20ed0c3599b3e911a131ee0ec5ac11337", size = 77817, upload-time = "2026-05-22T14:48:28.221Z" },
+ { url = "https://files.pythonhosted.org/packages/97/73/ce10f0e71c0cfaa1a65faadb8efd4852028b3bb9ba28932b8889df769d38/wrapt-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:368eac1e20fd0bb03dd3cc42bf9887154c3861b60989389ccb5fac032617d215", size = 80736, upload-time = "2026-05-22T14:48:30.139Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/4c/89f4a6818fafbbd840330e4fa3873073e1bfc166133a64cac7f8fde7a5e3/wrapt-2.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:c754dafdf5aaf0b401b644a90a30046929a0dd1a536e0ff0ec959a59155d9c7f", size = 79099, upload-time = "2026-05-22T14:48:31.405Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/f2/9a8741c46f8c208ac0a45b25ba170bcb4fb72a2781d5fb97dbd7b6be73cb/wrapt-2.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ed928d0fda15fc0adc8d13305c8b3c0f2fba5b0669950c9e6d019d9162a3b3e8", size = 82802, upload-time = "2026-05-22T14:48:33.307Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/0d/e9c855716a3705eef1416456bdf062b60620726fdc59428ff670fc3c60dc/wrapt-2.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fafb4e739e43544d12cb4abd1605fd4683b6ca6a9ad682b7fd8f4d21973eafa8", size = 83329, upload-time = "2026-05-22T14:48:34.593Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/d6/a88f1c13112b7831adac75cea65d8310e0d696d570c8961844c90a57b865/wrapt-2.2.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:74d6a0c31472fe5d814917266b9f46495d7c61ed890af08b468acea92fb89a8d", size = 202937, upload-time = "2026-05-22T14:48:35.859Z" },
+ { url = "https://files.pythonhosted.org/packages/42/65/e29d54aef06a4d898a5b8a25589a0b3769bde454f922fad8f6f89fbfb650/wrapt-2.2.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab5be648d5a0b86b7438864f8df3c705a65cef35a2fd3e5561e3e203167e0f27", size = 209997, upload-time = "2026-05-22T14:48:38.153Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/91/e4454263516cf0e12640912fbca9a83654e424f0a6ddb79f5cd7ce14bf33/wrapt-2.2.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d8f204c8e3a8bf9ece17e0a83d137fd807440977f8a5e762d59306795011440", size = 194856, upload-time = "2026-05-22T14:48:39.69Z" },
+ { url = "https://files.pythonhosted.org/packages/de/d0/fe0ee202286afdf4a7f77dd29f195703145764d572aec209c5086e57d924/wrapt-2.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d047f6498c973874ba08ac3f97c69a2c4b2211c8de6f4c205f75cb1c9522596e", size = 205654, upload-time = "2026-05-22T14:48:43.456Z" },
+ { url = "https://files.pythonhosted.org/packages/23/b6/87d860dfc6460c246af70b1fd5c8b76df77571b42a493459423ded94fd7d/wrapt-2.2.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:7a4fdb9326aab4a5a477a1640e5ad786a8495901009d7e7b038371edd23a9d2b", size = 192206, upload-time = "2026-05-22T14:48:44.858Z" },
+ { url = "https://files.pythonhosted.org/packages/df/46/3eea8cde077d985f239a38c0257087b8064fd9ee9b1a99e282d2c86da4ef/wrapt-2.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c8cc5094b08abeae52da9c73c8a32003623be691a5193df2f4e3eac3d557c394", size = 198428, upload-time = "2026-05-22T14:48:46.319Z" },
+ { url = "https://files.pythonhosted.org/packages/18/dc/b927ee9c7fc67adc3a5658f246a0d275425eb840ba36e7b702e70f18bde8/wrapt-2.2.1-cp313-cp313t-win32.whl", hash = "sha256:9907a4402ab6db12b7077a0ea5d7a4d028ecb22c8eee2b53527080d347cd1562", size = 79448, upload-time = "2026-05-22T14:48:47.901Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/b3/fd30b473fe498c70e6b9a5f328b8d3fbaf1b8c3c481465f59724bba8eb70/wrapt-2.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:5590d63f5243251641cf543009b4c9314a79d0598fdb8a8e4cfc918494536c53", size = 83021, upload-time = "2026-05-22T14:48:49.201Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/f3/96c39153a8737a6e9aa85adef254ac4195bea3f2d24efc60472ccc3c9e2e/wrapt-2.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:c318a64b53d97b841d7b5e637517e50a27be64bc695128422953d4b21710954e", size = 80295, upload-time = "2026-05-22T14:48:50.479Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/a3/11d7f34ebbf3231bc907a3e6d5ee051b14d034c1bc7b65a97d5cc00516df/wrapt-2.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6f56a647e4eaf5f0ca40330fb070f566bdf9f7b0db89a1af20d71c28dcd7a0ab", size = 80879, upload-time = "2026-05-22T14:48:51.802Z" },
+ { url = "https://files.pythonhosted.org/packages/13/3c/b74cfd984cef560b900fb1a727af20352d89e1f06bf2e1114dd3f00f5f5a/wrapt-2.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:64b7deeda4b70408e382328d8bbe52a256fe9bc63ae3db86d804608367e5422c", size = 81462, upload-time = "2026-05-22T14:48:53.18Z" },
+ { url = "https://files.pythonhosted.org/packages/15/a3/7c8f704b8dc07dfe0a5d01c2edbfd88317aa8e5e3fa7c743eb7a085ae767/wrapt-2.2.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b9cf53ba90717db2e292401de290776c498d4bbfb0d4a559ca2895db8b9dcb5c", size = 167251, upload-time = "2026-05-22T14:48:54.562Z" },
+ { url = "https://files.pythonhosted.org/packages/80/85/a34d1888d97247da6c2ff6118c3a721c73ed8cc4dd198c00208bb73b6f80/wrapt-2.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf3638274ab9d9b724c9baa0b4c04e132cd6faefb78b4dd3dd1a02a4bdaad41e", size = 166316, upload-time = "2026-05-22T14:48:56.065Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/d7/72ffaeb01eebc704afe3fb99e840480f4bda45f0fa66e3381b6a39251c8f/wrapt-2.2.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aed9658797d0b45d6c49adcfc6b41f66e6f2d0c6de3ec79e16cf4b1855df240f", size = 157952, upload-time = "2026-05-22T14:48:57.924Z" },
+ { url = "https://files.pythonhosted.org/packages/24/5b/36f5d6b024e4edfdd90b140742d11ebcf7836daf5c9daf326c55c24db412/wrapt-2.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1d676ee388bc42a04d56dd7deb5605244dac2e35cc2fadbb43c9fa25bbd93508", size = 166130, upload-time = "2026-05-22T14:48:59.384Z" },
+ { url = "https://files.pythonhosted.org/packages/81/06/9296d9e97bfdef5483dfcc859d57b095b257144b2bc5300ab521e06f4bc7/wrapt-2.2.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e395f7bc31851ef9b612050368cb446e9bc14cd7454b025018980349caf25ae5", size = 156604, upload-time = "2026-05-22T14:49:00.921Z" },
+ { url = "https://files.pythonhosted.org/packages/53/37/16953929ed6776175720e58fc966e779926d8d71e2c7b2273230590ca71f/wrapt-2.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f1845c2a8cc1180ccccfa45785dd06f562730d19ef75be180334254012b6283", size = 166007, upload-time = "2026-05-22T14:49:02.332Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/73/20ee58c0612dae7c31131a7095345812ed2c7b389019e175f68cde34e5b4/wrapt-2.2.1-cp314-cp314-win32.whl", hash = "sha256:436addbc4bb4fc0a88c702577f51195d7d73683a7f3e0e5b253d8404d7847243", size = 78327, upload-time = "2026-05-22T14:49:03.722Z" },
+ { url = "https://files.pythonhosted.org/packages/22/b3/ef7c3295d02e0448a71c639a36a057f46d524d057c9486291a7a3039e65c/wrapt-2.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:50972a1d974ea07725a7f6b1cec5f8759008afd030a0024843ebe7d52de47f2b", size = 81144, upload-time = "2026-05-22T14:49:05.093Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/dc/7bdf336953f99f4ceb0a584bb8870e42c8f26f93ea10c87834dad62f1668/wrapt-2.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:1c9934ea5d92957e3cd0adbc0845539dccfd62710ebe16195a8c66c53954db36", size = 79569, upload-time = "2026-05-22T14:49:06.413Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/6d/6dfae80150ff1919c356d1dd528f049bcdfaae29b4d284bc957e022caef4/wrapt-2.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17de18fc12cea55b8a9587314cb830573e37fb33b247a7515696350863714188", size = 82892, upload-time = "2026-05-22T14:49:07.925Z" },
+ { url = "https://files.pythonhosted.org/packages/82/7b/4e34766a7d7804ffce9e71befe47e9b3225dc350c49c94493c4ab39fd3a5/wrapt-2.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9dec1aca52dddde7df94818310fa2fe79739c8f385b2014c4cb1035f5508199", size = 83333, upload-time = "2026-05-22T14:49:09.257Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/57/0b34db3e8de44ccfece62d7b337abd1631dd810f5adc5f3db571727836b5/wrapt-2.2.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:69f2e9244542cb34dd59c7f073445b9e54ad9f3fce8d93606c368a1b499fc413", size = 202899, upload-time = "2026-05-22T14:49:10.572Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/45/ac0c459f154b99d92789a6cba7ca727185b83513b986f8ec7fe2aacddcbf/wrapt-2.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d83966dc7f4f45e8b97b5933685ac2e6e67fc0e19246ea314bceb9a8970c956", size = 209986, upload-time = "2026-05-22T14:49:12.229Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/e4/77e37ff33ad018fa81ade52c25fa327b80b56f81d734279a63614fcb4cbc/wrapt-2.2.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:78b0aa6bfb7be8deed0ab23e7aa028cc5210c29bc2d32a04d52b50e517a7307e", size = 194893, upload-time = "2026-05-22T14:49:14.139Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/9d/7ea651d1ab032fc5fa222fbec91d0f8a1397f6ae04ebb93fa7219aa921d7/wrapt-2.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:05d5cb74d1b232ec8cfa130a8f900708699ff2491d97b8f85a4cdc5996294b85", size = 205636, upload-time = "2026-05-22T14:49:15.714Z" },
+ { url = "https://files.pythonhosted.org/packages/09/af/8e88031a701275b9085c54e64bc88c0b1cd55c77eadd400691c371cd76c4/wrapt-2.2.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f6518b94edb9150452e9aba08027d4cc293433753ec1fbefb4629a21cbc74181", size = 192267, upload-time = "2026-05-22T14:49:17.283Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/a8/e657ca876b06710194f243d81c4b0896ade646e244bdbec2d87c8c56a8bd/wrapt-2.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ed55af48b3eb28f43228ca2306788892bcb629eb2b5c4876e2a3659872c2f17a", size = 198378, upload-time = "2026-05-22T14:49:18.785Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/59/822efe4ea722a3961331bfa35b7d90937790d2c20f0616de1997ccc3aebd/wrapt-2.2.1-cp314-cp314t-win32.whl", hash = "sha256:2e08688ab16525897da6589d56d0aebaf417bbe91c2d8e3b96203b1efa596e85", size = 80226, upload-time = "2026-05-22T14:49:20.264Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/31/2a7dc5f6abb2fca0b6e1610e120419f603650aceb4f1d3ac4cae0354e162/wrapt-2.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:fd0135d34387f5fd087d9be368ea77ea89cf2451dc1cd1c622d35021bcb3ab50", size = 83835, upload-time = "2026-05-22T14:49:21.634Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/c0/782b86e28d1ceebeb74cccea12d2cd3d2ba0bd68e3dec20b1bc5873f6127/wrapt-2.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:f70db64e8266d7c45d3b735f2e08eeb434b5e03da9a479ae42b2e2e486a21a00", size = 80722, upload-time = "2026-05-22T14:49:23.59Z" },
+ { url = "https://files.pythonhosted.org/packages/53/46/29ac9daf11a86c22a8c38cd9236c62928ccae83f7ceb06bd3b0467cf9d05/wrapt-2.2.1-py3-none-any.whl", hash = "sha256:3aafea2975caef8ca49400640dde02cc7426e798f24870ed01f490bc3cffd32f", size = 61000, upload-time = "2026-05-22T14:49:41.593Z" },
+]
diff --git a/packages/sample-app/pyproject.toml b/packages/sample-app/pyproject.toml
index fe7b26742d..04d2e30326 100644
--- a/packages/sample-app/pyproject.toml
+++ b/packages/sample-app/pyproject.toml
@@ -58,6 +58,7 @@ dependencies = [
"opentelemetry-instrumentation-vertexai",
"opentelemetry-instrumentation-google-generativeai",
"opentelemetry-instrumentation-groq",
+ "opentelemetry-instrumentation-deepseek",
"opentelemetry-instrumentation-crewai",
"opentelemetry-instrumentation-openai-agents",
"opentelemetry-instrumentation-agno",
@@ -78,6 +79,7 @@ opentelemetry-instrumentation-replicate = { path = "../opentelemetry-instrumenta
opentelemetry-instrumentation-vertexai = { path = "../opentelemetry-instrumentation-vertexai", editable = true }
opentelemetry-instrumentation-google-generativeai = { path = "../opentelemetry-instrumentation-google-generativeai", editable = true }
opentelemetry-instrumentation-groq = { path = "../opentelemetry-instrumentation-groq", editable = true }
+opentelemetry-instrumentation-deepseek = { path = "../opentelemetry-instrumentation-deepseek", editable = true }
opentelemetry-instrumentation-crewai = { path = "../opentelemetry-instrumentation-crewai", editable = true }
opentelemetry-instrumentation-openai-agents = { path = "../opentelemetry-instrumentation-openai-agents", editable = true }
opentelemetry-instrumentation-agno = { path = "../opentelemetry-instrumentation-agno", editable = true }
diff --git a/packages/sample-app/uv.lock b/packages/sample-app/uv.lock
index f4ca95e07b..6e4f0d75b4 100644
--- a/packages/sample-app/uv.lock
+++ b/packages/sample-app/uv.lock
@@ -3708,7 +3708,7 @@ wheels = [
[[package]]
name = "opentelemetry-instrumentation-agno"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-agno" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -3748,7 +3748,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-alephalpha"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-alephalpha" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -3786,7 +3786,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-anthropic"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-anthropic" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -3824,7 +3824,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-bedrock"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-bedrock" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -3860,7 +3860,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-chromadb"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-chromadb" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -3895,7 +3895,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-cohere"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-cohere" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -3933,7 +3933,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-crewai"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-crewai" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -3967,9 +3967,47 @@ test = [
{ name = "pytest-sugar", specifier = "==1.0.0" },
]
+[[package]]
+name = "opentelemetry-instrumentation-deepseek"
+version = "0.61.0"
+source = { editable = "../opentelemetry-instrumentation-deepseek" }
+dependencies = [
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-instrumentation" },
+ { name = "opentelemetry-semantic-conventions" },
+ { name = "opentelemetry-semantic-conventions-ai" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "openai", marker = "extra == 'instruments'", specifier = ">=1.0.0" },
+ { name = "opentelemetry-api", specifier = ">=1.38.0,<2" },
+ { name = "opentelemetry-instrumentation", specifier = ">=0.59b0" },
+ { name = "opentelemetry-semantic-conventions", specifier = ">=0.59b0" },
+ { name = "opentelemetry-semantic-conventions-ai", specifier = ">=0.5.1,<0.6.0" },
+]
+provides-extras = ["instruments"]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "autopep8", specifier = ">=2.2.0,<3" },
+ { name = "pytest", specifier = ">=8.2.2,<9" },
+ { name = "pytest-sugar", specifier = "==1.0.0" },
+ { name = "ruff", specifier = ">=0.4.0" },
+]
+test = [
+ { name = "openai", specifier = ">=1.0.0" },
+ { name = "opentelemetry-sdk", specifier = ">=1.38.0,<2" },
+ { name = "pytest", specifier = ">=8.2.2,<9" },
+ { name = "pytest-asyncio", specifier = ">=0.23.7,<0.24.0" },
+ { name = "pytest-recording", specifier = ">=0.13.1,<0.14.0" },
+ { name = "pytest-sugar", specifier = "==1.0.0" },
+ { name = "vcrpy", specifier = ">=8.0.0,<9" },
+]
+
[[package]]
name = "opentelemetry-instrumentation-google-generativeai"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-google-generativeai" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4006,7 +4044,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-groq"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-groq" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4044,7 +4082,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-haystack"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-haystack" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4081,7 +4119,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-lancedb"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-lancedb" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4121,7 +4159,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-langchain"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-langchain" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4175,7 +4213,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-llamaindex"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-llamaindex" }
dependencies = [
{ name = "inflection" },
@@ -4238,7 +4276,7 @@ wheels = [
[[package]]
name = "opentelemetry-instrumentation-marqo"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-marqo" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4276,7 +4314,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-mcp"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-mcp" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4316,7 +4354,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-milvus"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-milvus" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4330,7 +4368,7 @@ requires-dist = [
{ name = "opentelemetry-api", specifier = ">=1.38.0,<2" },
{ name = "opentelemetry-instrumentation", specifier = ">=0.59b0" },
{ name = "opentelemetry-semantic-conventions", specifier = ">=0.59b0" },
- { name = "opentelemetry-semantic-conventions-ai", specifier = ">=0.5.1,<0.6.0" },
+ { name = "opentelemetry-semantic-conventions-ai", editable = "../opentelemetry-semantic-conventions-ai" },
{ name = "pymilvus", marker = "extra == 'instruments'" },
]
provides-extras = ["instruments"]
@@ -4352,7 +4390,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-mistralai"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-mistralai" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4389,7 +4427,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-ollama"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-ollama" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4427,7 +4465,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-openai"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-openai" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4464,7 +4502,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-openai-agents"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-openai-agents" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4503,7 +4541,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-pinecone"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-pinecone" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4517,7 +4555,7 @@ requires-dist = [
{ name = "opentelemetry-api", specifier = ">=1.38.0,<2" },
{ name = "opentelemetry-instrumentation", specifier = ">=0.59b0" },
{ name = "opentelemetry-semantic-conventions", specifier = ">=0.59b0" },
- { name = "opentelemetry-semantic-conventions-ai", specifier = ">=0.5.1,<0.6.0" },
+ { name = "opentelemetry-semantic-conventions-ai", editable = "../opentelemetry-semantic-conventions-ai" },
{ name = "pinecone", marker = "extra == 'instruments'", specifier = ">=5.1.0,<9" },
]
provides-extras = ["instruments"]
@@ -4542,7 +4580,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-qdrant"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-qdrant" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4590,7 +4628,7 @@ wheels = [
[[package]]
name = "opentelemetry-instrumentation-replicate"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-replicate" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4642,7 +4680,7 @@ wheels = [
[[package]]
name = "opentelemetry-instrumentation-sagemaker"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-sagemaker" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4707,7 +4745,7 @@ wheels = [
[[package]]
name = "opentelemetry-instrumentation-together"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-together" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4745,7 +4783,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-transformers"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-transformers" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4792,7 +4830,7 @@ wheels = [
[[package]]
name = "opentelemetry-instrumentation-vertexai"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-vertexai" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4830,7 +4868,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-voyageai"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-voyageai" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4868,7 +4906,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-watsonx"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-watsonx" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4906,7 +4944,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-weaviate"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-weaviate" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -4945,7 +4983,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-writer"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-writer" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -5023,15 +5061,25 @@ wheels = [
[[package]]
name = "opentelemetry-semantic-conventions-ai"
-version = "0.5.1"
-source = { registry = "https://pypi.org/simple" }
+version = "0.5.2"
+source = { editable = "../opentelemetry-semantic-conventions-ai" }
dependencies = [
{ name = "opentelemetry-sdk" },
{ name = "opentelemetry-semantic-conventions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/24/02/10aeacc37a38a3a8fa16ff67bec1ae3bf882539f6f9efb0f70acf802ca2d/opentelemetry_semantic_conventions_ai-0.5.1.tar.gz", hash = "sha256:153906200d8c1d2f8e09bd78dbef526916023de85ac3dab35912bfafb69ff04c", size = 26533, upload-time = "2026-03-26T14:20:38.73Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/55/22/41fb05f1dc5fda2c468e05a41814c20859016c85117b66c8a257cae814f6/opentelemetry_semantic_conventions_ai-0.5.1-py3-none-any.whl", hash = "sha256:25aeb22bd261543b4898a73824026d96770e5351209c7d07a0b1314762b1f6e4", size = 11250, upload-time = "2026-03-26T14:20:37.108Z" },
+
+[package.metadata]
+requires-dist = [
+ { name = "opentelemetry-sdk", specifier = ">=1.38.0,<2" },
+ { name = "opentelemetry-semantic-conventions", specifier = ">=0.59b0" },
+]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "autopep8", specifier = ">=2.2.0,<3" },
+ { name = "pytest", specifier = ">=8.2.2,<9" },
+ { name = "pytest-sugar", specifier = "==1.0.0" },
+ { name = "ruff", specifier = ">=0.4.0" },
]
[[package]]
@@ -6462,6 +6510,7 @@ dependencies = [
{ name = "opentelemetry-instrumentation-anthropic" },
{ name = "opentelemetry-instrumentation-bedrock" },
{ name = "opentelemetry-instrumentation-crewai" },
+ { name = "opentelemetry-instrumentation-deepseek" },
{ name = "opentelemetry-instrumentation-google-generativeai" },
{ name = "opentelemetry-instrumentation-groq" },
{ name = "opentelemetry-instrumentation-haystack" },
@@ -6530,6 +6579,7 @@ requires-dist = [
{ name = "opentelemetry-instrumentation-anthropic", editable = "../opentelemetry-instrumentation-anthropic" },
{ name = "opentelemetry-instrumentation-bedrock", editable = "../opentelemetry-instrumentation-bedrock" },
{ name = "opentelemetry-instrumentation-crewai", editable = "../opentelemetry-instrumentation-crewai" },
+ { name = "opentelemetry-instrumentation-deepseek", editable = "../opentelemetry-instrumentation-deepseek" },
{ name = "opentelemetry-instrumentation-google-generativeai", editable = "../opentelemetry-instrumentation-google-generativeai" },
{ name = "opentelemetry-instrumentation-groq", editable = "../opentelemetry-instrumentation-groq" },
{ name = "opentelemetry-instrumentation-haystack", editable = "../opentelemetry-instrumentation-haystack" },
@@ -7090,7 +7140,7 @@ wheels = [
[[package]]
name = "traceloop-sdk"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../traceloop-sdk" }
dependencies = [
{ name = "aiohttp" },
@@ -7108,6 +7158,7 @@ dependencies = [
{ name = "opentelemetry-instrumentation-chromadb" },
{ name = "opentelemetry-instrumentation-cohere" },
{ name = "opentelemetry-instrumentation-crewai" },
+ { name = "opentelemetry-instrumentation-deepseek" },
{ name = "opentelemetry-instrumentation-google-generativeai" },
{ name = "opentelemetry-instrumentation-groq" },
{ name = "opentelemetry-instrumentation-haystack" },
@@ -7161,6 +7212,7 @@ requires-dist = [
{ name = "opentelemetry-instrumentation-chromadb", editable = "../opentelemetry-instrumentation-chromadb" },
{ name = "opentelemetry-instrumentation-cohere", editable = "../opentelemetry-instrumentation-cohere" },
{ name = "opentelemetry-instrumentation-crewai", editable = "../opentelemetry-instrumentation-crewai" },
+ { name = "opentelemetry-instrumentation-deepseek", editable = "../opentelemetry-instrumentation-deepseek" },
{ name = "opentelemetry-instrumentation-google-generativeai", editable = "../opentelemetry-instrumentation-google-generativeai" },
{ name = "opentelemetry-instrumentation-groq", editable = "../opentelemetry-instrumentation-groq" },
{ name = "opentelemetry-instrumentation-haystack", editable = "../opentelemetry-instrumentation-haystack" },
diff --git a/packages/traceloop-sdk/pyproject.toml b/packages/traceloop-sdk/pyproject.toml
index 8c913da49c..35adb7bcbb 100644
--- a/packages/traceloop-sdk/pyproject.toml
+++ b/packages/traceloop-sdk/pyproject.toml
@@ -52,6 +52,7 @@ dependencies = [
"opentelemetry-instrumentation-alephalpha",
"opentelemetry-instrumentation-marqo",
"opentelemetry-instrumentation-groq",
+ "opentelemetry-instrumentation-deepseek",
"opentelemetry-instrumentation-mcp",
"colorama>=0.4.6,<0.5.0",
"tenacity>=8.2.3,<10.0",
@@ -162,6 +163,7 @@ opentelemetry-instrumentation-bedrock = { path = "../opentelemetry-instrumentati
opentelemetry-instrumentation-chromadb = { path = "../opentelemetry-instrumentation-chromadb", editable = true }
opentelemetry-instrumentation-cohere = { path = "../opentelemetry-instrumentation-cohere", editable = true }
opentelemetry-instrumentation-crewai = { path = "../opentelemetry-instrumentation-crewai", editable = true }
+opentelemetry-instrumentation-deepseek = { path = "../opentelemetry-instrumentation-deepseek", editable = true }
opentelemetry-instrumentation-google-generativeai = { path = "../opentelemetry-instrumentation-google-generativeai", editable = true }
opentelemetry-instrumentation-groq = { path = "../opentelemetry-instrumentation-groq", editable = true }
opentelemetry-instrumentation-haystack = { path = "../opentelemetry-instrumentation-haystack", editable = true }
diff --git a/packages/traceloop-sdk/uv.lock b/packages/traceloop-sdk/uv.lock
index 66a99bad13..8b2ca97c88 100644
--- a/packages/traceloop-sdk/uv.lock
+++ b/packages/traceloop-sdk/uv.lock
@@ -1517,7 +1517,7 @@ wheels = [
[[package]]
name = "opentelemetry-instrumentation-agno"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-agno" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -1557,7 +1557,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-alephalpha"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-alephalpha" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -1595,7 +1595,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-anthropic"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-anthropic" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -1633,7 +1633,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-bedrock"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-bedrock" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -1669,7 +1669,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-chromadb"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-chromadb" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -1704,7 +1704,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-cohere"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-cohere" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -1742,7 +1742,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-crewai"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-crewai" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -1776,9 +1776,47 @@ test = [
{ name = "pytest-sugar", specifier = "==1.0.0" },
]
+[[package]]
+name = "opentelemetry-instrumentation-deepseek"
+version = "0.61.0"
+source = { editable = "../opentelemetry-instrumentation-deepseek" }
+dependencies = [
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-instrumentation" },
+ { name = "opentelemetry-semantic-conventions" },
+ { name = "opentelemetry-semantic-conventions-ai" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "openai", marker = "extra == 'instruments'", specifier = ">=1.0.0" },
+ { name = "opentelemetry-api", specifier = ">=1.38.0,<2" },
+ { name = "opentelemetry-instrumentation", specifier = ">=0.59b0" },
+ { name = "opentelemetry-semantic-conventions", specifier = ">=0.59b0" },
+ { name = "opentelemetry-semantic-conventions-ai", specifier = ">=0.5.1,<0.6.0" },
+]
+provides-extras = ["instruments"]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "autopep8", specifier = ">=2.2.0,<3" },
+ { name = "pytest", specifier = ">=8.2.2,<9" },
+ { name = "pytest-sugar", specifier = "==1.0.0" },
+ { name = "ruff", specifier = ">=0.4.0" },
+]
+test = [
+ { name = "openai", specifier = ">=1.0.0" },
+ { name = "opentelemetry-sdk", specifier = ">=1.38.0,<2" },
+ { name = "pytest", specifier = ">=8.2.2,<9" },
+ { name = "pytest-asyncio", specifier = ">=0.23.7,<0.24.0" },
+ { name = "pytest-recording", specifier = ">=0.13.1,<0.14.0" },
+ { name = "pytest-sugar", specifier = "==1.0.0" },
+ { name = "vcrpy", specifier = ">=8.0.0,<9" },
+]
+
[[package]]
name = "opentelemetry-instrumentation-google-generativeai"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-google-generativeai" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -1815,7 +1853,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-groq"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-groq" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -1853,7 +1891,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-haystack"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-haystack" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -1890,7 +1928,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-lancedb"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-lancedb" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -1930,7 +1968,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-langchain"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-langchain" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -1984,7 +2022,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-llamaindex"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-llamaindex" }
dependencies = [
{ name = "inflection" },
@@ -2047,7 +2085,7 @@ wheels = [
[[package]]
name = "opentelemetry-instrumentation-marqo"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-marqo" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -2085,7 +2123,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-mcp"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-mcp" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -2125,7 +2163,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-milvus"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-milvus" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -2161,7 +2199,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-mistralai"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-mistralai" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -2198,7 +2236,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-ollama"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-ollama" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -2236,7 +2274,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-openai"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-openai" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -2273,7 +2311,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-openai-agents"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-openai-agents" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -2312,7 +2350,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-pinecone"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-pinecone" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -2351,7 +2389,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-qdrant"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-qdrant" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -2399,7 +2437,7 @@ wheels = [
[[package]]
name = "opentelemetry-instrumentation-replicate"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-replicate" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -2451,7 +2489,7 @@ wheels = [
[[package]]
name = "opentelemetry-instrumentation-sagemaker"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-sagemaker" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -2516,7 +2554,7 @@ wheels = [
[[package]]
name = "opentelemetry-instrumentation-together"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-together" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -2554,7 +2592,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-transformers"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-transformers" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -2601,7 +2639,7 @@ wheels = [
[[package]]
name = "opentelemetry-instrumentation-vertexai"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-vertexai" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -2639,7 +2677,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-voyageai"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-voyageai" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -2677,7 +2715,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-watsonx"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-watsonx" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -2715,7 +2753,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-weaviate"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-weaviate" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -2754,7 +2792,7 @@ test = [
[[package]]
name = "opentelemetry-instrumentation-writer"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "../opentelemetry-instrumentation-writer" }
dependencies = [
{ name = "opentelemetry-api" },
@@ -3878,7 +3916,7 @@ wheels = [
[[package]]
name = "traceloop-sdk"
-version = "0.60.0"
+version = "0.61.0"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
@@ -3896,6 +3934,7 @@ dependencies = [
{ name = "opentelemetry-instrumentation-chromadb" },
{ name = "opentelemetry-instrumentation-cohere" },
{ name = "opentelemetry-instrumentation-crewai" },
+ { name = "opentelemetry-instrumentation-deepseek" },
{ name = "opentelemetry-instrumentation-google-generativeai" },
{ name = "opentelemetry-instrumentation-groq" },
{ name = "opentelemetry-instrumentation-haystack" },
@@ -3978,6 +4017,7 @@ requires-dist = [
{ name = "opentelemetry-instrumentation-chromadb", editable = "../opentelemetry-instrumentation-chromadb" },
{ name = "opentelemetry-instrumentation-cohere", editable = "../opentelemetry-instrumentation-cohere" },
{ name = "opentelemetry-instrumentation-crewai", editable = "../opentelemetry-instrumentation-crewai" },
+ { name = "opentelemetry-instrumentation-deepseek", editable = "../opentelemetry-instrumentation-deepseek" },
{ name = "opentelemetry-instrumentation-google-generativeai", editable = "../opentelemetry-instrumentation-google-generativeai" },
{ name = "opentelemetry-instrumentation-groq", editable = "../opentelemetry-instrumentation-groq" },
{ name = "opentelemetry-instrumentation-haystack", editable = "../opentelemetry-instrumentation-haystack" },