diff --git a/CHANGELOG.md b/CHANGELOG.md index cd62674e..ca032e40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -281,4 +281,4 @@ Users can be now overwrite the input and ouput attributes of spans created by in - Added utility to set input and output data for any active span in a trace -[0.1.86]: https://github.com/KeyValueSoftwareSystems/netra-sdk-py/tree/main +[0.1.89]: https://github.com/KeyValueSoftwareSystems/netra-sdk-py/tree/main diff --git a/netra/__init__.py b/netra/__init__.py index bf4efa91..e27118c1 100644 --- a/netra/__init__.py +++ b/netra/__init__.py @@ -292,6 +292,20 @@ def shutdown(cls) -> None: except Exception: pass + # Close simulation HTTP client + if hasattr(cls, "simulation") and cls.simulation is not None: + try: + cls.simulation.close() + except Exception: + pass + + # Close evaluation HTTP client + if hasattr(cls, "evaluation") and cls.evaluation is not None: + try: + cls.evaluation.close() + except Exception: + pass + @classmethod def get_meter(cls, name: str = "netra", version: Optional[str] = None) -> otel_metrics.Meter: """ @@ -409,6 +423,47 @@ def set_custom_event(cls, event_name: str, attributes: Any) -> None: else: logger.warning("Both event_name and attributes must be provided for custom events.") + @classmethod + def record_exception( + cls, + exception: BaseException, + attributes: Optional[Dict[str, Any]] = None, + ) -> None: + """Record a caught exception on the currently active span. + + Use this inside ``except`` blocks to attach exception details to the + current span when the exception is being handled and will not propagate + to the SDK's automatic capture logic. + + The method adds a standard OpenTelemetry exception event (with type, + message, and stacktrace), sets the span status to ERROR, and records + the ``netra.error_message`` attribute for consistency with the rest of + the SDK. + + Args: + exception: The exception instance to record. + attributes: Optional extra attributes to attach to the exception + event. + + Example:: + + @workflow + def process_order(order_id: str) -> str: + try: + result = call_payment_api(order_id) + except PaymentError as exc: + Netra.record_exception(exc) + return "fallback_result" + return result + """ + if not isinstance(exception, BaseException): + logger.error( + "record_exception: exception must be a BaseException instance, got %s", + type(exception), + ) + return + SessionManager.record_exception(exception, attributes=attributes) + @classmethod def add_conversation(cls, conversation_type: ConversationType, role: str, content: Any) -> None: """ @@ -461,6 +516,28 @@ def set_root_output(cls, value: Any) -> None: """ SessionManager.set_root_output(value) + @classmethod + def set_root_output_stream(cls, value: Any) -> Any: + """ + Wrap a stream so the accumulated output is set on the root span when iteration ends. + + The returned object is a transparent proxy — iterate over it instead of the original:: + + stream = Netra.set_root_output_stream(stream) + for chunk in stream: + ... + + Supports both sync and async iterables. Returns *value* unchanged if no active trace + context exists or if *value* is not iterable. + + Args: + value: The stream to wrap (Netra-instrumented or any generic iterable). + + Returns: + A wrapped stream proxy, or *value* unchanged if wrapping is not possible. + """ + return SessionManager.set_root_output_stream(value) + @classmethod def start_span( cls, diff --git a/netra/evaluation/__init__.py b/netra/evaluation/__init__.py index 2f2959b2..72828217 100644 --- a/netra/evaluation/__init__.py +++ b/netra/evaluation/__init__.py @@ -1,16 +1,20 @@ from netra.evaluation.api import Evaluation from netra.evaluation.evaluator import BaseEvaluator from netra.evaluation.models import ( + Dataset, DatasetItem, EvaluatorConfig, EvaluatorContext, EvaluatorOutput, LocalDataset, ScoreType, + TurnType, ) __all__ = [ "Evaluation", + "Dataset", + "TurnType", "DatasetItem", "BaseEvaluator", "EvaluatorContext", diff --git a/netra/evaluation/api.py b/netra/evaluation/api.py index edb97885..98beb1c0 100644 --- a/netra/evaluation/api.py +++ b/netra/evaluation/api.py @@ -1,10 +1,11 @@ import asyncio import concurrent.futures import logging -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable, Optional from netra.config import Config from netra.evaluation.client import EvaluationHttpClient +from netra.evaluation.constants import DEFAULT_CONCURRENCY, LOG_PREFIX, MIN_CONCURRENCY, SPAN_NAME_PREFIX from netra.evaluation.models import ( AddDatasetItemResponse, CreateDatasetResponse, @@ -35,11 +36,17 @@ class Evaluation: - """Public entry-point exposed as Netra.evaluation""" + """Public entry-point exposed as Netra.evaluation. + + Attributes: + _config: The Netra configuration object. + _client: The HTTP client for evaluation API calls. + """ + + __slots__ = ("_config", "_client") def __init__(self, config: Config) -> None: - """ - Initialize the evaluation client. + """Initialize the evaluation client. Args: config: The configuration object. @@ -47,20 +54,28 @@ def __init__(self, config: Config) -> None: self._config = config self._client = EvaluationHttpClient(config) - def create_dataset(self, name: str, tags: Optional[List[str]] = None, turn_type: TurnType = TurnType.SINGLE) -> Any: - """ - Create an empty dataset and return its id on success, else None. + def close(self) -> None: + """Release resources held by the evaluation client.""" + self._client.close() + + def create_dataset( + self, + name: str, + tags: Optional[list[str]] = None, + turn_type: TurnType = TurnType.SINGLE, + ) -> Optional[CreateDatasetResponse]: + """Create an empty dataset and return its info on success, else None. Args: name: The name of the dataset. tags: Optional list of tags to associate with the dataset. - turn_type: The turn type of the dataset, either "single" or "multi". Defaults to "single". + turn_type: The turn type of the dataset. Defaults to "single". Returns: - A backend JSON response containing dataset info (id, name, tags, etc.) on success, + A CreateDatasetResponse on success, or None on failure. """ if not name: - logger.error("netra.evaluation: Failed to create dataset: dataset name is required") + logger.error("%s: Failed to create dataset: dataset name is required", LOG_PREFIX) return None response = self._client.create_dataset(name=name, tags=tags, turn_type=turn_type) @@ -84,23 +99,24 @@ def add_dataset_item( self, dataset_id: str, item: DatasetItem, - ) -> Any: - """ - Add a single item to an existing dataset + ) -> Optional[AddDatasetItemResponse]: + """Add a single item to an existing dataset. Args: dataset_id: The id of the dataset to which the item will be added. item: The dataset item to add. Returns: - A backend JSON response containing dataset item info (id, input, expected_output, etc.) on success + An AddDatasetItemResponse on success, or None on failure. """ - if not item.input: - logger.error("netra.evaluation: Skipping dataset item without required 'input'") + logger.error("%s: Skipping dataset item without required 'input'", LOG_PREFIX) return None response = self._client.add_dataset_item(dataset_id=dataset_id, item=item) + if not response: + return None + return AddDatasetItemResponse( dataset_id=response.get("datasetId", ""), project_id=response.get("projectId", ""), @@ -120,30 +136,29 @@ def add_dataset_item( deleted_at=response.get("deletedAt", None), ) - def get_dataset(self, dataset_id: str) -> Any: - """ - Get a dataset by ID. + def get_dataset(self, dataset_id: str) -> Optional[GetDatasetItemsResponse]: + """Get a dataset by ID. Args: dataset_id: The id of the dataset to retrieve. Returns: - A backend JSON response containing dataset info (id, input, expected_output etc.) on success, + A GetDatasetItemsResponse on success, or None on failure. """ if not dataset_id: - logger.error("netra.evaluation: Failed to get dataset: dataset id is required") + logger.error("%s: Failed to get dataset: dataset id is required", LOG_PREFIX) return None response = self._client.get_dataset(dataset_id) if not response: return None - dataset_items: List[DatasetItem] = [] - for item in response: - item_id = item.get("id") - item_input = item.get("input") - item_dataset_id = item.get("datasetId") - item_expected_output = item.get("expectedOutput", "") + dataset_items: list[DatasetRecord] = [] + for raw_item in response: + item_id = raw_item.get("id") + item_input = raw_item.get("input") + item_dataset_id = raw_item.get("datasetId") + item_expected_output = raw_item.get("expectedOutput", "") if item_id is None or item_dataset_id is None or item_input is None: - logger.warning("netra.evaluation: Skipping dataset item with missing required fields: %s", item) + logger.warning("%s: Skipping dataset item with missing required fields: %s", LOG_PREFIX, raw_item) continue try: dataset_items.append( @@ -155,17 +170,16 @@ def get_dataset(self, dataset_id: str) -> Any: ) ) except Exception as exc: - logger.error("netra.evaluation: Failed to parse dataset item: %s", exc) + logger.exception("%s: Failed to parse dataset item: %s", LOG_PREFIX, exc) return GetDatasetItemsResponse(items=dataset_items) def create_run( self, name: str, dataset_id: Optional[str] = None, - evaluators_config: Optional[List[EvaluatorConfig]] = None, - ) -> Any: - """ - Create a new run for the given dataset and evaluators + evaluators_config: Optional[list[EvaluatorConfig]] = None, + ) -> Optional[str]: + """Create a new run for the given dataset and evaluators. Args: name: The name of the run. @@ -173,46 +187,44 @@ def create_run( evaluators_config: Optional list of evaluators to be used for the run. Returns: - run_id: The id of the created run. + The id of the created run, or None on failure. """ - if not name: - logger.error("netra.evaluation: Failed to create run: run name is required") + logger.error("%s: Failed to create run: run name is required", LOG_PREFIX) return None - evaluators_config_dicts: Optional[List[Dict[str, Any]]] = None + evaluators_config_dicts: Optional[list[dict[str, Any]]] = None if evaluators_config: evaluators_config_dicts = [e.model_dump(by_alias=True) for e in evaluators_config] response = self._client.create_run(name=name, dataset_id=dataset_id, evaluators_config=evaluators_config_dicts) - run_id = response.get("id", None) - return run_id + if not response: + return None + run_id = response.get("id") + return str(run_id) if run_id is not None else None - def get_run_results(self, run_id: str) -> Any: - """ - Fetch test run results based on run ID. + def get_run_results(self, run_id: str) -> Optional[dict[str, Any]]: + """Fetch test run results based on run ID. Args: run_id: The id of the run to fetch. Returns: - The JSON response containing run results. + The JSON response containing run results, or None on failure. """ if not run_id: - logger.error("netra.evaluation: Failed to get run: run_id is required") + logger.error("%s: Failed to get run: run_id is required", LOG_PREFIX) return None - response = self._client.get_run_results(run_id) - return response + return self._client.get_run_results(run_id) def run_test_suite( self, name: str, data: Dataset, task: Callable[[Any], Any], - evaluators: Optional[List[Any]] = None, - max_concurrency: int = 50, - ) -> Optional[Dict[str, Any]]: - """ - Netra evaluation function to initiate a test suite. + evaluators: Optional[list[Any]] = None, + max_concurrency: int = DEFAULT_CONCURRENCY, + ) -> Optional[dict[str, Any]]: + """Run a complete evaluation test suite. Args: name: The name of the run. @@ -225,6 +237,7 @@ def run_test_suite( A dictionary containing the run id and the results of the test suite. """ validate_run_inputs(name, data, task) + max_concurrency = max(MIN_CONCURRENCY, max_concurrency) items = list(data.items) dataset_id = extract_dataset_id(items) @@ -236,18 +249,15 @@ def run_test_suite( evaluators_config=evaluators_config, ) if not run_id: - logger.error("netra.evaluation: Failed to create run") + logger.error("%s: Failed to create run", LOG_PREFIX) return None - logger.info("netra.evaluation: Initiated test run") + logger.info("%s: Initiated test run", LOG_PREFIX) try: result = run_async_safely( self._run_test_suite_async(name, data, task, evaluators, max_concurrency, run_id=run_id) ) return result - except Exception: - self._client.post_run_status(run_id, "failed") - raise except BaseException: self._client.post_run_status(run_id, "failed") raise @@ -257,12 +267,11 @@ async def _run_test_suite_async( name: str, data: Dataset, task: Callable[[Any], Any], - evaluators: Optional[List[Any]], + evaluators: Optional[list[Any]], max_concurrency: int, run_id: Optional[str] = None, - ) -> Optional[Dict[str, Any]]: - """ - Async implementation of run_test_suite. + ) -> Optional[dict[str, Any]]: + """Async implementation of run_test_suite. Args: name: The name of the run. @@ -277,17 +286,15 @@ async def _run_test_suite_async( """ items = list(data.items) total_items = len(items) - max_workers = max(5, max_concurrency) - results: List[Dict[str, Any]] = [] - bg_eval_tasks: List[asyncio.Task[None]] = [] + results: list[dict[str, Any]] = [] + background_evaluator_tasks: list[asyncio.Task[None]] = [] completed_count = 0 lock = asyncio.Lock() loop = asyncio.get_running_loop() async def on_item_completed(result: ItemProcessingResult) -> None: - """ - Handle completion of a single item processing. + """Handle completion of a single item processing. Args: result: The result of item processing. @@ -297,42 +304,41 @@ async def on_item_completed(result: ItemProcessingResult) -> None: results.append(result.item_entry) if result.should_run_evaluators and run_id: eval_task = asyncio.create_task(self._run_evaluators_for_item(run_id, result.ctx, evaluators or [])) - bg_eval_tasks.append(eval_task) + background_evaluator_tasks.append(eval_task) completed_count += 1 logger.info( - "netra.evaluation: %d/%d items processed (status=%s)", + "%s: %d/%d items processed (status=%s)", + LOG_PREFIX, completed_count, total_items, result.status, ) - def process_item_sync(idx: int, item: Any) -> ItemProcessingResult: - """ - Synchronous wrapper for thread pool execution. + def process_item_sync(idx: int, dataset_item: Any) -> ItemProcessingResult: + """Synchronous wrapper for thread pool execution. Args: idx: The index of the item. - item: The dataset item to process. + dataset_item: The dataset item to process. """ - return run_async_safely(self._process_single_item(idx, item, run_id, name, task, evaluators)) + return run_async_safely(self._process_single_item(idx, dataset_item, run_id, name, task, evaluators)) - async def process_item(idx: int, item: Any) -> None: - """ - Process a single item and handle its completion. + async def process_item(idx: int, dataset_item: Any) -> None: + """Process a single item and handle its completion. Args: idx: The index of the item. - item: The dataset item to process. + dataset_item: The dataset item to process. """ - result = await loop.run_in_executor(executor, process_item_sync, idx, item) + result = await loop.run_in_executor(executor, process_item_sync, idx, dataset_item) await on_item_completed(result) - with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: + with concurrent.futures.ThreadPoolExecutor(max_workers=max_concurrency) as executor: await asyncio.gather(*[process_item(i, item) for i, item in enumerate(items)]) - if bg_eval_tasks: - await asyncio.gather(*bg_eval_tasks, return_exceptions=True) + if background_evaluator_tasks: + await asyncio.gather(*background_evaluator_tasks, return_exceptions=True) self._client.post_run_status(run_id, "completed") # type:ignore[arg-type] return {"runId": run_id, "items": results} @@ -344,10 +350,9 @@ async def _process_single_item( run_id: Optional[str], run_name: str, task: Callable[[Any], Any], - evaluators: Optional[List[Any]], + evaluators: Optional[list[Any]], ) -> ItemProcessingResult: - """ - Process a single dataset item through the execution pipeline. + """Process a single dataset item through the execution pipeline. Args: idx: Index of the item in the dataset. @@ -386,15 +391,14 @@ async def _process_single_item( ) def _create_item_context(self, idx: int, item: Any) -> ItemContext: - """ - Create an ItemContext from a dataset item. + """Create an ItemContext from a dataset item. Args: idx: The index of the item. item: The dataset item. Returns: - ItemContext: The created ItemContext. + The created ItemContext. """ if isinstance(item, DatasetRecord): return ItemContext( @@ -416,22 +420,21 @@ async def _execute_item_pipeline( run_name: str, ctx: ItemContext, task: Callable[[Any], Any], - ) -> Dict[str, Any]: - """ - Execute the full pipeline for a single item. + ) -> dict[str, Any]: + """Execute the full pipeline for a single item. Args: run_id: The run ID. run_name: The name of the run. ctx: The item context. task: The task function to execute. - evaluators: Optional list of evaluators. - results: List to append results to. - bg_eval_tasks: List to append background evaluation tasks to. + + Returns: + A dict containing the item processing status. """ - span_name = f"TestRun.{run_name}" + span_name = f"{SPAN_NAME_PREFIX}.{run_name}" - with SpanWrapper(span_name, module_name="netra.evaluation") as span: + with SpanWrapper(span_name, module_name=LOG_PREFIX) as span: otel_span = span.get_current_span() if otel_span: span_context = otel_span.get_span_context() @@ -446,55 +449,43 @@ async def _execute_item_pipeline( "status": ctx.status, } - def _post_triggered_status(self, run_id: str, ctx: ItemContext) -> str: - """ - Post agent_triggered status and return test_run_item_id. + def _post_completed_status(self, run_id: str, ctx: ItemContext) -> Optional[str]: + """Post completed/failed status with task output. Args: run_id: The run ID. ctx: The item context. Returns: - str: The test_run_item_id. - """ - payload = build_item_payload(ctx, status="agent_triggered") - response = self._client.post_run_item(run_id, payload) - - if isinstance(response, dict): - item_id = response.get("id") or response.get("testRunItemId") - if item_id: - return str(item_id) - return f"local-{ctx.index}" - - def _post_completed_status(self, run_id: str, ctx: ItemContext) -> Any: - """ - Post completed/failed status with task output. - - Args: - run_id: The run ID. - ctx: The item context. + The run item id from the backend, or None. """ payload = build_item_payload(ctx, status=ctx.status, include_output=True) - run_item_id = self._client.post_run_item(run_id, payload) - return run_item_id + return self._client.post_run_item(run_id, payload) async def _run_evaluators_for_item( self, run_id: str, ctx: ItemContext, - evaluators: List[Any], + evaluators: list[Any], ) -> None: - """ - Run all evaluators for a single item after span ingestion. + """Run all evaluators for a single item after span ingestion. Args: run_id: The run ID. ctx: The item context. evaluators: List of evaluators. """ - await self._client.wait_for_span_ingestion(ctx.span_id) + ingestion_ok = await self._client.wait_for_span_ingestion(ctx.span_id) + if not ingestion_ok: + logger.warning( + "%s: Span ingestion timed out for item %d (span_id=%s), skipping evaluator submission", + LOG_PREFIX, + ctx.index, + ctx.span_id, + ) + return - evaluator_results: List[Dict[str, Any]] = [] + evaluator_results: list[dict[str, Any]] = [] for evaluator in evaluators: try: result = await run_single_evaluator( @@ -506,7 +497,14 @@ async def _run_evaluators_for_item( ) if result: evaluator_results.append(result) - except Exception: + except Exception as exc: + logger.exception( + "%s: Evaluator '%s' failed for item %d: %s", + LOG_PREFIX, + getattr(getattr(evaluator, "config", None), "name", "unknown"), + ctx.index, + exc, + ) continue if evaluator_results and ctx.test_run_item_id: diff --git a/netra/evaluation/client.py b/netra/evaluation/client.py index b3881c14..4ebab8d4 100644 --- a/netra/evaluation/client.py +++ b/netra/evaluation/client.py @@ -1,34 +1,71 @@ import asyncio import logging -import os import time -from typing import Any, Dict, List, Optional +from typing import Any, Optional import httpx from netra.config import Config +from netra.evaluation.constants import ( + DEFAULT_TIMEOUT, + ENV_TIMEOUT, + LOG_PREFIX, + TELEMETRY_SUFFIX, + URL_CREATE_DATASET, + URL_CREATE_RUN, + URL_DATASET_ITEMS, + URL_GET_DATASET, + URL_GET_RUN, + URL_LOCAL_EVALUATIONS, + URL_RUN_ITEM, + URL_RUN_STATUS, + URL_SPAN, +) from netra.evaluation.models import DatasetItem, TurnType +from netra.evaluation.utils import parse_env_float logger = logging.getLogger(__name__) class EvaluationHttpClient: - """ - Internal HTTP client for Evaluation APIs. + """Internal HTTP client for Evaluation APIs. + + Attributes: + _client: The underlying httpx client instance. """ + __slots__ = ("_client",) + def __init__(self, config: Config) -> None: - """ - Initialize HTTP client for evaluation endpoints. + """Initialize HTTP client for evaluation endpoints. Args: config: The configuration object. """ self._client: Optional[httpx.Client] = self._create_client(config) - def _create_client(self, config: Config) -> Optional[httpx.Client]: + def close(self) -> None: + """Close the underlying HTTP client and release connection resources.""" + if self._client: + try: + self._client.close() + except Exception: + logger.debug("%s: Error closing HTTP client", LOG_PREFIX, exc_info=True) + finally: + self._client = None + + def _ensure_client(self) -> Optional[httpx.Client]: + """Return the underlying client, logging an error if it is not initialized. + + Returns: + The httpx client, or None if not available. """ - Create an HTTP client for evaluation endpoints. + if not self._client: + logger.error("%s: Client not initialized", LOG_PREFIX) + return self._client + + def _create_client(self, config: Config) -> Optional[httpx.Client]: + """Create an HTTP client for evaluation endpoints. Args: config: The configuration object. @@ -38,22 +75,21 @@ def _create_client(self, config: Config) -> Optional[httpx.Client]: """ endpoint = (config.otlp_endpoint or "").strip() if not endpoint: - logger.error("netra.evaluation: NETRA_OTLP_ENDPOINT is required for evaluation APIs") + logger.error("%s: NETRA_OTLP_ENDPOINT is required for evaluation APIs", LOG_PREFIX) return None base_url = self._resolve_base_url(endpoint) headers = self._build_headers(config) - timeout = self._get_timeout() + timeout = parse_env_float(ENV_TIMEOUT, DEFAULT_TIMEOUT) try: return httpx.Client(base_url=base_url, headers=headers, timeout=timeout) - except Exception as exc: - logger.error("netra.evaluation: Failed to initialize evaluation HTTP client: %s", exc) + except Exception: + logger.exception("%s: Failed to initialize evaluation HTTP client", LOG_PREFIX) return None def _resolve_base_url(self, endpoint: str) -> str: - """ - Resolve base URL from endpoint. + """Extract base URL, removing telemetry suffix if present. Args: endpoint: The endpoint to resolve. @@ -62,154 +98,195 @@ def _resolve_base_url(self, endpoint: str) -> str: The resolved base URL. """ base_url = endpoint.rstrip("/") - if base_url.endswith("/telemetry"): - base_url = base_url[: -len("/telemetry")] + if base_url.endswith(TELEMETRY_SUFFIX): + base_url = base_url[: -len(TELEMETRY_SUFFIX)] return base_url - def _build_headers(self, config: Config) -> Dict[str, str]: - """ - Build Headers for Evaluation Client + def _build_headers(self, config: Config) -> dict[str, str]: + """Build request headers from configuration. Args: config: The configuration object. Returns: - The headers for evaluation client. + Dictionary of HTTP headers. """ - headers: Dict[str, str] = dict(config.headers or {}) - api_key = config.api_key - if api_key: - headers["x-api-key"] = api_key + headers: dict[str, str] = dict(config.headers or {}) + if config.api_key: + headers["x-api-key"] = config.api_key return headers - def _get_timeout(self) -> float: - """ - Get timeout for evaluation client. + def _extract_error_message( + self, + response: Optional[httpx.Response], + exc: Exception, + ) -> str: + """Extract error message from response or exception. - Returns: - The timeout for evaluation client. - """ - timeout_env = os.getenv("NETRA_EVALUATION_TIMEOUT") - if not timeout_env: - return 10.0 - try: - return float(timeout_env) - except ValueError: - logger.warning( - "netra.evaluation: Invalid NETRA_EVALUATION_TIMEOUT value '%s', using default 10.0", - timeout_env, - ) - return 10.0 + Args: + response: The HTTP response object, if available. + exc: The exception that was raised. - def create_dataset( - self, name: Optional[str], tags: Optional[List[str]] = None, turn_type: TurnType = TurnType.SINGLE - ) -> Any: + Returns: + A descriptive error message string. """ - Create an empty dataset + if response is not None: + try: + response_json = response.json() + error_data = response_json.get("error", {}) + if isinstance(error_data, dict): + msg = error_data.get("message") + if isinstance(msg, str): + return msg + except Exception: + logger.debug("%s: Could not parse error from response body", LOG_PREFIX, exc_info=True) + return str(exc) + + def _post_data( + self, + url: str, + payload: dict[str, Any], + error_context: str, + ) -> Optional[dict[str, Any]]: + """Send a POST request and return the unwrapped ``data`` envelope. Args: - name: The name of the dataset. - tags: Optional list of tags to associate with the dataset. - turn_type: The turn type of the dataset, either "single" or "multi". Defaults to "single". + url: The endpoint URL. + payload: The JSON payload to send. + error_context: Description used in the error log message. Returns: - A backend JSON response containing dataset info (id, name, tags, etc.) on success, - or None if creation fails. + The ``data`` dict from the response envelope, or None on failure. """ - if not self._client: - logger.error("netra.evaluation: Evaluation client is not initialized; cannot create dataset") + client = self._ensure_client() + if not client: return None + + response: Optional[httpx.Response] = None try: - url = "/evaluations/dataset" - payload: Dict[str, Any] = {"name": name, "tags": tags if tags else [], "turnType": turn_type.value} - response = self._client.post(url, json=payload) + response = client.post(url, json=payload) response.raise_for_status() - data = response.json() - if isinstance(data, dict) and "data" in data: - logger.info("netra.evaluation: Dataset created successfully") - return data.get("data", {}) - except Exception: - response_json = response.json() - logger.error( - "netra.evaluation: Failed to create dataset: %s", response_json.get("error").get("message", "") - ) + body = response.json() + if isinstance(body, dict) and "data" in body: + result = body["data"] + return result if isinstance(result, dict) else None + return None + except Exception as exc: + error_msg = self._extract_error_message(response, exc) + logger.exception("%s: %s: %s", LOG_PREFIX, error_context, error_msg) return None - def add_dataset_item(self, dataset_id: str, item: DatasetItem) -> Any: - """ - Add a single item to an existing dataset and return backend data (e.g., new item id). + def _get_data( + self, + url: str, + error_context: str, + ) -> Optional[Any]: + """Send a GET request and return the unwrapped ``data`` envelope. Args: - dataset_id: The id of the dataset to which the item will be added. - item_payload: The dataset item to add. + url: The endpoint URL. + error_context: Description used in the error log message. Returns: - A backend JSON response on success or {"success": False} on error. + The ``data`` value from the response envelope, or None on failure. """ - if not self._client: - logger.error("netra.evaluation: Evaluation client is not initialized; cannot add item to dataset") - return {"success": False} + client = self._ensure_client() + if not client: + return None + + response: Optional[httpx.Response] = None try: - url = f"/evaluations/dataset/{dataset_id}/items" - item_payload: Dict[str, Any] = { - "input": item.input if item.input else None, - "expectedOutput": item.expected_output if item.expected_output else None, - "tags": item.tags if item.tags else None, - "metadata": item.metadata if item.metadata else None, - } - response = self._client.post(url, json=item_payload) + response = client.get(url) response.raise_for_status() - data = response.json() - if isinstance(data, dict) and "data" in data: - logger.info("netra.evaluation: Dataset item added successfully") - return data.get("data", {}) - except Exception: - response_json = response.json() - logger.error( - "netra.evaluation: Failed to add item to dataset '%s': %s", - dataset_id, - response_json.get("error").get("message", ""), - ) + body = response.json() + if isinstance(body, dict) and "data" in body: + return body["data"] + return None + except Exception as exc: + error_msg = self._extract_error_message(response, exc) + logger.exception("%s: %s: %s", LOG_PREFIX, error_context, error_msg) return None - def get_dataset(self, dataset_id: str) -> Any: + def create_dataset( + self, + name: Optional[str], + tags: Optional[list[str]] = None, + turn_type: TurnType = TurnType.SINGLE, + ) -> Optional[dict[str, Any]]: + """Create an empty dataset. + + Args: + name: The name of the dataset. + tags: Optional list of tags to associate with the dataset. + turn_type: The turn type of the dataset. Defaults to "single". + + Returns: + A backend JSON response containing dataset info on success, or None on failure. + """ + payload: dict[str, Any] = {"name": name, "tags": tags if tags else [], "turnType": turn_type.value} + result = self._post_data(URL_CREATE_DATASET, payload, "Failed to create dataset") + if result is not None: + logger.info("%s: Dataset created successfully", LOG_PREFIX) + return result + + def add_dataset_item(self, dataset_id: str, item: DatasetItem) -> Optional[dict[str, Any]]: + """Add a single item to an existing dataset. + + Args: + dataset_id: The id of the dataset to which the item will be added. + item: The dataset item to add. + + Returns: + A backend JSON response on success, or None on failure. """ - Fetch dataset items for a dataset id. + url = URL_DATASET_ITEMS.format(dataset_id=dataset_id) + item_payload: dict[str, Any] = { + "input": item.input if item.input else None, + "expectedOutput": item.expected_output if item.expected_output else None, + "tags": item.tags if item.tags else None, + "metadata": item.metadata if item.metadata else None, + } + result = self._post_data(url, item_payload, f"Failed to add item to dataset '{dataset_id}'") + if result is not None: + logger.info("%s: Dataset item added successfully", LOG_PREFIX) + return result + + def get_dataset(self, dataset_id: str) -> Optional[list[dict[str, Any]]]: + """Fetch dataset items for a dataset id. Args: dataset_id: The id of the dataset to fetch. Returns: - A list of dataset items. + A list of dataset item dicts, or None on failure. """ - if not self._client: - logger.error("netra.evaluation: Evaluation client is not initialized; cannot fetch dataset") - return {"success": False} + client = self._ensure_client() + if not client: + return None + + response: Optional[httpx.Response] = None try: - url = f"/evaluations/dataset/{dataset_id}" - response = self._client.get(url) + url = URL_GET_DATASET.format(dataset_id=dataset_id) + response = client.get(url) response.raise_for_status() - data = response.json() - if isinstance(data, dict) and "data" in data: - logger.info("netra.evaluation: Dataset fetched successfully") - return data.get("data", []) - except Exception: - response_json = response.json() - logger.error( - "netra.evaluation: Failed to fetch dataset '%s': %s", - dataset_id, - response_json.get("error").get("message", ""), - ) + body = response.json() + if isinstance(body, dict) and "data" in body: + logger.info("%s: Dataset fetched successfully", LOG_PREFIX) + data = body["data"] + return data if isinstance(data, list) else None + return None + except Exception as exc: + error_msg = self._extract_error_message(response, exc) + logger.exception("%s: Failed to fetch dataset '%s': %s", LOG_PREFIX, dataset_id, error_msg) return None def create_run( self, name: str, dataset_id: Optional[str] = None, - evaluators_config: Optional[List[Dict[str, Any]]] = None, - ) -> Any: - """ - Create a new run based on the provided name, dataset_id, and evaluators_config. + evaluators_config: Optional[list[dict[str, Any]]] = None, + ) -> Optional[dict[str, Any]]: + """Create a new run based on the provided name, dataset_id, and evaluators_config. Args: name: The name of the run. @@ -217,166 +294,92 @@ def create_run( evaluators_config: Optional list of evaluators to be used for the run. Returns: - A backend JSON response containing run_id + A backend JSON response containing run info, or None on failure. """ - if not self._client: - logger.error("netra.evaluation: Evaluation client is not initialized; cannot create run") - return {"success": False} - try: - url = f"/evaluations/test_run" - payload: Dict[str, Any] = { - "name": name, - "datasetId": dataset_id if dataset_id else None, - "localEvaluators": evaluators_config if evaluators_config else [], - } - response = self._client.post(url, json=payload) - response.raise_for_status() - data = response.json() - if isinstance(data, dict) and "data" in data: - return data.get("data", {}) - except Exception: - response_json = response.json() - logger.error( - "netra.evaluation: Failed to create run '%s': %s", name, response_json.get("error").get("message", "") - ) - return {"success": False} + payload: dict[str, Any] = { + "name": name, + "datasetId": dataset_id if dataset_id else None, + "localEvaluators": evaluators_config if evaluators_config else [], + } + return self._post_data(URL_CREATE_RUN, payload, f"Failed to create run '{name}'") - def post_run_item(self, run_id: str, payload: Dict[str, Any]) -> Any: - """ - Submit a new run item to the backend. + def post_run_item(self, run_id: str, payload: dict[str, Any]) -> Optional[str]: + """Submit a new run item to the backend. Args: run_id: The id of the run to which the item will be added. payload: The run item to add. Returns: - A backend JSON response on success or {"success": False} on error. + The run item id on success, or None on failure. """ - if not self._client: - logger.error("netra.evaluation: Evaluation client is not initialized; cannot post run item") - return {"success": False} - try: - url = f"/evaluations/run/{run_id}/item" - response = self._client.post(url, json=payload) - response.raise_for_status() - data = response.json() - if isinstance(data, dict) and "data" in data: - run_item = data.get("data", {}).get("item") - run_item_id = run_item.get("id") - return run_item_id - return data - except Exception: - response_json = response.json() - logger.error( - "netra.evaluation: Failed to post run item for run '%s': %s", - run_id, - response_json.get("error").get("message", ""), - ) - return {"success": False} + url = URL_RUN_ITEM.format(run_id=run_id) + result = self._post_data(url, payload, f"Failed to post run item for run '{run_id}'") + if result is not None: + run_item = result.get("item", {}) + if isinstance(run_item, dict): + item_id = run_item.get("id") + return str(item_id) if item_id is not None else None + return None def submit_local_evaluations( - self, run_id: str, test_run_item_id: str, evaluator_results: List[Dict[str, Any]] - ) -> Any: - """ - Submit local evaluations result + self, + run_id: str, + test_run_item_id: str, + evaluator_results: list[dict[str, Any]], + ) -> Optional[dict[str, Any]]: + """Submit local evaluations result. Args: - run_id: The id of the run to which the item will be added. + run_id: The id of the run. test_run_item_id: The id of the test run item. evaluator_results: The evaluator results to submit. Returns: - A backend JSON response containing confirmation of the submission. + A backend JSON response containing confirmation, or None on failure. """ - if not self._client: - logger.error("netra.evaluation: Evaluation client is not initialized; cannot submit local evaluations") - return {"success": False} - try: - url = f"/evaluations/run/{run_id}/item/{test_run_item_id}/local-evaluations" - payload: Dict[str, Any] = {"evaluatorResults": evaluator_results} - response = self._client.post(url, json=payload) - response.raise_for_status() - data = response.json() - if isinstance(data, dict) and "data" in data: - return data.get("data", {}) - return data - except Exception: - response_json = response.json() - logger.error( - "netra.evaluation: Failed to submit local evaluations for run '%s', item '%s': %s", - run_id, - test_run_item_id, - response_json.get("error").get("message", ""), - ) - return {"success": False} - - def post_run_status(self, run_id: str, status: str) -> Any: - """ - Submit the run status + url = URL_LOCAL_EVALUATIONS.format(run_id=run_id, test_run_item_id=test_run_item_id) + request_payload: dict[str, Any] = {"evaluatorResults": evaluator_results} + return self._post_data( + url, + request_payload, + f"Failed to submit local evaluations for run '{run_id}', item '{test_run_item_id}'", + ) + + def post_run_status(self, run_id: str, status: str) -> Optional[dict[str, Any]]: + """Submit the run status. Args: - run_id: The id of the run to which the item will be added. - status: The status of the run. + run_id: The id of the run to update. + status: The status of the run. - Returns: - A backend JSON response containing confirmation of the submission. - """ - if not self._client: - logger.error("netra.evaluation: Evaluation client is not initialized; cannot post run status") - return {"success": False} - try: - url = f"/evaluations/run/{run_id}/status" - payload: Dict[str, Any] = {"status": status} - response = self._client.post(url, json=payload) - response.raise_for_status() - data = response.json() - if isinstance(data, dict) and "data" in data: - logger.info("netra.evaluation: Completed test run successfully") - return data.get("data", {}) - return data - except Exception: - response_json = response.json() - logger.error( - "netra.evaluation: Failed to post run status for run '%s': %s", - run_id, - response_json.get("error").get("message", ""), - ) - return {"success": False} - - def get_run_results(self, run_id: str) -> Any: + Returns: + A backend JSON response containing confirmation, or None on failure. """ - Fetch test run results by run ID. + payload: dict[str, Any] = {"status": status} + url = URL_RUN_STATUS.format(run_id=run_id) + result = self._post_data(url, payload, f"Failed to post run status for run '{run_id}'") + if result is not None: + logger.info("%s: Test run status updated to '%s'", LOG_PREFIX, status) + return result + + def get_run_results(self, run_id: str) -> Optional[dict[str, Any]]: + """Fetch test run results by run ID. Args: run_id: The id of the run to fetch. Returns: - A JSON response containing run results. + A JSON response containing run results, or None on failure. """ - if not self._client: - logger.error("netra.evaluation: Evaluation client is not initialized; cannot fetch run") - return None - try: - url = f"/evaluations/run/{run_id}" - response = self._client.get(url) - response.raise_for_status() - data = response.json() - if isinstance(data, dict) and "data" in data: - logger.info("netra.evaluation: Run fetched successfully") - return data.get("data", {}) - return data - except Exception: - response_json = response.json() - logger.error( - "netra.evaluation: Failed to fetch run results for run '%s': %s", - run_id, - response_json.get("error").get("message", ""), - ) - return None + url = URL_GET_RUN.format(run_id=run_id) + result = self._get_data(url, f"Failed to fetch run results for run '{run_id}'") + if result is not None: + logger.info("%s: Run fetched successfully", LOG_PREFIX) + return result if isinstance(result, dict) else None - def get_span_by_id(self, span_id: str) -> Any: - """ - Check if a span exists in the backend. + def get_span_by_id(self, span_id: str) -> Optional[dict[str, Any]]: + """Check if a span exists in the backend. Args: span_id: The span ID to check. @@ -384,17 +387,18 @@ def get_span_by_id(self, span_id: str) -> Any: Returns: The span data if found, None otherwise. """ - if not self._client: - logger.error("netra.evaluation: Evaluation client is not initialized; cannot get span") + if not self._ensure_client(): return None + try: - url = f"sdk/traces/spans/{span_id}" - response = self._client.get(url) + url = URL_SPAN.format(span_id=span_id) + response = self._client.get(url) # type:ignore[union-attr] response.raise_for_status() - data = response.json() - if isinstance(data, dict): - return data.get("data", data) - return data + body = response.json() + if isinstance(body, dict): + data = body.get("data", body) + return data if isinstance(data, dict) else None + return None except Exception: return None @@ -405,8 +409,7 @@ async def wait_for_span_ingestion( poll_interval_seconds: float = 1.0, initial_delay_seconds: float = 0.5, ) -> bool: - """ - Wait until a span is available in the backend. + """Wait until a span is available in the backend. Polls the GET /spans/:id endpoint to verify span availability before running evaluators. diff --git a/netra/evaluation/constants.py b/netra/evaluation/constants.py new file mode 100644 index 00000000..d22b6bd9 --- /dev/null +++ b/netra/evaluation/constants.py @@ -0,0 +1,37 @@ +"""Shared constants for the evaluation module.""" + +# --------------------------------------------------------------------------- +# Logging +# --------------------------------------------------------------------------- +LOG_PREFIX = "netra.evaluation" + +# --------------------------------------------------------------------------- +# Concurrency +# --------------------------------------------------------------------------- +MIN_CONCURRENCY = 1 +DEFAULT_CONCURRENCY = 5 + +# --------------------------------------------------------------------------- +# Span / tracing +# --------------------------------------------------------------------------- +SPAN_NAME_PREFIX = "TestRun" + +# --------------------------------------------------------------------------- +# API endpoints +# --------------------------------------------------------------------------- +URL_CREATE_DATASET = "/evaluations/dataset" +URL_DATASET_ITEMS = "/evaluations/dataset/{dataset_id}/items" +URL_GET_DATASET = "/evaluations/dataset/{dataset_id}" +URL_CREATE_RUN = "/evaluations/test_run" +URL_RUN_ITEM = "/evaluations/run/{run_id}/item" +URL_LOCAL_EVALUATIONS = "/evaluations/run/{run_id}/item/{test_run_item_id}/local-evaluations" +URL_RUN_STATUS = "/evaluations/run/{run_id}/status" +URL_GET_RUN = "/evaluations/run/{run_id}" +URL_SPAN = "sdk/traces/spans/{span_id}" +TELEMETRY_SUFFIX = "/telemetry" + +# --------------------------------------------------------------------------- +# HTTP client timeouts +# --------------------------------------------------------------------------- +DEFAULT_TIMEOUT = 10.0 +ENV_TIMEOUT = "NETRA_EVALUATION_TIMEOUT" diff --git a/netra/evaluation/evaluator.py b/netra/evaluation/evaluator.py index e46e58f1..5a010353 100644 --- a/netra/evaluation/evaluator.py +++ b/netra/evaluation/evaluator.py @@ -31,6 +31,7 @@ def evaluate(self, context: EvaluatorContext) -> EvaluatorOutput: ) # Usage: + dataset = ... result = Netra.evaluation.run_test_suite( name="Copywriting Assistant v1", data=dataset, diff --git a/netra/evaluation/models.py b/netra/evaluation/models.py index c0ab1405..f12397ba 100644 --- a/netra/evaluation/models.py +++ b/netra/evaluation/models.py @@ -1,16 +1,19 @@ -import asyncio -from dataclasses import dataclass, field +"""Data models for the evaluation module.""" + +from dataclasses import dataclass from enum import Enum -from typing import Any, Dict, List, Optional +from typing import Any, Optional from pydantic import BaseModel, Field class CreateDatasetResponse(BaseModel): # type:ignore[misc] + """Response from creating a dataset.""" + project_id: str organization_id: str name: str - tags: Optional[List[str]] = [] + tags: Optional[list[str]] = Field(default_factory=list) created_by: str updated_by: str updated_at: str @@ -20,6 +23,8 @@ class CreateDatasetResponse(BaseModel): # type:ignore[misc] class AddDatasetItemResponse(BaseModel): # type:ignore[misc] + """Response from adding a dataset item.""" + dataset_id: str project_id: str organization_id: str @@ -27,18 +32,20 @@ class AddDatasetItemResponse(BaseModel): # type:ignore[misc] input: Any expected_output: Optional[Any] = None is_active: bool - tags: Optional[List[str]] = [] + tags: Optional[list[str]] = Field(default_factory=list) created_by: str updated_by: str updated_at: str source_id: Optional[str] = None - metadata: Optional[Dict[str, Any]] = None + metadata: Optional[dict[str, Any]] = None id: str created_at: str deleted_at: Optional[str] = None class DatasetRecord(BaseModel): # type:ignore[misc] + """A single record fetched from a remote dataset.""" + id: str input: Any dataset_id: str @@ -46,23 +53,31 @@ class DatasetRecord(BaseModel): # type:ignore[misc] class GetDatasetItemsResponse(BaseModel): # type:ignore[misc] - items: List[DatasetRecord] + """Response from fetching dataset items.""" + + items: list[DatasetRecord] class DatasetItem(BaseModel): # type:ignore[misc] + """A single dataset item provided by the user.""" + input: Any expected_output: Optional[Any] = None - metadata: Optional[Dict[str, Any]] = None - tags: Optional[List[str]] = None + metadata: Optional[dict[str, Any]] = None + tags: Optional[list[str]] = None class ScoreType(str, Enum): + """Supported evaluator score types.""" + BOOLEAN = "boolean" NUMERICAL = "numerical" CATEGORICAL = "categorical" class EvaluatorConfig(BaseModel): # type:ignore[misc] + """Configuration for a single evaluator.""" + name: str label: str score_type: ScoreType = Field(alias="scoreType") @@ -73,13 +88,17 @@ class EvaluatorConfig(BaseModel): # type:ignore[misc] class EvaluatorContext(BaseModel): # type:ignore[misc] + """Context passed to an evaluator's evaluate() method.""" + input: Any task_output: Any expected_output: Any = None - metadata: Optional[Dict[str, Any]] = None + metadata: Optional[dict[str, Any]] = None class EvaluatorOutput(BaseModel): # type:ignore[misc] + """Result returned from an evaluator's evaluate() method.""" + evaluator_name: str result: Any is_passed: bool @@ -87,17 +106,19 @@ class EvaluatorOutput(BaseModel): # type:ignore[misc] class Dataset(BaseModel): # type:ignore[misc] - items: List[DatasetItem] | List[DatasetRecord] + """Container for dataset items used by run_test_suite.""" + items: list[DatasetItem] | list[DatasetRecord] -@dataclass + +@dataclass(slots=True) class ItemContext: """Context for a single dataset item during test suite execution.""" index: int item_input: Any expected_output: Any = None - metadata: Optional[Dict[str, Any]] = None + metadata: Optional[dict[str, Any]] = None dataset_item_id: Optional[str] = None trace_id: str = "" span_id: str = "" @@ -107,34 +128,24 @@ class ItemContext: status: str = "pending" -@dataclass -class RunContext: - """Shared context for a test suite run.""" - - run_id: str - run_name: str - evaluators: Optional[List[Any]] = None - poller: Optional[Any] = None - results: List[Dict[str, Any]] = field(default_factory=list) - bg_eval_tasks: List[asyncio.Task[None]] = field(default_factory=list) - - class LocalDataset(BaseModel): # type:ignore[misc] """Local dataset class for running test suite locally.""" - items: List[DatasetItem] + items: list[DatasetItem] class TurnType(str, Enum): + """Turn type for a dataset.""" + SINGLE = "single" MULTI = "multi" -@dataclass +@dataclass(slots=True) class ItemProcessingResult: """Result of processing a single dataset item.""" - item_entry: Dict[str, Any] + item_entry: dict[str, Any] should_run_evaluators: bool ctx: ItemContext status: str diff --git a/netra/evaluation/utils.py b/netra/evaluation/utils.py index a40aeebf..6c7181ef 100644 --- a/netra/evaluation/utils.py +++ b/netra/evaluation/utils.py @@ -1,24 +1,53 @@ +"""Utility functions for the evaluation module.""" + import asyncio import logging +import os import threading -from typing import Any, Awaitable, Callable, Dict, List, Optional, TypeVar +from typing import Any, Awaitable, Callable, Optional, TypeVar from opentelemetry import baggage from opentelemetry import context as otel_context +from netra.evaluation.constants import LOG_PREFIX from netra.evaluation.models import DatasetRecord, EvaluatorConfig, EvaluatorContext, ItemContext logger = logging.getLogger(__name__) -T = TypeVar("T") +_T = TypeVar("_T") -def get_session_id_from_baggage() -> Optional[str]: +def parse_env_float(env_var: str, default: float) -> float: + """Read an environment variable and parse it as a float. + + Args: + env_var: Name of the environment variable. + default: Value to return when the variable is unset or invalid. + + Returns: + The parsed float, or *default* on failure. """ - Get the session ID from the OpenTelemetry baggage. + raw = os.getenv(env_var) + if not raw: + return default + try: + return float(raw) + except ValueError: + logger.warning( + "%s: Invalid value '%s' for %s, using default %.1f", + LOG_PREFIX, + raw, + env_var, + default, + ) + return default + + +def get_session_id_from_baggage() -> Optional[str]: + """Get the session ID from the OpenTelemetry baggage. Returns: - session_id: The session ID if found, None otherwise. + The session ID if found, None otherwise. """ ctx = otel_context.get_current() session_id = baggage.get_baggage("session_id", ctx) @@ -28,82 +57,80 @@ def get_session_id_from_baggage() -> Optional[str]: def format_trace_id(trace_id: int) -> str: - """ - Format the trace ID as a 32-digit hexadecimal string. + """Format the trace ID as a 32-digit hexadecimal string. + + Args: + trace_id: The integer trace ID to format. - Return: - trace_id: The formatted trace ID. + Returns: + The formatted trace ID. """ return f"{trace_id:032x}" def format_span_id(span_id: int) -> str: - """ - Format the span ID as a 16-digit hexadecimal string. - - Return: - span_id: The formatted span ID. - """ - return f"{span_id:016x}" + """Format the span ID as a 16-digit hexadecimal string. + Args: + span_id: The integer span ID to format. -async def run_callable_maybe_async(fn: Callable[..., Any], *args: Any, **kwargs: Any) -> Any: - """ - Run callable + Returns: + The formatted span ID. """ - result = fn(*args, **kwargs) - if asyncio.iscoroutine(result): - return await result - return result + return f"{span_id:016x}" -def run_async_safely(coroutine: Awaitable[T]) -> T: - """Run an async coroutine from sync code. +def run_async_safely(coro: Awaitable[_T]) -> _T: + """Run an async coroutine from synchronous code. - If there is already an event loop running in this thread, we execute in a - dedicated thread to avoid 'asyncio.run() cannot be called from a running event loop'. + When called from a context that already has a running event loop (e.g. a + Jupyter notebook, or an async framework like FastAPI), ``asyncio.run()`` + would raise. In that case we spin up a **new daemon thread** with its own + event loop so the caller's loop is never blocked or re-entered. Args: - coroutine: The coroutine to run. + coro: The coroutine to execute. Returns: - The result of the coroutine. - """ + The result of the coroutine execution. + Raises: + Exception: Re-raises any exception from the coroutine. + """ try: loop = asyncio.get_running_loop() except RuntimeError: loop = None if loop and loop.is_running(): - result_container: Dict[str, T] = {} - error_container: Dict[str, Exception] = {} + result_holder: dict[str, _T] = {} + error_holder: dict[str, BaseException] = {} - def _runner() -> None: + def runner() -> None: try: - result_container["result"] = asyncio.run(coroutine) # type: ignore[arg-type] - except Exception as exc: # pragma: no cover - error_container["error"] = exc + result_holder["value"] = asyncio.run(coro) # type: ignore[arg-type] + except BaseException as exc: + error_holder["exc"] = exc - thread = threading.Thread(target=_runner, daemon=True) + thread = threading.Thread(target=runner, daemon=True) thread.start() thread.join() - if "error" in error_container: - raise error_container["error"] - return result_container.get("result") # type: ignore[return-value] - return asyncio.run(coroutine) # type: ignore[arg-type] + if "exc" in error_holder: + raise error_holder["exc"] + return result_holder.get("value") # type: ignore[return-value] + + return asyncio.run(coro) # type: ignore[arg-type] def extract_evaluator_config(evaluator: Any) -> Optional[EvaluatorConfig]: - """ - Extract evaluator configuration from an evaluator object. + """Extract evaluator configuration from an evaluator object. Args: evaluator: The evaluator object. Returns: - Optional[EvaluatorConfig]: The evaluator configuration if found, None otherwise. + The evaluator configuration if found, None otherwise. """ if not hasattr(evaluator, "config"): return None @@ -114,15 +141,14 @@ def extract_evaluator_config(evaluator: Any) -> Optional[EvaluatorConfig]: async def execute_task(task: Callable[[Any], Any], item_input: Any) -> tuple[Any, str]: - """ - Execute a task function (sync or async) and return (output, status). + """Execute a task function (sync or async) and return (output, status). Args: task: The task function to execute. item_input: The input to the task function. Returns: - tuple[Any, str]: The output of the task function and the status of the execution. + A tuple of (task_output, status_string). """ try: result = task(item_input) @@ -138,10 +164,9 @@ async def run_single_evaluator( item_input: Any, task_output: Any, expected_output: Any, - metadata: Optional[Dict[str, Any]], -) -> Optional[Dict[str, Any]]: - """ - Run a single evaluator and return normalized result. + metadata: Optional[dict[str, Any]], +) -> Optional[dict[str, Any]]: + """Run a single evaluator and return normalized result. Args: evaluator: The evaluator object. @@ -151,7 +176,7 @@ async def run_single_evaluator( metadata: Optional metadata to be passed to the evaluator. Returns: - Optional[Dict[str, Any]]: The normalized result of the evaluator if successful, None otherwise. + The normalized result dict if successful, None otherwise. """ if not hasattr(evaluator, "evaluate"): return None @@ -185,22 +210,21 @@ async def run_single_evaluator( def build_item_payload( - ctx: "ItemContext", + ctx: ItemContext, status: str, include_output: bool = False, -) -> Dict[str, Any]: - """ - Build a payload dict for posting item status. +) -> dict[str, Any]: + """Build a payload dict for posting item status. Args: ctx: The item context. - status: The status of the item. + status: The status of the item (e.g. "completed", "failed"). include_output: Whether to include the task output in the payload. Returns: - Dict[str, Any]: The payload dict. + The payload dict ready for HTTP submission. """ - payload: Dict[str, Any] = { + payload: dict[str, Any] = { "traceId": ctx.trace_id, "sessionId": ctx.session_id, } @@ -213,7 +237,7 @@ def build_item_payload( if ctx.metadata: payload["metadata"] = ctx.metadata - if ctx.status == "failed": + if status == "failed": payload["status"] = "failed" return payload @@ -225,37 +249,35 @@ def build_item_payload( def validate_run_inputs( name: str, - data: Any, + dataset: Any, task: Callable[[Any], Any], ) -> None: - """ - Validate required inputs for run_test_suite. + """Validate required inputs for run_test_suite. Args: name: The name of the run. - data: The dataset to be used for the test suite. + dataset: The dataset to be used for the test suite. task: The task to be executed for each item in the dataset. Raises: ValueError: If any required input is missing or invalid. """ if not name: - raise ValueError("netra.evaluation: run name is required") - if not data: - raise ValueError("netra.evaluation: data is required") + raise ValueError(f"{LOG_PREFIX}: run name is required") + if not dataset: + raise ValueError(f"{LOG_PREFIX}: dataset is required") if task is None: - raise ValueError("netra.evaluation: task function is required") + raise ValueError(f"{LOG_PREFIX}: task function is required") -def extract_dataset_id(items: List[Any]) -> Optional[str]: # noqa: E501 - """ - Extract dataset_id from items if they are DatasetRecords. +def extract_dataset_id(items: list[Any]) -> Optional[str]: + """Extract dataset_id from items if they are DatasetRecords. Args: items: List of items. Returns: - Optional[str]: The dataset_id if found, None otherwise. + The dataset_id if found, None otherwise. """ if items and isinstance(items[0], DatasetRecord): dataset_id: str = items[0].dataset_id @@ -264,18 +286,17 @@ def extract_dataset_id(items: List[Any]) -> Optional[str]: # noqa: E501 def build_evaluators_config( - evaluators: Optional[List[Any]], -) -> List[EvaluatorConfig]: - """ - Build evaluator configurations from evaluator objects. + evaluators: Optional[list[Any]], +) -> list[EvaluatorConfig]: + """Build evaluator configurations from evaluator objects. Args: evaluators: List of evaluators. Returns: - List[EvaluatorConfig]: List of evaluator configurations. + List of evaluator configurations. """ - configs: List[EvaluatorConfig] = [] + configs: list[EvaluatorConfig] = [] if not evaluators: return configs @@ -285,6 +306,7 @@ def build_evaluators_config( continue try: configs.append(config) - except Exception: + except Exception as exc: + logger.warning("%s: Failed to extract evaluator config: %s", LOG_PREFIX, exc) continue return configs diff --git a/netra/instrumentation/__init__.py b/netra/instrumentation/__init__.py index 99468d32..dd93a217 100644 --- a/netra/instrumentation/__init__.py +++ b/netra/instrumentation/__init__.py @@ -4,30 +4,32 @@ from io import StringIO from typing import AbstractSet, Any, Callable, Optional -from traceloop.sdk import Instruments, Telemetry +from traceloop.sdk import Instruments from traceloop.sdk.utils.package_check import is_package_installed from netra.instrumentation.instruments import DEFAULT_INSTRUMENTS, CustomInstruments, InstrumentSet, NetraInstruments _ALL_SENTINEL = InstrumentSet.ALL -# Traceloop instrumentors that Netra always replaces with its own custom -# implementations. These are unconditionally added to the traceloop block -# list regardless of what the user passes. +# Traceloop instrumentors that Netra always replaces with its own custom implementations +# Built with getattr() so the SDK stays importable even when the installed +# traceloop-sdk version lacks some of these enum members. +_TRACELOOP_ALWAYS_BLOCKED_NAMES: tuple[str, ...] = ( + "AGNO", + "WEAVIATE", + "QDRANT", + "GOOGLE_GENERATIVEAI", + "MISTRAL", + "OPENAI", + "GROQ", + "REDIS", + "PYMYSQL", + "REQUESTS", + "URLLIB3", + "COHERE", +) _TRACELOOP_ALWAYS_BLOCKED: frozenset[Instruments] = frozenset( - { - Instruments.WEAVIATE, - Instruments.QDRANT, - Instruments.GOOGLE_GENERATIVEAI, - Instruments.MISTRAL, - Instruments.OPENAI, - Instruments.GROQ, - Instruments.REDIS, - Instruments.PYMYSQL, - Instruments.REQUESTS, - Instruments.URLLIB3, - Instruments.COHERE, - } + member for name in _TRACELOOP_ALWAYS_BLOCKED_NAMES if (member := getattr(Instruments, name, None)) is not None ) @@ -378,7 +380,7 @@ def init_instrumentations( if CustomInstruments.CEREBRAS in netra_custom_instruments: init_cerebras_instrumentation() - # Initialize cerebras instrumentation. + # Initialize cartesia instrumentation. if CustomInstruments.CARTESIA in netra_custom_instruments: init_cartesia_instrumentation() @@ -399,15 +401,14 @@ def init_groq_instrumentation() -> bool: """Initialize Groq instrumentation.""" try: if is_package_installed("groq"): - Telemetry().capture("instrumentation:groq:init") from netra.instrumentation.groq import NetraGroqInstrumentor instrumentor = NetraGroqInstrumentor() if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing Groq instrumentor") return False @@ -420,9 +421,8 @@ def init_deepgram_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing Deepgram instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing Deepgram instrumentor") return False @@ -434,16 +434,14 @@ def init_adk_instrumentation() -> bool: """ try: if is_package_installed("google-adk"): - Telemetry().capture("instrumentation:adk:init") from netra.instrumentation.google_adk import NetraGoogleADKInstrumentor instrumentor = NetraGoogleADKInstrumentor() if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing ADK instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing ADK instrumentor") return False @@ -455,16 +453,14 @@ def init_agno_instrumentation() -> bool: """ try: if is_package_installed("agno"): - Telemetry().capture("instrumentation:agno:init") from netra.instrumentation.agno import NetraAgnoInstrumentor instrumentor = NetraAgnoInstrumentor() if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing Agno instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing Agno instrumentor") return False @@ -476,16 +472,14 @@ def init_google_genai_instrumentation() -> bool: """ try: if is_package_installed("google-genai"): - Telemetry().capture("instrumentation:genai:init") from netra.instrumentation.google_genai import NetraGoogleGenAiInstrumentor instrumentor = NetraGoogleGenAiInstrumentor() if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing Google GenAI instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing Google GenAI instrumentor") return False @@ -503,9 +497,8 @@ def init_fastapi_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing FastAPI instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing FastAPI instrumentor") return False @@ -523,9 +516,8 @@ def init_qdrant_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing Qdrant instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing Qdrant instrumentor") return False @@ -543,9 +535,8 @@ def init_weviate_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing Weaviate instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing Weaviate instrumentor") return False @@ -563,9 +554,8 @@ def init_httpx_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing HTTPX instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing HTTPX instrumentor") return False @@ -583,9 +573,8 @@ def init_aiohttp_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing AIOHTTP instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing AIOHTTP instrumentor") return False @@ -603,9 +592,8 @@ def init_cohere_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing Cohere instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing Cohere instrumentor") return False @@ -624,9 +612,8 @@ def init_dspy_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing DSPy instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing DSPy instrumentor") return False @@ -641,14 +628,13 @@ def init_mistral_instrumentor() -> bool: from netra.instrumentation.mistralai import MistralAiInstrumentor instrumentor = MistralAiInstrumentor( - exception_logger=lambda e: Telemetry().log_exception(e), + exception_logger=lambda e: logging.exception("Error in Mistral instrumentor"), ) if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"-----Error initializing Mistral instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing Mistral instrumentor") return False @@ -666,9 +652,8 @@ def init_litellm_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing LiteLLM instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing LiteLLM instrumentor") return False @@ -686,9 +671,8 @@ def init_openai_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing OpenAI instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing OpenAI instrumentor") return False @@ -702,9 +686,8 @@ def init_aio_pika_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing aio_pika instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing aio_pika instrumentor") return False @@ -718,9 +701,8 @@ def init_aiokafka_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing aiokafka instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing aiokafka instrumentor") return False @@ -734,9 +716,8 @@ def init_aiopg_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing aiopg instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing aiopg instrumentor") return False @@ -750,9 +731,8 @@ def init_asyncclick_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing asyncclick instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing asyncclick instrumentor") return False @@ -766,9 +746,8 @@ def init_asyncio_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing asyncio instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing asyncio instrumentor") return False @@ -782,9 +761,8 @@ def init_asyncpg_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing asyncpg instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing asyncpg instrumentor") return False @@ -798,9 +776,8 @@ def init_aws_lambda_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing aws_lambda instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing aws_lambda instrumentor") return False @@ -814,9 +791,8 @@ def init_boto3sqs_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing boto3sqs instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing boto3sqs instrumentor") return False @@ -830,9 +806,8 @@ def init_botocore_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing botocore instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing botocore instrumentor") return False @@ -846,9 +821,8 @@ def init_cassandra_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing cassandra instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing cassandra instrumentor") return False @@ -862,9 +836,8 @@ def init_celery_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing celery instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing celery instrumentor") return False @@ -878,9 +851,8 @@ def init_click_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing click instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing click instrumentor") return False @@ -894,9 +866,8 @@ def init_confluent_kafka_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing confluent_kafka instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing confluent_kafka instrumentor") return False @@ -910,9 +881,8 @@ def init_django_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing django instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing django instrumentor") return False @@ -926,9 +896,8 @@ def init_elasticsearch_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing elasticsearch instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing elasticsearch instrumentor") return False @@ -942,9 +911,8 @@ def init_falcon_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing falcon instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing falcon instrumentor") return False @@ -958,9 +926,8 @@ def init_flask_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing flask instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing flask instrumentor") return False @@ -974,9 +941,8 @@ def init_grpc_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing grpc instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing grpc instrumentor") return False @@ -990,9 +956,8 @@ def init_jinja2_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing jinja2 instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing jinja2 instrumentor") return False @@ -1006,9 +971,8 @@ def init_kafka_python_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing kafka_python instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing kafka_python instrumentor") return False @@ -1022,9 +986,8 @@ def init_logging_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing logging instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing logging instrumentor") return False @@ -1038,9 +1001,8 @@ def init_mysql_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing mysql instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing mysql instrumentor") return False @@ -1054,9 +1016,8 @@ def init_mysqlclient_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing mysqlclient instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing mysqlclient instrumentor") return False @@ -1070,9 +1031,8 @@ def init_pika_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing pika instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing pika instrumentor") return False @@ -1086,9 +1046,8 @@ def init_psycopg_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing psycopg instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing psycopg instrumentor") return False @@ -1102,9 +1061,8 @@ def init_psycopg2_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing psycopg2 instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing psycopg2 instrumentor") return False @@ -1118,9 +1076,8 @@ def init_pymemcache_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing pymemcache instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing pymemcache instrumentor") return False @@ -1134,9 +1091,8 @@ def init_pymongo_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing pymongo instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing pymongo instrumentor") return False @@ -1150,9 +1106,8 @@ def init_pymssql_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing pymssql instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing pymssql instrumentor") return False @@ -1166,9 +1121,8 @@ def init_pymysql_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing pymysql instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing pymysql instrumentor") return False @@ -1182,9 +1136,8 @@ def init_redis_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing redis instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing redis instrumentor") return False @@ -1198,9 +1151,8 @@ def init_remoulade_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing remoulade instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing remoulade instrumentor") return False @@ -1214,9 +1166,8 @@ def init_requests_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing requests instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing requests instrumentor") return False @@ -1230,9 +1181,8 @@ def init_sqlalchemy_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing sqlalchemy instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing sqlalchemy instrumentor") return False @@ -1246,9 +1196,8 @@ def init_sqlite3_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing sqlite3 instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing sqlite3 instrumentor") return False @@ -1262,9 +1211,8 @@ def init_starlette_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing starlette instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing starlette instrumentor") return False @@ -1278,9 +1226,8 @@ def init_system_metrics_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing system_metrics instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing system_metrics instrumentor") return False @@ -1293,9 +1240,8 @@ def init_threading_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing threading instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing threading instrumentor") return False @@ -1309,9 +1255,8 @@ def init_tornado_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing tornado instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing tornado instrumentor") return False @@ -1325,9 +1270,8 @@ def init_tortoiseorm_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing tortoiseorm instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing tortoiseorm instrumentor") return False @@ -1340,9 +1284,8 @@ def init_urllib_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing urllib instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing urllib instrumentor") return False @@ -1356,9 +1299,8 @@ def init_urllib3_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing urllib3 instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing urllib3 instrumentor") return False @@ -1381,9 +1323,8 @@ def init_pydantic_ai_instrumentation() -> bool: else: return False return True - except Exception as e: - logging.error(f"Error initializing pydantic-ai instrumentation: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing pydantic-ai instrumentation") return False @@ -1401,9 +1342,8 @@ def init_cerebras_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing Cerebras instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing Cerebras instrumentor") return False @@ -1421,9 +1361,8 @@ def init_cartesia_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing Cartesia instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing Cartesia instrumentor") return False @@ -1441,9 +1380,8 @@ def init_elevenlabs_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing Elevenlabs instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing Elevenlabs instrumentor") return False @@ -1461,9 +1399,8 @@ def init_claude_agent_sdk_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing Claude Agent SDK instrumentor: {e}") - Telemetry().log_exception(e) + except Exception: + logging.exception("Error initializing Claude Agent SDK instrumentor") return False @@ -1480,6 +1417,6 @@ def init_subprocess_instrumentation() -> bool: if not instrumentor.is_instrumented_by_opentelemetry: instrumentor.instrument() return True - except Exception as e: - logging.error(f"Error initializing subprocess instrumentor: {e}") + except Exception: + logging.exception("Error initializing subprocess instrumentor") return False diff --git a/netra/instrumentation/agno/utils.py b/netra/instrumentation/agno/utils.py index 2a3829da..26be21aa 100644 --- a/netra/instrumentation/agno/utils.py +++ b/netra/instrumentation/agno/utils.py @@ -941,7 +941,7 @@ def set_request_attributes( span.set_attribute("input", input_content) -def set_response_attributes(span: Span, response: Any) -> None: +def set_response_attributes(span: Span, response: Any) -> Optional[str]: """Set response-side span attributes from an Agno response object. Writes token usage, output content, response ID, and output type. @@ -951,7 +951,7 @@ def set_response_attributes(span: Span, response: Any) -> None: response: The Agno response object (RunResponse, TeamRunOutput, etc.). """ if not span.is_recording(): - return + return None usage = extract_token_usage(response) if usage: @@ -965,6 +965,8 @@ def set_response_attributes(span: Span, response: Any) -> None: if response_id: span.set_attribute(ATTR_RESPONSE_ID, response_id) + return output + def sanitize_headers(raw_headers: List[Tuple[bytes, bytes]]) -> Dict[str, str]: """Convert ASGI raw header pairs to a dict with sensitive values redacted. diff --git a/netra/instrumentation/agno/wrappers.py b/netra/instrumentation/agno/wrappers.py index 8aab90d9..91dacbb9 100644 --- a/netra/instrumentation/agno/wrappers.py +++ b/netra/instrumentation/agno/wrappers.py @@ -157,6 +157,8 @@ def _set_common_span_attributes(span: Span, entity_type: str) -> None: class _BaseStreamWrapper: """Shared base for all span streaming wrappers.""" + _netra_stream_wrapper = True + def __init__(self, span: Span, response: Any, ctx_token: Any = None) -> None: """Initialise the streaming wrapper. @@ -222,10 +224,14 @@ class _AgentStreamOutputMixin: def _set_output_on_success(self) -> None: """Write accumulated run content and token usage to the span before it closes.""" + self._netra_output = "" if self._last_response is not None: - set_response_attributes(self._span, self._last_response) + output = set_response_attributes(self._span, self._last_response) + self._netra_output = output if output else "" if self._content_chunks: - self._span.set_attribute("output", "".join(self._content_chunks)) + output = "".join(self._content_chunks) + self._span.set_attribute("output", output) + self._netra_output = output class _LlmStreamOutputMixin: @@ -239,9 +245,11 @@ class _LlmStreamOutputMixin: def _set_output_on_success(self) -> None: """Write accumulated LLM content, token usage, and timing metrics to the span.""" output_str = None + self._netra_output = "" if self._content_chunks: content = "".join(self._content_chunks) output_str = json.dumps([{"role": "assistant", "content": content}]) + self._netra_output = content elif self._tool_calls: try: tc_serialized = serialize_value(self._tool_calls, clean=True) @@ -251,10 +259,12 @@ def _set_output_on_success(self) -> None: except (json.JSONDecodeError, ValueError): tc_data = tc_serialized output_str = json.dumps([{"role": "assistant", "tool_calls": tc_data}]) + self._netra_output = tc_serialized except Exception as e: logger.debug("netra.instrumentation.agno: failed to serialize tool_calls for LLM output: %s", e) elif self._last_response is not None: output_str = format_response_as_output(self._last_response) + self._netra_output = output_str if output_str else "" if output_str: self._span.set_attribute("output", output_str) set_llm_completion_attributes(self._span, output_str) diff --git a/netra/instrumentation/cerebras/wrappers.py b/netra/instrumentation/cerebras/wrappers.py index e0c2f065..2ebd2863 100644 --- a/netra/instrumentation/cerebras/wrappers.py +++ b/netra/instrumentation/cerebras/wrappers.py @@ -40,6 +40,8 @@ def _detect_streaming(args: Tuple[Any, ...], kwargs: Dict[str, Any]) -> bool: class StreamingWrapper(ObjectProxy): # type: ignore[misc] """Wrapper for streaming responses""" + _netra_stream_wrapper = True + def __init__(self, span: Span, response: Iterator[Any], request_kwargs: Dict[str, Any]) -> None: super().__init__(response) self._span = span @@ -59,6 +61,15 @@ def _ensure_choice(self, index: int) -> None: else: self._complete_response["choices"].append({"text": ""}) + def _extract_content_text(self) -> str: + """Extract the plain text content from the accumulated response.""" + parts = [] + for choice in self._complete_response.get("choices", []): + msg = choice.get("message", {}) + if content := msg.get("content"): + parts.append(content) + return "".join(parts) + def __iter__(self) -> Iterator[Any]: return self @@ -129,6 +140,7 @@ def _finalize_span(self) -> None: """Finalize span when streaming is complete""" record_span_timing(self._span, LLM_RESPONSE_DURATION) set_response_attributes(self._span, self._complete_response) + self._netra_output = self._extract_content_text() self._span.set_status(Status(StatusCode.OK)) self._span.end() @@ -136,6 +148,8 @@ def _finalize_span(self) -> None: class AsyncStreamingWrapper(ObjectProxy): # type: ignore[misc] """Async wrapper for streaming responses""" + _netra_stream_wrapper = True + def __init__(self, span: Span, response: AsyncIterator[Any], request_kwargs: Dict[str, Any]) -> None: super().__init__(response) self._span = span @@ -155,6 +169,15 @@ def _ensure_choice(self, index: int) -> None: else: self._complete_response["choices"].append({"text": ""}) + def _extract_content_text(self) -> str: + """Extract the plain text content from the accumulated response.""" + parts = [] + for choice in self._complete_response.get("choices", []): + msg = choice.get("message", {}) + if content := msg.get("content"): + parts.append(content) + return "".join(parts) + def __aiter__(self) -> AsyncIterator[Any]: return self @@ -227,6 +250,7 @@ def _finalize_span(self) -> None: """Finalize span when streaming is complete""" record_span_timing(self._span, LLM_RESPONSE_DURATION) set_response_attributes(self._span, self._complete_response) + self._netra_output = self._extract_content_text() self._span.set_status(Status(StatusCode.OK)) self._span.end() diff --git a/netra/instrumentation/google_genai/wrappers.py b/netra/instrumentation/google_genai/wrappers.py index 21472d9d..934b9e91 100644 --- a/netra/instrumentation/google_genai/wrappers.py +++ b/netra/instrumentation/google_genai/wrappers.py @@ -235,6 +235,8 @@ async def wrapper(wrapped: Callable[..., Any], instance: Any, args: Tuple[Any, . class StreamingWrapper: + _netra_stream_wrapper = True + def __init__(self, span: Span, response: Iterator[Any]) -> None: self._span = span self._buffer: dict[Any, Any] = {"chunk": None, "content": ""} @@ -272,11 +274,14 @@ def _process_chunk(self, chunk: Any) -> None: def _finalize_span(self) -> None: record_span_timing(self._span, LLM_RESPONSE_DURATION) set_response_attributes(self._span, self._buffer) + self._netra_output = self._buffer.get("content", "") if isinstance(self._buffer, dict) else "" self._span.set_status(Status(StatusCode.OK)) self._span.end() class AsyncStreamingWrapper: + _netra_stream_wrapper = True + def __init__(self, span: Span, response: AsyncIterator[Any]) -> None: self._span = span self._buffer: dict[Any, Any] = {"chunk": None, "content": ""} @@ -313,5 +318,6 @@ def _process_chunk(self, chunk: Any) -> None: def _finalize_span(self) -> None: record_span_timing(self._span, LLM_RESPONSE_DURATION) set_response_attributes(self._span, self._buffer) + self._netra_output = self._buffer.get("content", "") if isinstance(self._buffer, dict) else "" self._span.set_status(Status(StatusCode.OK)) self._span.end() diff --git a/netra/instrumentation/groq/wrappers.py b/netra/instrumentation/groq/wrappers.py index e64241cb..872e7f42 100644 --- a/netra/instrumentation/groq/wrappers.py +++ b/netra/instrumentation/groq/wrappers.py @@ -26,6 +26,8 @@ class StreamingWrapper(ObjectProxy): # type: ignore[misc] """Wrapper for streaming responses (OpenAI-style).""" + _netra_stream_wrapper = True + def __init__(self, span: Span, response: Iterator[Any], request_kwargs: Dict[str, Any]) -> None: super().__init__(response) self._span = span @@ -43,6 +45,15 @@ def _ensure_choice(self, index: int) -> None: else: self._complete_response["choices"].append({"text": ""}) + def _extract_content_text(self) -> str: + """Extract the plain text content from the accumulated response.""" + parts = [] + for choice in self._complete_response.get("choices", []): + msg = choice.get("message", {}) + if content := msg.get("content"): + parts.append(content) + return "".join(parts) + def __iter__(self) -> Iterator[Any]: return self @@ -98,6 +109,7 @@ def _process_chunk(self, chunk: Any) -> None: def _finalize_span(self) -> None: record_span_timing(self._span, LLM_RESPONSE_DURATION) set_response_attributes(self._span, self._complete_response) + self._netra_output = self._extract_content_text() self._span.set_status(Status(StatusCode.OK)) self._span.end() @@ -105,6 +117,8 @@ def _finalize_span(self) -> None: class AsyncStreamingWrapper(ObjectProxy): # type: ignore[misc] """Async wrapper for streaming responses (OpenAI-style).""" + _netra_stream_wrapper = True + def __init__(self, span: Span, response: AsyncIterator[Any], request_kwargs: Dict[str, Any]) -> None: super().__init__(response) self._span = span @@ -122,6 +136,15 @@ def _ensure_choice(self, index: int) -> None: else: self._complete_response["choices"].append({"text": ""}) + def _extract_content_text(self) -> str: + """Extract the plain text content from the accumulated response.""" + parts = [] + for choice in self._complete_response.get("choices", []): + msg = choice.get("message", {}) + if content := msg.get("content"): + parts.append(content) + return "".join(parts) + def __aiter__(self) -> AsyncIterator[Any]: return self @@ -177,6 +200,7 @@ def _process_chunk(self, chunk: Any) -> None: def _finalize_span(self) -> None: record_span_timing(self._span, LLM_RESPONSE_DURATION) set_response_attributes(self._span, self._complete_response) + self._netra_output = self._extract_content_text() self._span.set_status(Status(StatusCode.OK)) self._span.end() diff --git a/netra/instrumentation/instruments.py b/netra/instrumentation/instruments.py index a3840c9d..e161f994 100644 --- a/netra/instrumentation/instruments.py +++ b/netra/instrumentation/instruments.py @@ -183,6 +183,7 @@ def __new__(cls, value: Any, origin: Optional[Type[Enum]] = None) -> "Instrument URLLIB = ("urllib", CustomInstruments) URLLIB3 = ("urllib3", CustomInstruments) VERTEXAI = ("vertexai", Instruments) + VOYAGEAI = ("voyageai", Instruments) WATSONX = ("watsonx", Instruments) WEAVIATEDB = ("weaviate_db", CustomInstruments) WRITER = ("writer", Instruments) diff --git a/netra/instrumentation/litellm/wrappers.py b/netra/instrumentation/litellm/wrappers.py index 659acfd9..7ff657fc 100644 --- a/netra/instrumentation/litellm/wrappers.py +++ b/netra/instrumentation/litellm/wrappers.py @@ -335,6 +335,8 @@ async def wrapper( class StreamingWrapper(ObjectProxy): # type: ignore[misc] """Wrapper for streaming responses""" + _netra_stream_wrapper = True + def __init__(self, span: Span, response: Iterator[Any], request_kwargs: Dict[str, Any]) -> None: super().__init__(response) self._span = span @@ -354,6 +356,15 @@ def _ensure_choice(self, index: int) -> None: else: self._complete_response["choices"].append({"text": ""}) + def _extract_content_text(self) -> str: + """Extract the plain text content from the accumulated response.""" + parts = [] + for choice in self._complete_response.get("choices", []): + msg = choice.get("message", {}) + if content := msg.get("content"): + parts.append(content) + return "".join(parts) + def __enter__(self) -> "StreamingWrapper": if hasattr(self.__wrapped__, "__enter__"): self.__wrapped__.__enter__() @@ -444,6 +455,7 @@ def _finalize_span(self) -> None: """Finalize span when streaming is complete""" record_span_timing(self._span, LLM_RESPONSE_DURATION) set_response_attributes(self._span, self._complete_response) + self._netra_output = self._extract_content_text() self._span.set_status(Status(StatusCode.OK)) self._span.end() @@ -451,6 +463,8 @@ def _finalize_span(self) -> None: class AsyncStreamingWrapper(ObjectProxy): # type: ignore[misc] """Async wrapper for streaming responses""" + _netra_stream_wrapper = True + def __init__(self, span: Span, response: AsyncIterator[Any], request_kwargs: Dict[str, Any]) -> None: super().__init__(response) self._span = span @@ -470,6 +484,15 @@ def _ensure_choice(self, index: int) -> None: else: self._complete_response["choices"].append({"text": ""}) + def _extract_content_text(self) -> str: + """Extract the plain text content from the accumulated response.""" + parts = [] + for choice in self._complete_response.get("choices", []): + msg = choice.get("message", {}) + if content := msg.get("content"): + parts.append(content) + return "".join(parts) + async def __aenter__(self) -> "AsyncStreamingWrapper": if hasattr(self.__wrapped__, "__aenter__"): await self.__wrapped__.__aenter__() @@ -560,5 +583,6 @@ def _finalize_span(self) -> None: """Finalize span when streaming is complete""" record_span_timing(self._span, LLM_RESPONSE_DURATION) set_response_attributes(self._span, self._complete_response) + self._netra_output = self._extract_content_text() self._span.set_status(Status(StatusCode.OK)) self._span.end() diff --git a/netra/instrumentation/openai/wrappers.py b/netra/instrumentation/openai/wrappers.py index e32e82cf..3d013f5f 100644 --- a/netra/instrumentation/openai/wrappers.py +++ b/netra/instrumentation/openai/wrappers.py @@ -281,6 +281,8 @@ async def wrapper(wrapped: Callable[..., Awaitable[Any]], instance: Any, args: A class StreamingWrapper(ObjectProxy): # type: ignore[misc] """Wrapper for streaming responses""" + _netra_stream_wrapper = True + def __init__(self, span: Span, response: Iterator[Any], request_kwargs: Dict[str, Any]) -> None: super().__init__(response) self._span = span @@ -300,6 +302,15 @@ def _ensure_choice(self, index: int) -> None: else: self._complete_response["choices"].append({"text": ""}) + def _extract_content_text(self) -> str: + """Extract the plain text content from the accumulated response.""" + parts = [] + for choice in self._complete_response.get("choices", []): + msg = choice.get("message", {}) + if content := msg.get("content"): + parts.append(content) + return "".join(parts) + def __enter__(self) -> "StreamingWrapper": if hasattr(self.__wrapped__, "__enter__"): self.__wrapped__.__enter__() @@ -412,6 +423,7 @@ def _finalize_span(self) -> None: msg["tool_calls"] = [msg["tool_calls"][i] for i in sorted(msg["tool_calls"].keys())] record_span_timing(self._span, LLM_RESPONSE_DURATION) set_response_attributes(self._span, self._complete_response) + self._netra_output = self._extract_content_text() self._span.set_status(Status(StatusCode.OK)) self._span.end() @@ -419,6 +431,8 @@ def _finalize_span(self) -> None: class AsyncStreamingWrapper(ObjectProxy): # type: ignore[misc] """Async wrapper for streaming responses""" + _netra_stream_wrapper = True + def __init__(self, span: Span, response: AsyncIterator[Any], request_kwargs: Dict[str, Any]) -> None: super().__init__(response) self._span = span @@ -438,6 +452,15 @@ def _ensure_choice(self, index: int) -> None: else: self._complete_response["choices"].append({"text": ""}) + def _extract_content_text(self) -> str: + """Extract the plain text content from the accumulated response.""" + parts = [] + for choice in self._complete_response.get("choices", []): + msg = choice.get("message", {}) + if content := msg.get("content"): + parts.append(content) + return "".join(parts) + async def __aenter__(self) -> "AsyncStreamingWrapper": if hasattr(self.__wrapped__, "__aenter__"): await self.__wrapped__.__aenter__() @@ -550,5 +573,6 @@ def _finalize_span(self) -> None: msg["tool_calls"] = [msg["tool_calls"][i] for i in sorted(msg["tool_calls"].keys())] record_span_timing(self._span, LLM_RESPONSE_DURATION) set_response_attributes(self._span, self._complete_response) + self._netra_output = self._extract_content_text() self._span.set_status(Status(StatusCode.OK)) self._span.end() diff --git a/netra/instrumentation/stream_utils.py b/netra/instrumentation/stream_utils.py new file mode 100644 index 00000000..0a81440b --- /dev/null +++ b/netra/instrumentation/stream_utils.py @@ -0,0 +1,201 @@ +""" +Utilities for wrapping stream objects so that when iteration completes, the +accumulated output is automatically set on the root span of the current trace. + +Three flows are supported: + + 1. Netra-wrapped stream (``_netra_stream_wrapper = True``) + The inner instrumentation wrapper has already accumulated the output + in ``_netra_output``. The outer tap simply delegates iteration and + reads that attribute once the inner wrapper signals exhaustion. + + 2. Generic / unknown stream + Any iterable whose type Netra does not know about. Chunks are + converted to strings via ``str(chunk)`` and concatenated. + + 3. Objects that carry no iterator protocol are returned unchanged with a + warning log. +""" + +import logging +from typing import Any, Callable, List, Union + +from opentelemetry.trace import Span + +from netra.session_manager import NETRA_USER_OUTPUT +from netra.utils import serialize_value + +logger = logging.getLogger(__name__) + + +def _set_output_on_root(root_span: Span, output: Any) -> None: + """Write serialized *output* to *root_span* as ``NETRA_USER_OUTPUT``.""" + try: + serialized = serialize_value(output) + if serialized: + root_span.set_attribute(NETRA_USER_OUTPUT, serialized) + except Exception: + logger.warning("root_output_stream: failed to set output on root span", exc_info=True) + + +# Extractors — injected at construction time, kept stateless +def _netra_extractor(wrapper: Union["RootOutputSyncStreamWrapper", "RootOutputAsyncStreamWrapper"]) -> Any: + """Read accumulated output from the inner Netra wrapper.""" + inner = wrapper._stream + output = getattr(inner, "_netra_output", None) + if output is not None: + return output + # Nested wrapping: inner is another RootOutput* wrapper with _chunks + chunks = getattr(inner, "_chunks", None) + if chunks is not None: + return "".join(chunks) + return None + + +def _generic_extractor(wrapper: Union["RootOutputSyncStreamWrapper", "RootOutputAsyncStreamWrapper"]) -> Any: + """Return the concatenated stringified chunks.""" + return "".join(wrapper._chunks) + + +# Sync wrapper +class RootOutputSyncStreamWrapper: + """Wraps a sync iterable; on exhaustion sets the output on the root span.""" + + _netra_stream_wrapper = True + + def __init__(self, stream: Any, root_span: Span, extractor: Callable[[Any], Any]) -> None: + self._stream = stream + self._root_span = root_span + self._extractor = extractor + self._chunks: List[str] = [] + self._track_chunks: bool = extractor is _generic_extractor + self._committed = False + + def __iter__(self) -> "RootOutputSyncStreamWrapper": + return self + + def __next__(self) -> Any: + try: + chunk = next(self._stream) + if self._track_chunks: + self._chunks.append(str(chunk)) + return chunk + except StopIteration: + self._commit() + raise + + def __enter__(self) -> "RootOutputSyncStreamWrapper": + if hasattr(self._stream, "__enter__"): + self._stream.__enter__() + return self + + def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: + if hasattr(self._stream, "__exit__"): + self._stream.__exit__(exc_type, exc_val, exc_tb) + if exc_type is None: + self._commit() + + def __getattr__(self, name: str) -> Any: + return getattr(self._stream, name) + + def __del__(self) -> None: + if not self._committed: + self._commit() + + def _commit(self) -> None: + if self._committed: + return + self._committed = True + try: + _set_output_on_root(self._root_span, self._extractor(self)) + except Exception: + logger.debug("RootOutputSyncWrapper: failed to commit output to root span", exc_info=True) + + +# Async wrapper +class RootOutputAsyncStreamWrapper: + """Wraps an async iterable; on exhaustion sets the output on the root span.""" + + _netra_stream_wrapper = True + + def __init__(self, stream: Any, root_span: Span, extractor: Callable[[Any], Any]) -> None: + self._stream = stream + self._root_span = root_span + self._extractor = extractor + self._chunks: List[str] = [] + self._track_chunks: bool = extractor is _generic_extractor + self._committed = False + + def __aiter__(self) -> "RootOutputAsyncStreamWrapper": + return self + + async def __anext__(self) -> Any: + try: + chunk = await self._stream.__anext__() + if self._track_chunks: + self._chunks.append(str(chunk)) + return chunk + except StopAsyncIteration: + self._commit() + raise + + async def __aenter__(self) -> "RootOutputAsyncStreamWrapper": + if hasattr(self._stream, "__aenter__"): + await self._stream.__aenter__() + return self + + async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: + if hasattr(self._stream, "__aexit__"): + await self._stream.__aexit__(exc_type, exc_val, exc_tb) + if exc_type is None: + self._commit() + + def __getattr__(self, name: str) -> Any: + return getattr(self._stream, name) + + def __del__(self) -> None: + if not self._committed: + self._commit() + + def _commit(self) -> None: + if self._committed: + return + self._committed = True + try: + _set_output_on_root(self._root_span, self._extractor(self)) + except Exception: + logger.debug("RootOutputAsyncWrapper: failed to commit output to root span", exc_info=True) + + +def wrap_stream_for_root_output(stream: Any, root_span: Span) -> Any: + """Wrap *stream* so the accumulated output is set on *root_span* when iteration ends. + + Detection order: + 1. ``_netra_stream_wrapper`` attribute present (Netra-wrapped) + 2. Has ``__aiter__`` or ``__iter__`` (generic) + 3. Not iterable (return unchanged) + + Args: + stream: The stream to wrap. May be sync or async. + root_span: The root OTel span that will receive the ``NETRA_USER_OUTPUT`` attribute. + + Returns: + A :class:`RootOutputSyncWrapper`, :class:`RootOutputAsyncWrapper`, or the + original *stream* unchanged if it is not iterable. + """ + is_netra = getattr(stream, "_netra_stream_wrapper", False) + extractor: Callable[[Union["RootOutputSyncStreamWrapper", "RootOutputAsyncStreamWrapper"]], Any] = ( + _netra_extractor if is_netra else _generic_extractor + ) + + if hasattr(stream, "__aiter__"): + return RootOutputAsyncStreamWrapper(stream, root_span, extractor) + + if hasattr(stream, "__iter__"): + return RootOutputSyncStreamWrapper(stream, root_span, extractor) + + logger.warning( + "set_root_output_stream: passed object of type %s is not iterable; returning unchanged", + type(stream).__name__, + ) + return stream diff --git a/netra/session_manager.py b/netra/session_manager.py index 7eee85e2..62543279 100644 --- a/netra/session_manager.py +++ b/netra/session_manager.py @@ -1,4 +1,3 @@ -import json import logging from datetime import datetime from enum import Enum @@ -9,11 +8,15 @@ from opentelemetry import trace from netra.config import Config -from netra.utils import process_content_for_max_len +from netra.utils import process_content_for_max_len, serialize_value logger = logging.getLogger(__name__) +NETRA_USER_INPUT = "netra.user.input" +NETRA_USER_OUTPUT = "netra.user.output" + + class ConversationType(str, Enum): INPUT = "input" OUTPUT = "output" @@ -399,11 +402,8 @@ def set_input(cls, value: Any) -> None: value: The input value to record. """ try: - if isinstance(value, (dict, list)): - serialized = json.dumps(value, default=str)[: Config.ATTRIBUTE_MAX_LEN] - else: - serialized = str(value)[: Config.ATTRIBUTE_MAX_LEN] - cls.set_attribute_on_active_span("netra.user.input", serialized) + serialized = serialize_value(value) + cls.set_attribute_on_active_span(NETRA_USER_INPUT, serialized) except Exception: logger.exception("SessionManager.set_input: failed to set input attribute") @@ -419,11 +419,8 @@ def set_output(cls, value: Any) -> None: value: The output value to record. """ try: - if isinstance(value, (dict, list)): - serialized = json.dumps(value, default=str)[: Config.ATTRIBUTE_MAX_LEN] - else: - serialized = str(value)[: Config.ATTRIBUTE_MAX_LEN] - cls.set_attribute_on_active_span("netra.user.output", serialized) + serialized = serialize_value(value) + cls.set_attribute_on_active_span(NETRA_USER_OUTPUT, serialized) except Exception: logger.exception("SessionManager.set_output: failed to set output attribute") @@ -438,11 +435,8 @@ def set_root_input(cls, value: Any) -> None: value: The input value to record. """ try: - if isinstance(value, (dict, list)): - serialized = json.dumps(value, default=str)[: Config.ATTRIBUTE_MAX_LEN] - else: - serialized = str(value)[: Config.ATTRIBUTE_MAX_LEN] - cls.set_attribute_on_root_span("netra.user.input", serialized) + serialized = serialize_value(value) + cls.set_attribute_on_root_span(NETRA_USER_INPUT, serialized) except Exception: logger.exception("SessionManager.set_root_input: failed to set input attribute") @@ -457,14 +451,44 @@ def set_root_output(cls, value: Any) -> None: value: The output value to record. """ try: - if isinstance(value, (dict, list)): - serialized = json.dumps(value, default=str)[: Config.ATTRIBUTE_MAX_LEN] - else: - serialized = str(value)[: Config.ATTRIBUTE_MAX_LEN] - cls.set_attribute_on_root_span("netra.user.output", serialized) + serialized = serialize_value(value) + cls.set_attribute_on_root_span(NETRA_USER_OUTPUT, serialized) except Exception: logger.exception("SessionManager.set_root_output: failed to set output attribute") + @classmethod + def set_root_output_stream(cls, value: Any) -> Any: + """Wrap a stream so that the accumulated output is set on the root span when iteration ends. + + The stream is wrapped transparently — the user should iterate over the returned object + instead of the original stream. On exhaustion (or garbage collection), the output is + automatically written to the ``netra.user.output`` attribute of the root span for the + current trace, which is then promoted to ``output`` by the export pipeline. + + Supports both sync iterables and async iterables. + + Args: + value: The stream to wrap. May be a Netra-instrumented wrapper or any generic iterable. + + Returns: + A wrapped stream proxy. Returns *value* unchanged if no active trace context + exists or if *value* is not iterable, so callers can always reassign safely:: + + stream = Netra.set_root_output_stream(stream) + """ + try: + from netra.instrumentation.stream_utils import wrap_stream_for_root_output + from netra.processors.root_span_processor import RootSpanProcessor + + root_span = RootSpanProcessor.get_root_span(trace.get_current_span()) + if not root_span: + logger.warning("SessionManager.set_root_output_stream: no root span found for current trace") + return value + return wrap_stream_for_root_output(value, root_span) + except Exception: + logger.exception("SessionManager.set_root_output_stream: failed to wrap stream") + return value + @classmethod def set_attribute_on_root_span(cls, attr_key: str, attr_value: Any) -> None: """Set an attribute on the root span of the current trace. @@ -492,6 +516,35 @@ def set_attribute_on_root_span(cls, attr_key: str, attr_value: Any) -> None: except Exception: logger.exception("Failed to set attribute '%s' on root span", attr_key) + @staticmethod + def record_exception( + exception: BaseException, + attributes: Optional[Dict[str, Any]] = None, + ) -> None: + """Record a caught exception on the currently active span. + + Adds a standard OTel exception event to the span and marks its status + as ERROR. Intended to be called from within user exception-handling + blocks where the exception would otherwise not propagate to the SDK's + automatic capture logic. + + Args: + exception: The exception instance to record. + attributes: Optional extra attributes to attach to the exception + event. + """ + try: + span = trace.get_current_span() + if not (span and getattr(span, "is_recording", lambda: False)()): + logger.warning("record_exception: no active recording span to record exception on") + return + + span.record_exception(exception, attributes=attributes) + span.set_status(trace.Status(trace.StatusCode.ERROR, str(exception))) + span.set_attribute(f"{Config.LIBRARY_NAME}.error_message", str(exception)) + except Exception: + logger.exception("Failed to record exception on active span") + @staticmethod def set_attribute_on_active_span(attr_key: str, attr_value: Any) -> None: """ diff --git a/netra/simulation/__init__.py b/netra/simulation/__init__.py index 79c7cfd8..efcb9d02 100644 --- a/netra/simulation/__init__.py +++ b/netra/simulation/__init__.py @@ -2,6 +2,8 @@ from netra.simulation.models import ( ConversationResponse, ConversationStatus, + FileData, + ProcessedFile, SimulationItem, TaskResult, ) @@ -12,6 +14,8 @@ "BaseTask", "ConversationResponse", "ConversationStatus", + "FileData", + "ProcessedFile", "SimulationItem", "TaskResult", ] diff --git a/netra/simulation/api.py b/netra/simulation/api.py index dfea287e..1b8c441b 100644 --- a/netra/simulation/api.py +++ b/netra/simulation/api.py @@ -1,5 +1,3 @@ -"""Public API for running multi-turn conversation simulations.""" - import asyncio import concurrent.futures import logging @@ -8,7 +6,8 @@ from netra.config import Config from netra.simulation.client import SimulationHttpClient -from netra.simulation.models import SimulationItem +from netra.simulation.constants import DEFAULT_MAX_TURNS, LOG_PREFIX, SPAN_NAME +from netra.simulation.models import ConversationStatus, FileData, SimulationItem from netra.simulation.task import BaseTask from netra.simulation.utils import ( execute_task, @@ -20,9 +19,6 @@ logger = logging.getLogger(__name__) -_LOG_PREFIX = "netra.simulation" -_SPAN_NAME = "Netra.Simulation.TestRun" - class Simulation: """Public API for running multi-turn conversation simulations. @@ -43,6 +39,10 @@ def __init__(self, config: Config) -> None: self._config = config self._client = SimulationHttpClient(config) + def close(self) -> None: + """Release resources held by the simulation client.""" + self._client.close() + def run_simulation( self, name: str, @@ -50,16 +50,18 @@ def run_simulation( task: BaseTask, context: Optional[dict[str, Any]] = None, max_concurrency: int = 5, + max_turns: int = DEFAULT_MAX_TURNS, ) -> Optional[dict[str, Any]]: """Run a multi-turn conversation simulation. Args: name: Name of the simulation run. dataset_id: Identifier of the dataset to simulate. - task: A BaseTask instance whose run() method receives (message, session_id) + task: A BaseTask instance whose run() method receives (message, session_id, files) and returns TaskResult. Can be sync or async. context: Optional context data for the simulation. max_concurrency: Maximum parallel executions (default: 5). + max_turns: Maximum conversation turns per item before aborting (default: 50). Returns: Dictionary with simulation results, or None on failure. @@ -77,51 +79,60 @@ def run_simulation( return None run_id = run_result.get("run_id") - run_items = run_result.get("simulation_items") - if not run_items: - logger.error("%s: No items returned from create_run", _LOG_PREFIX) + simulation_items = run_result.get("simulation_items") + if not simulation_items: + logger.error("%s: No items returned from create_run", LOG_PREFIX) return None - logger.info("%s: Starting simulation with %d items", _LOG_PREFIX, len(run_items)) + logger.info("%s: Starting simulation with %d items", LOG_PREFIX, len(simulation_items)) try: result = run_async_safely( - self._run_simulation_async(run_id, run_items, task, max_concurrency) # type:ignore[arg-type] + self._run_simulation_async( + run_id, simulation_items, task, max_concurrency, max_turns # type:ignore[arg-type] + ) ) elapsed_time = time.time() - start_time - logger.info("%s: Simulation completed in %.2f seconds", _LOG_PREFIX, elapsed_time) + logger.info("%s: Simulation completed in %.2f seconds", LOG_PREFIX, elapsed_time) self._client.post_run_status(run_id, "completed") # type:ignore[arg-type] return result - except BaseException: - logger.error("%s: Run simulation failed", _LOG_PREFIX) + except Exception: + logger.exception("%s: Run simulation failed", LOG_PREFIX) self._client.post_run_status(run_id, "failed") # type:ignore[arg-type] return None async def _run_simulation_async( self, run_id: str, - run_items: list[SimulationItem], + simulation_items: list[SimulationItem], task: BaseTask, max_concurrency: int, + max_turns: int, ) -> dict[str, Any]: - """Async implementation of run_simulation with semaphore-based concurrency. + """Orchestrate concurrent simulation execution. + + Each simulation item is dispatched to a thread via ``run_in_executor``. + Inside each thread, ``run_async_safely`` creates a **new** event loop + so that async user tasks (``BaseTask.run``) work correctly without + nesting into the orchestrator's loop. This two-level design lets us + honour ``max_concurrency`` while supporting both sync and async tasks + transparently. Args: run_id: The simulation run identifier. - run_items: List of simulation items to process. + simulation_items: List of simulation items to process. task: The BaseTask instance to execute (sync or async). max_concurrency: Maximum concurrent executions. + max_turns: Maximum conversation turns per item. Returns: Dictionary with simulation results. """ - - max_workers = min(5, max_concurrency) results: dict[str, Any] = { "success": True, "completed": [], "failed": [], - "total_items": len(run_items), + "total_items": len(simulation_items), } processed_count = 0 lock = asyncio.Lock() @@ -129,8 +140,7 @@ async def _run_simulation_async( loop = asyncio.get_running_loop() def run_item_in_thread(run_item: SimulationItem) -> dict[str, Any]: - """ - Run a single simulation item in a thread. + """Run a single simulation item in a dedicated thread/event-loop. Args: run_item: The simulation item to run. @@ -138,11 +148,10 @@ def run_item_in_thread(run_item: SimulationItem) -> dict[str, Any]: Returns: Dictionary with simulation result. """ - return run_async_safely(self._execute_conversation(run_id, run_item, task)) + return run_async_safely(self._execute_conversation(run_id, run_item, task, max_turns)) async def process_item(run_item: SimulationItem) -> None: - """ - Process a single simulation item and handle its completion. + """Process a single simulation item and record its outcome. Args: run_item: The simulation item to process. @@ -155,14 +164,14 @@ async def process_item(run_item: SimulationItem) -> None: processed_count += 1 logger.info( "%s: %d/%d processed (run_item_id=%s)", - _LOG_PREFIX, + LOG_PREFIX, processed_count, - len(run_items), + len(simulation_items), run_item.run_item_id, ) - with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: - tasks = [asyncio.create_task(process_item(run_item)) for run_item in run_items] + with concurrent.futures.ThreadPoolExecutor(max_workers=max_concurrency) as executor: + tasks = [asyncio.create_task(process_item(item)) for item in simulation_items] try: await asyncio.gather(*tasks) except (asyncio.CancelledError, KeyboardInterrupt): @@ -172,7 +181,7 @@ async def process_item(run_item: SimulationItem) -> None: executor.shutdown(wait=False, cancel_futures=True) logger.info( "%s: Completed=%d, Failed=%d", - _LOG_PREFIX, + LOG_PREFIX, len(results["completed"]), len(results["failed"]), ) @@ -183,13 +192,15 @@ async def _execute_conversation( run_id: str, run_item: SimulationItem, task: BaseTask, - ) -> Any: + max_turns: int, + ) -> dict[str, Any]: """Execute a multi-turn conversation for a single simulation item. Args: run_id: The simulation run identifier. run_item: The simulation item to process. task: The BaseTask instance to execute (sync or async). + max_turns: Safety limit on the number of conversation turns. Returns: Dictionary with execution result including success status. @@ -197,27 +208,30 @@ async def _execute_conversation( run_item_id = run_item.run_item_id message = run_item.message turn_id = run_item.turn_id + raw_files: list[FileData] = run_item.files session_id: Optional[str] = None - while True: + for turn_number in range(1, max_turns + 1): try: - with SpanWrapper(_SPAN_NAME, module_name=_LOG_PREFIX) as span: + with SpanWrapper(SPAN_NAME, module_name=LOG_PREFIX) as span: trace_id = "" otel_span = span.get_current_span() if otel_span: span_context = otel_span.get_span_context() trace_id = format_trace_id(span_context.trace_id) - response_message, task_session_id = await execute_task(task, message, session_id) + response_message, task_session_id = await execute_task( + task, message, session_id, raw_files=raw_files + ) if task_session_id: session_id = task_session_id - response = self._client.trigger_conversation( - message=response_message, - turn_id=turn_id, - session_id=session_id or "", - trace_id=trace_id, - ) + response = self._client.trigger_conversation( + message=response_message, + turn_id=turn_id, + session_id=session_id or "", + trace_id=trace_id, + ) if response is None: error_msg = "Failed to get conversation response" @@ -228,10 +242,10 @@ async def _execute_conversation( "turn_id": turn_id, } - if response.decision == "stop": + if response.decision == ConversationStatus.STOP: logger.info( "%s: Completed run_item_id=%s reason=%s", - _LOG_PREFIX, + LOG_PREFIX, run_item_id, response.reason, ) @@ -243,12 +257,13 @@ async def _execute_conversation( message = response.next_user_message # type:ignore[assignment] turn_id = response.next_turn_id # type:ignore[assignment] + raw_files = response.next_files except Exception as exc: error_msg = str(exc) - logger.error( + logger.exception( "%s: Task failed run_item_id=%s, turn_id=%s: %s", - _LOG_PREFIX, + LOG_PREFIX, run_item_id, turn_id, error_msg, @@ -260,3 +275,13 @@ async def _execute_conversation( "error": error_msg, "turn_id": turn_id, } + + error_msg = f"Exceeded maximum turns ({max_turns}) for run_item_id={run_item_id}" + logger.error("%s: %s", LOG_PREFIX, error_msg) + self._client.report_failure(run_id=run_id, run_item_id=run_item_id, error=error_msg) + return { + "run_item_id": run_item_id, + "success": False, + "error": error_msg, + "turn_id": turn_id, + } diff --git a/netra/simulation/client.py b/netra/simulation/client.py index d4951854..d938c5a9 100644 --- a/netra/simulation/client.py +++ b/netra/simulation/client.py @@ -1,19 +1,24 @@ -"""HTTP client for simulation API endpoints.""" - import logging -import os from typing import Any, Optional import httpx from netra.config import Config -from netra.simulation.models import ConversationResponse, SimulationItem +from netra.simulation.constants import ( + DEFAULT_TIMEOUT, + ENV_TIMEOUT, + LOG_PREFIX, + TELEMETRY_SUFFIX, + URL_AGENT_RESPONSE, + URL_CREATE_RUN, + URL_RUN_ITEM_STATUS, + URL_RUN_STATUS, +) +from netra.simulation.models import ConversationResponse, ConversationStatus, FileData, SimulationItem +from netra.simulation.utils import parse_env_float logger = logging.getLogger(__name__) -_DEFAULT_TIMEOUT = 10.0 -_LOG_PREFIX = "netra.simulation" - class SimulationHttpClient: """Internal HTTP client for simulation API endpoints. @@ -32,6 +37,26 @@ def __init__(self, config: Config) -> None: """ self._client: Optional[httpx.Client] = self._create_client(config) + def close(self) -> None: + """Close the underlying HTTP client and release connection resources.""" + if self._client: + try: + self._client.close() + except Exception: + logger.debug("%s: Error closing HTTP client", LOG_PREFIX, exc_info=True) + finally: + self._client = None + + def _ensure_client(self) -> Optional[httpx.Client]: + """Return the underlying client, logging an error if it is not initialized. + + Returns: + The httpx client, or None if not available. + """ + if not self._client: + logger.error("%s: Client not initialized", LOG_PREFIX) + return self._client + def _create_client(self, config: Config) -> Optional[httpx.Client]: """Create and configure the HTTP client. @@ -43,17 +68,17 @@ def _create_client(self, config: Config) -> Optional[httpx.Client]: """ endpoint = (config.otlp_endpoint or "").strip() if not endpoint: - logger.error("%s: NETRA_OTLP_ENDPOINT is required", _LOG_PREFIX) + logger.error("%s: NETRA_OTLP_ENDPOINT is required", LOG_PREFIX) return None base_url = self._resolve_base_url(endpoint) headers = self._build_headers(config) - timeout = self._get_timeout() + timeout = parse_env_float(ENV_TIMEOUT, DEFAULT_TIMEOUT) try: return httpx.Client(base_url=base_url, headers=headers, timeout=timeout) - except Exception as exc: - logger.error("%s: Failed to create HTTP client: %s", _LOG_PREFIX, exc) + except Exception: + logger.exception("%s: Failed to create HTTP client", LOG_PREFIX) return None def _resolve_base_url(self, endpoint: str) -> str: @@ -66,8 +91,8 @@ def _resolve_base_url(self, endpoint: str) -> str: The cleaned base URL. """ base_url = endpoint.rstrip("/") - if base_url.endswith("/telemetry"): - base_url = base_url[: -len("/telemetry")] + if base_url.endswith(TELEMETRY_SUFFIX): + base_url = base_url[: -len(TELEMETRY_SUFFIX)] return base_url def _build_headers(self, config: Config) -> dict[str, str]: @@ -84,26 +109,6 @@ def _build_headers(self, config: Config) -> dict[str, str]: headers["x-api-key"] = config.api_key return headers - def _get_timeout(self) -> float: - """Get timeout from environment or use default. - - Returns: - The timeout value in seconds. - """ - timeout_str = os.getenv("NETRA_SIMULATION_TIMEOUT") - if not timeout_str: - return _DEFAULT_TIMEOUT - try: - return float(timeout_str) - except ValueError: - logger.warning( - "%s: Invalid timeout '%s', using default %.1f", - _LOG_PREFIX, - timeout_str, - _DEFAULT_TIMEOUT, - ) - return _DEFAULT_TIMEOUT - def create_run( self, name: str, @@ -120,26 +125,25 @@ def create_run( Returns: Dictionary containing run_id and simulation_items, or None on failure. """ - if not self._client: - logger.error("%s: Client not initialized", _LOG_PREFIX) + if not self._ensure_client(): return None response: Optional[httpx.Response] = None try: - url = "/evaluations/test_run/multi-turn" + url = URL_CREATE_RUN payload: dict[str, Any] = { "name": name, "datasetId": dataset_id, "context": context or {}, } - response = self._client.post(url, json=payload, timeout=500) + response = self._client.post(url, json=payload) # type:ignore[union-attr] response.raise_for_status() data = response.json() response_data = data.get("data", {}) user_messages = response_data.get("userMessages", []) if not user_messages: - logger.warning("%s: No user messages returned from create_run", _LOG_PREFIX) + logger.warning("%s: No user messages returned from create_run", LOG_PREFIX) return None run_id = response_data.get("id", "") @@ -148,6 +152,7 @@ def create_run( run_item_id=msg.get("testRunItemId", ""), message=msg.get("userMessage", ""), turn_id=msg.get("turnId", ""), + files=self._parse_files(msg.get("attachments")), ) for msg in user_messages ] @@ -158,7 +163,7 @@ def create_run( except Exception as exc: error_msg = self._extract_error_message(response, exc) - logger.error("%s: Failed to create simulation run: %s", _LOG_PREFIX, error_msg) + logger.exception("%s: Failed to create simulation run: %s", LOG_PREFIX, error_msg) return None def trigger_conversation( @@ -179,13 +184,12 @@ def trigger_conversation( Returns: ConversationResponse with next turn info, or None on failure. """ - if not self._client: - logger.error("%s: Client not initialized", _LOG_PREFIX) + if not self._ensure_client(): return None response: Optional[httpx.Response] = None try: - url = "/evaluations/turn/agent-response" + url = URL_AGENT_RESPONSE payload: dict[str, Any] = { "turnId": turn_id, "agentResponse": {"message": message}, @@ -193,14 +197,15 @@ def trigger_conversation( "traceId": trace_id, } - response = self._client.post(url, json=payload, timeout=500) + response = self._client.post(url, json=payload) # type:ignore[union-attr] response.raise_for_status() data = response.json() response_data = data.get("data", {}) - decision = response_data.get("decision", "continue") + raw_decision = response_data.get("decision", "continue") + decision = ConversationStatus(raw_decision) - if decision == "stop": + if decision == ConversationStatus.STOP: return ConversationResponse( decision=decision, reason=response_data.get("reason", ""), @@ -208,7 +213,7 @@ def trigger_conversation( user_messages = response_data.get("userMessages", []) if not user_messages: - logger.warning("%s: No user messages in continue response", _LOG_PREFIX) + logger.warning("%s: No user messages in continue response", LOG_PREFIX) return None next_msg = next(iter(user_messages)) @@ -216,12 +221,12 @@ def trigger_conversation( decision=decision, next_turn_id=next_msg.get("turnId", ""), next_user_message=next_msg.get("userMessage", ""), - next_run_item_id=next_msg.get("testRunItemId", ""), + next_files=self._parse_files(next_msg.get("attachments")), ) except Exception as exc: error_msg = self._extract_error_message(response, exc) - logger.error("%s: Failed to trigger conversation: %s", _LOG_PREFIX, error_msg) + logger.exception("%s: Failed to trigger conversation: %s", LOG_PREFIX, error_msg) raise def report_failure(self, run_id: str, run_item_id: str, error: str) -> None: @@ -232,20 +237,19 @@ def report_failure(self, run_id: str, run_item_id: str, error: str) -> None: run_item_id: Identifier of the run item. error: Error message describing the failure. """ - if not self._client: - logger.error("%s: Client not initialized", _LOG_PREFIX) + if not self._ensure_client(): return response: Optional[httpx.Response] = None try: - url = f"/evaluations/run/{run_id}/item/{run_item_id}/status" + url = URL_RUN_ITEM_STATUS.format(run_id=run_id, run_item_id=run_item_id) payload: dict[str, Any] = {"status": "failed", "failureReason": error} - response = self._client.patch(url, json=payload) + response = self._client.patch(url, json=payload) # type:ignore[union-attr] response.raise_for_status() - logger.info("%s: Reported failure - %s", _LOG_PREFIX, error) + logger.info("%s: Reported failure - %s", LOG_PREFIX, error) except Exception as exc: error_msg = self._extract_error_message(response, exc) - logger.error("%s: Failed to report failure: %s", _LOG_PREFIX, error_msg) + logger.exception("%s: Failed to report failure: %s", LOG_PREFIX, error_msg) def post_run_status(self, run_id: str, status: str) -> Any: """Submit the run status. @@ -257,26 +261,60 @@ def post_run_status(self, run_id: str, status: str) -> Any: Returns: Backend JSON response containing confirmation, or error dict. """ - if not self._client: - logger.error("%s: Client not initialized; cannot post run status", _LOG_PREFIX) + if not self._ensure_client(): return {"success": False} response: Optional[httpx.Response] = None try: - url = f"/evaluations/run/{run_id}/status" + url = URL_RUN_STATUS.format(run_id=run_id) payload: dict[str, Any] = {"status": status} - response = self._client.post(url, json=payload) + response = self._client.post(url, json=payload) # type:ignore[union-attr] response.raise_for_status() data = response.json() if isinstance(data, dict) and "data" in data: - logger.info("%s: Test run status %s", _LOG_PREFIX, status) + logger.info("%s: Test run status %s", LOG_PREFIX, status) return data.get("data", {}) return data except Exception as exc: error_msg = self._extract_error_message(response, exc) - logger.error("%s: Failed to post run status for run '%s': %s", _LOG_PREFIX, run_id, error_msg) + logger.exception("%s: Failed to post run status for run '%s': %s", LOG_PREFIX, run_id, error_msg) return {"success": False} + @staticmethod + def _parse_files(raw_files: list[dict[str, str]] | None) -> list[FileData]: + """Parse raw file entries from the backend response into FileData objects. + + Args: + raw_files: List of file dictionaries from the JSON response, or None. + + Returns: + List of FileData objects. Malformed entries are skipped. + """ + if not raw_files or not isinstance(raw_files, list): + return [] + + parsed: list[FileData] = [] + for entry in raw_files: + if not isinstance(entry, dict): + continue + file_name = entry.get("fileName", "") + download_url = entry.get("downloadUrl", "") + if not file_name or not download_url: + logger.warning( + "%s: Skipping file entry with missing fileName or downloadUrl", + LOG_PREFIX, + ) + continue + parsed.append( + FileData( + file_name=file_name, + content_type=entry.get("contentType", ""), + description=entry.get("description"), + download_url=download_url, + ) + ) + return parsed + def _extract_error_message( self, response: Optional[httpx.Response], @@ -298,5 +336,5 @@ def _extract_error_message( if isinstance(error_data, dict): return error_data.get("message", str(exc)) except Exception: - pass + logger.debug("%s: Could not parse error from response body", LOG_PREFIX, exc_info=True) return str(exc) diff --git a/netra/simulation/constants.py b/netra/simulation/constants.py new file mode 100644 index 00000000..20579473 --- /dev/null +++ b/netra/simulation/constants.py @@ -0,0 +1,38 @@ +"""Shared constants for the simulation module.""" + +# --------------------------------------------------------------------------- +# Logging +# --------------------------------------------------------------------------- +LOG_PREFIX = "netra.simulation" + +# --------------------------------------------------------------------------- +# Span / tracing +# --------------------------------------------------------------------------- +SPAN_NAME = "Netra.Simulation.TestRun" + +# --------------------------------------------------------------------------- +# Conversation limits +# --------------------------------------------------------------------------- +DEFAULT_MAX_TURNS = 50 + +# --------------------------------------------------------------------------- +# API endpoints +# --------------------------------------------------------------------------- +URL_CREATE_RUN = "/evaluations/test_run/multi-turn" +URL_AGENT_RESPONSE = "/evaluations/turn/agent-response" +URL_RUN_ITEM_STATUS = "/evaluations/run/{run_id}/item/{run_item_id}/status" +URL_RUN_STATUS = "/evaluations/run/{run_id}/status" +TELEMETRY_SUFFIX = "/telemetry" + +# --------------------------------------------------------------------------- +# HTTP client timeouts +# --------------------------------------------------------------------------- +DEFAULT_TIMEOUT = 500.0 +ENV_TIMEOUT = "NETRA_SIMULATION_TIMEOUT" + +# --------------------------------------------------------------------------- +# File download +# --------------------------------------------------------------------------- +DEFAULT_FILE_DOWNLOAD_TIMEOUT = 30.0 +ENV_FILE_DOWNLOAD_TIMEOUT = "NETRA_SIMULATION_FILE_DOWNLOAD_TIMEOUT" +MAX_FILE_DOWNLOAD_WORKERS = 8 diff --git a/netra/simulation/models.py b/netra/simulation/models.py index 258f6554..26b68e90 100644 --- a/netra/simulation/models.py +++ b/netra/simulation/models.py @@ -1,6 +1,6 @@ """Data models for the simulation module.""" -from dataclasses import dataclass +from dataclasses import dataclass, field from enum import Enum from typing import Optional @@ -12,6 +12,40 @@ class ConversationStatus(Enum): STOP = "stop" +@dataclass(slots=True, frozen=True) +class FileData: + """Raw file metadata received from the backend. + + Attributes: + file_name: Name of the file. + content_type: MIME type of the file content. + description: Optional description of the file. + download_url: Pre-signed URL to download the file. + """ + + file_name: str + content_type: str + description: Optional[str] + download_url: str + + +@dataclass(slots=True, frozen=True) +class ProcessedFile: + """File after download and base64 encoding, delivered to the user task. + + Attributes: + file_name: Name of the file. + content_type: MIME type of the file content. + description: Optional description of the file. + data: Base64-encoded file content. + """ + + file_name: str + content_type: str + description: Optional[str] + data: str + + @dataclass(slots=True, frozen=True) class SimulationItem: """Represents a single item in a simulation run. @@ -20,11 +54,13 @@ class SimulationItem: run_item_id: Unique identifier for the run item. message: The user message content. turn_id: Identifier for the conversation turn. + files: File metadata attached to this item. """ run_item_id: str message: str turn_id: str + files: list[FileData] = field(default_factory=list) @dataclass(slots=True) @@ -32,18 +68,18 @@ class ConversationResponse: """Response from the conversation trigger API. Attributes: - decision: The decision to continue or stop the conversation. + decision: Whether to continue or stop the conversation. reason: Optional reason for stopping the conversation. next_turn_id: Identifier for the next turn if continuing. next_user_message: The next user message if continuing. - next_run_item_id: Identifier for the next run item if continuing. + next_files: File metadata for the next turn if continuing. """ - decision: str + decision: ConversationStatus reason: Optional[str] = None next_turn_id: Optional[str] = None next_user_message: Optional[str] = None - next_run_item_id: Optional[str] = None + next_files: list[FileData] = field(default_factory=list) @dataclass(slots=True, frozen=True) diff --git a/netra/simulation/task.py b/netra/simulation/task.py index bdfc39b7..8ecb40f4 100644 --- a/netra/simulation/task.py +++ b/netra/simulation/task.py @@ -8,7 +8,7 @@ from abc import ABC, abstractmethod from typing import Awaitable, Optional -from netra.simulation.models import TaskResult +from netra.simulation.models import ProcessedFile, TaskResult class BaseTask(ABC): @@ -18,13 +18,18 @@ class BaseTask(ABC): Subclasses must: - Implement run(): Executes the task logic and returns a TaskResult. - The run method receives a message and optional session_id, and must return - a TaskResult containing the response message and session_id. + The framework always passes ``message``, ``session_id``, and ``files`` + to ``run()``. Tasks that don't need file attachments can simply ignore + the ``files`` parameter. Example: class MyTask(BaseTask): - def run(self, message: str, session_id: Optional[str] = None) -> TaskResult: - # Call your LLM or agent here + def run( + self, + message: str, + session_id: Optional[str] = None, + files: Optional[list[ProcessedFile]] = None, + ) -> TaskResult: response = my_agent.chat(message, session_id=session_id) return TaskResult( message=response.text, @@ -37,10 +42,31 @@ def run(self, message: str, session_id: Optional[str] = None) -> TaskResult: task=MyTask(), ) + Example with file uploads: + class MyFileTask(BaseTask): + def run( + self, + message: str, + session_id: Optional[str] = None, + files: Optional[list[ProcessedFile]] = None, + ) -> TaskResult: + if files: + for f in files: + print(f.file_name, f.content_type, len(f.data)) + response = my_agent.chat(message, session_id=session_id, files=files) + return TaskResult( + message=response.text, + session_id=response.session_id or session_id or "default", + ) + Async Example: class MyAsyncTask(BaseTask): - async def run(self, message: str, session_id: Optional[str] = None) -> TaskResult: - # Call your async LLM or agent here + async def run( + self, + message: str, + session_id: Optional[str] = None, + files: Optional[list[ProcessedFile]] = None, + ) -> TaskResult: response = await my_async_agent.chat(message, session_id=session_id) return TaskResult( message=response.text, @@ -49,7 +75,12 @@ async def run(self, message: str, session_id: Optional[str] = None) -> TaskResul """ @abstractmethod - def run(self, message: str, session_id: Optional[str] = None) -> TaskResult | Awaitable[TaskResult]: + def run( + self, + message: str, + session_id: Optional[str] = None, + files: Optional[list[ProcessedFile]] = None, + ) -> TaskResult | Awaitable[TaskResult]: """ Execute the task logic. @@ -60,6 +91,8 @@ def run(self, message: str, session_id: Optional[str] = None) -> TaskResult | Aw message: The input message from the simulation. session_id: Optional session identifier for conversation continuity. Will be None for the first turn of a conversation. + files: Optional list of base64-encoded file attachments from the + dataset item. Will be None when no files are attached. Returns: TaskResult: The task result containing: diff --git a/netra/simulation/utils.py b/netra/simulation/utils.py index 341cd26a..24c078bb 100644 --- a/netra/simulation/utils.py +++ b/netra/simulation/utils.py @@ -1,16 +1,53 @@ """Utility functions for the simulation module.""" import asyncio +import base64 +import concurrent.futures import logging +import os import threading -from typing import Awaitable, Optional, Tuple, TypeVar +from typing import Awaitable, Optional, TypeVar -from netra.simulation.models import TaskResult +import httpx + +from netra.simulation.constants import ( + DEFAULT_FILE_DOWNLOAD_TIMEOUT, + ENV_FILE_DOWNLOAD_TIMEOUT, + LOG_PREFIX, + MAX_FILE_DOWNLOAD_WORKERS, +) +from netra.simulation.models import FileData, ProcessedFile, TaskResult from netra.simulation.task import BaseTask logger = logging.getLogger(__name__) -T = TypeVar("T") +_T = TypeVar("_T") + + +def parse_env_float(env_var: str, default: float) -> float: + """Read an environment variable and parse it as a float. + + Args: + env_var: Name of the environment variable. + default: Value to return when the variable is unset or invalid. + + Returns: + The parsed float, or *default* on failure. + """ + raw = os.getenv(env_var) + if not raw: + return default + try: + return float(raw) + except ValueError: + logger.warning( + "%s: Invalid value '%s' for %s, using default %.1f", + LOG_PREFIX, + raw, + env_var, + default, + ) + return default def format_trace_id(trace_id: int) -> str: @@ -47,11 +84,14 @@ def validate_simulation_inputs( return True -def run_async_safely(coro: Awaitable[T]) -> T: - """Run an async coroutine from sync code. +def run_async_safely(coro: Awaitable[_T]) -> _T: + """Run an async coroutine from synchronous code. - If an event loop is already running, executes in a dedicated thread - to avoid 'asyncio.run() cannot be called from a running event loop'. + When called from a context that already has a running event loop (e.g. a + Jupyter notebook, or an async framework like FastAPI), ``asyncio.run()`` + would raise. In that case we spin up a **new daemon thread** with its own + event loop via ``asyncio.run()`` so the caller's loop is never blocked or + re-entered. Args: coro: The coroutine to execute. @@ -68,7 +108,7 @@ def run_async_safely(coro: Awaitable[T]) -> T: loop = None if loop and loop.is_running(): - result_holder: dict[str, T] = {} + result_holder: dict[str, _T] = {} error_holder: dict[str, BaseException] = {} def runner() -> None: @@ -88,17 +128,76 @@ def runner() -> None: return asyncio.run(coro) # type: ignore[arg-type] +def _download_single_file(file_data: FileData, timeout: float) -> ProcessedFile: + """Download a single file and base64-encode its content. + + Args: + file_data: Metadata for the file to download. + timeout: HTTP request timeout in seconds. + + Returns: + A ProcessedFile with the base64-encoded content. + + Raises: + RuntimeError: If the download or encoding fails. + """ + try: + response = httpx.get(file_data.download_url, timeout=timeout) + response.raise_for_status() + encoded = base64.b64encode(response.content).decode("ascii") + return ProcessedFile( + file_name=file_data.file_name, + content_type=file_data.content_type, + description=file_data.description, + data=encoded, + ) + except Exception as exc: + raise RuntimeError(f"Failed to download file '{file_data.file_name}': {exc}") from exc + + +def process_files(files: list[FileData]) -> list[ProcessedFile]: + """Download files from pre-signed URLs and base64-encode their content. + + Downloads run concurrently via a thread pool. If any file fails, the + entire batch is aborted with a ``RuntimeError`` so that file-aware tasks + never receive a partial file list. + + Args: + files: List of FileData objects containing download URLs. + + Returns: + List of ProcessedFile objects with base64-encoded data. + + Raises: + RuntimeError: If a file download or encoding fails. + """ + if not files: + return [] + + timeout = parse_env_float(ENV_FILE_DOWNLOAD_TIMEOUT, DEFAULT_FILE_DOWNLOAD_TIMEOUT) + + if len(files) == 1: + return [_download_single_file(files[0], timeout)] + + max_workers = min(MAX_FILE_DOWNLOAD_WORKERS, len(files)) + with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as pool: + futures = [pool.submit(_download_single_file, fd, timeout) for fd in files] + return [f.result() for f in futures] + + async def execute_task( task: BaseTask, message: str, session_id: Optional[str], -) -> Tuple[str, Optional[str]]: + raw_files: Optional[list[FileData]] = None, +) -> tuple[str, Optional[str]]: """Execute a task's run method (sync or async) and extract message and session_id. Args: task: The BaseTask instance to execute. message: The input message to pass to the task. session_id: The current session identifier. + raw_files: Raw file metadata from the backend. Returns: A tuple of (response_message, session_id). @@ -106,7 +205,9 @@ async def execute_task( Raises: ValueError: If the task returns an unsupported type. """ - result = task.run(message=message, session_id=session_id) + processed_files = process_files(raw_files) if raw_files else None + + result = task.run(message=message, session_id=session_id, files=processed_files) if asyncio.iscoroutine(result): result = await result diff --git a/netra/span_wrapper.py b/netra/span_wrapper.py index edb7986b..77843a44 100644 --- a/netra/span_wrapper.py +++ b/netra/span_wrapper.py @@ -160,9 +160,12 @@ def __exit__(self, exc_type: Optional[type], exc_val: Optional[Exception], exc_t # Handle status and errors if exc_type is None and self.status == "pending": - self.status = "success" - if self.span: - self.span.set_status(Status(StatusCode.OK)) + if self._span_has_error_status(): + self.status = "error" + else: + self.status = "success" + if self.span: + self.span.set_status(Status(StatusCode.OK)) elif exc_type is not None: self.status = "error" self.error_message = str(exc_val) @@ -210,6 +213,26 @@ def __exit__(self, exc_type: Optional[type], exc_val: Optional[Exception], exc_t # Don't suppress exceptions return False + def _span_has_error_status(self) -> Any: + """Check whether the underlying OTel span has already been set to ERROR. + + This handles the case where ``Netra.record_exception()`` (or any direct + OTel API call) marked the span as errored while the exception was caught + by user code, so ``__exit__`` receives ``exc_type is None``. + + Returns: + True if the span's status code is ERROR, False otherwise. + """ + if self.span is None: + return False + try: + status = getattr(self.span, "status", None) + if status is not None: + return status.status_code == StatusCode.ERROR + except Exception: + logger.exception("Failed to check span status on span '%s'", self.name) + return False + def set_attribute(self, key: str, value: str) -> "SpanWrapper": """ Set a single attribute and return self for method chaining. diff --git a/netra/utils.py b/netra/utils.py index 7d6d69b8..79265bf9 100644 --- a/netra/utils.py +++ b/netra/utils.py @@ -8,6 +8,7 @@ import logging from typing import AbstractSet, Any, Optional, Set +from netra.config import Config from netra.instrumentation.instruments import ( DEFAULT_INSTRUMENTS_FOR_ROOT, InstrumentSet, @@ -89,6 +90,20 @@ def process_content_for_max_len(content: Any, max_len: int) -> Any: return content +def serialize_value(value: Any) -> str: + """Serialize *value* to a string capped at ``Config.ATTRIBUTE_MAX_LEN``.""" + if value is None: + return "" + try: + import json + + serialized = json.dumps(value) if isinstance(value, (dict, list)) else str(value) + return truncate_string(serialized, Config.ATTRIBUTE_MAX_LEN) + except Exception: + logger.debug("utils: failed to serialize value", exc_info=True) + return "" + + def resolve_root_instruments( root_instruments: Optional[AbstractSet[NetraInstruments]], block_instruments: Optional[AbstractSet[NetraInstruments]], diff --git a/poetry.lock b/poetry.lock index aa76cca4..9bbd8f7a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -137,27 +137,6 @@ files = [ frozenlist = ">=1.1.0" typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} -[[package]] -name = "alembic" -version = "1.18.4" -description = "A database migration tool for SQLAlchemy." -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a"}, - {file = "alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc"}, -] - -[package.dependencies] -Mako = "*" -SQLAlchemy = ">=1.4.23" -tomli = {version = "*", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.12" - -[package.extras] -tz = ["tzdata"] - [[package]] name = "annotated-types" version = "0.7.0" @@ -170,54 +149,25 @@ files = [ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] -[[package]] -name = "anthropic" -version = "0.57.1" -description = "The official Python library for the anthropic API" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "anthropic-0.57.1-py3-none-any.whl", hash = "sha256:33afc1f395af207d07ff1bffc0a3d1caac53c371793792569c5d2f09283ea306"}, - {file = "anthropic-0.57.1.tar.gz", hash = "sha256:7815dd92245a70d21f65f356f33fc80c5072eada87fb49437767ea2918b2c4b0"}, -] - -[package.dependencies] -anyio = ">=3.5.0,<5" -distro = ">=1.7.0,<2" -httpx = ">=0.25.0,<1" -jiter = ">=0.4.0,<1" -pydantic = ">=1.9.0,<3" -sniffio = "*" -typing-extensions = ">=4.10,<5" - -[package.extras] -aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.6)"] -bedrock = ["boto3 (>=1.28.57)", "botocore (>=1.31.57)"] -vertex = ["google-auth[requests] (>=2,<3)"] - [[package]] name = "anyio" -version = "4.9.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" +version = "4.13.0" +description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, - {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, + {file = "anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708"}, + {file = "anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc"}, ] [package.dependencies] exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" -sniffio = ">=1.1" typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] -trio = ["trio (>=0.26.1)"] +trio = ["trio (>=0.32.0)"] [[package]] name = "argcomplete" @@ -265,21 +215,6 @@ files = [ {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, ] -[[package]] -name = "asyncer" -version = "0.0.8" -description = "Asyncer, async and await, focused on developer experience." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "asyncer-0.0.8-py3-none-any.whl", hash = "sha256:5920d48fc99c8f8f0f1576e1882f5022885589c5fcbc46ce4224ec3e53776eeb"}, - {file = "asyncer-0.0.8.tar.gz", hash = "sha256:a589d980f57e20efb07ed91d0dbe67f1d2fd343e7142c66d3a099f05c620739c"}, -] - -[package.dependencies] -anyio = ">=3.4.0,<5.0" - [[package]] name = "attrs" version = "25.3.0" @@ -332,18 +267,6 @@ files = [ pycodestyle = ">=2.12.0" tomli = {version = "*", markers = "python_version < \"3.11\""} -[[package]] -name = "backoff" -version = "2.2.1" -description = "Function decoration for backoff and retry" -optional = false -python-versions = ">=3.7,<4.0" -groups = ["main"] -files = [ - {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, - {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, -] - [[package]] name = "black" version = "25.1.0" @@ -442,18 +365,6 @@ files = [ [package.dependencies] numpy = {version = ">=1.19.0,<3.0.0", markers = "python_version >= \"3.9\""} -[[package]] -name = "cachetools" -version = "7.0.1" -description = "Extensible memoizing collections and decorators" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "cachetools-7.0.1-py3-none-any.whl", hash = "sha256:8f086515c254d5664ae2146d14fc7f65c9a4bce75152eb247e5a9c5e6d7b2ecf"}, - {file = "cachetools-7.0.1.tar.gz", hash = "sha256:e31e579d2c5b6e2944177a0397150d312888ddf4e16e12f1016068f0c03b8341"}, -] - [[package]] name = "catalogue" version = "2.0.10" @@ -685,6 +596,7 @@ files = [ {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, ] +markers = {main = "extra == \"presidio\""} [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -711,18 +623,6 @@ azure = ["azure-storage-blob (>=12)", "azure-storage-file-datalake (>=12)"] gs = ["google-cloud-storage"] s3 = ["boto3 (>=1.34.0)"] -[[package]] -name = "cloudpickle" -version = "3.1.2" -description = "Pickler class to extend the standard pickle.Pickler functionality" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a"}, - {file = "cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414"}, -] - [[package]] name = "colorama" version = "0.4.6" @@ -735,24 +635,6 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -[[package]] -name = "colorlog" -version = "6.10.1" -description = "Add colours to the output of Python's logging module." -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c"}, - {file = "colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} - -[package.extras] -development = ["black", "flake8", "mypy", "pytest", "types-colorama"] - [[package]] name = "commitizen" version = "4.8.3" @@ -857,6 +739,17 @@ ssh = ["bcrypt (>=3.1.5)"] test = ["certifi (>=2024)", "cryptography-vectors (==44.0.3)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] +[[package]] +name = "cuid" +version = "0.4" +description = "Fast, scalable unique ID generation" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "cuid-0.4.tar.gz", hash = "sha256:74eaba154916a2240405c3631acee708c263ef8fa05a86820b87d0f59f84e978"}, +] + [[package]] name = "cymem" version = "2.0.11" @@ -934,18 +827,6 @@ wrapt = ">=1.10,<2" [package.extras] dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"] -[[package]] -name = "diskcache" -version = "5.6.3" -description = "Disk Cache -- Disk and file backed persistent cache." -optional = false -python-versions = ">=3" -groups = ["main"] -files = [ - {file = "diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19"}, - {file = "diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc"}, -] - [[package]] name = "distlib" version = "0.3.9" @@ -958,62 +839,6 @@ files = [ {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, ] -[[package]] -name = "distro" -version = "1.9.0" -description = "Distro - an OS platform information API" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, - {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, -] - -[[package]] -name = "dspy" -version = "3.0.3" -description = "DSPy" -optional = false -python-versions = "<3.14,>=3.10" -groups = ["main"] -files = [ - {file = "dspy-3.0.3-py3-none-any.whl", hash = "sha256:d19cc38ab3ec7edcb3db56a3463a606268dd2e83280595062b052bcfe0cfd24f"}, - {file = "dspy-3.0.3.tar.gz", hash = "sha256:4f77c9571a0f5071495b81acedd44ded1dacd4cdcb4e9fe942da144274f7fbf8"}, -] - -[package.dependencies] -anyio = "*" -asyncer = "0.0.8" -backoff = ">=2.2" -cachetools = ">=5.5.0" -cloudpickle = ">=3.0.0" -diskcache = ">=5.6.0" -gepa = {version = "0.0.7", extras = ["dspy"]} -joblib = ">=1.3,<2.0" -json-repair = ">=0.30.0" -litellm = ">=1.64.0" -magicattr = ">=0.1.6" -numpy = ">=1.26.0" -openai = ">=0.28.1" -optuna = ">=3.4.0" -orjson = ">=3.9.0" -pydantic = ">=2.0" -regex = ">=2023.10.3" -requests = ">=2.31.0" -rich = ">=13.7.1" -tenacity = ">=8.2.3" -tqdm = ">=4.66.1" -xxhash = ">=3.5.0" - -[package.extras] -anthropic = ["anthropic (>=0.18.0,<1.0.0)"] -dev = ["build (>=1.0.3)", "datamodel_code_generator (>=0.26.3)", "litellm (>=1.64.0) ; sys_platform == \"win32\"", "litellm[proxy] (>=1.64.0) ; sys_platform != \"win32\"", "pillow (>=10.1.0)", "pre-commit (>=3.7.0)", "pytest (>=6.2.5)", "pytest-asyncio (>=0.26.0)", "pytest-mock (>=3.12.0)", "ruff (>=0.3.0)"] -langchain = ["langchain_core"] -mcp = ["mcp ; python_version >= \"3.10\""] -test-extras = ["datasets (>=2.14.6)", "langchain_core", "mcp ; python_version >= \"3.10\"", "optuna (>=3.4.0)", "pandas (>=2.1.1)"] -weaviate = ["weaviate-client (>=4.5.4,<4.6.0)"] - [[package]] name = "emoji" version = "2.14.1" @@ -1049,94 +874,6 @@ typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} [package.extras] test = ["pytest (>=6)"] -[[package]] -name = "fastuuid" -version = "0.14.0" -description = "Python bindings to Rust's UUID library." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "fastuuid-0.14.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:6e6243d40f6c793c3e2ee14c13769e341b90be5ef0c23c82fa6515a96145181a"}, - {file = "fastuuid-0.14.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:13ec4f2c3b04271f62be2e1ce7e95ad2dd1cf97e94503a3760db739afbd48f00"}, - {file = "fastuuid-0.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b2fdd48b5e4236df145a149d7125badb28e0a383372add3fbaac9a6b7a394470"}, - {file = "fastuuid-0.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f74631b8322d2780ebcf2d2d75d58045c3e9378625ec51865fe0b5620800c39d"}, - {file = "fastuuid-0.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83cffc144dc93eb604b87b179837f2ce2af44871a7b323f2bfed40e8acb40ba8"}, - {file = "fastuuid-0.14.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a771f135ab4523eb786e95493803942a5d1fc1610915f131b363f55af53b219"}, - {file = "fastuuid-0.14.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4edc56b877d960b4eda2c4232f953a61490c3134da94f3c28af129fb9c62a4f6"}, - {file = "fastuuid-0.14.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bcc96ee819c282e7c09b2eed2b9bd13084e3b749fdb2faf58c318d498df2efbe"}, - {file = "fastuuid-0.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7a3c0bca61eacc1843ea97b288d6789fbad7400d16db24e36a66c28c268cfe3d"}, - {file = "fastuuid-0.14.0-cp310-cp310-win32.whl", hash = "sha256:7f2f3efade4937fae4e77efae1af571902263de7b78a0aee1a1653795a093b2a"}, - {file = "fastuuid-0.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:ae64ba730d179f439b0736208b4c279b8bc9c089b102aec23f86512ea458c8a4"}, - {file = "fastuuid-0.14.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:73946cb950c8caf65127d4e9a325e2b6be0442a224fd51ba3b6ac44e1912ce34"}, - {file = "fastuuid-0.14.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:12ac85024637586a5b69645e7ed986f7535106ed3013640a393a03e461740cb7"}, - {file = "fastuuid-0.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:05a8dde1f395e0c9b4be515b7a521403d1e8349443e7641761af07c7ad1624b1"}, - {file = "fastuuid-0.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09378a05020e3e4883dfdab438926f31fea15fd17604908f3d39cbeb22a0b4dc"}, - {file = "fastuuid-0.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbb0c4b15d66b435d2538f3827f05e44e2baafcc003dd7d8472dc67807ab8fd8"}, - {file = "fastuuid-0.14.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cd5a7f648d4365b41dbf0e38fe8da4884e57bed4e77c83598e076ac0c93995e7"}, - {file = "fastuuid-0.14.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c0a94245afae4d7af8c43b3159d5e3934c53f47140be0be624b96acd672ceb73"}, - {file = "fastuuid-0.14.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2b29e23c97e77c3a9514d70ce343571e469098ac7f5a269320a0f0b3e193ab36"}, - {file = "fastuuid-0.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1e690d48f923c253f28151b3a6b4e335f2b06bf669c68a02665bc150b7839e94"}, - {file = "fastuuid-0.14.0-cp311-cp311-win32.whl", hash = "sha256:a6f46790d59ab38c6aa0e35c681c0484b50dc0acf9e2679c005d61e019313c24"}, - {file = "fastuuid-0.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:e150eab56c95dc9e3fefc234a0eedb342fac433dacc273cd4d150a5b0871e1fa"}, - {file = "fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77e94728324b63660ebf8adb27055e92d2e4611645bf12ed9d88d30486471d0a"}, - {file = "fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:caa1f14d2102cb8d353096bc6ef6c13b2c81f347e6ab9d6fbd48b9dea41c153d"}, - {file = "fastuuid-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d23ef06f9e67163be38cece704170486715b177f6baae338110983f99a72c070"}, - {file = "fastuuid-0.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c9ec605ace243b6dbe3bd27ebdd5d33b00d8d1d3f580b39fdd15cd96fd71796"}, - {file = "fastuuid-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:808527f2407f58a76c916d6aa15d58692a4a019fdf8d4c32ac7ff303b7d7af09"}, - {file = "fastuuid-0.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fb3c0d7fef6674bbeacdd6dbd386924a7b60b26de849266d1ff6602937675c8"}, - {file = "fastuuid-0.14.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab3f5d36e4393e628a4df337c2c039069344db5f4b9d2a3c9cea48284f1dd741"}, - {file = "fastuuid-0.14.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b9a0ca4f03b7e0b01425281ffd44e99d360e15c895f1907ca105854ed85e2057"}, - {file = "fastuuid-0.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3acdf655684cc09e60fb7e4cf524e8f42ea760031945aa8086c7eae2eeeabeb8"}, - {file = "fastuuid-0.14.0-cp312-cp312-win32.whl", hash = "sha256:9579618be6280700ae36ac42c3efd157049fe4dd40ca49b021280481c78c3176"}, - {file = "fastuuid-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:d9e4332dc4ba054434a9594cbfaf7823b57993d7d8e7267831c3e059857cf397"}, - {file = "fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77a09cb7427e7af74c594e409f7731a0cf887221de2f698e1ca0ebf0f3139021"}, - {file = "fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:9bd57289daf7b153bfa3e8013446aa144ce5e8c825e9e366d455155ede5ea2dc"}, - {file = "fastuuid-0.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ac60fc860cdf3c3f327374db87ab8e064c86566ca8c49d2e30df15eda1b0c2d5"}, - {file = "fastuuid-0.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab32f74bd56565b186f036e33129da77db8be09178cd2f5206a5d4035fb2a23f"}, - {file = "fastuuid-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e678459cf4addaedd9936bbb038e35b3f6b2061330fd8f2f6a1d80414c0f87"}, - {file = "fastuuid-0.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e3cc56742f76cd25ecb98e4b82a25f978ccffba02e4bdce8aba857b6d85d87b"}, - {file = "fastuuid-0.14.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cb9a030f609194b679e1660f7e32733b7a0f332d519c5d5a6a0a580991290022"}, - {file = "fastuuid-0.14.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:09098762aad4f8da3a888eb9ae01c84430c907a297b97166b8abc07b640f2995"}, - {file = "fastuuid-0.14.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1383fff584fa249b16329a059c68ad45d030d5a4b70fb7c73a08d98fd53bcdab"}, - {file = "fastuuid-0.14.0-cp313-cp313-win32.whl", hash = "sha256:a0809f8cc5731c066c909047f9a314d5f536c871a7a22e815cc4967c110ac9ad"}, - {file = "fastuuid-0.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:0df14e92e7ad3276327631c9e7cec09e32572ce82089c55cb1bb8df71cf394ed"}, - {file = "fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:b852a870a61cfc26c884af205d502881a2e59cc07076b60ab4a951cc0c94d1ad"}, - {file = "fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c7502d6f54cd08024c3ea9b3514e2d6f190feb2f46e6dbcd3747882264bb5f7b"}, - {file = "fastuuid-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ca61b592120cf314cfd66e662a5b54a578c5a15b26305e1b8b618a6f22df714"}, - {file = "fastuuid-0.14.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa75b6657ec129d0abded3bec745e6f7ab642e6dba3a5272a68247e85f5f316f"}, - {file = "fastuuid-0.14.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8a0dfea3972200f72d4c7df02c8ac70bad1bb4c58d7e0ec1e6f341679073a7f"}, - {file = "fastuuid-0.14.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1bf539a7a95f35b419f9ad105d5a8a35036df35fdafae48fb2fd2e5f318f0d75"}, - {file = "fastuuid-0.14.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:9a133bf9cc78fdbd1179cb58a59ad0100aa32d8675508150f3658814aeefeaa4"}, - {file = "fastuuid-0.14.0-cp314-cp314-musllinux_1_1_i686.whl", hash = "sha256:f54d5b36c56a2d5e1a31e73b950b28a0d83eb0c37b91d10408875a5a29494bad"}, - {file = "fastuuid-0.14.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:ec27778c6ca3393ef662e2762dba8af13f4ec1aaa32d08d77f71f2a70ae9feb8"}, - {file = "fastuuid-0.14.0-cp314-cp314-win32.whl", hash = "sha256:e23fc6a83f112de4be0cc1990e5b127c27663ae43f866353166f87df58e73d06"}, - {file = "fastuuid-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:df61342889d0f5e7a32f7284e55ef95103f2110fee433c2ae7c2c0956d76ac8a"}, - {file = "fastuuid-0.14.0-cp38-cp38-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:47c821f2dfe95909ead0085d4cb18d5149bca704a2b03e03fb3f81a5202d8cea"}, - {file = "fastuuid-0.14.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:3964bab460c528692c70ab6b2e469dd7a7b152fbe8c18616c58d34c93a6cf8d4"}, - {file = "fastuuid-0.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c501561e025b7aea3508719c5801c360c711d5218fc4ad5d77bf1c37c1a75779"}, - {file = "fastuuid-0.14.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dce5d0756f046fa792a40763f36accd7e466525c5710d2195a038f93ff96346"}, - {file = "fastuuid-0.14.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193ca10ff553cf3cc461572da83b5780fc0e3eea28659c16f89ae5202f3958d4"}, - {file = "fastuuid-0.14.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0737606764b29785566f968bd8005eace73d3666bd0862f33a760796e26d1ede"}, - {file = "fastuuid-0.14.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0976c0dff7e222513d206e06341503f07423aceb1db0b83ff6851c008ceee06"}, - {file = "fastuuid-0.14.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6fbc49a86173e7f074b1a9ec8cf12ca0d54d8070a85a06ebf0e76c309b84f0d0"}, - {file = "fastuuid-0.14.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:de01280eabcd82f7542828ecd67ebf1551d37203ecdfd7ab1f2e534edb78d505"}, - {file = "fastuuid-0.14.0-cp38-cp38-win32.whl", hash = "sha256:af5967c666b7d6a377098849b07f83462c4fedbafcf8eb8bc8ff05dcbe8aa209"}, - {file = "fastuuid-0.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3091e63acf42f56a6f74dc65cfdb6f99bfc79b5913c8a9ac498eb7ca09770a8"}, - {file = "fastuuid-0.14.0-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2ec3d94e13712a133137b2805073b65ecef4a47217d5bac15d8ac62376cefdb4"}, - {file = "fastuuid-0.14.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:139d7ff12bb400b4a0c76be64c28cbe2e2edf60b09826cbfd85f33ed3d0bbe8b"}, - {file = "fastuuid-0.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d55b7e96531216fc4f071909e33e35e5bfa47962ae67d9e84b00a04d6e8b7173"}, - {file = "fastuuid-0.14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0eb25f0fd935e376ac4334927a59e7c823b36062080e2e13acbaf2af15db836"}, - {file = "fastuuid-0.14.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:089c18018fdbdda88a6dafd7d139f8703a1e7c799618e33ea25eb52503d28a11"}, - {file = "fastuuid-0.14.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fc37479517d4d70c08696960fad85494a8a7a0af4e93e9a00af04d74c59f9e3"}, - {file = "fastuuid-0.14.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:73657c9f778aba530bc96a943d30e1a7c80edb8278df77894fe9457540df4f85"}, - {file = "fastuuid-0.14.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d31f8c257046b5617fc6af9c69be066d2412bdef1edaa4bdf6a214cf57806105"}, - {file = "fastuuid-0.14.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5816d41f81782b209843e52fdef757a361b448d782452d96abedc53d545da722"}, - {file = "fastuuid-0.14.0-cp39-cp39-win32.whl", hash = "sha256:448aa6833f7a84bfe37dd47e33df83250f404d591eb83527fa2cac8d1e57d7f3"}, - {file = "fastuuid-0.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:84b0779c5abbdec2a9511d5ffbfcd2e53079bf889824b32be170c0d8ef5fc74c"}, - {file = "fastuuid-0.14.0.tar.gz", hash = "sha256:178947fc2f995b38497a74172adee64fdeb8b7ec18f2a5934d037641ba265d26"}, -] - [[package]] name = "filelock" version = "3.18.0" @@ -1148,6 +885,7 @@ files = [ {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, ] +markers = {main = "extra == \"presidio\""} [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] @@ -1289,9 +1027,10 @@ files = [ name = "fsspec" version = "2025.5.1" description = "File-system specification" -optional = false +optional = true python-versions = ">=3.9" groups = ["main"] +markers = "extra == \"presidio\"" files = [ {file = "fsspec-2025.5.1-py3-none-any.whl", hash = "sha256:24d3a2e663d5fc735ab256263c4075f374a174c3410c0b25e5bd1970bceaa462"}, {file = "fsspec-2025.5.1.tar.gz", hash = "sha256:2e55e47a540b91843b755e83ded97c6e897fa0942b11490113f09e9c443c2475"}, @@ -1325,24 +1064,6 @@ test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask[dataframe,test]", "moto test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard"] tqdm = ["tqdm"] -[[package]] -name = "gepa" -version = "0.0.7" -description = "A framework for optimizing textual system components (AI prompts, code snippets, etc.) using LLM-based reflection and Pareto-efficient evolutionary search." -optional = false -python-versions = "<3.14,>=3.10" -groups = ["main"] -files = [ - {file = "gepa-0.0.7-py3-none-any.whl", hash = "sha256:59b8b74f5e384a62d6f590ac6ffe0fa8a0e62fee8d8d6c539f490823d0ffb25c"}, - {file = "gepa-0.0.7.tar.gz", hash = "sha256:3fb98c2908f6e4cbe701a6f0088c4ea599185a801a02b7872b0c624142679cf7"}, -] - -[package.extras] -build = ["build", "packaging", "requests", "semver", "setuptools (>=77.0.1)", "twine", "wheel"] -dev = ["build (>=1.0.3)", "gepa[build]", "gepa[test]", "pre-commit", "ruff (>=0.3.0)"] -full = ["datasets (>=2.14.6)", "litellm (>=1.64.0)", "tqdm (>=4.66.1)", "wandb"] -test = ["gepa[full]", "pytest"] - [[package]] name = "googleapis-common-protos" version = "1.70.0" @@ -1361,74 +1082,6 @@ protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4 [package.extras] grpc = ["grpcio (>=1.44.0,<2.0.0)"] -[[package]] -name = "greenlet" -version = "3.3.2" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.10" -groups = ["main"] -markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\"" -files = [ - {file = "greenlet-3.3.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9bc885b89709d901859cf95179ec9f6bb67a3d2bb1f0e88456461bd4b7f8fd0d"}, - {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b568183cf65b94919be4438dc28416b234b678c608cafac8874dfeeb2a9bbe13"}, - {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:527fec58dc9f90efd594b9b700662ed3fb2493c2122067ac9c740d98080a620e"}, - {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:508c7f01f1791fbc8e011bd508f6794cb95397fdb198a46cb6635eb5b78d85a7"}, - {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad0c8917dd42a819fe77e6bdfcb84e3379c0de956469301d9fd36427a1ca501f"}, - {file = "greenlet-3.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:97245cc10e5515dbc8c3104b2928f7f02b6813002770cfaffaf9a6e0fc2b94ef"}, - {file = "greenlet-3.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8c1fdd7d1b309ff0da81d60a9688a8bd044ac4e18b250320a96fc68d31c209ca"}, - {file = "greenlet-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:5d0e35379f93a6d0222de929a25ab47b5eb35b5ef4721c2b9cbcc4036129ff1f"}, - {file = "greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86"}, - {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ebd458fa8285960f382841da585e02201b53a5ec2bac6b156fc623b5ce4499f"}, - {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a443358b33c4ec7b05b79a7c8b466f5d275025e750298be7340f8fc63dff2a55"}, - {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4375a58e49522698d3e70cc0b801c19433021b5c37686f7ce9c65b0d5c8677d2"}, - {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358"}, - {file = "greenlet-3.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:442b6057453c8cb29b4fb36a2ac689382fc71112273726e2423f7f17dc73bf99"}, - {file = "greenlet-3.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45abe8eb6339518180d5a7fa47fa01945414d7cca5ecb745346fc6a87d2750be"}, - {file = "greenlet-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e692b2dae4cc7077cbb11b47d258533b48c8fde69a33d0d8a82e2fe8d8531d5"}, - {file = "greenlet-3.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:02b0a8682aecd4d3c6c18edf52bc8e51eacdd75c8eac52a790a210b06aa295fd"}, - {file = "greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd"}, - {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd"}, - {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac"}, - {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb"}, - {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070"}, - {file = "greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79"}, - {file = "greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395"}, - {file = "greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f"}, - {file = "greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643"}, - {file = "greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4"}, - {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986"}, - {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92"}, - {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd"}, - {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab"}, - {file = "greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a"}, - {file = "greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b"}, - {file = "greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124"}, - {file = "greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327"}, - {file = "greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab"}, - {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082"}, - {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9"}, - {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3cb43ce200f59483eb82949bf1835a99cf43d7571e900d7c8d5c62cdf25d2f9"}, - {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506"}, - {file = "greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce"}, - {file = "greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5"}, - {file = "greenlet-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:8c4dd0f3997cf2512f7601563cc90dfb8957c0cff1e3a1b23991d4ea1776c492"}, - {file = "greenlet-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:cd6f9e2bbd46321ba3bbb4c8a15794d32960e3b0ae2cc4d49a1a53d314805d71"}, - {file = "greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54"}, - {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4"}, - {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff"}, - {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:59b3e2c40f6706b05a9cd299c836c6aa2378cabe25d021acd80f13abf81181cf"}, - {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4"}, - {file = "greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727"}, - {file = "greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e"}, - {file = "greenlet-3.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c04c5e06ec3e022cbfe2cd4a846e1d4e50087444f875ff6d2c2ad8445495cf1a"}, - {file = "greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2"}, -] - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil", "setuptools"] - [[package]] name = "grpcio" version = "1.73.1" @@ -1509,10 +1162,10 @@ files = [ name = "hf-xet" version = "1.1.5" description = "Fast transfer of large files with the Hugging Face Hub." -optional = false +optional = true python-versions = ">=3.8" groups = ["main"] -markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\"" +markers = "extra == \"presidio\" and (platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\")" files = [ {file = "hf_xet-1.1.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f52c2fa3635b8c37c7764d8796dfa72706cc4eded19d638331161e82b0792e23"}, {file = "hf_xet-1.1.5-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:9fa6e3ee5d61912c4a113e0708eaaef987047616465ac7aa30f7121a48fc1af8"}, @@ -1578,9 +1231,10 @@ zstd = ["zstandard (>=0.18.0)"] name = "huggingface-hub" version = "0.33.4" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" -optional = false +optional = true python-versions = ">=3.8.0" groups = ["main"] +markers = "extra == \"presidio\"" files = [ {file = "huggingface_hub-0.33.4-py3-none-any.whl", hash = "sha256:09f9f4e7ca62547c70f8b82767eefadd2667f4e116acba2e3e62a5a81815a7bb"}, {file = "huggingface_hub-0.33.4.tar.gz", hash = "sha256:6af13478deae120e765bfd92adad0ae1aec1ad8c439b46f23058ad5956cbca0a"}, @@ -1725,105 +1379,6 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] -[[package]] -name = "jiter" -version = "0.10.0" -description = "Fast iterable JSON parser." -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "jiter-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303"}, - {file = "jiter-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8b3e0068c26ddedc7abc6fac37da2d0af16b921e288a5a613f4b86f050354f"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:286299b74cc49e25cd42eea19b72aa82c515d2f2ee12d11392c56d8701f52224"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ed5649ceeaeffc28d87fb012d25a4cd356dcd53eff5acff1f0466b831dda2a7"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2ab0051160cb758a70716448908ef14ad476c3774bd03ddce075f3c1f90a3d6"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03997d2f37f6b67d2f5c475da4412be584e1cec273c1cfc03d642c46db43f8cf"}, - {file = "jiter-0.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c404a99352d839fed80d6afd6c1d66071f3bacaaa5c4268983fc10f769112e90"}, - {file = "jiter-0.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66e989410b6666d3ddb27a74c7e50d0829704ede652fd4c858e91f8d64b403d0"}, - {file = "jiter-0.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b532d3af9ef4f6374609a3bcb5e05a1951d3bf6190dc6b176fdb277c9bbf15ee"}, - {file = "jiter-0.10.0-cp310-cp310-win32.whl", hash = "sha256:da9be20b333970e28b72edc4dff63d4fec3398e05770fb3205f7fb460eb48dd4"}, - {file = "jiter-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:f59e533afed0c5b0ac3eba20d2548c4a550336d8282ee69eb07b37ea526ee4e5"}, - {file = "jiter-0.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3bebe0c558e19902c96e99217e0b8e8b17d570906e72ed8a87170bc290b1e978"}, - {file = "jiter-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:558cc7e44fd8e507a236bee6a02fa17199ba752874400a0ca6cd6e2196cdb7dc"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d613e4b379a07d7c8453c5712ce7014e86c6ac93d990a0b8e7377e18505e98d"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f62cf8ba0618eda841b9bf61797f21c5ebd15a7a1e19daab76e4e4b498d515b2"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:919d139cdfa8ae8945112398511cb7fca58a77382617d279556b344867a37e61"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13ddbc6ae311175a3b03bd8994881bc4635c923754932918e18da841632349db"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c440ea003ad10927a30521a9062ce10b5479592e8a70da27f21eeb457b4a9c5"}, - {file = "jiter-0.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc347c87944983481e138dea467c0551080c86b9d21de6ea9306efb12ca8f606"}, - {file = "jiter-0.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:13252b58c1f4d8c5b63ab103c03d909e8e1e7842d302473f482915d95fefd605"}, - {file = "jiter-0.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d1bbf3c465de4a24ab12fb7766a0003f6f9bce48b8b6a886158c4d569452dc5"}, - {file = "jiter-0.10.0-cp311-cp311-win32.whl", hash = "sha256:db16e4848b7e826edca4ccdd5b145939758dadf0dc06e7007ad0e9cfb5928ae7"}, - {file = "jiter-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c9c1d5f10e18909e993f9641f12fe1c77b3e9b533ee94ffa970acc14ded3812"}, - {file = "jiter-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e274728e4a5345a6dde2d343c8da018b9d4bd4350f5a472fa91f66fda44911b"}, - {file = "jiter-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7202ae396446c988cb2a5feb33a543ab2165b786ac97f53b59aafb803fef0744"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ba7722d6748b6920ed02a8f1726fb4b33e0fd2f3f621816a8b486c66410ab2"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:371eab43c0a288537d30e1f0b193bc4eca90439fc08a022dd83e5e07500ed026"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c675736059020365cebc845a820214765162728b51ab1e03a1b7b3abb70f74c"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c5867d40ab716e4684858e4887489685968a47e3ba222e44cde6e4a2154f959"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395bb9a26111b60141757d874d27fdea01b17e8fac958b91c20128ba8f4acc8a"}, - {file = "jiter-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6842184aed5cdb07e0c7e20e5bdcfafe33515ee1741a6835353bb45fe5d1bd95"}, - {file = "jiter-0.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62755d1bcea9876770d4df713d82606c8c1a3dca88ff39046b85a048566d56ea"}, - {file = "jiter-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533efbce2cacec78d5ba73a41756beff8431dfa1694b6346ce7af3a12c42202b"}, - {file = "jiter-0.10.0-cp312-cp312-win32.whl", hash = "sha256:8be921f0cadd245e981b964dfbcd6fd4bc4e254cdc069490416dd7a2632ecc01"}, - {file = "jiter-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7c7d785ae9dda68c2678532a5a1581347e9c15362ae9f6e68f3fdbfb64f2e49"}, - {file = "jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644"}, - {file = "jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041"}, - {file = "jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca"}, - {file = "jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4"}, - {file = "jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e"}, - {file = "jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d"}, - {file = "jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4"}, - {file = "jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca"}, - {file = "jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070"}, - {file = "jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca"}, - {file = "jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522"}, - {file = "jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c189c4f1779c05f75fc17c0c1267594ed918996a231593a21a5ca5438445216"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15720084d90d1098ca0229352607cd68256c76991f6b374af96f36920eae13c4"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f2fb68e5f1cfee30e2b2a09549a00683e0fde4c6a2ab88c94072fc33cb7426"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce541693355fc6da424c08b7edf39a2895f58d6ea17d92cc2b168d20907dee12"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c50c40272e189d50006ad5c73883caabb73d4e9748a688b216e85a9a9ca3b9"}, - {file = "jiter-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa3402a2ff9815960e0372a47b75c76979d74402448509ccd49a275fa983ef8a"}, - {file = "jiter-0.10.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:1956f934dca32d7bb647ea21d06d93ca40868b505c228556d3373cbd255ce853"}, - {file = "jiter-0.10.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:fcedb049bdfc555e261d6f65a6abe1d5ad68825b7202ccb9692636c70fcced86"}, - {file = "jiter-0.10.0-cp314-cp314-win32.whl", hash = "sha256:ac509f7eccca54b2a29daeb516fb95b6f0bd0d0d8084efaf8ed5dfc7b9f0b357"}, - {file = "jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00"}, - {file = "jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5"}, - {file = "jiter-0.10.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bd6292a43c0fc09ce7c154ec0fa646a536b877d1e8f2f96c19707f65355b5a4d"}, - {file = "jiter-0.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39de429dcaeb6808d75ffe9effefe96a4903c6a4b376b2f6d08d77c1aaee2f18"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52ce124f13a7a616fad3bb723f2bfb537d78239d1f7f219566dc52b6f2a9e48d"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:166f3606f11920f9a1746b2eea84fa2c0a5d50fd313c38bdea4edc072000b0af"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28dcecbb4ba402916034fc14eba7709f250c4d24b0c43fc94d187ee0580af181"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86c5aa6910f9bebcc7bc4f8bc461aff68504388b43bfe5e5c0bd21efa33b52f4"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ceeb52d242b315d7f1f74b441b6a167f78cea801ad7c11c36da77ff2d42e8a28"}, - {file = "jiter-0.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ff76d8887c8c8ee1e772274fcf8cc1071c2c58590d13e33bd12d02dc9a560397"}, - {file = "jiter-0.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a9be4d0fa2b79f7222a88aa488bd89e2ae0a0a5b189462a12def6ece2faa45f1"}, - {file = "jiter-0.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ab7fd8738094139b6c1ab1822d6f2000ebe41515c537235fd45dabe13ec9324"}, - {file = "jiter-0.10.0-cp39-cp39-win32.whl", hash = "sha256:5f51e048540dd27f204ff4a87f5d79294ea0aa3aa552aca34934588cf27023cf"}, - {file = "jiter-0.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:1b28302349dc65703a9e4ead16f163b1c339efffbe1049c30a44b001a2a4fff9"}, - {file = "jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500"}, -] - -[[package]] -name = "joblib" -version = "1.5.3" -description = "Lightweight pipelining with Python functions" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713"}, - {file = "joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3"}, -] - [[package]] name = "json-repair" version = "0.44.1" @@ -1836,43 +1391,6 @@ files = [ {file = "json_repair-0.44.1.tar.gz", hash = "sha256:1130eb9733b868dac1340b43cb2effebb519ae6d52dd2d0728c6cca517f1e0b4"}, ] -[[package]] -name = "jsonschema" -version = "4.26.0" -description = "An implementation of JSON Schema validation for Python" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce"}, - {file = "jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326"}, -] - -[package.dependencies] -attrs = ">=22.2.0" -jsonschema-specifications = ">=2023.03.6" -referencing = ">=0.28.4" -rpds-py = ">=0.25.0" - -[package.extras] -format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] -format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "rfc3987-syntax (>=1.1.0)", "uri-template", "webcolors (>=24.6.0)"] - -[[package]] -name = "jsonschema-specifications" -version = "2025.9.1" -description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, - {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, -] - -[package.dependencies] -referencing = ">=0.31.0" - [[package]] name = "langcodes" version = "3.5.0" @@ -1913,73 +1431,6 @@ marisa-trie = ">=1.1.0" build = ["build", "twine"] test = ["pytest", "pytest-cov"] -[[package]] -name = "litellm" -version = "1.81.15" -description = "Library to easily interface with LLM API providers" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["main"] -files = [ - {file = "litellm-1.81.15-py3-none-any.whl", hash = "sha256:2fa253658702509ce09fe0e172e5a47baaadf697fb0f784c7fd4ff665ae76ae1"}, - {file = "litellm-1.81.15.tar.gz", hash = "sha256:a8a6277a53280762051c5818ebc76dd5f036368b9426c6f21795ae7f1ac6ebdc"}, -] - -[package.dependencies] -aiohttp = ">=3.10" -click = "*" -fastuuid = ">=0.13.0" -httpx = ">=0.23.0" -importlib-metadata = ">=6.8.0" -jinja2 = ">=3.1.2,<4.0.0" -jsonschema = ">=4.23.0,<5.0.0" -openai = ">=2.8.0" -pydantic = ">=2.5.0,<3.0.0" -python-dotenv = ">=0.2.0" -tiktoken = ">=0.7.0" -tokenizers = "*" - -[package.extras] -caching = ["diskcache (>=5.6.1,<6.0.0)"] -extra-proxy = ["a2a-sdk (>=0.3.22,<0.4.0) ; python_version >= \"3.10\"", "azure-identity (>=1.15.0,<2.0.0) ; python_version >= \"3.9\"", "azure-keyvault-secrets (>=4.8.0,<5.0.0)", "google-cloud-iam (>=2.19.1,<3.0.0)", "google-cloud-kms (>=2.21.3,<3.0.0)", "prisma (==0.11.0)", "redisvl (>=0.4.1,<0.5.0) ; python_version >= \"3.9\" and python_version < \"3.14\"", "resend (>=0.8.0)"] -google = ["google-cloud-aiplatform (>=1.38.0)"] -grpc = ["grpcio (>=1.62.3,<1.68.dev0 || >1.71.0,!=1.71.1,!=1.72.0,!=1.72.1,!=1.73.0) ; python_version < \"3.14\"", "grpcio (>=1.75.0) ; python_version >= \"3.14\""] -mlflow = ["mlflow (>3.1.4) ; python_version >= \"3.10\""] -proxy = ["PyJWT (>=2.10.1,<3.0.0) ; python_version >= \"3.9\"", "apscheduler (>=3.10.4,<4.0.0)", "azure-identity (>=1.15.0,<2.0.0) ; python_version >= \"3.9\"", "azure-storage-blob (>=12.25.1,<13.0.0)", "backoff", "boto3 (==1.40.76)", "cryptography", "fastapi (>=0.120.1)", "fastapi-sso (>=0.16.0,<0.17.0)", "gunicorn (>=23.0.0,<24.0.0)", "litellm-enterprise (==0.1.32)", "litellm-proxy-extras (==0.4.47)", "mcp (>=1.25.0,<2.0.0) ; python_version >= \"3.10\"", "orjson (>=3.9.7,<4.0.0)", "polars (>=1.31.0,<2.0.0) ; python_version >= \"3.10\"", "pynacl (>=1.5.0,<2.0.0)", "pyroscope-io (>=0.8,<0.9) ; sys_platform != \"win32\"", "python-multipart (>=0.0.22,<0.0.23) ; python_version >= \"3.10\"", "pyyaml (>=6.0.1,<7.0.0)", "rich (==13.7.1)", "rq", "soundfile (>=0.12.1,<0.13.0)", "uvicorn (>=0.32.1,<1.0.0)", "uvloop (>=0.21.0,<0.22.0) ; sys_platform != \"win32\"", "websockets (>=15.0.1,<16.0.0)"] -semantic-router = ["semantic-router (>=0.1.12) ; python_version >= \"3.9\" and python_version < \"3.14\""] -utils = ["numpydoc"] - -[[package]] -name = "magicattr" -version = "0.1.6" -description = "A getattr and setattr that works on nested objects, lists, dicts, and any combination thereof without resorting to eval" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "magicattr-0.1.6-py2.py3-none-any.whl", hash = "sha256:d96b18ee45b5ee83b09c17e15d3459a64de62d538808c2f71182777dd9dbbbdf"}, -] - -[[package]] -name = "mako" -version = "1.3.10" -description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59"}, - {file = "mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28"}, -] - -[package.dependencies] -MarkupSafe = ">=0.9.2" - -[package.extras] -babel = ["Babel"] -lingua = ["lingua"] -testing = ["pytest"] - [[package]] name = "marisa-trie" version = "1.2.1" @@ -2077,9 +1528,10 @@ test = ["hypothesis", "pytest", "readme-renderer"] name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false +optional = true python-versions = ">=3.8" groups = ["main"] +markers = "extra == \"presidio\"" files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -2185,26 +1637,15 @@ files = [ name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -optional = false +optional = true python-versions = ">=3.7" groups = ["main"] +markers = "extra == \"presidio\"" files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] -[[package]] -name = "monotonic" -version = "1.6" -description = "An implementation of time.monotonic() for Python 2 & < 3.3" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "monotonic-1.6-py2.py3-none-any.whl", hash = "sha256:68687e19a14f11f26d140dd5c86f3dba4bf5df58003000ed467e0e2a69bca96c"}, - {file = "monotonic-1.6.tar.gz", hash = "sha256:3a55207bcfed53ddd5c5bae174524062935efed17792e9de2ad0205ce9ad63f7"}, -] - [[package]] name = "mpmath" version = "1.3.0" @@ -2498,9 +1939,10 @@ files = [ name = "numpy" version = "2.2.6" description = "Fundamental package for array computing in Python" -optional = false +optional = true python-versions = ">=3.10" groups = ["main"] +markers = "extra == \"presidio\"" files = [ {file = "numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb"}, {file = "numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"}, @@ -2781,159 +2223,142 @@ files = [ {file = "nvidia_nvtx_cu12-12.6.77-py3-none-win_amd64.whl", hash = "sha256:2fb11a4af04a5e6c84073e6404d26588a34afd35379f0855a99797897efa75c0"}, ] -[[package]] -name = "openai" -version = "2.24.0" -description = "The official Python library for the openai API" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "openai-2.24.0-py3-none-any.whl", hash = "sha256:fed30480d7d6c884303287bde864980a4b137b60553ffbcf9ab4a233b7a73d94"}, - {file = "openai-2.24.0.tar.gz", hash = "sha256:1e5769f540dbd01cb33bc4716a23e67b9d695161a734aff9c5f925e2bf99a673"}, -] - -[package.dependencies] -anyio = ">=3.5.0,<5" -distro = ">=1.7.0,<2" -httpx = ">=0.23.0,<1" -jiter = ">=0.10.0,<1" -pydantic = ">=1.9.0,<3" -sniffio = "*" -tqdm = ">4" -typing-extensions = ">=4.11,<5" - -[package.extras] -aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.9)"] -datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] -realtime = ["websockets (>=13,<16)"] -voice-helpers = ["numpy (>=2.0.2)", "sounddevice (>=0.5.1)"] - [[package]] name = "opentelemetry-api" -version = "1.34.1" +version = "1.39.1" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_api-1.34.1-py3-none-any.whl", hash = "sha256:b7df4cb0830d5a6c29ad0c0691dbae874d8daefa934b8b1d642de48323d32a8c"}, - {file = "opentelemetry_api-1.34.1.tar.gz", hash = "sha256:64f0bd06d42824843731d05beea88d4d4b6ae59f9fe347ff7dfa2cc14233bbb3"}, + {file = "opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950"}, + {file = "opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c"}, ] [package.dependencies] importlib-metadata = ">=6.0,<8.8.0" typing-extensions = ">=4.5.0" -[[package]] -name = "opentelemetry-exporter-otlp" -version = "1.34.1" -description = "OpenTelemetry Collector Exporters" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "opentelemetry_exporter_otlp-1.34.1-py3-none-any.whl", hash = "sha256:f4a453e9cde7f6362fd4a090d8acf7881d1dc585540c7b65cbd63e36644238d4"}, - {file = "opentelemetry_exporter_otlp-1.34.1.tar.gz", hash = "sha256:71c9ad342d665d9e4235898d205db17c5764cd7a69acb8a5dcd6d5e04c4c9988"}, -] - -[package.dependencies] -opentelemetry-exporter-otlp-proto-grpc = "1.34.1" -opentelemetry-exporter-otlp-proto-http = "1.34.1" - [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.34.1" +version = "1.39.1" description = "OpenTelemetry Protobuf encoding" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_exporter_otlp_proto_common-1.34.1-py3-none-any.whl", hash = "sha256:8e2019284bf24d3deebbb6c59c71e6eef3307cd88eff8c633e061abba33f7e87"}, - {file = "opentelemetry_exporter_otlp_proto_common-1.34.1.tar.gz", hash = "sha256:b59a20a927facd5eac06edaf87a07e49f9e4a13db487b7d8a52b37cb87710f8b"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.39.1-py3-none-any.whl", hash = "sha256:08f8a5862d64cc3435105686d0216c1365dc5701f86844a8cd56597d0c764fde"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.39.1.tar.gz", hash = "sha256:763370d4737a59741c89a67b50f9e39271639ee4afc999dadfe768541c027464"}, ] [package.dependencies] -opentelemetry-proto = "1.34.1" +opentelemetry-proto = "1.39.1" [[package]] name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.34.1" +version = "1.39.1" description = "OpenTelemetry Collector Protobuf over gRPC Exporter" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_exporter_otlp_proto_grpc-1.34.1-py3-none-any.whl", hash = "sha256:04bb8b732b02295be79f8a86a4ad28fae3d4ddb07307a98c7aa6f331de18cca6"}, - {file = "opentelemetry_exporter_otlp_proto_grpc-1.34.1.tar.gz", hash = "sha256:7c841b90caa3aafcfc4fee58487a6c71743c34c6dc1787089d8b0578bbd794dd"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.39.1-py3-none-any.whl", hash = "sha256:fa1c136a05c7e9b4c09f739469cbdb927ea20b34088ab1d959a849b5cc589c18"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.39.1.tar.gz", hash = "sha256:772eb1c9287485d625e4dbe9c879898e5253fea111d9181140f51291b5fec3ad"}, ] [package.dependencies] -googleapis-common-protos = ">=1.52,<2.0" +googleapis-common-protos = ">=1.57,<2.0" grpcio = [ {version = ">=1.63.2,<2.0.0", markers = "python_version < \"3.13\""}, {version = ">=1.66.2,<2.0.0", markers = "python_version >= \"3.13\""}, ] opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.34.1" -opentelemetry-proto = "1.34.1" -opentelemetry-sdk = ">=1.34.1,<1.35.0" -typing-extensions = ">=4.5.0" +opentelemetry-exporter-otlp-proto-common = "1.39.1" +opentelemetry-proto = "1.39.1" +opentelemetry-sdk = ">=1.39.1,<1.40.0" +typing-extensions = ">=4.6.0" + +[package.extras] +gcp-auth = ["opentelemetry-exporter-credential-provider-gcp (>=0.59b0)"] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.34.1" +version = "1.39.1" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_exporter_otlp_proto_http-1.34.1-py3-none-any.whl", hash = "sha256:5251f00ca85872ce50d871f6d3cc89fe203b94c3c14c964bbdc3883366c705d8"}, - {file = "opentelemetry_exporter_otlp_proto_http-1.34.1.tar.gz", hash = "sha256:aaac36fdce46a8191e604dcf632e1f9380c7d5b356b27b3e0edb5610d9be28ad"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.39.1-py3-none-any.whl", hash = "sha256:d9f5207183dd752a412c4cd564ca8875ececba13be6e9c6c370ffb752fd59985"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.39.1.tar.gz", hash = "sha256:31bdab9745c709ce90a49a0624c2bd445d31a28ba34275951a6a362d16a0b9cb"}, ] [package.dependencies] googleapis-common-protos = ">=1.52,<2.0" opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.34.1" -opentelemetry-proto = "1.34.1" -opentelemetry-sdk = ">=1.34.1,<1.35.0" +opentelemetry-exporter-otlp-proto-common = "1.39.1" +opentelemetry-proto = "1.39.1" +opentelemetry-sdk = ">=1.39.1,<1.40.0" requests = ">=2.7,<3.0" typing-extensions = ">=4.5.0" +[package.extras] +gcp-auth = ["opentelemetry-exporter-credential-provider-gcp (>=0.59b0)"] + [[package]] name = "opentelemetry-instrumentation" -version = "0.55b1" +version = "0.60b1" description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation-0.55b1-py3-none-any.whl", hash = "sha256:cbb1496b42bc394e01bc63701b10e69094e8564e281de063e4328d122cc7a97e"}, - {file = "opentelemetry_instrumentation-0.55b1.tar.gz", hash = "sha256:2dc50aa207b9bfa16f70a1a0571e011e737a9917408934675b89ef4d5718c87b"}, + {file = "opentelemetry_instrumentation-0.60b1-py3-none-any.whl", hash = "sha256:04480db952b48fb1ed0073f822f0ee26012b7be7c3eac1a3793122737c78632d"}, + {file = "opentelemetry_instrumentation-0.60b1.tar.gz", hash = "sha256:57ddc7974c6eb35865af0426d1a17132b88b2ed8586897fee187fd5b8944bd6a"}, ] [package.dependencies] opentelemetry-api = ">=1.4,<2.0" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-semantic-conventions = "0.60b1" packaging = ">=18.0" wrapt = ">=1.0.0,<2.0.0" +[[package]] +name = "opentelemetry-instrumentation-agno" +version = "0.60.0" +description = "OpenTelemetry Agno instrumentation" +optional = false +python-versions = "<4,>=3.10" +groups = ["main"] +files = [ + {file = "opentelemetry_instrumentation_agno-0.60.0-py3-none-any.whl", hash = "sha256:191529e70cdde4d7cfb405e17b75bc93b7883b197aabaa007c820a82617dbb71"}, + {file = "opentelemetry_instrumentation_agno-0.60.0.tar.gz", hash = "sha256:8923c36bd75bf00cb70264c1a64dad188c1aa3a944849abb0ec02743557cb8ed"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.28.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["agno"] + [[package]] name = "opentelemetry-instrumentation-aio-pika" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry Aio-pika instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_aio_pika-0.55b1-py3-none-any.whl", hash = "sha256:1f7277df3275d497276c8f45e62771dda2d9557de9e0ee4c86e537fa888a2dd2"}, - {file = "opentelemetry_instrumentation_aio_pika-0.55b1.tar.gz", hash = "sha256:376e85e20c7937255be6705c4effdb488290937928ccfb53c0d45825b5074e7a"}, + {file = "opentelemetry_instrumentation_aio_pika-0.60b1-py3-none-any.whl", hash = "sha256:a27af7c054f5ba7f49ac1cff1cdec54061096c7387c20f0123bd2a81b1478016"}, + {file = "opentelemetry_instrumentation_aio_pika-0.60b1.tar.gz", hash = "sha256:718cbc066be3ba579fc28ee4e1458bfe8681782e98c3bf7b04145f8165f22f3b"}, ] [package.dependencies] opentelemetry-api = ">=1.5,<2.0" -opentelemetry-instrumentation = "0.55b1" +opentelemetry-instrumentation = "0.60b1" wrapt = ">=1.0.0,<2.0.0" [package.extras] @@ -2941,21 +2366,21 @@ instruments = ["aio-pika (>=7.2.0,<10.0.0)"] [[package]] name = "opentelemetry-instrumentation-aiohttp-client" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry aiohttp client instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_aiohttp_client-0.55b1-py3-none-any.whl", hash = "sha256:d2b3300b50362561b90a92d768c3599e7b1110fa2b5977ea193cb46260699753"}, - {file = "opentelemetry_instrumentation_aiohttp_client-0.55b1.tar.gz", hash = "sha256:d1f21eb41fac54a5ece4592e82921cb1ec97ad04d714e073a4dac2dad3b5af5c"}, + {file = "opentelemetry_instrumentation_aiohttp_client-0.60b1-py3-none-any.whl", hash = "sha256:34c5097256a30b16c5a2a88a409ed82b92972a494c43212c85632d204a78c2a1"}, + {file = "opentelemetry_instrumentation_aiohttp_client-0.60b1.tar.gz", hash = "sha256:d0e7d5aa057791ca4d9090b0d3c9982f253c1a24b6bc78a734fc18d8dd97927b"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" -opentelemetry-util-http = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" +opentelemetry-util-http = "0.60b1" wrapt = ">=1.0.0,<2.0.0" [package.extras] @@ -2963,20 +2388,20 @@ instruments = ["aiohttp (>=3.0,<4.0)"] [[package]] name = "opentelemetry-instrumentation-aiokafka" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry aiokafka instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_aiokafka-0.55b1-py3-none-any.whl", hash = "sha256:1bc10f819359a5644662fef3d690d0301d908886b21199c1d9a5f668922b4183"}, - {file = "opentelemetry_instrumentation_aiokafka-0.55b1.tar.gz", hash = "sha256:1385de193a57aaf9cbf4ebecc84e30f331d2f9dfbdce9d244d3cc2d570e595f1"}, + {file = "opentelemetry_instrumentation_aiokafka-0.60b1-py3-none-any.whl", hash = "sha256:1a073f1d66b92b8e7f4c7f5bd783efeb0c26b291d4d3dd29f4c54fce884b1f50"}, + {file = "opentelemetry_instrumentation_aiokafka-0.60b1.tar.gz", hash = "sha256:db06278ae8d415f8fe114b7fc54cf62c8e9e5f73f2e8fa33ffaa9a5bd240ed7e"}, ] [package.dependencies] opentelemetry-api = ">=1.27,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" typing-extensions = ">=4.1,<5.0" [package.extras] @@ -2984,20 +2409,20 @@ instruments = ["aiokafka (>=0.8,<1.0)"] [[package]] name = "opentelemetry-instrumentation-aiopg" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry aiopg instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_aiopg-0.55b1-py3-none-any.whl", hash = "sha256:cd1ff90ecc7fc4c46501f035c0461fdb56a5319ed2a8c3500a0cd743e7c1cfa7"}, - {file = "opentelemetry_instrumentation_aiopg-0.55b1.tar.gz", hash = "sha256:dde1f58b9997bf95268e570c8632833c0aca06c0a315b94f90651958d00972c1"}, + {file = "opentelemetry_instrumentation_aiopg-0.60b1-py3-none-any.whl", hash = "sha256:45160bc9a7b6fe7a7324fb8053b8b99117a362f9a3c668498c160141337a21e6"}, + {file = "opentelemetry_instrumentation_aiopg-0.60b1.tar.gz", hash = "sha256:7d143ee19c6289ea34bb6ab7ff24729b2ccb87d71a4717981326ef794f798419"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-instrumentation-dbapi = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-instrumentation-dbapi = "0.60b1" wrapt = ">=1.0.0,<2.0.0" [package.extras] @@ -3005,78 +2430,84 @@ instruments = ["aiopg (>=0.13.0,<2.0.0)"] [[package]] name = "opentelemetry-instrumentation-alephalpha" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Aleph Alpha instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_alephalpha-0.45.6-py3-none-any.whl", hash = "sha256:a93a8ef3240db52b53823846c99cde70d1c862630f0f48c72bbf42817846c844"}, - {file = "opentelemetry_instrumentation_alephalpha-0.45.6.tar.gz", hash = "sha256:5a5725368c714591b83b978a0bf8f9da437e5e5d6edcb9781b6ceb71c464a1ab"}, + {file = "opentelemetry_instrumentation_alephalpha-0.60.0-py3-none-any.whl", hash = "sha256:fb66837cbecaa0bdf45c9a63ee98dd326ea55e524852cf5a187dbd2c08af5f18"}, + {file = "opentelemetry_instrumentation_alephalpha-0.60.0.tar.gz", hash = "sha256:7444b13c1ae12964a490a4be4d6ca224170539dc941c79d78302a6801cd691a2"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["aleph-alpha-client"] [[package]] name = "opentelemetry-instrumentation-anthropic" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Anthropic instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_anthropic-0.45.6-py3-none-any.whl", hash = "sha256:fec68faf76e4ba0a836641e4b7a3f7bf13483e69c341699183577c29a27cbdb7"}, - {file = "opentelemetry_instrumentation_anthropic-0.45.6.tar.gz", hash = "sha256:dce75f4447fa2321e7cb41f193e4ff211bc2007e49abbe748c4be2df291ff493"}, + {file = "opentelemetry_instrumentation_anthropic-0.60.0-py3-none-any.whl", hash = "sha256:04a366e235c32b185b676b3b5ce2a995d4d508e473c6b6881aeb24369290bb80"}, + {file = "opentelemetry_instrumentation_anthropic-0.60.0.tar.gz", hash = "sha256:728b425374775d694463d8a2e99e116678a544dc9f721edad6f275f80911592c"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["anthropic"] [[package]] name = "opentelemetry-instrumentation-asgi" -version = "0.55b1" +version = "0.60b1" description = "ASGI instrumentation for OpenTelemetry" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_asgi-0.55b1-py3-none-any.whl", hash = "sha256:186620f7d0a71c8c817c5cbe91c80faa8f9c50967d458b8131c5694e21eb8583"}, - {file = "opentelemetry_instrumentation_asgi-0.55b1.tar.gz", hash = "sha256:615cde388dd3af4d0e52629a6c75828253618aebcc6e65d93068463811528606"}, + {file = "opentelemetry_instrumentation_asgi-0.60b1-py3-none-any.whl", hash = "sha256:d48def2dbed10294c99cfcf41ebbd0c414d390a11773a41f472d20000fcddc25"}, + {file = "opentelemetry_instrumentation_asgi-0.60b1.tar.gz", hash = "sha256:16bfbe595cd24cda309a957456d0fc2523f41bc7b076d1f2d7e98a1ad9876d6f"}, ] [package.dependencies] asgiref = ">=3.0,<4.0" opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" -opentelemetry-util-http = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" +opentelemetry-util-http = "0.60b1" [package.extras] instruments = ["asgiref (>=3.0,<4.0)"] [[package]] name = "opentelemetry-instrumentation-asyncclick" -version = "0.55b1" +version = "0.60b1" description = "Async Click instrumentation for OpenTelemetry" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_asyncclick-0.55b1-py3-none-any.whl", hash = "sha256:26170a2270f218c7a18a42da9de943930b8cd2a0c5af5f139fb57253972d3f6a"}, - {file = "opentelemetry_instrumentation_asyncclick-0.55b1.tar.gz", hash = "sha256:3ca146ed467d6cd0d5150e5d4c8428f6e5b72581268b39963f3d58f28a7fd16e"}, + {file = "opentelemetry_instrumentation_asyncclick-0.60b1-py3-none-any.whl", hash = "sha256:f244bded5382d96c055a38a401d67e7dc744a8c1ca6234c8146fcdcee776f623"}, + {file = "opentelemetry_instrumentation_asyncclick-0.60b1.tar.gz", hash = "sha256:d6995f18b7d22e55c2737dd2489d086d7776176bdc51fba7e7420e108a137acd"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" typing-extensions = ">=4.12,<5.0" wrapt = ">=1.0,<2.0" @@ -3085,95 +2516,97 @@ instruments = ["asyncclick (>=8.0,<9.0)"] [[package]] name = "opentelemetry-instrumentation-asyncio" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry instrumentation for asyncio" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_asyncio-0.55b1-py3-none-any.whl", hash = "sha256:94d9c2910c15eec0557212c6b07733aa0846201dac5384dff6e3c3551094a271"}, - {file = "opentelemetry_instrumentation_asyncio-0.55b1.tar.gz", hash = "sha256:21a1cbe38e6378363cf73639ece78109e6b209ee2e93f40d92931153e0687e27"}, + {file = "opentelemetry_instrumentation_asyncio-0.60b1-py3-none-any.whl", hash = "sha256:9f326c6947a23dccc02a07ab432b0c62b62bf7312a7608c38771014217199316"}, + {file = "opentelemetry_instrumentation_asyncio-0.60b1.tar.gz", hash = "sha256:0ddb8ada367c7102662c42433740779a055eace08d65079c86ef65e75d1a66b3"}, ] [package.dependencies] opentelemetry-api = ">=1.14,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" wrapt = ">=1.0.0,<2.0.0" [[package]] name = "opentelemetry-instrumentation-asyncpg" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry instrumentation for AsyncPG" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_asyncpg-0.55b1-py3-none-any.whl", hash = "sha256:7fea06c742c385b6a43a0ba0a6e2b39ac52d723ee0637238c60446b3ab06b276"}, - {file = "opentelemetry_instrumentation_asyncpg-0.55b1.tar.gz", hash = "sha256:de7a232966a4de88f929177cce5e0262916d38dae5e94dae9e2f201f55644d18"}, + {file = "opentelemetry_instrumentation_asyncpg-0.60b1-py3-none-any.whl", hash = "sha256:3d64346571822188445bef7c8219709e6ee76f9ed2583a07c503e619f6dd4169"}, + {file = "opentelemetry_instrumentation_asyncpg-0.60b1.tar.gz", hash = "sha256:56dbafc800b83de839e8048cc0a8b76dc2d4182fce6f1fd94aa95ba3f8a2f800"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" [package.extras] instruments = ["asyncpg (>=0.12.0)"] [[package]] name = "opentelemetry-instrumentation-aws-lambda" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry AWS Lambda instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_aws_lambda-0.55b1-py3-none-any.whl", hash = "sha256:d3af24dd69591f0e40d5c2e7c29385543f0ee63debe79cb12bac923f3936fe16"}, - {file = "opentelemetry_instrumentation_aws_lambda-0.55b1.tar.gz", hash = "sha256:7b4c2a27a031f422d352fdd3bcbad2e9a655773161d81ff02dfeb9a29b587cc7"}, + {file = "opentelemetry_instrumentation_aws_lambda-0.60b1-py3-none-any.whl", hash = "sha256:e85810c4207b410e785b67dd94251aeec3a01c59a21f0dfa01dfb6bedabffcd6"}, + {file = "opentelemetry_instrumentation_aws_lambda-0.60b1.tar.gz", hash = "sha256:75e432626cf06e94763daeff0abf08d7b609e29e9ce23a394af1cce5270905a0"}, ] [package.dependencies] -opentelemetry-instrumentation = "0.55b1" +opentelemetry-instrumentation = "0.60b1" opentelemetry-propagator-aws-xray = ">=1.0,<2.0" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-semantic-conventions = "0.60b1" [[package]] name = "opentelemetry-instrumentation-bedrock" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Bedrock instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_bedrock-0.45.6-py3-none-any.whl", hash = "sha256:9397ffd7972a75313fdb5d3c80d77f2dd682288e9113a07c58f8b43e76e76370"}, - {file = "opentelemetry_instrumentation_bedrock-0.45.6.tar.gz", hash = "sha256:73b6711eacd9dc5db85e09b3b38c552c0a0cc0fdd16029777d81523cf0db4c9a"}, + {file = "opentelemetry_instrumentation_bedrock-0.60.0-py3-none-any.whl", hash = "sha256:c74c1fe4b04130eaf633f5fbcae1940a59284c9cf5a397bb1611df0f6a33db62"}, + {file = "opentelemetry_instrumentation_bedrock-0.60.0.tar.gz", hash = "sha256:379280a14b009fd1bcd597ed176fba4a4dfd88dbcb3b9c88ef559ce40a43e0f7"}, ] [package.dependencies] -anthropic = ">=0.17.0" -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" -tokenizers = ">=0.13.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +enrichment = ["anthropic (>=0.17.0)"] +instruments = ["boto3"] [[package]] name = "opentelemetry-instrumentation-boto3sqs" -version = "0.55b1" +version = "0.60b1" description = "Boto3 SQS service tracing for OpenTelemetry" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_boto3sqs-0.55b1-py3-none-any.whl", hash = "sha256:82887ffca0ae94d0781c076e040ed6068e53bb508873a8a253ba8f704e56ab7e"}, - {file = "opentelemetry_instrumentation_boto3sqs-0.55b1.tar.gz", hash = "sha256:7a86d586b2d5c68d01650c7442068c0b181450796ba237839905f6db0cc403ef"}, + {file = "opentelemetry_instrumentation_boto3sqs-0.60b1-py3-none-any.whl", hash = "sha256:6d6386b8ec83fc3f877b55ad30a9c579837fe59b1f53a5927336cb0cfc9e97c1"}, + {file = "opentelemetry_instrumentation_boto3sqs-0.60b1.tar.gz", hash = "sha256:f13333abbc42241fbdde9b1ad84b420bd9814a4de7e00e134db7a0ddbfec0ba0"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" wrapt = ">=1.0.0,<2.0.0" [package.extras] @@ -3181,41 +2614,41 @@ instruments = ["boto3 (>=1.0,<2.0)"] [[package]] name = "opentelemetry-instrumentation-botocore" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry Botocore instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_botocore-0.55b1-py3-none-any.whl", hash = "sha256:a00a4a846b6caa26e0be42e96cd032f496c83a25a7d8298a1904978b088eb370"}, - {file = "opentelemetry_instrumentation_botocore-0.55b1.tar.gz", hash = "sha256:7d027a4371ac90f9c17c32c98012a484065ee2a4be6755493d8b9cbc73ee76fe"}, + {file = "opentelemetry_instrumentation_botocore-0.60b1-py3-none-any.whl", hash = "sha256:b5cb1e267545cdd96a81a1bef690b1d00c20497ac4d815ef7c79fb3c572d6fc6"}, + {file = "opentelemetry_instrumentation_botocore-0.60b1.tar.gz", hash = "sha256:198e7d74b78b1b19ea47a5f2191171227557c37bfc4f49076958d129f848a8ed"}, ] [package.dependencies] -opentelemetry-api = ">=1.30,<2.0" -opentelemetry-instrumentation = "0.55b1" +opentelemetry-api = ">=1.37,<2.0" +opentelemetry-instrumentation = "0.60b1" opentelemetry-propagator-aws-xray = ">=1.0,<2.0" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-semantic-conventions = "0.60b1" [package.extras] instruments = ["botocore (>=1.0,<2.0)"] [[package]] name = "opentelemetry-instrumentation-cassandra" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry Cassandra instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_cassandra-0.55b1-py3-none-any.whl", hash = "sha256:66c13874cbc181f6e21cd536158a60c5b896e09465b2dc8be11c399a3905d0f5"}, - {file = "opentelemetry_instrumentation_cassandra-0.55b1.tar.gz", hash = "sha256:a11ecad84d1218a67869e16b0d01c0cd17338b07390987ae70882a0cc208d42e"}, + {file = "opentelemetry_instrumentation_cassandra-0.60b1-py3-none-any.whl", hash = "sha256:773f6a273934ab20c2d4061d58c1ada75694631277d86712002499e1a6c3a6b2"}, + {file = "opentelemetry_instrumentation_cassandra-0.60b1.tar.gz", hash = "sha256:37c8359b318ef09d8d92fce8470b0a1578759e9f11f64471540ca65e97476d53"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" wrapt = ">=1.0.0,<2.0.0" [package.extras] @@ -3223,58 +2656,61 @@ instruments = ["cassandra-driver (>=3.25,<4.0)", "scylla-driver (>=3.25,<4.0)"] [[package]] name = "opentelemetry-instrumentation-celery" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry Celery Instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_celery-0.55b1-py3-none-any.whl", hash = "sha256:499c6b63678a82f16bcbb5f0844e742fd6662e358759e107f91fc59af5150a52"}, - {file = "opentelemetry_instrumentation_celery-0.55b1.tar.gz", hash = "sha256:29be68398d74e3ba0b281c82af5427c28a9a4bc248b0856768024a8f9bcfbb44"}, + {file = "opentelemetry_instrumentation_celery-0.60b1-py3-none-any.whl", hash = "sha256:ee946f85a3e6893d8edf09402c2c773cacc09854dcea35ae2a694320f85403cf"}, + {file = "opentelemetry_instrumentation_celery-0.60b1.tar.gz", hash = "sha256:896bb9eda2d7c4a39bbc5bee2caae9c06a3a41ba283bafc414b224bc8a0f04c8"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" [package.extras] instruments = ["celery (>=4.0,<6.0)"] [[package]] name = "opentelemetry-instrumentation-chromadb" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Chroma DB instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_chromadb-0.45.6-py3-none-any.whl", hash = "sha256:85ba04f774e99baf5520ac96aa27a220dc1b3b405bf29b60e241f0dda3406cbc"}, - {file = "opentelemetry_instrumentation_chromadb-0.45.6.tar.gz", hash = "sha256:bcd1fea11cd3600099ef4409b6280e523fe800fc697a827e4101632737aee85c"}, + {file = "opentelemetry_instrumentation_chromadb-0.60.0-py3-none-any.whl", hash = "sha256:d6c1fed1a501578e694bd7e6fbc8468fc5266a3f00b8052f79a7544e0fc70b5b"}, + {file = "opentelemetry_instrumentation_chromadb-0.60.0.tar.gz", hash = "sha256:44f382886d9cae7b045e23d17ecad47970d5730d453a33b0875a12856c2471cc"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["chromadb"] [[package]] name = "opentelemetry-instrumentation-click" -version = "0.55b1" +version = "0.60b1" description = "Click instrumentation for OpenTelemetry" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_click-0.55b1-py3-none-any.whl", hash = "sha256:c90a22998e46b96b45b42390bc5cf21d6ed194ec99ac99681406236e4dd24fc4"}, - {file = "opentelemetry_instrumentation_click-0.55b1.tar.gz", hash = "sha256:b7cdf9419b2863b1f067f0ebcf053b2a3cc1087629ec1a52462c1e32c7caaacb"}, + {file = "opentelemetry_instrumentation_click-0.60b1-py3-none-any.whl", hash = "sha256:f50d12129d5a857d4d69d3b4968c28d42c8563ad26fcc8d30f9bfc3a617f1dc0"}, + {file = "opentelemetry_instrumentation_click-0.60b1.tar.gz", hash = "sha256:530da3e9abb05e1cb7198f210487f08020f71ef0f299ff3b2414e9a2547930bf"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" wrapt = ">=1.0.0,<2.0.0" [package.extras] @@ -3282,117 +2718,123 @@ instruments = ["click (>=8.1.3,<9.0.0)"] [[package]] name = "opentelemetry-instrumentation-cohere" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Cohere instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_cohere-0.45.6-py3-none-any.whl", hash = "sha256:cb8c29690225077f7ea60482c62f5e71b81c6fb14f7f292bbca818075c0f2d58"}, - {file = "opentelemetry_instrumentation_cohere-0.45.6.tar.gz", hash = "sha256:14c718aca13695636d68565e42168d5bf54b5bd86703b411ec44ca6fc435a81a"}, + {file = "opentelemetry_instrumentation_cohere-0.60.0-py3-none-any.whl", hash = "sha256:bc34cbcbd3a0fbf076ecc1b9af9195e088729bbae53c87bb03d2888c19ea1fd7"}, + {file = "opentelemetry_instrumentation_cohere-0.60.0.tar.gz", hash = "sha256:1e849d6a128441e9184e8e31cd0c338d20d2e7371361ee2c52a91949a228fb3e"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["cohere"] [[package]] name = "opentelemetry-instrumentation-confluent-kafka" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry Confluent Kafka instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_confluent_kafka-0.55b1-py3-none-any.whl", hash = "sha256:0e101e4541b1ec0122fabb749ceb859eeb0a992041f10ab8669e1fd53e9a5f5c"}, - {file = "opentelemetry_instrumentation_confluent_kafka-0.55b1.tar.gz", hash = "sha256:d95d2740720ee9f52db35c90af5e4ce5d3b49edd2332dbe69da2431525bbf226"}, + {file = "opentelemetry_instrumentation_confluent_kafka-0.60b1-py3-none-any.whl", hash = "sha256:e25ca438f0a65266e5e580062a53e24df4e8d7e1902634f6c56122db8408e5bc"}, + {file = "opentelemetry_instrumentation_confluent_kafka-0.60b1.tar.gz", hash = "sha256:8272ecfa6ee07fefb21bcddcb1942d05d903e2b1516cb1c463a3e9d7f8dcdad1"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" +opentelemetry-instrumentation = "0.60b1" wrapt = ">=1.0.0,<2.0.0" [package.extras] -instruments = ["confluent-kafka (>=1.8.2,<=2.7.0)"] +instruments = ["confluent-kafka (>=1.8.2,<=2.11.0)"] [[package]] name = "opentelemetry-instrumentation-crewai" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry crewAI instrumentation" optional = false python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_crewai-0.45.6-py3-none-any.whl", hash = "sha256:658a03f376f81f269e2ebb4c77813822a0bf9bda9ba943bb4bce3ea558dc1828"}, - {file = "opentelemetry_instrumentation_crewai-0.45.6.tar.gz", hash = "sha256:467502fb8c179a87c213d1e707ffc93d6591651209b4e0be8a41a8ed3f907795"}, + {file = "opentelemetry_instrumentation_crewai-0.60.0-py3-none-any.whl", hash = "sha256:2d9ed4a093793bf68619b4df3a39c25b16ae01ca199fb4a3d65fc7912de11138"}, + {file = "opentelemetry_instrumentation_crewai-0.60.0.tar.gz", hash = "sha256:a80da4bb1cca72ab92432ecc303582e1d5e9b22927be664f56a95501c2b59679"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["crewai (>=1.0.0,<2)"] [[package]] name = "opentelemetry-instrumentation-dbapi" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry Database API instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_dbapi-0.55b1-py3-none-any.whl", hash = "sha256:745d14fc63595d632be56385dff8af4f7c86e995710370b9dd1b4c2b470667c1"}, - {file = "opentelemetry_instrumentation_dbapi-0.55b1.tar.gz", hash = "sha256:b1f1d1fa9bb0da89edced6f224f3e9dbc1675ccd93dbebb5c48a432220173774"}, + {file = "opentelemetry_instrumentation_dbapi-0.60b1-py3-none-any.whl", hash = "sha256:5577189f678de5b9828c930c8fb72e8f1999b304131777b60099e5c4b3948193"}, + {file = "opentelemetry_instrumentation_dbapi-0.60b1.tar.gz", hash = "sha256:a239d328249b86fba5e42900b98bf31ee99c07092530feca18afde92c600f901"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" wrapt = ">=1.0.0,<2.0.0" [[package]] name = "opentelemetry-instrumentation-django" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry Instrumentation for Django" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_django-0.55b1-py3-none-any.whl", hash = "sha256:070853d9136a658fc2b033375a3c0dce649678ddbbe3bdcf1a9ed5c9c2b6928e"}, - {file = "opentelemetry_instrumentation_django-0.55b1.tar.gz", hash = "sha256:5c179b7fd377f0f617dafcd9f9c07ad980e88fb0983071935b47f9a60ce2c2aa"}, + {file = "opentelemetry_instrumentation_django-0.60b1-py3-none-any.whl", hash = "sha256:3f6b4ba201eee35406dab965b254eed0c64fa1ef42e4a7b0296ad1b30e8e3f81"}, + {file = "opentelemetry_instrumentation_django-0.60b1.tar.gz", hash = "sha256:765b69c7ccdea7e9ebfd0b9e68387956b8f74816f3e39775d5b06a20f16b0522"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-instrumentation-wsgi = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" -opentelemetry-util-http = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-instrumentation-wsgi = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" +opentelemetry-util-http = "0.60b1" [package.extras] -asgi = ["opentelemetry-instrumentation-asgi (==0.55b1)"] +asgi = ["opentelemetry-instrumentation-asgi (==0.60b1)"] instruments = ["django (>=1.10)"] [[package]] name = "opentelemetry-instrumentation-elasticsearch" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry elasticsearch instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_elasticsearch-0.55b1-py3-none-any.whl", hash = "sha256:5ef26bc2692838c5d97effa92febaae805519aad3fc962c02552e2ec7b736da1"}, - {file = "opentelemetry_instrumentation_elasticsearch-0.55b1.tar.gz", hash = "sha256:3481ade51055a16040a9e2af9ff8d6e337a0df91ffa11eb41e21cadd9b7bdcdc"}, + {file = "opentelemetry_instrumentation_elasticsearch-0.60b1-py3-none-any.whl", hash = "sha256:400a91af0f9b74e2573245482420b6043da2655f5ba124bd650a6ca36953678c"}, + {file = "opentelemetry_instrumentation_elasticsearch-0.60b1.tar.gz", hash = "sha256:754a642d455dcecf96542874753efc01bfae86619be6a71e8ec3713d986e5932"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" wrapt = ">=1.0.0,<2.0.0" [package.extras] @@ -3400,22 +2842,22 @@ instruments = ["elasticsearch (>=6.0)"] [[package]] name = "opentelemetry-instrumentation-falcon" -version = "0.55b1" +version = "0.60b1" description = "Falcon instrumentation for OpenTelemetry" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_falcon-0.55b1-py3-none-any.whl", hash = "sha256:377e444eae2a3403907f8370b7752fba1147715db23ef14770d8c6500842e914"}, - {file = "opentelemetry_instrumentation_falcon-0.55b1.tar.gz", hash = "sha256:1320c5bcbe29e1545b085cf3366434699ea9a6b03bfd44db80a431ad88c8d5f8"}, + {file = "opentelemetry_instrumentation_falcon-0.60b1-py3-none-any.whl", hash = "sha256:974c89844e91d3ccc8f88e8d73fc3a06bea442a30c3dd35fcd8096d2baf8f3ec"}, + {file = "opentelemetry_instrumentation_falcon-0.60b1.tar.gz", hash = "sha256:00098347f611d6cee32e672ff85fc0aa287180d37f32cd1e0d10d7ba36f4b64d"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-instrumentation-wsgi = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" -opentelemetry-util-http = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-instrumentation-wsgi = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" +opentelemetry-util-http = "0.60b1" packaging = ">=20.0" [package.extras] @@ -3423,44 +2865,44 @@ instruments = ["falcon (>=1.4.1,<5.0.0)"] [[package]] name = "opentelemetry-instrumentation-fastapi" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry FastAPI Instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_fastapi-0.55b1-py3-none-any.whl", hash = "sha256:af4c09aebb0bd6b4a0881483b175e76547d2bc96329c94abfb794bf44f29f6bb"}, - {file = "opentelemetry_instrumentation_fastapi-0.55b1.tar.gz", hash = "sha256:bb9f8c13a053e7ff7da221248067529cc320e9308d57f3908de0afa36f6c5744"}, + {file = "opentelemetry_instrumentation_fastapi-0.60b1-py3-none-any.whl", hash = "sha256:af94b7a239ad1085fc3a820ecf069f67f579d7faf4c085aaa7bd9b64eafc8eaf"}, + {file = "opentelemetry_instrumentation_fastapi-0.60b1.tar.gz", hash = "sha256:de608955f7ff8eecf35d056578346a5365015fd7d8623df9b1f08d1c74769c01"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-instrumentation-asgi = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" -opentelemetry-util-http = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-instrumentation-asgi = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" +opentelemetry-util-http = "0.60b1" [package.extras] instruments = ["fastapi (>=0.92,<1.0)"] [[package]] name = "opentelemetry-instrumentation-flask" -version = "0.55b1" +version = "0.60b1" description = "Flask instrumentation for OpenTelemetry" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_flask-0.55b1-py3-none-any.whl", hash = "sha256:243164cb8c4eae6f89d30f91f8870bd66f2c145bad005a470094e83851891ff0"}, - {file = "opentelemetry_instrumentation_flask-0.55b1.tar.gz", hash = "sha256:db95a29e87694f9d96744880cfaf7b6672247a839c8ed5c4162a655ba2e9e2d8"}, + {file = "opentelemetry_instrumentation_flask-0.60b1-py3-none-any.whl", hash = "sha256:070cb00f3f1214a4b680c7ab6ae1e5a14a54fd05ef163323500326002dfcfed4"}, + {file = "opentelemetry_instrumentation_flask-0.60b1.tar.gz", hash = "sha256:88cb0f6178d8a9b66f6aabb008e2735f28e3114def90b28004a2b295ca9b67e8"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-instrumentation-wsgi = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" -opentelemetry-util-http = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-instrumentation-wsgi = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" +opentelemetry-util-http = "0.60b1" packaging = ">=21.0" [package.extras] @@ -3468,56 +2910,62 @@ instruments = ["flask (>=1.0)"] [[package]] name = "opentelemetry-instrumentation-google-generativeai" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Google Generative AI instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_google_generativeai-0.45.6-py3-none-any.whl", hash = "sha256:8c80c4f48a2a2977c41c2c0b92da7c31eead1a7e0aac7f9f60c97424c75320af"}, - {file = "opentelemetry_instrumentation_google_generativeai-0.45.6.tar.gz", hash = "sha256:53590a018d80c089ca044142d251b2886a8c1018cfcef6a9e33ab11b0d2bf24c"}, + {file = "opentelemetry_instrumentation_google_generativeai-0.60.0-py3-none-any.whl", hash = "sha256:d629de8a81e4dc1049927d68d1c6590ded2aac0707e59626fc4b2fd92828e867"}, + {file = "opentelemetry_instrumentation_google_generativeai-0.60.0.tar.gz", hash = "sha256:7470128272c542a9a1e32e504a67610a18b0e92d9b6cae70cd79bc0afc0db63f"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["google-genai"] [[package]] name = "opentelemetry-instrumentation-groq" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Groq instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_groq-0.45.6-py3-none-any.whl", hash = "sha256:e3f9af3a5b99783cf101a778d03c15700160009b19794ade2af33ae3ba7ffda2"}, - {file = "opentelemetry_instrumentation_groq-0.45.6.tar.gz", hash = "sha256:0b6026605c7a808a72ed90fced7b8b910ee715c29ebc7bc0385b1346978fb76e"}, + {file = "opentelemetry_instrumentation_groq-0.60.0-py3-none-any.whl", hash = "sha256:e6058679a212e0948769f35ae4288450b0d96afa7e6de9f3a29268b233094859"}, + {file = "opentelemetry_instrumentation_groq-0.60.0.tar.gz", hash = "sha256:422d5e4551e2cc35a7b4741b15744aa628f8e700389f3e4576c1cd073fa10003"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["groq"] [[package]] name = "opentelemetry-instrumentation-grpc" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry gRPC instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_grpc-0.55b1-py3-none-any.whl", hash = "sha256:585773b069ad7aacaed3409dabb09139a55b20c46ab63951d2d521c8e66f39cc"}, - {file = "opentelemetry_instrumentation_grpc-0.55b1.tar.gz", hash = "sha256:de81a5589e8f7dbca2ebff668cb57b5102567b5a42300ee76bf3592cab242e8f"}, + {file = "opentelemetry_instrumentation_grpc-0.60b1-py3-none-any.whl", hash = "sha256:f7a81a87b2a26842fc62cba0743a475f151e77eb21d5b93902fbfd0518a7cca7"}, + {file = "opentelemetry_instrumentation_grpc-0.60b1.tar.gz", hash = "sha256:049573ddfe4c32af151348d2dbeddaaca788a57c320e70770ec216b7e35ccfd4"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" wrapt = ">=1.0.0,<2.0.0" [package.extras] @@ -3525,39 +2973,42 @@ instruments = ["grpcio (>=1.42.0)"] [[package]] name = "opentelemetry-instrumentation-haystack" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Haystack instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_haystack-0.45.6-py3-none-any.whl", hash = "sha256:6eb7e44bd4accc3cd408a3f4b016f68d6bdccf04dddb182250070aa62af5ceb0"}, - {file = "opentelemetry_instrumentation_haystack-0.45.6.tar.gz", hash = "sha256:bdd5fe694bfe901200a8f0de5d0d66e3b8a0110b19cb6bff7b00d11b680a50d8"}, + {file = "opentelemetry_instrumentation_haystack-0.60.0-py3-none-any.whl", hash = "sha256:ddbb5cf0e9467f493d4b0eead7cc756a6861eafc26ee9627ba56a6d5a872ff5e"}, + {file = "opentelemetry_instrumentation_haystack-0.60.0.tar.gz", hash = "sha256:132ed4da842a641c8cbb2a5d0cfb32e02b485de3914e17bb69c60f758ae8cdfa"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["haystack-ai"] [[package]] name = "opentelemetry-instrumentation-httpx" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry HTTPX Instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_httpx-0.55b1-py3-none-any.whl", hash = "sha256:5fe22fcc3ad78a1da85cbd5d35d6acfb208521c164ad1dd75594230a266c6811"}, - {file = "opentelemetry_instrumentation_httpx-0.55b1.tar.gz", hash = "sha256:3121a9196a25a72b65cb16188a1b09f61e365694c75534b306d09088e5f90041"}, + {file = "opentelemetry_instrumentation_httpx-0.60b1-py3-none-any.whl", hash = "sha256:f37636dd742ad2af83d896ba69601ed28da51fa4e25d1ab62fde89ce413e275b"}, + {file = "opentelemetry_instrumentation_httpx-0.60b1.tar.gz", hash = "sha256:a506ebaf28c60112cbe70ad4f0338f8603f148938cb7b6794ce1051cd2b270ae"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" -opentelemetry-util-http = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" +opentelemetry-util-http = "0.60b1" wrapt = ">=1.0.0,<2.0.0" [package.extras] @@ -3565,19 +3016,19 @@ instruments = ["httpx (>=0.18.0)"] [[package]] name = "opentelemetry-instrumentation-jinja2" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry jinja2 instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_jinja2-0.55b1-py3-none-any.whl", hash = "sha256:afc30594d9e3919dffdc4374354ca93a71f808b75ce57e19ef82bdd401737667"}, - {file = "opentelemetry_instrumentation_jinja2-0.55b1.tar.gz", hash = "sha256:d8c9d4423c2f577d7f31f09ec035a0da97531264f8512ba1ad5cbfdeabb5a2d5"}, + {file = "opentelemetry_instrumentation_jinja2-0.60b1-py3-none-any.whl", hash = "sha256:a33423fde73d089500c0e1723f0fa998277b873acad0c777d195ea1bcf249f99"}, + {file = "opentelemetry_instrumentation_jinja2-0.60b1.tar.gz", hash = "sha256:0255d0c09415a1ac2d5ee2bacb3b696c08b7c1bac9e4cbdf7f6983f20257b695"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" +opentelemetry-instrumentation = "0.60b1" wrapt = ">=1.0.0,<2.0.0" [package.extras] @@ -3585,277 +3036,307 @@ instruments = ["jinja2 (>=2.7,<4.0)"] [[package]] name = "opentelemetry-instrumentation-kafka-python" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry Kafka-Python instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_kafka_python-0.55b1-py3-none-any.whl", hash = "sha256:60ce3039993b49e4dd7118ddb3b5137f593aefac9f6cec3043e9dcf28263e95e"}, - {file = "opentelemetry_instrumentation_kafka_python-0.55b1.tar.gz", hash = "sha256:7ae80fbfe708338e0ebbfdb61f34ae94acc23b65e996f7c621d2ee86461c1f33"}, + {file = "opentelemetry_instrumentation_kafka_python-0.60b1-py3-none-any.whl", hash = "sha256:c6061ce4afa1cdbb9e88ad596f6a22fda37e84121f03621e4ecf6c86c068f586"}, + {file = "opentelemetry_instrumentation_kafka_python-0.60b1.tar.gz", hash = "sha256:64be84f9df228ad5adaa149e88e4c5792515c2b2431a49d6dc14591028a53e60"}, ] [package.dependencies] opentelemetry-api = ">=1.5,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" [package.extras] -instruments = ["kafka-python (>=2.0,<3.0)", "kafka-python-ng (>=2.0,<3.0)"] +instruments-any = ["kafka-python (>=2.0,<3.0)", "kafka-python-ng (>=2.0,<3.0)"] [[package]] name = "opentelemetry-instrumentation-lancedb" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Lancedb instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_lancedb-0.45.6-py3-none-any.whl", hash = "sha256:4f4055145013fa625ef05ea8a9de84354dbc6c4a4b5c9af5ddb21164838d20d5"}, - {file = "opentelemetry_instrumentation_lancedb-0.45.6.tar.gz", hash = "sha256:36a89a7838b6ae55eef011a54cefa70805ef520d8420d1d5b11e1abb4991d6ff"}, + {file = "opentelemetry_instrumentation_lancedb-0.60.0-py3-none-any.whl", hash = "sha256:7c9211473c0accb3811f4fe0eee77d2f2ddde8842b2b609f57f3d90b5ee4e160"}, + {file = "opentelemetry_instrumentation_lancedb-0.60.0.tar.gz", hash = "sha256:e895dce1d59e45bea1545a4276fb76e73cef97cfbffed730132c4e39bb25d8c4"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["lancedb"] [[package]] name = "opentelemetry-instrumentation-langchain" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Langchain instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_langchain-0.45.6-py3-none-any.whl", hash = "sha256:af69d956d04c9b0552d31aee33b897563fb551fc709104f33604ccc7a9dc1014"}, - {file = "opentelemetry_instrumentation_langchain-0.45.6.tar.gz", hash = "sha256:16a51ad65e299c9ae56904fcbc49fee7ca0fb06ae5f4992cb57245e183375b44"}, + {file = "opentelemetry_instrumentation_langchain-0.60.0-py3-none-any.whl", hash = "sha256:bdaa2701c50d230317a92c8089f494bcb7163f49efc92b91f4abed7e76c312db"}, + {file = "opentelemetry_instrumentation_langchain-0.60.0.tar.gz", hash = "sha256:93bded5ba67a79662397899e8e1635936cb91b7ecea3164c531f98e48c5c7fd1"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["langchain"] [[package]] name = "opentelemetry-instrumentation-llamaindex" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry LlamaIndex instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_llamaindex-0.45.6-py3-none-any.whl", hash = "sha256:0765d77525a55ae43cba5bea8308f15dca0dc559928837c172368f264993ed4e"}, - {file = "opentelemetry_instrumentation_llamaindex-0.45.6.tar.gz", hash = "sha256:6d97610671742b21defb1420a32ac57eb415ab3cfaa91970116c636508564dd2"}, + {file = "opentelemetry_instrumentation_llamaindex-0.60.0-py3-none-any.whl", hash = "sha256:534119b0a83a96810a9628f1c8af46b5429758a1d9c2a29e6c3a961499df34ec"}, + {file = "opentelemetry_instrumentation_llamaindex-0.60.0.tar.gz", hash = "sha256:cb24c650dd70524ead722bb566785b6b7fef60e224b788a8c6eb7279d238321b"}, ] [package.dependencies] inflection = ">=0.5.1,<0.6.0" -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["llama-index"] +llamaparse = ["llama-parse"] [[package]] name = "opentelemetry-instrumentation-logging" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry Logging instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_logging-0.55b1-py3-none-any.whl", hash = "sha256:1b34b7bfcfa6a22f58f2000f041f5c169c5074738cf23bd33599f60ae1ecf1c5"}, - {file = "opentelemetry_instrumentation_logging-0.55b1.tar.gz", hash = "sha256:8ab1e68a2496d36ed2388ec3178495d9fa31f805b93c5845f83f1fab718f28d0"}, + {file = "opentelemetry_instrumentation_logging-0.60b1-py3-none-any.whl", hash = "sha256:f2e18cbc7e1dd3628c80e30d243897fdc93c5b7e0c8ae60abd2b9b6a99f82343"}, + {file = "opentelemetry_instrumentation_logging-0.60b1.tar.gz", hash = "sha256:98f4b9c7aeb9314a30feee7c002c7ea9abea07c90df5f97fb058b850bc45b89a"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" +opentelemetry-instrumentation = "0.60b1" [[package]] name = "opentelemetry-instrumentation-marqo" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Marqo instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_marqo-0.45.6-py3-none-any.whl", hash = "sha256:508a209c8fc7651344dc020ebe514a7fe5d5bf153730f62b76a3942c392f4ddd"}, - {file = "opentelemetry_instrumentation_marqo-0.45.6.tar.gz", hash = "sha256:8ac4ab77c700c55cebea377f89910a2c20ddc5840833735ca45ecf5148b0e88e"}, + {file = "opentelemetry_instrumentation_marqo-0.60.0-py3-none-any.whl", hash = "sha256:c8e21c590b3f68e814a59d1cad6fad2acd8a6207f657fc08e6fe6b655d7599f2"}, + {file = "opentelemetry_instrumentation_marqo-0.60.0.tar.gz", hash = "sha256:d4053039648b198c74ec46aba7b50584b8ac4e5e25649406992e8e423e9fb9f0"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["marqo"] [[package]] name = "opentelemetry-instrumentation-mcp" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry mcp instrumentation" optional = false python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_mcp-0.45.6-py3-none-any.whl", hash = "sha256:4567208b472bbb961876afaca9e95b374afa99b920cdffc382beec09cc23b355"}, - {file = "opentelemetry_instrumentation_mcp-0.45.6.tar.gz", hash = "sha256:fe27b254fbd99c386120d715df83a90ab66e568ffa0d8896d58e550634be9088"}, + {file = "opentelemetry_instrumentation_mcp-0.60.0-py3-none-any.whl", hash = "sha256:7eba357d330601247ba76e9c62f6195761e811a99447bfb2b7660af6993b7bd5"}, + {file = "opentelemetry_instrumentation_mcp-0.60.0.tar.gz", hash = "sha256:f398c27b3efcefdd434f2d31c90c60784401a1e848e2cf7ccf91e46ac48baf6f"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-exporter-otlp = "1.34.1" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["mcp"] [[package]] name = "opentelemetry-instrumentation-milvus" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Milvus instrumentation" optional = false python-versions = "<4,>=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_milvus-0.45.6-py3-none-any.whl", hash = "sha256:5840c8e3b3a9b4b17f98b2037fee326ebeaaaacadfdc67a0d9c5ea03dd58c0c5"}, - {file = "opentelemetry_instrumentation_milvus-0.45.6.tar.gz", hash = "sha256:7688af8c66e28c416fe3c33f745cef492298bcf9c4308f220977c7a6e3ac1a6a"}, + {file = "opentelemetry_instrumentation_milvus-0.60.0-py3-none-any.whl", hash = "sha256:b084553fc7ac64f75d588bfb5d7ae3c89fab1c8175e37c17e2df7a39525ce759"}, + {file = "opentelemetry_instrumentation_milvus-0.60.0.tar.gz", hash = "sha256:c69ab9f73913afad771a2a05bd44a1dea3ac9e6b63d55fac0f9d676e896bcfbd"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["pymilvus"] [[package]] name = "opentelemetry-instrumentation-mistralai" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Mistral AI instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_mistralai-0.45.6-py3-none-any.whl", hash = "sha256:5c85edd9dc9adfe2396ee369b6942a632f8e0200f2d661637eac401c3fb2df87"}, - {file = "opentelemetry_instrumentation_mistralai-0.45.6.tar.gz", hash = "sha256:b21773735a33924646a846f06a68930cc532e6c38e31dd51351c84178694bbb5"}, + {file = "opentelemetry_instrumentation_mistralai-0.60.0-py3-none-any.whl", hash = "sha256:12ba99fae9acf2444af42a9eb5e8b1d04ea26599141b6c7892c9b2d7ddc67c5c"}, + {file = "opentelemetry_instrumentation_mistralai-0.60.0.tar.gz", hash = "sha256:f0dd13d0bf0414ab166cc64d52634b4ed7374202f31aa5d782d28b5aae94c5f1"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["mistralai"] [[package]] name = "opentelemetry-instrumentation-mysql" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry MySQL instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_mysql-0.55b1-py3-none-any.whl", hash = "sha256:05bfabfea152c5b88d3254ab15eac377151e4124b121326722c943024d6b0ea4"}, - {file = "opentelemetry_instrumentation_mysql-0.55b1.tar.gz", hash = "sha256:32f469f8aa50101dc02e44cef62e9a2d7adaeb42c4764dceb9bbec4e81eaffcd"}, + {file = "opentelemetry_instrumentation_mysql-0.60b1-py3-none-any.whl", hash = "sha256:fa72ae6933ac7b17b3587821d6ee264cc814a86021087076fed8a140cd576b9e"}, + {file = "opentelemetry_instrumentation_mysql-0.60b1.tar.gz", hash = "sha256:6d41a025944e5ae29bdbc06feab0d40f6ad8b7c7c4dec952d8b1f7b2078af53b"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-instrumentation-dbapi = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-instrumentation-dbapi = "0.60b1" [package.extras] instruments = ["mysql-connector-python (>=8.0,<10.0)"] [[package]] name = "opentelemetry-instrumentation-mysqlclient" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry mysqlclient instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_mysqlclient-0.55b1-py3-none-any.whl", hash = "sha256:8464ee3a619a44178ef2c31c38626a359750cc41346de6e87163880bd09f4079"}, - {file = "opentelemetry_instrumentation_mysqlclient-0.55b1.tar.gz", hash = "sha256:084738014510088369bd0b70672466d4309a15e72062e29fdc2d0414e9efcb88"}, + {file = "opentelemetry_instrumentation_mysqlclient-0.60b1-py3-none-any.whl", hash = "sha256:681a3b333bcb9a0029dce8d5f6e0dd05cbe4062024a2b65e4d22dd2d0b306302"}, + {file = "opentelemetry_instrumentation_mysqlclient-0.60b1.tar.gz", hash = "sha256:17d0bcda55fe973289aa5849d82be0f9a53fe26eb0defaaef62b021d1b9f4cc4"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-instrumentation-dbapi = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-instrumentation-dbapi = "0.60b1" [package.extras] instruments = ["mysqlclient (<3)"] [[package]] name = "opentelemetry-instrumentation-ollama" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Ollama instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_ollama-0.45.6-py3-none-any.whl", hash = "sha256:0fc87ff7ba5cf323d69724e82b3428005dec6f499cc7520ad3edb36635b31ed1"}, - {file = "opentelemetry_instrumentation_ollama-0.45.6.tar.gz", hash = "sha256:79b1623f4fb27ebe29faeea466b15cf3fee3d79c91fc4472424dfde79dfa95ad"}, + {file = "opentelemetry_instrumentation_ollama-0.60.0-py3-none-any.whl", hash = "sha256:60dc19b7e7469a45d2b81bf300024260a469798a8950b33b267feef6705c4104"}, + {file = "opentelemetry_instrumentation_ollama-0.60.0.tar.gz", hash = "sha256:7a48ecb3f440790c07862f406a1fd342e665f59dcb802109c4289cda29fcb807"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["ollama"] [[package]] name = "opentelemetry-instrumentation-openai" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry OpenAI instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_openai-0.45.6-py3-none-any.whl", hash = "sha256:1aac8a53fa4565f2ea048048e43c7472e1426fb71ab67281fd36f9de0e63b6ef"}, - {file = "opentelemetry_instrumentation_openai-0.45.6.tar.gz", hash = "sha256:1781a43a85ebec186a97475de64ad1415e46e93e285b425748bc0e516a1ec6e1"}, + {file = "opentelemetry_instrumentation_openai-0.60.0-py3-none-any.whl", hash = "sha256:90abd3647c59add0ef295787d8cc0a46300a96b0308a67ef838479e0b2bccdff"}, + {file = "opentelemetry_instrumentation_openai-0.60.0.tar.gz", hash = "sha256:7e1a65e76b10f715675d77cf08fdef389b9ac942c9a53ab0e95e19d5d4335154"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["openai"] [[package]] name = "opentelemetry-instrumentation-openai-agents" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry OpenAI Agents instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_openai_agents-0.45.6-py3-none-any.whl", hash = "sha256:c0899c87f05827c2a63a9bc15f66c8edcdacac129914da0e08695b213074a9d1"}, - {file = "opentelemetry_instrumentation_openai_agents-0.45.6.tar.gz", hash = "sha256:d9db02ba900a992a3f904c49a081b3c8a08ea0114c049740784665ad7d2b0f13"}, + {file = "opentelemetry_instrumentation_openai_agents-0.60.0-py3-none-any.whl", hash = "sha256:2aa42bc5e2ea0b653040917650daeb55ba846c45165f7a97f336b532a0e09d9d"}, + {file = "opentelemetry_instrumentation_openai_agents-0.60.0.tar.gz", hash = "sha256:74b5e3a4b698cf7e37f18525d2fe711cda1e2f5d2eed465fc21972a6da610ff8"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["openai-agents"] [[package]] name = "opentelemetry-instrumentation-pika" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry pika instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_pika-0.55b1-py3-none-any.whl", hash = "sha256:6d92e38d948c769adb8f92a880fc5182c66ceab589ea3d222c122cafabc03a8f"}, - {file = "opentelemetry_instrumentation_pika-0.55b1.tar.gz", hash = "sha256:ed6ccbbd89e83589b945a10e37847ede9c21a36abc6040878b94623ab069b5a6"}, + {file = "opentelemetry_instrumentation_pika-0.60b1-py3-none-any.whl", hash = "sha256:12c94bc4ccfc47a93a36bea8753aeb013155a96029a61ae4dedddef4a56054fd"}, + {file = "opentelemetry_instrumentation_pika-0.60b1.tar.gz", hash = "sha256:71eac891d742d196f2309e328e31fd57bb8aa72293b21c71b8d9133690a21422"}, ] [package.dependencies] opentelemetry-api = ">=1.5,<2.0" -opentelemetry-instrumentation = "0.55b1" +opentelemetry-instrumentation = "0.60b1" packaging = ">=20.0" wrapt = ">=1.0.0,<2.0.0" @@ -3864,78 +3345,81 @@ instruments = ["pika (>=0.12.0)"] [[package]] name = "opentelemetry-instrumentation-pinecone" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Pinecone instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_pinecone-0.45.6-py3-none-any.whl", hash = "sha256:1eb86bfc9c45565a605afe11de3a77c0acdb59b33f8bf6721b4ad93983568f75"}, - {file = "opentelemetry_instrumentation_pinecone-0.45.6.tar.gz", hash = "sha256:b08da007b6430ee5e444b5ab09af347b55cf91455dfa6664029e556752de3c38"}, + {file = "opentelemetry_instrumentation_pinecone-0.60.0-py3-none-any.whl", hash = "sha256:b26d432fd4aadf1a9c0252c8e25db001e6cfcd8e991db2419c4bb5ec2bc4c90b"}, + {file = "opentelemetry_instrumentation_pinecone-0.60.0.tar.gz", hash = "sha256:23607a1d88a51216f6ed5db0fd59fc412e96274b9e02ae1dc68901357d09e39f"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["pinecone (>=5.1.0,<9)"] [[package]] name = "opentelemetry-instrumentation-psycopg" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry psycopg instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_psycopg-0.55b1-py3-none-any.whl", hash = "sha256:1e5dad5360f1ba58d7e8aa3c967c4c01a76fa83c856dd1d38da0a1281d49fa60"}, - {file = "opentelemetry_instrumentation_psycopg-0.55b1.tar.gz", hash = "sha256:25399229e8cbeadc042447b77c151f546f261d1b4962c5be3836d2662bf7d928"}, + {file = "opentelemetry_instrumentation_psycopg-0.60b1-py3-none-any.whl", hash = "sha256:67fa627561f00b903020843b9254a1be6f3f8c64501033afe629a574a2325ec2"}, + {file = "opentelemetry_instrumentation_psycopg-0.60b1.tar.gz", hash = "sha256:c13a81a898307ebdaa8aa4a0ac17753cb308270fcb07a17a857f74b6021b5a12"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-instrumentation-dbapi = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-instrumentation-dbapi = "0.60b1" [package.extras] instruments = ["psycopg (>=3.1.0)"] [[package]] name = "opentelemetry-instrumentation-psycopg2" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry psycopg2 instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_psycopg2-0.55b1-py3-none-any.whl", hash = "sha256:8aa965a24b63a06e3dfedc97bc24e05666939a8e7b62131c1de4bf31f491bc70"}, - {file = "opentelemetry_instrumentation_psycopg2-0.55b1.tar.gz", hash = "sha256:fa7035b328b77196e03a631921e0fc10f4b1d4d36d9b66fe9a38971bb88af6d4"}, + {file = "opentelemetry_instrumentation_psycopg2-0.60b1-py3-none-any.whl", hash = "sha256:f3841fe83400ee33c51ba5c38f4034534c3415075a4ebf01b97e49f8e0e5dd3e"}, + {file = "opentelemetry_instrumentation_psycopg2-0.60b1.tar.gz", hash = "sha256:46f46c47e11bf59b9746f35761995e4513fb985bab08d4e1f876a2c46ed4eeec"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-instrumentation-dbapi = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-instrumentation-dbapi = "0.60b1" [package.extras] -instruments = ["psycopg2 (>=2.7.3.1)", "psycopg2-binary (>=2.7.3.1)"] +instruments-any = ["psycopg2 (>=2.7.3.1)", "psycopg2-binary (>=2.7.3.1)"] [[package]] name = "opentelemetry-instrumentation-pymemcache" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry pymemcache instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_pymemcache-0.55b1-py3-none-any.whl", hash = "sha256:e6593b07510ee8e94650a6f505fb4cd8a6427c657b32896ff6572bf495ec93e9"}, - {file = "opentelemetry_instrumentation_pymemcache-0.55b1.tar.gz", hash = "sha256:4bdb2507d6dc11b1c92acfbe50d368d6a4f7692f75f9a6e76ccc5bf239e3c431"}, + {file = "opentelemetry_instrumentation_pymemcache-0.60b1-py3-none-any.whl", hash = "sha256:3b84a483bac49c3bd346b74654e598d4304a6fbfb6fd9b5bca3e81ab293d0173"}, + {file = "opentelemetry_instrumentation_pymemcache-0.60b1.tar.gz", hash = "sha256:0899ad780646045f4aede71deb713d6309edd32057f24a13bf3cc37ed97e6bd7"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" wrapt = ">=1.0.0,<2.0.0" [package.extras] @@ -3943,78 +3427,81 @@ instruments = ["pymemcache (>=1.3.5,<5)"] [[package]] name = "opentelemetry-instrumentation-pymongo" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry pymongo instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_pymongo-0.55b1-py3-none-any.whl", hash = "sha256:f731f2c47461fe9ed289fb4784c9f8e305145026e68694a537c01135ca727eea"}, - {file = "opentelemetry_instrumentation_pymongo-0.55b1.tar.gz", hash = "sha256:7ceeef8b1b0a25aabcbae5c19c9646b8530f815244c9c085d3a5fd11a235e9ef"}, + {file = "opentelemetry_instrumentation_pymongo-0.60b1-py3-none-any.whl", hash = "sha256:179cff51e4b018fa92f6acb7aea1dfc5440364e66561db9d5ca0dc0227e0a6dc"}, + {file = "opentelemetry_instrumentation_pymongo-0.60b1.tar.gz", hash = "sha256:448f895b981b356dbcce2af0311a1efe9c5a6a65ea3590d85623febea0776aa8"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" [package.extras] instruments = ["pymongo (>=3.1,<5.0)"] [[package]] name = "opentelemetry-instrumentation-pymssql" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry pymssql instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_pymssql-0.55b1-py3-none-any.whl", hash = "sha256:92a0eca1d610b788738bb3bb28ef311feeaaf89f9dbdb8f7f52a4e689a785b9b"}, - {file = "opentelemetry_instrumentation_pymssql-0.55b1.tar.gz", hash = "sha256:b7092113448846aaf3c7243cc0a188930e85d4b640682c789b4aae2182b18fb5"}, + {file = "opentelemetry_instrumentation_pymssql-0.60b1-py3-none-any.whl", hash = "sha256:8a323547d451c5f95617fe9df29469e442cb0409809f0620ed5acff665f7efd8"}, + {file = "opentelemetry_instrumentation_pymssql-0.60b1.tar.gz", hash = "sha256:bd966e538392bb4aa378eac70534b560294c281aca61aa50a1db1c0fc6997b04"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-instrumentation-dbapi = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-instrumentation-dbapi = "0.60b1" [package.extras] instruments = ["pymssql (>=2.1.5,<3)"] [[package]] name = "opentelemetry-instrumentation-qdrant" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Qdrant instrumentation" optional = false python-versions = "<4,>=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_qdrant-0.45.6-py3-none-any.whl", hash = "sha256:253e3bf0ed35f7a7146244c20c0251078fe5cc4d144c7a25d337a7d6378f825a"}, - {file = "opentelemetry_instrumentation_qdrant-0.45.6.tar.gz", hash = "sha256:fc6b30a01e5165e8899481c075b893c6ccce2b62e452c6845ec56be2c0909e10"}, + {file = "opentelemetry_instrumentation_qdrant-0.60.0-py3-none-any.whl", hash = "sha256:50fa81761c3beb2b010dd47ac4c41ea6529801f56d53b5c07dfc67a9f8e997ee"}, + {file = "opentelemetry_instrumentation_qdrant-0.60.0.tar.gz", hash = "sha256:209cedaf531abd5d2c72832d2d083ab0f74da2871f89efdc56826bbe3e7019ce"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["qdrant-client"] [[package]] name = "opentelemetry-instrumentation-redis" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry Redis instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_redis-0.55b1-py3-none-any.whl", hash = "sha256:8f40d742e1666e0e971f2385b47ddf6f55da2fe6bf77d2ff5f8f3b27cd5746b6"}, - {file = "opentelemetry_instrumentation_redis-0.55b1.tar.gz", hash = "sha256:bce9b47907e08ede4961b845030fbffbf8e6f515e1b48b4697c5f36704a97743"}, + {file = "opentelemetry_instrumentation_redis-0.60b1-py3-none-any.whl", hash = "sha256:33bef0ff9af6f2d88de90c1cd7e25675c10a16d4f9ee5ae7592b28bb08b78139"}, + {file = "opentelemetry_instrumentation_redis-0.60b1.tar.gz", hash = "sha256:ecafa8f81c88917b59f0d842fb3d157f3a8edc71fb4b85bebca3bc19432ce7b8"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" wrapt = ">=1.12.1" [package.extras] @@ -4022,97 +3509,103 @@ instruments = ["redis (>=2.6)"] [[package]] name = "opentelemetry-instrumentation-remoulade" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry Remoulade instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_remoulade-0.55b1-py3-none-any.whl", hash = "sha256:709929d744757918870b9761ac1260e0bb874a547ae92e7f7890f5260f961174"}, - {file = "opentelemetry_instrumentation_remoulade-0.55b1.tar.gz", hash = "sha256:04f64b9d6caa5439ec8a69e00c1dd7407798154bc33bcc3191d0e673a6cf1b5d"}, + {file = "opentelemetry_instrumentation_remoulade-0.60b1-py3-none-any.whl", hash = "sha256:d5960f3b46d6ef06f1e6164b367b99e6b39292aa8ce57357756608d42b965d98"}, + {file = "opentelemetry_instrumentation_remoulade-0.60b1.tar.gz", hash = "sha256:ff596332f79513adb2cb8cc290fe8ac546626e5d457e21a44ecbc54f98a91e4e"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" [package.extras] instruments = ["remoulade (>=0.50)"] [[package]] name = "opentelemetry-instrumentation-replicate" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Replicate instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_replicate-0.45.6-py3-none-any.whl", hash = "sha256:020c805ca86f2d6b8af54edb2fdde0eb5aecdc12f9d0aa5f6bcd8ef3078dd381"}, - {file = "opentelemetry_instrumentation_replicate-0.45.6.tar.gz", hash = "sha256:5bc857e5adbc6bb6cd1b7d02429de37e00113c8bfa72569c25f01092b456af5a"}, + {file = "opentelemetry_instrumentation_replicate-0.60.0-py3-none-any.whl", hash = "sha256:d226c1c52e971cf6e566ccf4588aa7d7987c6dc7d82853a3d1b24e1a94eae104"}, + {file = "opentelemetry_instrumentation_replicate-0.60.0.tar.gz", hash = "sha256:be68c74a9ab31c2309e498457dc0cc667a765d29874ce587f97d7644b9c657d0"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["replicate"] [[package]] name = "opentelemetry-instrumentation-requests" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry requests instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_requests-0.55b1-py3-none-any.whl", hash = "sha256:c9ba0a67850b49aa965e760e87e4b68e52530e5373a0b3c15d290a8997136619"}, - {file = "opentelemetry_instrumentation_requests-0.55b1.tar.gz", hash = "sha256:3a04ae7bc90af08acef074b369275cf77c60533b319fa91cad76a380fd035c83"}, + {file = "opentelemetry_instrumentation_requests-0.60b1-py3-none-any.whl", hash = "sha256:eec9fac3fab84737f663a2e08b12cb095b4bd67643b24587a8ecfa3cf4d0ca4c"}, + {file = "opentelemetry_instrumentation_requests-0.60b1.tar.gz", hash = "sha256:9a1063c16c44a3ba6e81870c4fa42a0fac3ecef5a4d60a11d0976eec9046f3d4"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" -opentelemetry-util-http = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" +opentelemetry-util-http = "0.60b1" [package.extras] instruments = ["requests (>=2.0,<3.0)"] [[package]] name = "opentelemetry-instrumentation-sagemaker" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry SageMaker instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_sagemaker-0.45.6-py3-none-any.whl", hash = "sha256:1268132665164dbb78fa3ac48fc9e18b23f889668a6d1e75395dd4ddb19db2b6"}, - {file = "opentelemetry_instrumentation_sagemaker-0.45.6.tar.gz", hash = "sha256:b74553513e51976e90c044c1ab2f1266bae07d69fcded7949f30a3fb410145cf"}, + {file = "opentelemetry_instrumentation_sagemaker-0.60.0-py3-none-any.whl", hash = "sha256:210d738a76e5d4dd58b68e81541ca2465f8050c09a3cc4b965d73bd829a60ea1"}, + {file = "opentelemetry_instrumentation_sagemaker-0.60.0.tar.gz", hash = "sha256:5ba78692d08142edb6c4518e87905d5e8702b3a514404b3dcbb268892c6169c8"}, ] [package.dependencies] -opentelemetry-api = ">=1.26.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["boto3"] [[package]] name = "opentelemetry-instrumentation-sqlalchemy" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry SQLAlchemy instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_sqlalchemy-0.55b1-py3-none-any.whl", hash = "sha256:d6b3cac2cc3301083608d3c0e2b3979f62c6ab327a12f5a7c779f9ab05eb6633"}, - {file = "opentelemetry_instrumentation_sqlalchemy-0.55b1.tar.gz", hash = "sha256:3a25cfb75de9bb14d26ab274b90d5613867c976e93cde0c5fb673cb731006532"}, + {file = "opentelemetry_instrumentation_sqlalchemy-0.60b1-py3-none-any.whl", hash = "sha256:486a5f264d264c44e07e0320e33fd19d09cecd2fd4b99c1064046e77a27d9f9f"}, + {file = "opentelemetry_instrumentation_sqlalchemy-0.60b1.tar.gz", hash = "sha256:b614e874a7c0a692838a0da613d1654e81a0612867836a1f0765e40e9c8cc49b"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" packaging = ">=21.0" wrapt = ">=1.11.2" @@ -4121,58 +3614,58 @@ instruments = ["sqlalchemy (>=1.0.0,<2.1.0)"] [[package]] name = "opentelemetry-instrumentation-sqlite3" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry SQLite3 instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_sqlite3-0.55b1-py3-none-any.whl", hash = "sha256:1e6f8a715a7761236dc1a7028a2fda0b45e2d44f121ecc4543405db2359e9b8e"}, - {file = "opentelemetry_instrumentation_sqlite3-0.55b1.tar.gz", hash = "sha256:568b184456ce34aa1fafd4e6b756c1df6e70bbe95e9d153f2fb06ecef8c7bd2f"}, + {file = "opentelemetry_instrumentation_sqlite3-0.60b1-py3-none-any.whl", hash = "sha256:7666853b9df186b81e587320aaa03da3f1ce46ba9277b62d8ea20a745886031c"}, + {file = "opentelemetry_instrumentation_sqlite3-0.60b1.tar.gz", hash = "sha256:d716b9d89d31dc426ccedefcdbf96cba1897dfe020d21e5e5ea82a782d03e1d6"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-instrumentation-dbapi = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-instrumentation-dbapi = "0.60b1" [[package]] name = "opentelemetry-instrumentation-starlette" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry Starlette Instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_starlette-0.55b1-py3-none-any.whl", hash = "sha256:59df23e787125b1f243410b038438160f8fe5aec01237d69c057c30f5728ba2f"}, - {file = "opentelemetry_instrumentation_starlette-0.55b1.tar.gz", hash = "sha256:39daf17e523ac602d8c660f8e25a72a8dda70f1617c7d67a818fc4c27812730c"}, + {file = "opentelemetry_instrumentation_starlette-0.60b1-py3-none-any.whl", hash = "sha256:a5bcf8c75da0501b5c6abb1ea53be699be22698229df59c8478be93ae2e486a8"}, + {file = "opentelemetry_instrumentation_starlette-0.60b1.tar.gz", hash = "sha256:282a25339acd8885e64f7dbaf3efb0e4b9f0bde04b9987ba846ba73d50978faa"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-instrumentation-asgi = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" -opentelemetry-util-http = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-instrumentation-asgi = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" +opentelemetry-util-http = "0.60b1" [package.extras] instruments = ["starlette (>=0.13)"] [[package]] name = "opentelemetry-instrumentation-system-metrics" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry System Metrics Instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_system_metrics-0.55b1-py3-none-any.whl", hash = "sha256:0617826df6e5c0369fe877df2442c1aaed6a3012926e2da76014837bc0a7bf91"}, - {file = "opentelemetry_instrumentation_system_metrics-0.55b1.tar.gz", hash = "sha256:68e742eb3d55b793d831b6a30bd09bbadf0098fd7e20ab0afaf24cb5079ffce6"}, + {file = "opentelemetry_instrumentation_system_metrics-0.60b1-py3-none-any.whl", hash = "sha256:21fb040ed6cfabc8ca97c63548fd01689f7ec92c64bbc6cfd08f30489a336fc6"}, + {file = "opentelemetry_instrumentation_system_metrics-0.60b1.tar.gz", hash = "sha256:b9c3a40f31f20c694c386bd28fa0eed5268532bb78f545bbd8e23bbe99d81e09"}, ] [package.dependencies] opentelemetry-api = ">=1.11,<2.0" -opentelemetry-instrumentation = "0.55b1" +opentelemetry-instrumentation = "0.60b1" psutil = ">=5.9.0,<8" [package.extras] @@ -4180,133 +3673,139 @@ instruments = ["psutil (>=5)"] [[package]] name = "opentelemetry-instrumentation-threading" -version = "0.55b1" +version = "0.60b1" description = "Thread context propagation support for OpenTelemetry" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_threading-0.55b1-py3-none-any.whl", hash = "sha256:f865542b32b219c8fd01deb03b8c3c9ba2eb3f0501ae303338403fd2242962c7"}, - {file = "opentelemetry_instrumentation_threading-0.55b1.tar.gz", hash = "sha256:4ed68502e7ed017bfc10b1f9e508cc5ccaea0e46ac1010f7f2541ab9c6eacd92"}, + {file = "opentelemetry_instrumentation_threading-0.60b1-py3-none-any.whl", hash = "sha256:92a52a60fee5e32bc6aa8f5acd749b15691ad0bc4457a310f5736b76a6d9d1de"}, + {file = "opentelemetry_instrumentation_threading-0.60b1.tar.gz", hash = "sha256:20b18a68abe5801fa9474336b7c27487d4af3e00b66f6a8734e4fdd75c8b0b43"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" +opentelemetry-instrumentation = "0.60b1" wrapt = ">=1.0.0,<2.0.0" [[package]] name = "opentelemetry-instrumentation-together" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Together AI instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_together-0.45.6-py3-none-any.whl", hash = "sha256:576360f02267a73b4888d6e2e93a04ae40be07788c9660c9bc3586435a99897a"}, - {file = "opentelemetry_instrumentation_together-0.45.6.tar.gz", hash = "sha256:7e1929cf021ae215e96da96cd094e885233a1843287fc437a2a9587c67e7c974"}, + {file = "opentelemetry_instrumentation_together-0.60.0-py3-none-any.whl", hash = "sha256:c1d0e5b3c61a5845d1db8086491b39bb2fff8df4b802eb644f1bfcbb7b8d99ff"}, + {file = "opentelemetry_instrumentation_together-0.60.0.tar.gz", hash = "sha256:afc97480db972a87b3622c51b927ed244387a139b6f8d6ad34c1b0cae970c0b5"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["together"] [[package]] name = "opentelemetry-instrumentation-tornado" -version = "0.55b1" +version = "0.60b1" description = "Tornado instrumentation for OpenTelemetry" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_tornado-0.55b1-py3-none-any.whl", hash = "sha256:7d406575dfb0c73b0a2f6c755abca7223f6ac789d52ac3efdf1eaa998cedd5a6"}, - {file = "opentelemetry_instrumentation_tornado-0.55b1.tar.gz", hash = "sha256:0c190938eec78cf88f14686b70303aa3624026ee3873d47ae2ca814f7acdb195"}, + {file = "opentelemetry_instrumentation_tornado-0.60b1-py3-none-any.whl", hash = "sha256:81f3c026a28fdd3b9a891ffa348237b169fdb779faef23ff21fc31209dcc47f1"}, + {file = "opentelemetry_instrumentation_tornado-0.60b1.tar.gz", hash = "sha256:3b313a4f2ffd1ca5838b099b564c4831c32c5a395de0e112375ba4899b930028"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" -opentelemetry-util-http = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" +opentelemetry-util-http = "0.60b1" [package.extras] instruments = ["tornado (>=5.1.1)"] [[package]] name = "opentelemetry-instrumentation-tortoiseorm" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry Instrumentation for Tortoise ORM" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_tortoiseorm-0.55b1-py3-none-any.whl", hash = "sha256:122641ed76e7a26a9c80c1fe3e0d58710a75f4816a30a8e357d26c121fd683a9"}, - {file = "opentelemetry_instrumentation_tortoiseorm-0.55b1.tar.gz", hash = "sha256:45d6cbabe2123c258a0d59da79c4e22e2f1b2fbbfae681d61400a1b942cd7b41"}, + {file = "opentelemetry_instrumentation_tortoiseorm-0.60b1-py3-none-any.whl", hash = "sha256:3814196c81f934f20da13eeafca4ecaa168ea951ebdfe2bddb7b8d9a9574739e"}, + {file = "opentelemetry_instrumentation_tortoiseorm-0.60b1.tar.gz", hash = "sha256:adee9a0ffbba654cad92ba6a185facdc4a254e52abd5f22db025ddd6c173af45"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" [package.extras] instruments = ["pydantic (>=1.10.2)", "tortoise-orm (>=0.17.0)"] [[package]] name = "opentelemetry-instrumentation-transformers" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry transformers instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_transformers-0.45.6-py3-none-any.whl", hash = "sha256:277015f41cc301868f1a9d2bd5194487d65a0a00a33abb2a1660c38f57ef0e06"}, - {file = "opentelemetry_instrumentation_transformers-0.45.6.tar.gz", hash = "sha256:c8eee05c27334a83747bd20db2d369720ca0a2581417176ac0ce63a2d89b0ae9"}, + {file = "opentelemetry_instrumentation_transformers-0.60.0-py3-none-any.whl", hash = "sha256:5ace061b0cd0f86ab1ca97a95ac1fa485c8c489f4524e05f5f4fee14eb3159b7"}, + {file = "opentelemetry_instrumentation_transformers-0.60.0.tar.gz", hash = "sha256:6d9132570003bf9ea6fd29327de8c1e43cb4f5b4a78016288acfc7046f27a9db"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["transformers"] [[package]] name = "opentelemetry-instrumentation-urllib" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry urllib instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_urllib-0.55b1-py3-none-any.whl", hash = "sha256:b0d3374ab830af55257d6276e50389fc97501e59a0b35e22b3f221135c26c3bc"}, - {file = "opentelemetry_instrumentation_urllib-0.55b1.tar.gz", hash = "sha256:9961b6035cc68df048e703089de14da4e05e6f123a1f3d61c9341a2a75123a8d"}, + {file = "opentelemetry_instrumentation_urllib-0.60b1-py3-none-any.whl", hash = "sha256:bf36188d684ca6454b7162492a66749181955011e0cc47a2324cbe66e7f13e81"}, + {file = "opentelemetry_instrumentation_urllib-0.60b1.tar.gz", hash = "sha256:7d6c56e45551bdbf21efc11bd463e10862e8fd04ed4a94b5695325a56440b13e"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" -opentelemetry-util-http = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" +opentelemetry-util-http = "0.60b1" [[package]] name = "opentelemetry-instrumentation-urllib3" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry urllib3 instrumentation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_urllib3-0.55b1-py3-none-any.whl", hash = "sha256:41c4a3a01194a713cd82c2067705f6ebc92652b9de56ac741594ce28afa01e09"}, - {file = "opentelemetry_instrumentation_urllib3-0.55b1.tar.gz", hash = "sha256:2999eb2652c7461ea308ff1b3a61726a695e9df1cc2635b2627017b3a42ee214"}, + {file = "opentelemetry_instrumentation_urllib3-0.60b1-py3-none-any.whl", hash = "sha256:4f17b5d41b25cc1b318260ca32f5321afc65017e4be533b65cd804c52855fdf7"}, + {file = "opentelemetry_instrumentation_urllib3-0.60b1.tar.gz", hash = "sha256:1f01cdde3be155ab181fc4cf3363457ff0901f417ac8a102712ee7b7539c9f39"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" -opentelemetry-util-http = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" +opentelemetry-util-http = "0.60b1" wrapt = ">=1.0.0,<2.0.0" [package.extras] @@ -4314,75 +3813,126 @@ instruments = ["urllib3 (>=1.0.0,<3.0.0)"] [[package]] name = "opentelemetry-instrumentation-vertexai" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Vertex AI instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" +groups = ["main"] +files = [ + {file = "opentelemetry_instrumentation_vertexai-0.60.0-py3-none-any.whl", hash = "sha256:aff18245f72d73070d0649f1e1bd9b9c38f07bffd2cab887f29fdcff3d60b404"}, + {file = "opentelemetry_instrumentation_vertexai-0.60.0.tar.gz", hash = "sha256:91e002f9bb7c9bcc01ba81d207add615772cb01f7ff90dc9a01540340c3f7976"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["google-cloud-aiplatform"] + +[[package]] +name = "opentelemetry-instrumentation-voyageai" +version = "0.60.0" +description = "OpenTelemetry Voyage AI instrumentation" +optional = false +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_vertexai-0.45.6-py3-none-any.whl", hash = "sha256:6920cc9a4d17110dfdecc6fec639e9638e57b232c2b8176458535d444492059e"}, - {file = "opentelemetry_instrumentation_vertexai-0.45.6.tar.gz", hash = "sha256:e8a55d05f90866d802adf5940da570d8c7eb25b47d1c85682f7695732b3e619c"}, + {file = "opentelemetry_instrumentation_voyageai-0.60.0-py3-none-any.whl", hash = "sha256:b44549bae92933e50baa81fe8c70574637de49d4079300900fb7af657b734f83"}, + {file = "opentelemetry_instrumentation_voyageai-0.60.0.tar.gz", hash = "sha256:733c526f0bbaedf8d9a1d47c19aa0f1fe8e7ea5cab8c1baac55198b6b052a896"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["voyageai"] [[package]] name = "opentelemetry-instrumentation-watsonx" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry IBM Watsonx Instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_watsonx-0.45.6-py3-none-any.whl", hash = "sha256:12b33d2c47c55b0fb174d4838ead88338bb1d165107ed7d1a4c514f7add459e3"}, - {file = "opentelemetry_instrumentation_watsonx-0.45.6.tar.gz", hash = "sha256:00570f9cc570261cb69f4f2a5bb6af4413bdf05ee9bff498ef00a009df39d435"}, + {file = "opentelemetry_instrumentation_watsonx-0.60.0-py3-none-any.whl", hash = "sha256:b9f6ca09dd6ca94904138b90ca02f1e51641e6a8bba0a84559975a20ecc510ba"}, + {file = "opentelemetry_instrumentation_watsonx-0.60.0.tar.gz", hash = "sha256:5cb450c93a336105b01146d6eae49a91768749149e3a94b4f8af7987c5fb2a5c"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["ibm-watson-machine-learning"] [[package]] name = "opentelemetry-instrumentation-weaviate" -version = "0.45.6" +version = "0.60.0" description = "OpenTelemetry Weaviate instrumentation" optional = false -python-versions = "<4,>=3.9" +python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_weaviate-0.45.6-py3-none-any.whl", hash = "sha256:300bebf8195da5b1398afc623704310f3a31ca359addd150d551dedf3cd4c931"}, - {file = "opentelemetry_instrumentation_weaviate-0.45.6.tar.gz", hash = "sha256:e5967c3c6d9d45a8c08c812bd8170fbb3d754dce1cb4b52893cafb5ebe0d3870"}, + {file = "opentelemetry_instrumentation_weaviate-0.60.0-py3-none-any.whl", hash = "sha256:37d6ec4eab5883fdc3e8a3c9703ad374b4723bbdb42b3c34f018465ee62242fd"}, + {file = "opentelemetry_instrumentation_weaviate-0.60.0.tar.gz", hash = "sha256:f6f44bd7e27e670fab83127d3ec99bca7d31ec2c0234622749eeb13e6e21f76b"}, ] [package.dependencies] -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation = ">=0.50b0" -opentelemetry-semantic-conventions = ">=0.50b0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["weaviate-client"] + +[[package]] +name = "opentelemetry-instrumentation-writer" +version = "0.60.0" +description = "OpenTelemetry Writer instrumentation" +optional = false +python-versions = "<4,>=3.10" +groups = ["main"] +files = [ + {file = "opentelemetry_instrumentation_writer-0.60.0-py3-none-any.whl", hash = "sha256:cfd1912e04bcc15b95d7855a659ad235f00467df80ec807c6d7a3d2044c1d943"}, + {file = "opentelemetry_instrumentation_writer-0.60.0.tar.gz", hash = "sha256:6dd5d5cd13c7faf81b8a0fdb0338f39f87af45115b9730d46a0261ee8bc91434"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-instrumentation = ">=0.59b0" +opentelemetry-semantic-conventions = ">=0.59b0" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" + +[package.extras] +instruments = ["writer"] [[package]] name = "opentelemetry-instrumentation-wsgi" -version = "0.55b1" +version = "0.60b1" description = "WSGI Middleware for OpenTelemetry" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation_wsgi-0.55b1-py3-none-any.whl", hash = "sha256:7653bf944ec2078264f550d50a3e07f09193ca67fe56b9b53407a39cdb7e1231"}, - {file = "opentelemetry_instrumentation_wsgi-0.55b1.tar.gz", hash = "sha256:a1a1ba188da720603c7ddbd470e446d994f28b433170968bd0394a3d8d4627ae"}, + {file = "opentelemetry_instrumentation_wsgi-0.60b1-py3-none-any.whl", hash = "sha256:5e7b432778ebf5a39af136227884a6ab2efc3c4e73e2dbb1d05ecf03ea196705"}, + {file = "opentelemetry_instrumentation_wsgi-0.60b1.tar.gz", hash = "sha256:eb553eec7ebfcf2945cc10d787a265e7abadb9ed1d1ebce8b13988d44fa0cf45"}, ] [package.dependencies] opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.55b1" -opentelemetry-semantic-conventions = "0.55b1" -opentelemetry-util-http = "0.55b1" +opentelemetry-instrumentation = "0.60b1" +opentelemetry-semantic-conventions = "0.60b1" +opentelemetry-util-http = "0.60b1" [[package]] name = "opentelemetry-propagator-aws-xray" @@ -4401,185 +3951,78 @@ opentelemetry-api = ">=1.12,<2.0" [[package]] name = "opentelemetry-proto" -version = "1.34.1" +version = "1.39.1" description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_proto-1.34.1-py3-none-any.whl", hash = "sha256:eb4bb5ac27f2562df2d6857fc557b3a481b5e298bc04f94cc68041f00cebcbd2"}, - {file = "opentelemetry_proto-1.34.1.tar.gz", hash = "sha256:16286214e405c211fc774187f3e4bbb1351290b8dfb88e8948af209ce85b719e"}, + {file = "opentelemetry_proto-1.39.1-py3-none-any.whl", hash = "sha256:22cdc78efd3b3765d09e68bfbd010d4fc254c9818afd0b6b423387d9dee46007"}, + {file = "opentelemetry_proto-1.39.1.tar.gz", hash = "sha256:6c8e05144fc0d3ed4d22c2289c6b126e03bcd0e6a7da0f16cedd2e1c2772e2c8"}, ] [package.dependencies] -protobuf = ">=5.0,<6.0" +protobuf = ">=5.0,<7.0" [[package]] name = "opentelemetry-sdk" -version = "1.34.1" +version = "1.39.1" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_sdk-1.34.1-py3-none-any.whl", hash = "sha256:308effad4059562f1d92163c61c8141df649da24ce361827812c40abb2a1e96e"}, - {file = "opentelemetry_sdk-1.34.1.tar.gz", hash = "sha256:8091db0d763fcd6098d4781bbc80ff0971f94e260739aa6afe6fd379cdf3aa4d"}, + {file = "opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c"}, + {file = "opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6"}, ] [package.dependencies] -opentelemetry-api = "1.34.1" -opentelemetry-semantic-conventions = "0.55b1" +opentelemetry-api = "1.39.1" +opentelemetry-semantic-conventions = "0.60b1" typing-extensions = ">=4.5.0" [[package]] name = "opentelemetry-semantic-conventions" -version = "0.55b1" +version = "0.60b1" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_semantic_conventions-0.55b1-py3-none-any.whl", hash = "sha256:5da81dfdf7d52e3d37f8fe88d5e771e191de924cfff5f550ab0b8f7b2409baed"}, - {file = "opentelemetry_semantic_conventions-0.55b1.tar.gz", hash = "sha256:ef95b1f009159c28d7a7849f5cbc71c4c34c845bb514d66adfdf1b3fff3598b3"}, + {file = "opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb"}, + {file = "opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953"}, ] [package.dependencies] -opentelemetry-api = "1.34.1" +opentelemetry-api = "1.39.1" typing-extensions = ">=4.5.0" [[package]] name = "opentelemetry-semantic-conventions-ai" -version = "0.4.13" +version = "0.5.1" description = "OpenTelemetry Semantic Conventions Extension for Large Language Models" optional = false python-versions = "<4,>=3.9" groups = ["main"] files = [ - {file = "opentelemetry_semantic_conventions_ai-0.4.13-py3-none-any.whl", hash = "sha256:883a30a6bb5deaec0d646912b5f9f6dcbb9f6f72557b73d0f2560bf25d13e2d5"}, - {file = "opentelemetry_semantic_conventions_ai-0.4.13.tar.gz", hash = "sha256:94efa9fb4ffac18c45f54a3a338ffeb7eedb7e1bb4d147786e77202e159f0036"}, + {file = "opentelemetry_semantic_conventions_ai-0.5.1-py3-none-any.whl", hash = "sha256:25aeb22bd261543b4898a73824026d96770e5351209c7d07a0b1314762b1f6e4"}, + {file = "opentelemetry_semantic_conventions_ai-0.5.1.tar.gz", hash = "sha256:153906200d8c1d2f8e09bd78dbef526916023de85ac3dab35912bfafb69ff04c"}, ] +[package.dependencies] +opentelemetry-sdk = ">=1.38.0,<2" +opentelemetry-semantic-conventions = ">=0.59b0" + [[package]] name = "opentelemetry-util-http" -version = "0.55b1" +version = "0.60b1" description = "Web util for OpenTelemetry" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "opentelemetry_util_http-0.55b1-py3-none-any.whl", hash = "sha256:e134218df8ff010e111466650e5f019496b29c3b4f1b7de0e8ff8ebeafeebdf4"}, - {file = "opentelemetry_util_http-0.55b1.tar.gz", hash = "sha256:29e119c1f6796cccf5fc2aedb55274435cde5976d0ac3fec3ca20a80118f821e"}, -] - -[[package]] -name = "optuna" -version = "4.7.0" -description = "A hyperparameter optimization framework" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "optuna-4.7.0-py3-none-any.whl", hash = "sha256:e41ec84018cecc10eabf28143573b1f0bde0ba56dba8151631a590ecbebc1186"}, - {file = "optuna-4.7.0.tar.gz", hash = "sha256:d91817e2079825557bd2e97de2e8c9ae260bfc99b32712502aef8a5095b2d2c0"}, -] - -[package.dependencies] -alembic = ">=1.5.0" -colorlog = "*" -numpy = "*" -packaging = ">=20.0" -PyYAML = "*" -sqlalchemy = ">=1.4.2" -tqdm = "*" - -[package.extras] -checking = ["mypy", "mypy_boto3_s3", "ruff", "scipy-stubs ; python_version >= \"3.10\"", "types-PyYAML", "types-redis", "types-setuptools", "types-tqdm", "typing_extensions (>=3.10.0.0)"] -document = ["ase", "cmaes (>=0.12.0)", "fvcore", "kaleido (<0.4)", "lightgbm", "matplotlib (!=3.6.0)", "pandas", "pillow", "plotly (>=4.9.0)", "scikit-learn", "sphinx", "sphinx-copybutton", "sphinx-gallery", "sphinx-notfound-page", "sphinx_rtd_theme (>=1.2.0)", "torch", "torchvision"] -optional = ["boto3", "cmaes (>=0.12.0)", "google-cloud-storage", "greenlet", "grpcio", "matplotlib (!=3.6.0)", "pandas", "plotly (>=4.9.0)", "protobuf (>=5.28.1)", "redis", "scikit-learn (>=0.24.2)", "scipy", "torch"] -test = ["fakeredis[lua]", "greenlet", "grpcio", "kaleido (<0.4)", "moto", "protobuf (>=5.28.1)", "pytest", "pytest-xdist", "scipy (>=1.9.2)", "torch"] - -[[package]] -name = "orjson" -version = "3.11.7" -description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "orjson-3.11.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a02c833f38f36546ba65a452127633afce4cf0dd7296b753d3bb54e55e5c0174"}, - {file = "orjson-3.11.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b63c6e6738d7c3470ad01601e23376aa511e50e1f3931395b9f9c722406d1a67"}, - {file = "orjson-3.11.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:043d3006b7d32c7e233b8cfb1f01c651013ea079e08dcef7189a29abd8befe11"}, - {file = "orjson-3.11.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57036b27ac8a25d81112eb0cc9835cd4833c5b16e1467816adc0015f59e870dc"}, - {file = "orjson-3.11.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:733ae23ada68b804b222c44affed76b39e30806d38660bf1eb200520d259cc16"}, - {file = "orjson-3.11.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5fdfad2093bdd08245f2e204d977facd5f871c88c4a71230d5bcbd0e43bf6222"}, - {file = "orjson-3.11.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cededd6738e1c153530793998e31c05086582b08315db48ab66649768f326baa"}, - {file = "orjson-3.11.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:14f440c7268c8f8633d1b3d443a434bd70cb15686117ea6beff8fdc8f5917a1e"}, - {file = "orjson-3.11.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:3a2479753bbb95b0ebcf7969f562cdb9668e6d12416a35b0dda79febf89cdea2"}, - {file = "orjson-3.11.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:71924496986275a737f38e3f22b4e0878882b3f7a310d2ff4dc96e812789120c"}, - {file = "orjson-3.11.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4a9eefdc70bf8bf9857f0290f973dec534ac84c35cd6a7f4083be43e7170a8f"}, - {file = "orjson-3.11.7-cp310-cp310-win32.whl", hash = "sha256:ae9e0b37a834cef7ce8f99de6498f8fad4a2c0bf6bfc3d02abd8ed56aa15b2de"}, - {file = "orjson-3.11.7-cp310-cp310-win_amd64.whl", hash = "sha256:d772afdb22555f0c58cfc741bdae44180122b3616faa1ecadb595cd526e4c993"}, - {file = "orjson-3.11.7-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9487abc2c2086e7c8eb9a211d2ce8855bae0e92586279d0d27b341d5ad76c85c"}, - {file = "orjson-3.11.7-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:79cacb0b52f6004caf92405a7e1f11e6e2de8bdf9019e4f76b44ba045125cd6b"}, - {file = "orjson-3.11.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2e85fe4698b6a56d5e2ebf7ae87544d668eb6bde1ad1226c13f44663f20ec9e"}, - {file = "orjson-3.11.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b8d14b71c0b12963fe8a62aac87119f1afdf4cb88a400f61ca5ae581449efcb5"}, - {file = "orjson-3.11.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91c81ef070c8f3220054115e1ef468b1c9ce8497b4e526cb9f68ab4dc0a7ac62"}, - {file = "orjson-3.11.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:411ebaf34d735e25e358a6d9e7978954a9c9d58cfb47bc6683cdc3964cd2f910"}, - {file = "orjson-3.11.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a16bcd08ab0bcdfc7e8801d9c4a9cc17e58418e4d48ddc6ded4e9e4b1a94062b"}, - {file = "orjson-3.11.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c0b51672e466fd7e56230ffbae7f1639e18d0ce023351fb75da21b71bc2c960"}, - {file = "orjson-3.11.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:136dcd6a2e796dfd9ffca9fc027d778567b0b7c9968d092842d3c323cef88aa8"}, - {file = "orjson-3.11.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:7ba61079379b0ae29e117db13bda5f28d939766e410d321ec1624afc6a0b0504"}, - {file = "orjson-3.11.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0527a4510c300e3b406591b0ba69b5dc50031895b0a93743526a3fc45f59d26e"}, - {file = "orjson-3.11.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a709e881723c9b18acddcfb8ba357322491ad553e277cf467e1e7e20e2d90561"}, - {file = "orjson-3.11.7-cp311-cp311-win32.whl", hash = "sha256:c43b8b5bab288b6b90dac410cca7e986a4fa747a2e8f94615aea407da706980d"}, - {file = "orjson-3.11.7-cp311-cp311-win_amd64.whl", hash = "sha256:6543001328aa857187f905308a028935864aefe9968af3848401b6fe80dbb471"}, - {file = "orjson-3.11.7-cp311-cp311-win_arm64.whl", hash = "sha256:1ee5cc7160a821dfe14f130bc8e63e7611051f964b463d9e2a3a573204446a4d"}, - {file = "orjson-3.11.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bd03ea7606833655048dab1a00734a2875e3e86c276e1d772b2a02556f0d895f"}, - {file = "orjson-3.11.7-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:89e440ebc74ce8ab5c7bc4ce6757b4a6b1041becb127df818f6997b5c71aa60b"}, - {file = "orjson-3.11.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ede977b5fe5ac91b1dffc0a517ca4542d2ec8a6a4ff7b2652d94f640796342a"}, - {file = "orjson-3.11.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b7b1dae39230a393df353827c855a5f176271c23434cfd2db74e0e424e693e10"}, - {file = "orjson-3.11.7-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed46f17096e28fb28d2975834836a639af7278aa87c84f68ab08fbe5b8bd75fa"}, - {file = "orjson-3.11.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3726be79e36e526e3d9c1aceaadbfb4a04ee80a72ab47b3f3c17fefb9812e7b8"}, - {file = "orjson-3.11.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0724e265bc548af1dedebd9cb3d24b4e1c1e685a343be43e87ba922a5c5fff2f"}, - {file = "orjson-3.11.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7745312efa9e11c17fbd3cb3097262d079da26930ae9ae7ba28fb738367cbad"}, - {file = "orjson-3.11.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f904c24bdeabd4298f7a977ef14ca2a022ca921ed670b92ecd16ab6f3d01f867"}, - {file = "orjson-3.11.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b9fc4d0f81f394689e0814617aadc4f2ea0e8025f38c226cbf22d3b5ddbf025d"}, - {file = "orjson-3.11.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:849e38203e5be40b776ed2718e587faf204d184fc9a008ae441f9442320c0cab"}, - {file = "orjson-3.11.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4682d1db3bcebd2b64757e0ddf9e87ae5f00d29d16c5cdf3a62f561d08cc3dd2"}, - {file = "orjson-3.11.7-cp312-cp312-win32.whl", hash = "sha256:f4f7c956b5215d949a1f65334cf9d7612dde38f20a95f2315deef167def91a6f"}, - {file = "orjson-3.11.7-cp312-cp312-win_amd64.whl", hash = "sha256:bf742e149121dc5648ba0a08ea0871e87b660467ef168a3a5e53bc1fbd64bb74"}, - {file = "orjson-3.11.7-cp312-cp312-win_arm64.whl", hash = "sha256:26c3b9132f783b7d7903bf1efb095fed8d4a3a85ec0d334ee8beff3d7a4749d5"}, - {file = "orjson-3.11.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1d98b30cc1313d52d4af17d9c3d307b08389752ec5f2e5febdfada70b0f8c733"}, - {file = "orjson-3.11.7-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:d897e81f8d0cbd2abb82226d1860ad2e1ab3ff16d7b08c96ca00df9d45409ef4"}, - {file = "orjson-3.11.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:814be4b49b228cfc0b3c565acf642dd7d13538f966e3ccde61f4f55be3e20785"}, - {file = "orjson-3.11.7-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d06e5c5fed5caedd2e540d62e5b1c25e8c82431b9e577c33537e5fa4aa909539"}, - {file = "orjson-3.11.7-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31c80ce534ac4ea3739c5ee751270646cbc46e45aea7576a38ffec040b4029a1"}, - {file = "orjson-3.11.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f50979824bde13d32b4320eedd513431c921102796d86be3eee0b58e58a3ecd1"}, - {file = "orjson-3.11.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e54f3808e2b6b945078c41aa8d9b5834b28c50843846e97807e5adb75fa9705"}, - {file = "orjson-3.11.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12b80df61aab7b98b490fe9e4879925ba666fccdfcd175252ce4d9035865ace"}, - {file = "orjson-3.11.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:996b65230271f1a97026fd0e6a753f51fbc0c335d2ad0c6201f711b0da32693b"}, - {file = "orjson-3.11.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ab49d4b2a6a1d415ddb9f37a21e02e0d5dbfe10b7870b21bf779fc21e9156157"}, - {file = "orjson-3.11.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:390a1dce0c055ddf8adb6aa94a73b45a4a7d7177b5c584b8d1c1947f2ba60fb3"}, - {file = "orjson-3.11.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1eb80451a9c351a71dfaf5b7ccc13ad065405217726b59fdbeadbcc544f9d223"}, - {file = "orjson-3.11.7-cp313-cp313-win32.whl", hash = "sha256:7477aa6a6ec6139c5cb1cc7b214643592169a5494d200397c7fc95d740d5fcf3"}, - {file = "orjson-3.11.7-cp313-cp313-win_amd64.whl", hash = "sha256:b9f95dcdea9d4f805daa9ddf02617a89e484c6985fa03055459f90e87d7a0757"}, - {file = "orjson-3.11.7-cp313-cp313-win_arm64.whl", hash = "sha256:800988273a014a0541483dc81021247d7eacb0c845a9d1a34a422bc718f41539"}, - {file = "orjson-3.11.7-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:de0a37f21d0d364954ad5de1970491d7fbd0fb1ef7417d4d56a36dc01ba0c0a0"}, - {file = "orjson-3.11.7-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:c2428d358d85e8da9d37cba18b8c4047c55222007a84f97156a5b22028dfbfc0"}, - {file = "orjson-3.11.7-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4bc6c6ac52cdaa267552544c73e486fecbd710b7ac09bc024d5a78555a22f6"}, - {file = "orjson-3.11.7-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd0d68edd7dfca1b2eca9361a44ac9f24b078de3481003159929a0573f21a6bf"}, - {file = "orjson-3.11.7-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:623ad1b9548ef63886319c16fa317848e465a21513b31a6ad7b57443c3e0dcf5"}, - {file = "orjson-3.11.7-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6e776b998ac37c0396093d10290e60283f59cfe0fc3fccbd0ccc4bd04dd19892"}, - {file = "orjson-3.11.7-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c6c3af76716f4a9c290371ba2e390ede06f6603edb277b481daf37f6f464e"}, - {file = "orjson-3.11.7-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a56df3239294ea5964adf074c54bcc4f0ccd21636049a2cf3ca9cf03b5d03cf1"}, - {file = "orjson-3.11.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bda117c4148e81f746655d5a3239ae9bd00cb7bc3ca178b5fc5a5997e9744183"}, - {file = "orjson-3.11.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:23d6c20517a97a9daf1d48b580fcdc6f0516c6f4b5038823426033690b4d2650"}, - {file = "orjson-3.11.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8ff206156006da5b847c9304b6308a01e8cdbc8cce824e2779a5ba71c3def141"}, - {file = "orjson-3.11.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:962d046ee1765f74a1da723f4b33e3b228fe3a48bd307acce5021dfefe0e29b2"}, - {file = "orjson-3.11.7-cp314-cp314-win32.whl", hash = "sha256:89e13dd3f89f1c38a9c9eba5fbf7cdc2d1feca82f5f290864b4b7a6aac704576"}, - {file = "orjson-3.11.7-cp314-cp314-win_amd64.whl", hash = "sha256:845c3e0d8ded9c9271cd79596b9b552448b885b97110f628fb687aee2eed11c1"}, - {file = "orjson-3.11.7-cp314-cp314-win_arm64.whl", hash = "sha256:4a2e9c5be347b937a2e0203866f12bba36082e89b402ddb9e927d5822e43088d"}, - {file = "orjson-3.11.7.tar.gz", hash = "sha256:9b1a67243945819ce55d24a30b59d6a168e86220452d2c96f4d1f093e71c0c49"}, + {file = "opentelemetry_util_http-0.60b1-py3-none-any.whl", hash = "sha256:66381ba28550c91bee14dcba8979ace443444af1ed609226634596b4b0faf199"}, + {file = "opentelemetry_util_http-0.60b1.tar.gz", hash = "sha256:0d97152ca8c8a41ced7172d29d3622a219317f74ae6bb3027cfbdcf22c3cc0d6"}, ] [[package]] @@ -4652,32 +4095,6 @@ files = [ dev = ["pre-commit", "tox"] testing = ["coverage", "pytest", "pytest-benchmark"] -[[package]] -name = "posthog" -version = "3.25.0" -description = "Integrate PostHog into any python application." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "posthog-3.25.0-py2.py3-none-any.whl", hash = "sha256:85db78c13d1ecb11aed06fad53759c4e8fb3633442c2f3d0336bc0ce8a585d30"}, - {file = "posthog-3.25.0.tar.gz", hash = "sha256:9168f3e7a0a5571b6b1065c41b3c171fbc68bfe72c3ac0bfd6e3d2fcdb7df2ca"}, -] - -[package.dependencies] -backoff = ">=1.10.0" -distro = ">=1.5.0" -monotonic = ">=1.5" -python-dateutil = ">2.1" -requests = ">=2.7,<3.0" -six = ">=1.5" - -[package.extras] -dev = ["black", "django-stubs", "flake8", "flake8-print", "isort", "lxml", "mypy", "mypy-baseline", "pre-commit", "pydantic", "types-mock", "types-python-dateutil", "types-requests", "types-setuptools", "types-six"] -langchain = ["langchain (>=0.2.0)"] -sentry = ["django", "sentry-sdk"] -test = ["anthropic", "coverage", "django", "flake8", "freezegun (==1.5.1)", "langchain-anthropic (>=0.2.0)", "langchain-community (>=0.2.0)", "langchain-openai (>=0.2.0)", "langgraph", "mock (>=2.0.0)", "openai", "parameterized (>=0.8.1)", "pydantic", "pylint", "pytest", "pytest-asyncio", "pytest-timeout"] - [[package]] name = "pre-commit" version = "4.2.0" @@ -5142,6 +4559,7 @@ files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, ] +markers = {main = "extra == \"presidio\""} [package.extras] windows-terminal = ["colorama (>=0.4.6)"] @@ -5170,36 +4588,6 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-dotenv" -version = "1.2.1" -description = "Read key-value pairs from a .env file and set them as environment variables" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61"}, - {file = "python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6"}, -] - -[package.extras] -cli = ["click (>=5.0)"] - [[package]] name = "pyyaml" version = "6.0.2" @@ -5262,6 +4650,7 @@ files = [ {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] +markers = {main = "extra == \"presidio\""} [[package]] name = "questionary" @@ -5278,30 +4667,14 @@ files = [ [package.dependencies] prompt_toolkit = ">=2.0,<4.0" -[[package]] -name = "referencing" -version = "0.37.0" -description = "JSON Referencing + Python" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, - {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, -] - -[package.dependencies] -attrs = ">=22.2.0" -rpds-py = ">=0.7.0" -typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} - [[package]] name = "regex" version = "2024.11.6" description = "Alternative regular expression module, to replace re." -optional = false +optional = true python-versions = ">=3.8" groups = ["main"] +markers = "extra == \"presidio\"" files = [ {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, @@ -5441,9 +4814,10 @@ requests = ">=1.0.0" name = "rich" version = "14.0.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false +optional = true python-versions = ">=3.8.0" groups = ["main"] +markers = "extra == \"presidio\"" files = [ {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"}, {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"}, @@ -5457,131 +4831,6 @@ typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.1 [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] -[[package]] -name = "rpds-py" -version = "0.30.0" -description = "Python bindings to Rust's persistent data structures (rpds)" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288"}, - {file = "rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7"}, - {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff"}, - {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7"}, - {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139"}, - {file = "rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464"}, - {file = "rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169"}, - {file = "rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425"}, - {file = "rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038"}, - {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7"}, - {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed"}, - {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85"}, - {file = "rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c"}, - {file = "rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825"}, - {file = "rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229"}, - {file = "rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad"}, - {file = "rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51"}, - {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5"}, - {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e"}, - {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394"}, - {file = "rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf"}, - {file = "rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b"}, - {file = "rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e"}, - {file = "rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2"}, - {file = "rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d"}, - {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7"}, - {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31"}, - {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95"}, - {file = "rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d"}, - {file = "rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15"}, - {file = "rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1"}, - {file = "rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a"}, - {file = "rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0"}, - {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94"}, - {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08"}, - {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27"}, - {file = "rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6"}, - {file = "rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d"}, - {file = "rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0"}, - {file = "rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f"}, - {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65"}, - {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f"}, - {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53"}, - {file = "rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed"}, - {file = "rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950"}, - {file = "rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6"}, - {file = "rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb"}, - {file = "rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5"}, - {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404"}, - {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856"}, - {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40"}, - {file = "rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0"}, - {file = "rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e"}, - {file = "rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84"}, -] - [[package]] name = "safetensors" version = "0.5.3" @@ -5656,18 +4905,6 @@ files = [ {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, ] -[[package]] -name = "six" -version = "1.17.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] -files = [ - {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, - {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, -] - [[package]] name = "smart-open" version = "7.3.0.post1" @@ -5695,18 +4932,6 @@ test = ["awscli", "moto[server]", "numpy", "pyopenssl", "pytest", "pytest-rerunf webhdfs = ["requests"] zst = ["zstandard"] -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - [[package]] name = "spacy" version = "3.8.7" @@ -5828,108 +5053,6 @@ files = [ {file = "spacy_loggers-1.0.5-py3-none-any.whl", hash = "sha256:196284c9c446cc0cdb944005384270d775fdeaf4f494d8e269466cfa497ef645"}, ] -[[package]] -name = "sqlalchemy" -version = "2.0.47" -description = "Database Abstraction Library" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "sqlalchemy-2.0.47-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33a917ede39406ddb93c3e642b5bc480be7c5fd0f3d0d6ae1036d466fb963f1a"}, - {file = "sqlalchemy-2.0.47-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:561d027c829b01e040bdade6b6f5b429249d056ef95d7bdcb9211539ecc82803"}, - {file = "sqlalchemy-2.0.47-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fa5072a37e68c565363c009b7afa5b199b488c87940ec02719860093a08f34ca"}, - {file = "sqlalchemy-2.0.47-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1e7ed17dd4312a298b6024bfd1baf51654bc49e3f03c798005babf0c7922d6a7"}, - {file = "sqlalchemy-2.0.47-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6992e353fcb0593eb42d95ad84b3e58fe40b5e37fd332b9ccba28f4b2f36d1fc"}, - {file = "sqlalchemy-2.0.47-cp310-cp310-win32.whl", hash = "sha256:05a6d58ed99ebd01303c92d29a0c9cbf70f637b3ddd155f5172c5a7239940998"}, - {file = "sqlalchemy-2.0.47-cp310-cp310-win_amd64.whl", hash = "sha256:4a7aa4a584cc97e268c11e700dea0b763874eaebb435e75e7d0ffee5d90f5030"}, - {file = "sqlalchemy-2.0.47-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a1dbf0913879c443617d6b64403cf2801c941651db8c60e96d204ed9388d6b0"}, - {file = "sqlalchemy-2.0.47-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:775effbb97ea3b00c4dd3aeaf3ba8acba6e3e2b4b41d17d67a27e696843dbc95"}, - {file = "sqlalchemy-2.0.47-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56cc834a3ffac34270cc2a41875e0f40e97aa651f4f3ca1cfbbf421c044cb62b"}, - {file = "sqlalchemy-2.0.47-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49b5e0c7244262f39e767c018e4fdb5e5dbc23cd54c5ddac8eea8f0ba32ef890"}, - {file = "sqlalchemy-2.0.47-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15cd822a3f1f6f77b5b841a30c1a07a07f7dee3385f17e638e1722de9ab683be"}, - {file = "sqlalchemy-2.0.47-cp311-cp311-win32.whl", hash = "sha256:9847a19548cd283a65e1ce0afd54016598d55ff72682d6fd3e493af6fc044064"}, - {file = "sqlalchemy-2.0.47-cp311-cp311-win_amd64.whl", hash = "sha256:722abf1c82aeca46a1a0803711244a48a298279eeaec9e02f7bfee9e064182e5"}, - {file = "sqlalchemy-2.0.47-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fa91b19d6b9821c04cc8f7aa2476429cc8887b9687c762815aa629f5c0edec1"}, - {file = "sqlalchemy-2.0.47-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c5bbbd14eff577c8c79cbfe39a0771eecd20f430f3678533476f0087138f356"}, - {file = "sqlalchemy-2.0.47-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5a6c555da8d4280a3c4c78c5b7a3f990cee2b2884e5f934f87a226191682ff7"}, - {file = "sqlalchemy-2.0.47-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ed48a1701d24dff3bb49a5bce94d6bc84cbe33d98af2aa2d3cdcce3dea1709ec"}, - {file = "sqlalchemy-2.0.47-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f3178c920ad98158f0b6309382194df04b14808fa6052ae07099fdde29d5602"}, - {file = "sqlalchemy-2.0.47-cp312-cp312-win32.whl", hash = "sha256:b9c11ac9934dd59ece9619fe42780a08abe2faab7b0543bb00d5eabea4f421b9"}, - {file = "sqlalchemy-2.0.47-cp312-cp312-win_amd64.whl", hash = "sha256:db43b72cf8274a99e089755c9c1e0b947159b71adbc2c83c3de2e38d5d607acb"}, - {file = "sqlalchemy-2.0.47-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:456a135b790da5d3c6b53d0ef71ac7b7d280b7f41eb0c438986352bf03ca7143"}, - {file = "sqlalchemy-2.0.47-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09a2f7698e44b3135433387da5d8846cf7cc7c10e5425af7c05fee609df978b6"}, - {file = "sqlalchemy-2.0.47-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bbc72e6a177c78d724f9106aaddc0d26a2ada89c6332b5935414eccf04cbd5"}, - {file = "sqlalchemy-2.0.47-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:75460456b043b78b6006e41bdf5b86747ee42eafaf7fffa3b24a6e9a456a2092"}, - {file = "sqlalchemy-2.0.47-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d9adaa616c3bc7d80f9ded57cd84b51d6617cad6a5456621d858c9f23aaee01"}, - {file = "sqlalchemy-2.0.47-cp313-cp313-win32.whl", hash = "sha256:76e09f974382a496a5ed985db9343628b1cb1ac911f27342e4cc46a8bac10476"}, - {file = "sqlalchemy-2.0.47-cp313-cp313-win_amd64.whl", hash = "sha256:0664089b0bf6724a0bfb49a0cf4d4da24868a0a5c8e937cd7db356d5dcdf2c66"}, - {file = "sqlalchemy-2.0.47-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed0c967c701ae13da98eb220f9ddab3044ab63504c1ba24ad6a59b26826ad003"}, - {file = "sqlalchemy-2.0.47-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3537943a61fd25b241e976426a0c6814434b93cf9b09d39e8e78f3c9eb9a487"}, - {file = "sqlalchemy-2.0.47-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:57f7e336a64a0dba686c66392d46b9bc7af2c57d55ce6dc1697b4ef32b043ceb"}, - {file = "sqlalchemy-2.0.47-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dff735a621858680217cb5142b779bad40ef7322ddbb7c12062190db6879772e"}, - {file = "sqlalchemy-2.0.47-cp313-cp313t-win32.whl", hash = "sha256:3893dc096bb3cca9608ea3487372ffcea3ae9b162f40e4d3c51dd49db1d1b2dc"}, - {file = "sqlalchemy-2.0.47-cp313-cp313t-win_amd64.whl", hash = "sha256:b5103427466f4b3e61f04833ae01f9a914b1280a2a8bcde3a9d7ab11f3755b42"}, - {file = "sqlalchemy-2.0.47-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b03010a5a5dfe71676bc83f2473ebe082478e32d77e6f082c8fe15a31c3b42a6"}, - {file = "sqlalchemy-2.0.47-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8e3371aa9024520883a415a09cc20c33cfd3eeccf9e0f4f4c367f940b9cbd44"}, - {file = "sqlalchemy-2.0.47-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9449f747e50d518c6e1b40cc379e48bfc796453c47b15e627ea901c201e48a6"}, - {file = "sqlalchemy-2.0.47-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:21410f60d5cac1d6bfe360e05bd91b179be4fa0aa6eea6be46054971d277608f"}, - {file = "sqlalchemy-2.0.47-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:819841dd5bb4324c284c09e2874cf96fe6338bfb57a64548d9b81a4e39c9871f"}, - {file = "sqlalchemy-2.0.47-cp314-cp314-win32.whl", hash = "sha256:e255ee44821a7ef45649c43064cf94e74f81f61b4df70547304b97a351e9b7db"}, - {file = "sqlalchemy-2.0.47-cp314-cp314-win_amd64.whl", hash = "sha256:209467ff73ea1518fe1a5aaed9ba75bb9e33b2666e2553af9ccd13387bf192cb"}, - {file = "sqlalchemy-2.0.47-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e78fd9186946afaa287f8a1fe147ead06e5d566b08c0afcb601226e9c7322a64"}, - {file = "sqlalchemy-2.0.47-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5740e2f31b5987ed9619d6912ae5b750c03637f2078850da3002934c9532f172"}, - {file = "sqlalchemy-2.0.47-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb9ac00d03de93acb210e8ec7243fefe3e012515bf5fd2f0898c8dff38bc77a4"}, - {file = "sqlalchemy-2.0.47-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c72a0b9eb2672d70d112cb149fbaf172d466bc691014c496aaac594f1988e706"}, - {file = "sqlalchemy-2.0.47-cp314-cp314t-win32.whl", hash = "sha256:c200db1128d72a71dc3c31c24b42eb9fd85b2b3e5a3c9ba1e751c11ac31250ff"}, - {file = "sqlalchemy-2.0.47-cp314-cp314t-win_amd64.whl", hash = "sha256:669837759b84e575407355dcff912835892058aea9b80bd1cb76d6a151cf37f7"}, - {file = "sqlalchemy-2.0.47-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fe3f8519a52ca5032015780de3fc4e6ab42c6e0bcf9d807143a3d17b3350d546"}, - {file = "sqlalchemy-2.0.47-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6acfc1f95ed0369e0c4100d98870c9c4bfd56818ddc825357a0a979d5973195"}, - {file = "sqlalchemy-2.0.47-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f530b939eca775f6f77fa359a3e7039209a96958c1aa28c1b796f600e0fee7cd"}, - {file = "sqlalchemy-2.0.47-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0a6fdf665268dfe0ba52fb2d8d62deee96b297d460e2797bdd52d2d1941dd8cd"}, - {file = "sqlalchemy-2.0.47-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:25b3c189dab94dedb6db9d4e06476ce955182e7f45412b096ae9033519e33ce8"}, - {file = "sqlalchemy-2.0.47-cp38-cp38-win32.whl", hash = "sha256:a8f991cac31b673aff1648cafb8b10022719e5a632bbadaa9c5d41511bd507a5"}, - {file = "sqlalchemy-2.0.47-cp38-cp38-win_amd64.whl", hash = "sha256:52be08b31f70bed2ed05c5c4b8237cf361a8581f32a5e89f9dfc295f795db10f"}, - {file = "sqlalchemy-2.0.47-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d7477190e90852c00bf654b70ae21e5b85b5ac4d09094cf82e84eb3abdb6c5a7"}, - {file = "sqlalchemy-2.0.47-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb0b3e4946bf05d68673a1857db1a16bd58207c83ebc4ed5732a6e60029bac2d"}, - {file = "sqlalchemy-2.0.47-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bc111a5b98881b7e1ab108921f2fcc09fa06abbd98f4f0ed6cb2c23e70cdd23"}, - {file = "sqlalchemy-2.0.47-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:97bf49caf4e405c18f3b9f695751c5bf14a9d8899c6e54eaeb49dda0d4fa009d"}, - {file = "sqlalchemy-2.0.47-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d2fa029051d3db77ad79a55c3ddf005bd7c038a111af0db9b56158857702aef6"}, - {file = "sqlalchemy-2.0.47-cp39-cp39-win32.whl", hash = "sha256:6e547682d508d141de942484b9976cbee91b7a50739d4ee25b3d0a62dd71a954"}, - {file = "sqlalchemy-2.0.47-cp39-cp39-win_amd64.whl", hash = "sha256:bb833131169444c87160aa95fcdd22ae86d0fa4ef174d36b3dfb9be363b4e574"}, - {file = "sqlalchemy-2.0.47-py3-none-any.whl", hash = "sha256:e2647043599297a1ef10e720cf310846b7f31b6c841fee093d2b09d81215eb93"}, - {file = "sqlalchemy-2.0.47.tar.gz", hash = "sha256:e3e7feb57b267fe897e492b9721ae46d5c7de6f9e8dee58aacf105dc4e154f3d"}, -] - -[package.dependencies] -greenlet = {version = ">=1", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} -typing-extensions = ">=4.6.0" - -[package.extras] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (>=1)"] -aioodbc = ["aioodbc", "greenlet (>=1)"] -aiosqlite = ["aiosqlite", "greenlet (>=1)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (>=1)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (>=1)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=8)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (>=1)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3_binary"] - [[package]] name = "srsly" version = "2.5.1" @@ -6148,80 +5271,6 @@ mxnet = ["mxnet (>=1.5.1,<1.6.0)"] tensorflow = ["tensorflow (>=2.0.0,<2.6.0)"] torch = ["torch (>=1.6.0)"] -[[package]] -name = "tiktoken" -version = "0.12.0" -description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "tiktoken-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3de02f5a491cfd179aec916eddb70331814bd6bf764075d39e21d5862e533970"}, - {file = "tiktoken-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6cfb6d9b7b54d20af21a912bfe63a2727d9cfa8fbda642fd8322c70340aad16"}, - {file = "tiktoken-0.12.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:cde24cdb1b8a08368f709124f15b36ab5524aac5fa830cc3fdce9c03d4fb8030"}, - {file = "tiktoken-0.12.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6de0da39f605992649b9cfa6f84071e3f9ef2cec458d08c5feb1b6f0ff62e134"}, - {file = "tiktoken-0.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6faa0534e0eefbcafaccb75927a4a380463a2eaa7e26000f0173b920e98b720a"}, - {file = "tiktoken-0.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:82991e04fc860afb933efb63957affc7ad54f83e2216fe7d319007dab1ba5892"}, - {file = "tiktoken-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:6fb2995b487c2e31acf0a9e17647e3b242235a20832642bb7a9d1a181c0c1bb1"}, - {file = "tiktoken-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e227c7f96925003487c33b1b32265fad2fbcec2b7cf4817afb76d416f40f6bb"}, - {file = "tiktoken-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c06cf0fcc24c2cb2adb5e185c7082a82cba29c17575e828518c2f11a01f445aa"}, - {file = "tiktoken-0.12.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f18f249b041851954217e9fd8e5c00b024ab2315ffda5ed77665a05fa91f42dc"}, - {file = "tiktoken-0.12.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:47a5bc270b8c3db00bb46ece01ef34ad050e364b51d406b6f9730b64ac28eded"}, - {file = "tiktoken-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:508fa71810c0efdcd1b898fda574889ee62852989f7c1667414736bcb2b9a4bd"}, - {file = "tiktoken-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1af81a6c44f008cba48494089dd98cccb8b313f55e961a52f5b222d1e507967"}, - {file = "tiktoken-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e68e3e593637b53e56f7237be560f7a394451cb8c11079755e80ae64b9e6def"}, - {file = "tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8"}, - {file = "tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b"}, - {file = "tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37"}, - {file = "tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad"}, - {file = "tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5"}, - {file = "tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3"}, - {file = "tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd"}, - {file = "tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3"}, - {file = "tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160"}, - {file = "tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa"}, - {file = "tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be"}, - {file = "tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a"}, - {file = "tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3"}, - {file = "tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697"}, - {file = "tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16"}, - {file = "tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a"}, - {file = "tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27"}, - {file = "tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb"}, - {file = "tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e"}, - {file = "tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25"}, - {file = "tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f"}, - {file = "tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646"}, - {file = "tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88"}, - {file = "tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff"}, - {file = "tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830"}, - {file = "tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b"}, - {file = "tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b"}, - {file = "tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3"}, - {file = "tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365"}, - {file = "tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e"}, - {file = "tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63"}, - {file = "tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0"}, - {file = "tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a"}, - {file = "tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0"}, - {file = "tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71"}, - {file = "tiktoken-0.12.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:d51d75a5bffbf26f86554d28e78bfb921eae998edc2675650fd04c7e1f0cdc1e"}, - {file = "tiktoken-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:09eb4eae62ae7e4c62364d9ec3a57c62eea707ac9a2b2c5d6bd05de6724ea179"}, - {file = "tiktoken-0.12.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:df37684ace87d10895acb44b7f447d4700349b12197a526da0d4a4149fde074c"}, - {file = "tiktoken-0.12.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:4c9614597ac94bb294544345ad8cf30dac2129c05e2db8dc53e082f355857af7"}, - {file = "tiktoken-0.12.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:20cf97135c9a50de0b157879c3c4accbb29116bcf001283d26e073ff3b345946"}, - {file = "tiktoken-0.12.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:15d875454bbaa3728be39880ddd11a5a2a9e548c29418b41e8fd8a767172b5ec"}, - {file = "tiktoken-0.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cff3688ba3c639ebe816f8d58ffbbb0aa7433e23e08ab1cade5d175fc973fb3"}, - {file = "tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931"}, -] - -[package.dependencies] -regex = ">=2022.1.18" -requests = ">=2.26.0" - -[package.extras] -blobfile = ["blobfile (>=2)"] - [[package]] name = "tldextract" version = "5.3.0" @@ -6249,9 +5298,10 @@ testing = ["mypy", "pytest", "pytest-gitignore", "pytest-mock", "responses", "ru name = "tokenizers" version = "0.21.2" description = "" -optional = false +optional = true python-versions = ">=3.9" groups = ["main"] +markers = "extra == \"presidio\"" files = [ {file = "tokenizers-0.21.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:342b5dfb75009f2255ab8dec0041287260fed5ce00c323eb6bab639066fef8ec"}, {file = "tokenizers-0.21.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:126df3205d6f3a93fea80c7a8a266a78c1bd8dd2fe043386bafdd7736a23e45f"}, @@ -6285,7 +5335,6 @@ description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["main", "dev"] -markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -6320,6 +5369,7 @@ files = [ {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] +markers = {main = "extra == \"presidio\" and python_version < \"3.11\"", dev = "python_version < \"3.11\""} [[package]] name = "tomlkit" @@ -6400,9 +5450,10 @@ optree = ["optree (>=0.13.0)"] name = "tqdm" version = "4.67.1" description = "Fast, Extensible Progress Meter" -optional = false +optional = true python-versions = ">=3.7" groups = ["main"] +markers = "extra == \"presidio\"" files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, @@ -6420,64 +5471,70 @@ telegram = ["requests"] [[package]] name = "traceloop-sdk" -version = "0.45.6" +version = "0.60.0" description = "Traceloop Software Development Kit (SDK) for Python" optional = false python-versions = "<4,>=3.10" groups = ["main"] files = [ - {file = "traceloop_sdk-0.45.6-py3-none-any.whl", hash = "sha256:e7cd2e15915e9955d53276f926c78e0b5ad2f931221b3ed3af655e08ccc18d0e"}, - {file = "traceloop_sdk-0.45.6.tar.gz", hash = "sha256:e7cdce57f413d0905f43ec5bab819264a3d927dd2974adad8d2f6786fa4af2e6"}, + {file = "traceloop_sdk-0.60.0-py3-none-any.whl", hash = "sha256:8d242feee537a783331211e0fbaa92cbd2ef4c08ac1dd8e8eecb4e2ef414ddba"}, + {file = "traceloop_sdk-0.60.0.tar.gz", hash = "sha256:f7a36307b38132aa5185b8bdc985a6f1d16b2b969705249612223e6524741e5c"}, ] [package.dependencies] -aiohttp = ">=3.11.11,<4.0.0" +aiohttp = ">=3.11.11,<4" colorama = ">=0.4.6,<0.5.0" -deprecated = ">=1.2.14,<2.0.0" -jinja2 = ">=3.1.5,<4.0.0" -opentelemetry-api = ">=1.28.0,<2.0.0" -opentelemetry-exporter-otlp-proto-grpc = ">=1.28.0,<2.0.0" -opentelemetry-exporter-otlp-proto-http = ">=1.28.0,<2.0.0" -opentelemetry-instrumentation-alephalpha = "0.45.6" -opentelemetry-instrumentation-anthropic = "0.45.6" -opentelemetry-instrumentation-bedrock = "0.45.6" -opentelemetry-instrumentation-chromadb = "0.45.6" -opentelemetry-instrumentation-cohere = "0.45.6" -opentelemetry-instrumentation-crewai = "0.45.6" -opentelemetry-instrumentation-google-generativeai = "0.45.6" -opentelemetry-instrumentation-groq = "0.45.6" -opentelemetry-instrumentation-haystack = "0.45.6" -opentelemetry-instrumentation-lancedb = "0.45.6" -opentelemetry-instrumentation-langchain = "0.45.6" -opentelemetry-instrumentation-llamaindex = "0.45.6" -opentelemetry-instrumentation-logging = ">=0.50b0" -opentelemetry-instrumentation-marqo = "0.45.6" -opentelemetry-instrumentation-mcp = "0.45.6" -opentelemetry-instrumentation-milvus = "0.45.6" -opentelemetry-instrumentation-mistralai = "0.45.6" -opentelemetry-instrumentation-ollama = "0.45.6" -opentelemetry-instrumentation-openai = "0.45.6" -opentelemetry-instrumentation-openai-agents = "0.45.6" -opentelemetry-instrumentation-pinecone = "0.45.6" -opentelemetry-instrumentation-qdrant = "0.45.6" -opentelemetry-instrumentation-redis = ">=0.50b0" -opentelemetry-instrumentation-replicate = "0.45.6" -opentelemetry-instrumentation-requests = ">=0.50b0" -opentelemetry-instrumentation-sagemaker = "0.45.6" -opentelemetry-instrumentation-sqlalchemy = ">=0.50b0" -opentelemetry-instrumentation-threading = ">=0.50b0" -opentelemetry-instrumentation-together = "0.45.6" -opentelemetry-instrumentation-transformers = "0.45.6" -opentelemetry-instrumentation-urllib3 = ">=0.50b0" -opentelemetry-instrumentation-vertexai = "0.45.6" -opentelemetry-instrumentation-watsonx = "0.45.6" -opentelemetry-instrumentation-weaviate = "0.45.6" -opentelemetry-sdk = ">=1.28.0,<2.0.0" -opentelemetry-semantic-conventions-ai = ">=0.4.12,<0.5.0" -posthog = ">3.0.2,<4" +cuid = ">=0.4,<0.5" +deprecated = ">=1.2.14,<2" +jinja2 = ">=3.1.5,<4" +opentelemetry-api = ">=1.38.0,<2" +opentelemetry-exporter-otlp-proto-grpc = ">=1.38.0,<2" +opentelemetry-exporter-otlp-proto-http = ">=1.38.0,<2" +opentelemetry-instrumentation-agno = "*" +opentelemetry-instrumentation-alephalpha = "*" +opentelemetry-instrumentation-anthropic = "*" +opentelemetry-instrumentation-bedrock = "*" +opentelemetry-instrumentation-chromadb = "*" +opentelemetry-instrumentation-cohere = "*" +opentelemetry-instrumentation-crewai = "*" +opentelemetry-instrumentation-google-generativeai = "*" +opentelemetry-instrumentation-groq = "*" +opentelemetry-instrumentation-haystack = "*" +opentelemetry-instrumentation-lancedb = "*" +opentelemetry-instrumentation-langchain = "*" +opentelemetry-instrumentation-llamaindex = "*" +opentelemetry-instrumentation-logging = ">=0.59b0" +opentelemetry-instrumentation-marqo = "*" +opentelemetry-instrumentation-mcp = "*" +opentelemetry-instrumentation-milvus = "*" +opentelemetry-instrumentation-mistralai = "*" +opentelemetry-instrumentation-ollama = "*" +opentelemetry-instrumentation-openai = "*" +opentelemetry-instrumentation-openai-agents = "*" +opentelemetry-instrumentation-pinecone = "*" +opentelemetry-instrumentation-qdrant = "*" +opentelemetry-instrumentation-redis = ">=0.59b0" +opentelemetry-instrumentation-replicate = "*" +opentelemetry-instrumentation-requests = ">=0.59b0" +opentelemetry-instrumentation-sagemaker = "*" +opentelemetry-instrumentation-sqlalchemy = ">=0.59b0" +opentelemetry-instrumentation-threading = ">=0.59b0" +opentelemetry-instrumentation-together = "*" +opentelemetry-instrumentation-transformers = "*" +opentelemetry-instrumentation-urllib3 = ">=0.59b0" +opentelemetry-instrumentation-vertexai = "*" +opentelemetry-instrumentation-voyageai = "*" +opentelemetry-instrumentation-watsonx = "*" +opentelemetry-instrumentation-weaviate = "*" +opentelemetry-instrumentation-writer = "*" +opentelemetry-sdk = ">=1.38.0,<2" +opentelemetry-semantic-conventions-ai = ">=0.5.1,<0.6.0" pydantic = ">=1" tenacity = ">=8.2.3,<10.0" +[package.extras] +datasets = ["pandas"] + [[package]] name = "transformers" version = "4.51.3" @@ -6803,156 +5860,6 @@ files = [ {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, ] -[[package]] -name = "xxhash" -version = "3.6.0" -description = "Python binding for xxHash" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "xxhash-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:87ff03d7e35c61435976554477a7f4cd1704c3596a89a8300d5ce7fc83874a71"}, - {file = "xxhash-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f572dfd3d0e2eb1a57511831cf6341242f5a9f8298a45862d085f5b93394a27d"}, - {file = "xxhash-3.6.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:89952ea539566b9fed2bbd94e589672794b4286f342254fad28b149f9615fef8"}, - {file = "xxhash-3.6.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e6f2ffb07a50b52465a1032c3cf1f4a5683f944acaca8a134a2f23674c2058"}, - {file = "xxhash-3.6.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b5b848ad6c16d308c3ac7ad4ba6bede80ed5df2ba8ed382f8932df63158dd4b2"}, - {file = "xxhash-3.6.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a034590a727b44dd8ac5914236a7b8504144447a9682586c3327e935f33ec8cc"}, - {file = "xxhash-3.6.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a8f1972e75ebdd161d7896743122834fe87378160c20e97f8b09166213bf8cc"}, - {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ee34327b187f002a596d7b167ebc59a1b729e963ce645964bbc050d2f1b73d07"}, - {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:339f518c3c7a850dd033ab416ea25a692759dc7478a71131fe8869010d2b75e4"}, - {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:bf48889c9630542d4709192578aebbd836177c9f7a4a2778a7d6340107c65f06"}, - {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5576b002a56207f640636056b4160a378fe36a58db73ae5c27a7ec8db35f71d4"}, - {file = "xxhash-3.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af1f3278bd02814d6dedc5dec397993b549d6f16c19379721e5a1d31e132c49b"}, - {file = "xxhash-3.6.0-cp310-cp310-win32.whl", hash = "sha256:aed058764db109dc9052720da65fafe84873b05eb8b07e5e653597951af57c3b"}, - {file = "xxhash-3.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:e82da5670f2d0d98950317f82a0e4a0197150ff19a6df2ba40399c2a3b9ae5fb"}, - {file = "xxhash-3.6.0-cp310-cp310-win_arm64.whl", hash = "sha256:4a082ffff8c6ac07707fb6b671caf7c6e020c75226c561830b73d862060f281d"}, - {file = "xxhash-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b47bbd8cf2d72797f3c2772eaaac0ded3d3af26481a26d7d7d41dc2d3c46b04a"}, - {file = "xxhash-3.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2b6821e94346f96db75abaa6e255706fb06ebd530899ed76d32cd99f20dc52fa"}, - {file = "xxhash-3.6.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d0a9751f71a1a65ce3584e9cae4467651c7e70c9d31017fa57574583a4540248"}, - {file = "xxhash-3.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b29ee68625ab37b04c0b40c3fafdf24d2f75ccd778333cfb698f65f6c463f62"}, - {file = "xxhash-3.6.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6812c25fe0d6c36a46ccb002f40f27ac903bf18af9f6dd8f9669cb4d176ab18f"}, - {file = "xxhash-3.6.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4ccbff013972390b51a18ef1255ef5ac125c92dc9143b2d1909f59abc765540e"}, - {file = "xxhash-3.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:297b7fbf86c82c550e12e8fb71968b3f033d27b874276ba3624ea868c11165a8"}, - {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dea26ae1eb293db089798d3973a5fc928a18fdd97cc8801226fae705b02b14b0"}, - {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7a0b169aafb98f4284f73635a8e93f0735f9cbde17bd5ec332480484241aaa77"}, - {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:08d45aef063a4531b785cd72de4887766d01dc8f362a515693df349fdb825e0c"}, - {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:929142361a48ee07f09121fe9e96a84950e8d4df3bb298ca5d88061969f34d7b"}, - {file = "xxhash-3.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:51312c768403d8540487dbbfb557454cfc55589bbde6424456951f7fcd4facb3"}, - {file = "xxhash-3.6.0-cp311-cp311-win32.whl", hash = "sha256:d1927a69feddc24c987b337ce81ac15c4720955b667fe9b588e02254b80446fd"}, - {file = "xxhash-3.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:26734cdc2d4ffe449b41d186bbeac416f704a482ed835d375a5c0cb02bc63fef"}, - {file = "xxhash-3.6.0-cp311-cp311-win_arm64.whl", hash = "sha256:d72f67ef8bf36e05f5b6c65e8524f265bd61071471cd4cf1d36743ebeeeb06b7"}, - {file = "xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c"}, - {file = "xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204"}, - {file = "xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490"}, - {file = "xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2"}, - {file = "xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa"}, - {file = "xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0"}, - {file = "xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2"}, - {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9"}, - {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e"}, - {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374"}, - {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d"}, - {file = "xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae"}, - {file = "xxhash-3.6.0-cp312-cp312-win32.whl", hash = "sha256:50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb"}, - {file = "xxhash-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c"}, - {file = "xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829"}, - {file = "xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec"}, - {file = "xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1"}, - {file = "xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6"}, - {file = "xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263"}, - {file = "xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546"}, - {file = "xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89"}, - {file = "xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d"}, - {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7"}, - {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db"}, - {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42"}, - {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11"}, - {file = "xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd"}, - {file = "xxhash-3.6.0-cp313-cp313-win32.whl", hash = "sha256:2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799"}, - {file = "xxhash-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392"}, - {file = "xxhash-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6"}, - {file = "xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702"}, - {file = "xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db"}, - {file = "xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54"}, - {file = "xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f"}, - {file = "xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5"}, - {file = "xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1"}, - {file = "xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee"}, - {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd"}, - {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729"}, - {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292"}, - {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf"}, - {file = "xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033"}, - {file = "xxhash-3.6.0-cp313-cp313t-win32.whl", hash = "sha256:1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec"}, - {file = "xxhash-3.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8"}, - {file = "xxhash-3.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746"}, - {file = "xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e"}, - {file = "xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405"}, - {file = "xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3"}, - {file = "xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6"}, - {file = "xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063"}, - {file = "xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7"}, - {file = "xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b"}, - {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd"}, - {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0"}, - {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152"}, - {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11"}, - {file = "xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5"}, - {file = "xxhash-3.6.0-cp314-cp314-win32.whl", hash = "sha256:a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f"}, - {file = "xxhash-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad"}, - {file = "xxhash-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679"}, - {file = "xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4"}, - {file = "xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67"}, - {file = "xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad"}, - {file = "xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b"}, - {file = "xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b"}, - {file = "xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca"}, - {file = "xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a"}, - {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99"}, - {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3"}, - {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6"}, - {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93"}, - {file = "xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518"}, - {file = "xxhash-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119"}, - {file = "xxhash-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f"}, - {file = "xxhash-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95"}, - {file = "xxhash-3.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7dac94fad14a3d1c92affb661021e1d5cbcf3876be5f5b4d90730775ccb7ac41"}, - {file = "xxhash-3.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6965e0e90f1f0e6cb78da568c13d4a348eeb7f40acfd6d43690a666a459458b8"}, - {file = "xxhash-3.6.0-cp38-cp38-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2ab89a6b80f22214b43d98693c30da66af910c04f9858dd39c8e570749593d7e"}, - {file = "xxhash-3.6.0-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4903530e866b7a9c1eadfd3fa2fbe1b97d3aed4739a80abf506eb9318561c850"}, - {file = "xxhash-3.6.0-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4da8168ae52c01ac64c511d6f4a709479da8b7a4a1d7621ed51652f93747dffa"}, - {file = "xxhash-3.6.0-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:97460eec202017f719e839a0d3551fbc0b2fcc9c6c6ffaa5af85bbd5de432788"}, - {file = "xxhash-3.6.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45aae0c9df92e7fa46fbb738737324a563c727990755ec1965a6a339ea10a1df"}, - {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0d50101e57aad86f4344ca9b32d091a2135a9d0a4396f19133426c88025b09f1"}, - {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9085e798c163ce310d91f8aa6b325dda3c2944c93c6ce1edb314030d4167cc65"}, - {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:a87f271a33fad0e5bf3be282be55d78df3a45ae457950deb5241998790326f87"}, - {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:9e040d3e762f84500961791fa3709ffa4784d4dcd7690afc655c095e02fff05f"}, - {file = "xxhash-3.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b0359391c3dad6de872fefb0cf5b69d55b0655c55ee78b1bb7a568979b2ce96b"}, - {file = "xxhash-3.6.0-cp38-cp38-win32.whl", hash = "sha256:e4ff728a2894e7f436b9e94c667b0f426b9c74b71f900cf37d5468c6b5da0536"}, - {file = "xxhash-3.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:01be0c5b500c5362871fc9cfdf58c69b3e5c4f531a82229ddb9eb1eb14138004"}, - {file = "xxhash-3.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc604dc06027dbeb8281aeac5899c35fcfe7c77b25212833709f0bff4ce74d2a"}, - {file = "xxhash-3.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:277175a73900ad43a8caeb8b99b9604f21fe8d7c842f2f9061a364a7e220ddb7"}, - {file = "xxhash-3.6.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cfbc5b91397c8c2972fdac13fb3e4ed2f7f8ccac85cd2c644887557780a9b6e2"}, - {file = "xxhash-3.6.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2762bfff264c4e73c0e507274b40634ff465e025f0eaf050897e88ec8367575d"}, - {file = "xxhash-3.6.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2f171a900d59d51511209f7476933c34a0c2c711078d3c80e74e0fe4f38680ec"}, - {file = "xxhash-3.6.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:780b90c313348f030b811efc37b0fa1431163cb8db8064cf88a7936b6ce5f222"}, - {file = "xxhash-3.6.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b242455eccdfcd1fa4134c431a30737d2b4f045770f8fe84356b3469d4b919"}, - {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a75ffc1bd5def584129774c158e108e5d768e10b75813f2b32650bb041066ed6"}, - {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1fc1ed882d1e8df932a66e2999429ba6cc4d5172914c904ab193381fba825360"}, - {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:44e342e8cc11b4e79dae5c57f2fb6360c3c20cc57d32049af8f567f5b4bcb5f4"}, - {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c2f9ccd5c4be370939a2e17602fbc49995299203da72a3429db013d44d590e86"}, - {file = "xxhash-3.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:02ea4cb627c76f48cd9fb37cf7ab22bd51e57e1b519807234b473faebe526796"}, - {file = "xxhash-3.6.0-cp39-cp39-win32.whl", hash = "sha256:6551880383f0e6971dc23e512c9ccc986147ce7bfa1cd2e4b520b876c53e9f3d"}, - {file = "xxhash-3.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:7c35c4cdc65f2a29f34425c446f2f5cdcd0e3c34158931e1cc927ece925ab802"}, - {file = "xxhash-3.6.0-cp39-cp39-win_arm64.whl", hash = "sha256:ffc578717a347baf25be8397cb10d2528802d24f94cfc005c0e44fef44b5cdd6"}, - {file = "xxhash-3.6.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0f7b7e2ec26c1666ad5fc9dbfa426a6a3367ceaf79db5dd76264659d509d73b0"}, - {file = "xxhash-3.6.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5dc1e14d14fa0f5789ec29a7062004b5933964bb9b02aae6622b8f530dc40296"}, - {file = "xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:881b47fc47e051b37d94d13e7455131054b56749b91b508b0907eb07900d1c13"}, - {file = "xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6dc31591899f5e5666f04cc2e529e69b4072827085c1ef15294d91a004bc1bd"}, - {file = "xxhash-3.6.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:15e0dac10eb9309508bfc41f7f9deaa7755c69e35af835db9cb10751adebc35d"}, - {file = "xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6"}, -] - [[package]] name = "yarl" version = "1.20.1" @@ -7098,4 +6005,4 @@ presidio = ["presidio-analyzer", "presidio-anonymizer", "stanza", "transformers" [metadata] lock-version = "2.1" python-versions = ">=3.10,<3.14" -content-hash = "95b125b556e6179f8ca13ace4435682b741ca66a68ccea928eb7ffe26502bae2" +content-hash = "c9c7f9add2597858bbe3ff1a9528a772d2341116e1609250968f50a66da9d289" diff --git a/pyproject.toml b/pyproject.toml index 07eacb57..0b1e26aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,55 +29,56 @@ classifiers = [ keywords = ["netra", "tracing", "observability", "sdk", "ai", "llm", "vector", "database"] dependencies = [ - "opentelemetry-api>=1.34.1,<1.40.0", - "opentelemetry-sdk>=1.34.1,<1.40.0", - "opentelemetry-exporter-otlp-proto-http>=0.55b1,<1.40.0", - "opentelemetry-instrumentation-fastapi>=0.55b1,<=0.60b1", - "traceloop-sdk>=0.45.6,<0.49.2", - "opentelemetry-instrumentation-httpx>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-aiohttp-client>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-aio-pika>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-aiokafka>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-aiopg>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-asyncclick>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-asyncio>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-asyncpg>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-aws-lambda>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-boto3sqs>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-botocore>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-cassandra>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-celery>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-click>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-confluent-kafka>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-django>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-elasticsearch>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-falcon>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-flask>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-grpc>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-jinja2>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-kafka-python>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-logging>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-mysql>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-mysqlclient>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-pika>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-psycopg>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-psycopg2>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-pymemcache>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-pymongo>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-pymssql>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-redis>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-remoulade>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-requests>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-sqlalchemy>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-sqlite3>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-starlette>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-system-metrics>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-threading>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-tornado>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-tortoiseorm>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-urllib>=0.55b1,<=0.60b1", - "opentelemetry-instrumentation-urllib3>=0.55b1,<=0.60b1", + "opentelemetry-api>=1.34.1,<=1.41.1", + "opentelemetry-sdk>=1.34.1,<=1.41.1", + "opentelemetry-exporter-otlp-proto-http>=0.55b1,<=1.41.1", + "opentelemetry-instrumentation-fastapi>=0.55b1,<=0.62b1", + "traceloop-sdk>=0.51.0,<=0.60.0", + "opentelemetry-instrumentation-httpx>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-aiohttp-client>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-aio-pika>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-aiokafka>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-aiopg>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-asyncclick>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-asyncio>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-asyncpg>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-aws-lambda>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-boto3sqs>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-botocore>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-cassandra>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-celery>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-click>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-confluent-kafka>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-django>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-elasticsearch>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-falcon>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-flask>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-grpc>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-jinja2>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-kafka-python>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-logging>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-mysql>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-mysqlclient>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-pika>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-psycopg>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-psycopg2>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-pymemcache>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-pymongo>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-pymssql>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-redis>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-remoulade>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-requests>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-sqlalchemy>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-sqlite3>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-starlette>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-system-metrics>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-threading>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-tornado>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-tortoiseorm>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-urllib>=0.55b1,<=0.62b1", + "opentelemetry-instrumentation-urllib3>=0.55b1,<=0.62b1", "json-repair==0.44.1", + "httpx>=0.27.0,<1.0.0", ] [project.urls] diff --git a/tests/test_evaluation.py b/tests/test_evaluation.py new file mode 100644 index 00000000..0a43e788 --- /dev/null +++ b/tests/test_evaluation.py @@ -0,0 +1,1215 @@ +""" +Unit tests for the netra/evaluation/ module. + +Covers models, utils, client, api, and evaluator layers with mocked +HTTP interactions and async helpers. +""" + +import asyncio +from typing import Any +from unittest.mock import MagicMock, patch + +import httpx +import pytest + +from netra.evaluation.evaluator import BaseEvaluator +from netra.evaluation.models import ( + AddDatasetItemResponse, + CreateDatasetResponse, + Dataset, + DatasetItem, + DatasetRecord, + EvaluatorConfig, + EvaluatorContext, + EvaluatorOutput, + GetDatasetItemsResponse, + ItemContext, + ItemProcessingResult, + LocalDataset, + ScoreType, + TurnType, +) +from netra.evaluation.utils import ( + build_evaluators_config, + build_item_payload, + execute_task, + extract_dataset_id, + format_span_id, + format_trace_id, + parse_env_float, + run_async_safely, + run_single_evaluator, + validate_run_inputs, +) + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +class PassEvaluator(BaseEvaluator): + """Evaluator that always passes.""" + + def evaluate(self, context: EvaluatorContext) -> EvaluatorOutput: + return EvaluatorOutput( + evaluator_name=self.config.name, + result=True, + is_passed=True, + reason="always passes", + ) + + +class FailEvaluator(BaseEvaluator): + """Evaluator that always fails.""" + + def evaluate(self, context: EvaluatorContext) -> EvaluatorOutput: + return EvaluatorOutput( + evaluator_name=self.config.name, + result=False, + is_passed=False, + reason="always fails", + ) + + +class AsyncEvaluator(BaseEvaluator): + """Async evaluator that always passes.""" + + async def evaluate(self, context: EvaluatorContext) -> EvaluatorOutput: # type: ignore[override] + return EvaluatorOutput( + evaluator_name=self.config.name, + result=True, + is_passed=True, + reason="async pass", + ) + + +class CrashingEvaluator(BaseEvaluator): + """Evaluator that raises an exception.""" + + def evaluate(self, context: EvaluatorContext) -> EvaluatorOutput: + raise RuntimeError("evaluator exploded") + + +def _make_evaluator_config(name: str = "test_eval", label: str = "Test Evaluator") -> EvaluatorConfig: + """Create a test EvaluatorConfig.""" + return EvaluatorConfig(name=name, label=label, scoreType=ScoreType.BOOLEAN) + + +def _make_config( + endpoint: str = "https://api.getnetra.ai/telemetry", + api_key: str = "key-1", +) -> MagicMock: + """Create a mock Config.""" + cfg = MagicMock() + cfg.otlp_endpoint = endpoint + cfg.api_key = api_key + cfg.headers = {} + return cfg + + +# --------------------------------------------------------------------------- +# Section 1: Models +# --------------------------------------------------------------------------- + + +class TestScoreType: + """Tests for ScoreType enum.""" + + def test_values(self) -> None: + assert ScoreType.BOOLEAN.value == "boolean" + assert ScoreType.NUMERICAL.value == "numerical" + assert ScoreType.CATEGORICAL.value == "categorical" + + +class TestTurnType: + """Tests for TurnType enum.""" + + def test_values(self) -> None: + assert TurnType.SINGLE.value == "single" + assert TurnType.MULTI.value == "multi" + + +class TestDatasetItem: + """Tests for DatasetItem model.""" + + def test_required_input(self) -> None: + item = DatasetItem(input="hello") + assert item.input == "hello" + assert item.expected_output is None + assert item.metadata is None + assert item.tags is None + + def test_all_fields(self) -> None: + item = DatasetItem( + input="hello", + expected_output="world", + metadata={"key": "val"}, + tags=["tag1"], + ) + assert item.expected_output == "world" + assert item.metadata == {"key": "val"} + assert item.tags == ["tag1"] + + +class TestDatasetRecord: + """Tests for DatasetRecord model.""" + + def test_creation(self) -> None: + record = DatasetRecord(id="r1", input="q", dataset_id="ds1") + assert record.id == "r1" + assert record.expected_output is None + + +class TestDataset: + """Tests for Dataset model.""" + + def test_with_dataset_items(self) -> None: + ds = Dataset(items=[DatasetItem(input="a"), DatasetItem(input="b")]) + assert len(ds.items) == 2 + + def test_with_dataset_records(self) -> None: + ds = Dataset( + items=[ + DatasetRecord(id="r1", input="a", dataset_id="ds1"), + DatasetRecord(id="r2", input="b", dataset_id="ds1"), + ] + ) + assert len(ds.items) == 2 + + +class TestEvaluatorConfig: + """Tests for EvaluatorConfig model.""" + + def test_alias(self) -> None: + config = EvaluatorConfig(name="e1", label="Eval 1", scoreType=ScoreType.BOOLEAN) + assert config.score_type == ScoreType.BOOLEAN + + def test_populate_by_name(self) -> None: + config = EvaluatorConfig(name="e1", label="Eval 1", scoreType=ScoreType.NUMERICAL) + assert config.score_type == ScoreType.NUMERICAL + + +class TestItemContext: + """Tests for ItemContext dataclass.""" + + def test_defaults(self) -> None: + ctx = ItemContext(index=0, item_input="hello") + assert ctx.status == "pending" + assert ctx.trace_id == "" + assert ctx.task_output is None + + def test_slots(self) -> None: + ctx = ItemContext(index=0, item_input="hello") + with pytest.raises(AttributeError): + ctx.nonexistent = True # type: ignore[attr-defined] + + +class TestItemProcessingResult: + """Tests for ItemProcessingResult dataclass.""" + + def test_creation(self) -> None: + ctx = ItemContext(index=0, item_input="x") + result = ItemProcessingResult( + item_entry={"index": 0}, + should_run_evaluators=True, + ctx=ctx, + status="completed", + ) + assert result.should_run_evaluators is True + + +class TestLocalDataset: + """Tests for LocalDataset model.""" + + def test_creation(self) -> None: + ld = LocalDataset(items=[DatasetItem(input="x")]) + assert len(ld.items) == 1 + + +class TestCreateDatasetResponse: + """Tests for CreateDatasetResponse model.""" + + def test_creation(self) -> None: + resp = CreateDatasetResponse( + project_id="p1", + organization_id="o1", + name="ds1", + created_by="user", + updated_by="user", + updated_at="2025-01-01", + id="id1", + created_at="2025-01-01", + ) + assert resp.id == "id1" + assert resp.deleted_at is None + + +class TestAddDatasetItemResponse: + """Tests for AddDatasetItemResponse model.""" + + def test_creation(self) -> None: + resp = AddDatasetItemResponse( + dataset_id="ds1", + project_id="p1", + organization_id="o1", + source="sdk", + input="hello", + is_active=True, + created_by="user", + updated_by="user", + updated_at="2025-01-01", + id="item1", + created_at="2025-01-01", + ) + assert resp.is_active is True + + +# --------------------------------------------------------------------------- +# Section 2: Utils +# --------------------------------------------------------------------------- + + +class TestParseEnvFloat: + """Tests for parse_env_float.""" + + def test_returns_default_when_unset(self) -> None: + assert parse_env_float("_NETRA_EVAL_TEST_NONEXISTENT_", 42.0) == 42.0 + + def test_parses_valid_value(self, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("_NETRA_EVAL_TEST_FLOAT_", "3.14") + assert parse_env_float("_NETRA_EVAL_TEST_FLOAT_", 1.0) == pytest.approx(3.14) + + def test_returns_default_on_invalid(self, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("_NETRA_EVAL_TEST_FLOAT_", "not-a-number") + assert parse_env_float("_NETRA_EVAL_TEST_FLOAT_", 7.0) == 7.0 + + +class TestFormatTraceId: + """Tests for format_trace_id.""" + + def test_zero(self) -> None: + assert format_trace_id(0) == "0" * 32 + + def test_known_value(self) -> None: + result = format_trace_id(255) + assert result == "0" * 30 + "ff" + assert len(result) == 32 + + +class TestFormatSpanId: + """Tests for format_span_id.""" + + def test_zero(self) -> None: + assert format_span_id(0) == "0" * 16 + + def test_known_value(self) -> None: + result = format_span_id(255) + assert result == "0" * 14 + "ff" + assert len(result) == 16 + + +class TestRunAsyncSafely: + """Tests for run_async_safely.""" + + def test_runs_coroutine(self) -> None: + async def coro() -> int: + return 42 + + assert run_async_safely(coro()) == 42 + + def test_propagates_exception(self) -> None: + async def coro() -> None: + raise ValueError("boom") + + with pytest.raises(ValueError, match="boom"): + run_async_safely(coro()) + + +class TestExecuteTask: + """Tests for execute_task.""" + + def test_sync_task(self) -> None: + def task(inp: str) -> str: + return f"result: {inp}" + + output, status = asyncio.run(execute_task(task, "hello")) + assert output == "result: hello" + assert status == "completed" + + def test_async_task(self) -> None: + async def task(inp: str) -> str: + return f"async: {inp}" + + output, status = asyncio.run(execute_task(task, "hello")) + assert output == "async: hello" + assert status == "completed" + + def test_failed_task(self) -> None: + def task(inp: str) -> str: + raise ValueError("task error") + + output, status = asyncio.run(execute_task(task, "hello")) + assert status == "failed" + assert "task error" in output + + +class TestValidateRunInputs: + """Tests for validate_run_inputs.""" + + def test_valid(self) -> None: + validate_run_inputs("name", Dataset(items=[DatasetItem(input="x")]), lambda x: x) + + def test_missing_name(self) -> None: + with pytest.raises(ValueError, match="run name is required"): + validate_run_inputs("", Dataset(items=[DatasetItem(input="x")]), lambda x: x) + + def test_missing_task(self) -> None: + with pytest.raises(ValueError, match="task function is required"): + validate_run_inputs("name", Dataset(items=[DatasetItem(input="x")]), None) # type: ignore[arg-type] + + +class TestExtractDatasetId: + """Tests for extract_dataset_id.""" + + def test_with_dataset_records(self) -> None: + items = [DatasetRecord(id="r1", input="a", dataset_id="ds1")] + assert extract_dataset_id(items) == "ds1" + + def test_with_dataset_items(self) -> None: + items = [DatasetItem(input="a")] + assert extract_dataset_id(items) is None + + def test_empty_list(self) -> None: + assert extract_dataset_id([]) is None + + +class TestBuildEvaluatorsConfig: + """Tests for build_evaluators_config.""" + + def test_none_evaluators(self) -> None: + assert build_evaluators_config(None) == [] + + def test_empty_evaluators(self) -> None: + assert build_evaluators_config([]) == [] + + def test_extracts_configs(self) -> None: + config = _make_evaluator_config() + evaluator = PassEvaluator(config) + result = build_evaluators_config([evaluator]) + assert len(result) == 1 + assert result[0].name == "test_eval" + + def test_skips_evaluators_without_config(self) -> None: + no_config = MagicMock(spec=[]) + result = build_evaluators_config([no_config]) + assert result == [] + + +class TestBuildItemPayload: + """Tests for build_item_payload.""" + + def test_completed_with_output(self) -> None: + ctx = ItemContext( + index=0, + item_input="hello", + expected_output="world", + trace_id="trace-1", + session_id="session-1", + task_output="result", + status="completed", + ) + payload = build_item_payload(ctx, status="completed", include_output=True) + assert payload["traceId"] == "trace-1" + assert payload["taskOutput"] == "result" + assert "status" not in payload + + def test_failed_status(self) -> None: + ctx = ItemContext( + index=0, + item_input="hello", + trace_id="trace-1", + status="failed", + ) + payload = build_item_payload(ctx, status="failed") + assert payload["status"] == "failed" + assert "taskOutput" not in payload + + def test_uses_passed_status_not_ctx_status(self) -> None: + ctx = ItemContext( + index=0, + item_input="hello", + trace_id="trace-1", + status="completed", + task_output="result", + ) + payload = build_item_payload(ctx, status="failed") + assert payload["status"] == "failed" + + def test_with_dataset_item_id(self) -> None: + ctx = ItemContext( + index=0, + item_input="hello", + dataset_item_id="item-1", + trace_id="trace-1", + ) + payload = build_item_payload(ctx, status="completed") + assert payload["datasetItemId"] == "item-1" + assert "input" not in payload + + def test_without_dataset_item_id(self) -> None: + ctx = ItemContext( + index=0, + item_input="hello", + expected_output="world", + metadata={"key": "val"}, + trace_id="trace-1", + ) + payload = build_item_payload(ctx, status="completed") + assert payload["input"] == "hello" + assert payload["expectedOutput"] == "world" + assert payload["metadata"] == {"key": "val"} + + +class TestRunSingleEvaluator: + """Tests for run_single_evaluator.""" + + def test_sync_evaluator(self) -> None: + config = _make_evaluator_config() + evaluator = PassEvaluator(config) + result = asyncio.run( + run_single_evaluator( + evaluator=evaluator, + item_input="hello", + task_output="world", + expected_output="world", + metadata=None, + ) + ) + assert result is not None + assert result["evaluatorName"] == "test_eval" + assert result["isPassed"] is True + + def test_async_evaluator(self) -> None: + config = _make_evaluator_config() + evaluator = AsyncEvaluator(config) + result = asyncio.run( + run_single_evaluator( + evaluator=evaluator, + item_input="hello", + task_output="world", + expected_output="world", + metadata=None, + ) + ) + assert result is not None + assert result["isPassed"] is True + + def test_evaluator_without_evaluate(self) -> None: + evaluator = MagicMock(spec=[]) + result = asyncio.run( + run_single_evaluator( + evaluator=evaluator, + item_input="hello", + task_output="world", + expected_output="world", + metadata=None, + ) + ) + assert result is None + + def test_name_mismatch_returns_none(self) -> None: + config = _make_evaluator_config(name="expected_name") + + class WrongNameEvaluator(BaseEvaluator): + def evaluate(self, context: EvaluatorContext) -> EvaluatorOutput: + return EvaluatorOutput( + evaluator_name="wrong_name", + result=True, + is_passed=True, + ) + + evaluator = WrongNameEvaluator(config) + result = asyncio.run( + run_single_evaluator( + evaluator=evaluator, + item_input="hello", + task_output="world", + expected_output="world", + metadata=None, + ) + ) + assert result is None + + +# --------------------------------------------------------------------------- +# Section 3: Client +# --------------------------------------------------------------------------- + + +class TestEvaluationHttpClient: + """Tests for EvaluationHttpClient.""" + + def test_create_client_with_valid_config(self) -> None: + from netra.evaluation.client import EvaluationHttpClient + + client = EvaluationHttpClient(_make_config()) + assert client._client is not None + client.close() + + def test_create_client_strips_telemetry_suffix(self) -> None: + from netra.evaluation.client import EvaluationHttpClient + + client = EvaluationHttpClient(_make_config(endpoint="https://api.getnetra.ai/telemetry")) + assert client._client is not None + assert "/telemetry" not in str(client._client.base_url) + client.close() + + def test_create_client_returns_none_on_empty_endpoint(self) -> None: + from netra.evaluation.client import EvaluationHttpClient + + client = EvaluationHttpClient(_make_config(endpoint="")) + assert client._client is None + + def test_close_sets_client_to_none(self) -> None: + from netra.evaluation.client import EvaluationHttpClient + + client = EvaluationHttpClient(_make_config()) + assert client._client is not None + client.close() + assert client._client is None + + def test_close_idempotent(self) -> None: + from netra.evaluation.client import EvaluationHttpClient + + client = EvaluationHttpClient(_make_config()) + client.close() + client.close() + assert client._client is None + + def test_ensure_client_returns_none_when_not_initialized(self) -> None: + from netra.evaluation.client import EvaluationHttpClient + + client = EvaluationHttpClient(_make_config(endpoint="")) + assert client._ensure_client() is None + + def test_extract_error_message_from_response(self) -> None: + from netra.evaluation.client import EvaluationHttpClient + + client = EvaluationHttpClient(_make_config(endpoint="")) + mock_response = MagicMock(spec=httpx.Response) + mock_response.json.return_value = {"error": {"message": "custom error"}} + result = client._extract_error_message(mock_response, ValueError("fallback")) + assert result == "custom error" + + def test_extract_error_message_fallback_on_none_response(self) -> None: + from netra.evaluation.client import EvaluationHttpClient + + client = EvaluationHttpClient(_make_config(endpoint="")) + result = client._extract_error_message(None, ValueError("fallback")) + assert result == "fallback" + + def test_extract_error_message_fallback_on_missing_error_key(self) -> None: + from netra.evaluation.client import EvaluationHttpClient + + client = EvaluationHttpClient(_make_config(endpoint="")) + mock_response = MagicMock(spec=httpx.Response) + mock_response.json.return_value = {"other": "data"} + result = client._extract_error_message(mock_response, ValueError("fallback")) + assert result == "fallback" + + def test_extract_error_message_fallback_on_json_parse_error(self) -> None: + from netra.evaluation.client import EvaluationHttpClient + + client = EvaluationHttpClient(_make_config(endpoint="")) + mock_response = MagicMock(spec=httpx.Response) + mock_response.json.side_effect = ValueError("not json") + result = client._extract_error_message(mock_response, RuntimeError("orig")) + assert result == "orig" + + @patch("netra.evaluation.client.httpx.Client") + def test_create_dataset_success(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.client import EvaluationHttpClient + + mock_response = MagicMock(spec=httpx.Response) + mock_response.json.return_value = {"data": {"id": "ds-1", "name": "test"}} + mock_response.raise_for_status = MagicMock() + + mock_instance = MagicMock() + mock_instance.post.return_value = mock_response + mock_client_cls.return_value = mock_instance + + client = EvaluationHttpClient(_make_config()) + result = client.create_dataset(name="test") + assert result is not None + assert result["id"] == "ds-1" + + @patch("netra.evaluation.client.httpx.Client") + def test_create_dataset_returns_none_on_error(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.client import EvaluationHttpClient + + mock_instance = MagicMock() + mock_instance.post.side_effect = httpx.ConnectError("timeout") + mock_client_cls.return_value = mock_instance + + client = EvaluationHttpClient(_make_config()) + result = client.create_dataset(name="test") + assert result is None + + def test_create_dataset_returns_none_without_client(self) -> None: + from netra.evaluation.client import EvaluationHttpClient + + client = EvaluationHttpClient(_make_config(endpoint="")) + result = client.create_dataset(name="test") + assert result is None + + @patch("netra.evaluation.client.httpx.Client") + def test_create_run_success(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.client import EvaluationHttpClient + + mock_response = MagicMock(spec=httpx.Response) + mock_response.json.return_value = {"data": {"id": "run-1"}} + mock_response.raise_for_status = MagicMock() + + mock_instance = MagicMock() + mock_instance.post.return_value = mock_response + mock_client_cls.return_value = mock_instance + + client = EvaluationHttpClient(_make_config()) + result = client.create_run(name="test") + assert result is not None + assert result["id"] == "run-1" + + @patch("netra.evaluation.client.httpx.Client") + def test_post_run_item_success(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.client import EvaluationHttpClient + + mock_response = MagicMock(spec=httpx.Response) + mock_response.json.return_value = {"data": {"item": {"id": "item-1"}}} + mock_response.raise_for_status = MagicMock() + + mock_instance = MagicMock() + mock_instance.post.return_value = mock_response + mock_client_cls.return_value = mock_instance + + client = EvaluationHttpClient(_make_config()) + result = client.post_run_item("run-1", {"traceId": "t1"}) + assert result == "item-1" + + @patch("netra.evaluation.client.httpx.Client") + def test_post_run_item_returns_none_on_error(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.client import EvaluationHttpClient + + mock_instance = MagicMock() + mock_instance.post.side_effect = httpx.ConnectError("timeout") + mock_client_cls.return_value = mock_instance + + client = EvaluationHttpClient(_make_config()) + result = client.post_run_item("run-1", {"traceId": "t1"}) + assert result is None + + @patch("netra.evaluation.client.httpx.Client") + def test_get_dataset_success(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.client import EvaluationHttpClient + + mock_response = MagicMock(spec=httpx.Response) + mock_response.json.return_value = {"data": [{"id": "item-1", "input": "q", "datasetId": "ds-1"}]} + mock_response.raise_for_status = MagicMock() + + mock_instance = MagicMock() + mock_instance.get.return_value = mock_response + mock_client_cls.return_value = mock_instance + + client = EvaluationHttpClient(_make_config()) + result = client.get_dataset("ds-1") + assert result is not None + assert len(result) == 1 + + @patch("netra.evaluation.client.httpx.Client") + def test_post_run_status_success(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.client import EvaluationHttpClient + + mock_response = MagicMock(spec=httpx.Response) + mock_response.json.return_value = {"data": {"status": "completed"}} + mock_response.raise_for_status = MagicMock() + + mock_instance = MagicMock() + mock_instance.post.return_value = mock_response + mock_client_cls.return_value = mock_instance + + client = EvaluationHttpClient(_make_config()) + result = client.post_run_status("run-1", "completed") + assert result == {"status": "completed"} + + @patch("netra.evaluation.client.httpx.Client") + def test_post_run_status_returns_none_on_error(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.client import EvaluationHttpClient + + mock_instance = MagicMock() + mock_instance.post.side_effect = httpx.ConnectError("timeout") + mock_client_cls.return_value = mock_instance + + client = EvaluationHttpClient(_make_config()) + result = client.post_run_status("run-1", "completed") + assert result is None + + @patch("netra.evaluation.client.httpx.Client") + def test_get_run_results_success(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.client import EvaluationHttpClient + + mock_response = MagicMock(spec=httpx.Response) + mock_response.json.return_value = {"data": {"items": []}} + mock_response.raise_for_status = MagicMock() + + mock_instance = MagicMock() + mock_instance.get.return_value = mock_response + mock_client_cls.return_value = mock_instance + + client = EvaluationHttpClient(_make_config()) + result = client.get_run_results("run-1") + assert result is not None + + @patch("netra.evaluation.client.httpx.Client") + def test_submit_local_evaluations_success(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.client import EvaluationHttpClient + + mock_response = MagicMock(spec=httpx.Response) + mock_response.json.return_value = {"data": {"success": True}} + mock_response.raise_for_status = MagicMock() + + mock_instance = MagicMock() + mock_instance.post.return_value = mock_response + mock_client_cls.return_value = mock_instance + + client = EvaluationHttpClient(_make_config()) + result = client.submit_local_evaluations("run-1", "item-1", [{"evaluatorName": "e1"}]) + assert result is not None + + @patch("netra.evaluation.client.httpx.Client") + def test_add_dataset_item_success(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.client import EvaluationHttpClient + + mock_response = MagicMock(spec=httpx.Response) + mock_response.json.return_value = {"data": {"id": "item-1", "input": "q"}} + mock_response.raise_for_status = MagicMock() + + mock_instance = MagicMock() + mock_instance.post.return_value = mock_response + mock_client_cls.return_value = mock_instance + + client = EvaluationHttpClient(_make_config()) + item = DatasetItem(input="hello", expected_output="world") + result = client.add_dataset_item("ds-1", item) + assert result is not None + assert result["id"] == "item-1" + + @patch("netra.evaluation.client.httpx.Client") + def test_get_span_by_id_success(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.client import EvaluationHttpClient + + mock_response = MagicMock(spec=httpx.Response) + mock_response.json.return_value = {"data": {"spanId": "span-1"}} + mock_response.raise_for_status = MagicMock() + + mock_instance = MagicMock() + mock_instance.get.return_value = mock_response + mock_client_cls.return_value = mock_instance + + client = EvaluationHttpClient(_make_config()) + result = client.get_span_by_id("span-1") + assert result is not None + + @patch("netra.evaluation.client.httpx.Client") + def test_get_span_by_id_returns_none_on_error(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.client import EvaluationHttpClient + + mock_instance = MagicMock() + mock_instance.get.side_effect = httpx.ConnectError("timeout") + mock_client_cls.return_value = mock_instance + + client = EvaluationHttpClient(_make_config()) + result = client.get_span_by_id("span-1") + assert result is None + + +# --------------------------------------------------------------------------- +# Section 4: API (Evaluation class) +# --------------------------------------------------------------------------- + + +class TestEvaluation: + """Tests for the Evaluation public API.""" + + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_create_dataset_success(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.api import Evaluation + + mock_client = MagicMock() + mock_client.create_dataset.return_value = { + "projectId": "p1", + "organizationId": "o1", + "name": "ds1", + "tags": [], + "createdBy": "user", + "updatedBy": "user", + "updatedAt": "2025-01-01", + "id": "ds-1", + "createdAt": "2025-01-01", + } + mock_client_cls.return_value = mock_client + + evaluation = Evaluation(_make_config()) + result = evaluation.create_dataset(name="ds1") + assert isinstance(result, CreateDatasetResponse) + assert result.id == "ds-1" + + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_create_dataset_returns_none_on_empty_name(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.api import Evaluation + + evaluation = Evaluation(_make_config()) + result = evaluation.create_dataset(name="") + assert result is None + + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_create_dataset_returns_none_on_client_failure(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.api import Evaluation + + mock_client = MagicMock() + mock_client.create_dataset.return_value = None + mock_client_cls.return_value = mock_client + + evaluation = Evaluation(_make_config()) + result = evaluation.create_dataset(name="ds1") + assert result is None + + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_add_dataset_item_success(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.api import Evaluation + + mock_client = MagicMock() + mock_client.add_dataset_item.return_value = { + "datasetId": "ds-1", + "projectId": "p1", + "organizationId": "o1", + "source": "sdk", + "input": "hello", + "createdBy": "user", + "updatedBy": "user", + "updatedAt": "2025-01-01", + "id": "item-1", + "createdAt": "2025-01-01", + } + mock_client_cls.return_value = mock_client + + evaluation = Evaluation(_make_config()) + result = evaluation.add_dataset_item("ds-1", DatasetItem(input="hello")) + assert isinstance(result, AddDatasetItemResponse) + assert result.id == "item-1" + + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_add_dataset_item_returns_none_on_empty_input(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.api import Evaluation + + evaluation = Evaluation(_make_config()) + result = evaluation.add_dataset_item("ds-1", DatasetItem(input="")) + assert result is None + + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_add_dataset_item_returns_none_on_client_failure(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.api import Evaluation + + mock_client = MagicMock() + mock_client.add_dataset_item.return_value = None + mock_client_cls.return_value = mock_client + + evaluation = Evaluation(_make_config()) + result = evaluation.add_dataset_item("ds-1", DatasetItem(input="hello")) + assert result is None + + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_get_dataset_success(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.api import Evaluation + + mock_client = MagicMock() + mock_client.get_dataset.return_value = [ + {"id": "item-1", "input": "q", "datasetId": "ds-1", "expectedOutput": "a"} + ] + mock_client_cls.return_value = mock_client + + evaluation = Evaluation(_make_config()) + result = evaluation.get_dataset("ds-1") + assert isinstance(result, GetDatasetItemsResponse) + assert len(result.items) == 1 + + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_get_dataset_returns_none_on_empty_id(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.api import Evaluation + + evaluation = Evaluation(_make_config()) + result = evaluation.get_dataset("") + assert result is None + + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_get_dataset_skips_invalid_items(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.api import Evaluation + + mock_client = MagicMock() + mock_client.get_dataset.return_value = [ + {"id": "item-1", "input": "q", "datasetId": "ds-1"}, + {"id": None, "input": "q", "datasetId": "ds-1"}, + ] + mock_client_cls.return_value = mock_client + + evaluation = Evaluation(_make_config()) + result = evaluation.get_dataset("ds-1") + assert isinstance(result, GetDatasetItemsResponse) + assert len(result.items) == 1 + + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_create_run_success(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.api import Evaluation + + mock_client = MagicMock() + mock_client.create_run.return_value = {"id": "run-1"} + mock_client_cls.return_value = mock_client + + evaluation = Evaluation(_make_config()) + result = evaluation.create_run(name="test") + assert result == "run-1" + + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_create_run_returns_none_on_empty_name(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.api import Evaluation + + evaluation = Evaluation(_make_config()) + result = evaluation.create_run(name="") + assert result is None + + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_create_run_returns_none_on_client_failure(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.api import Evaluation + + mock_client = MagicMock() + mock_client.create_run.return_value = None + mock_client_cls.return_value = mock_client + + evaluation = Evaluation(_make_config()) + result = evaluation.create_run(name="test") + assert result is None + + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_get_run_results(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.api import Evaluation + + mock_client = MagicMock() + mock_client.get_run_results.return_value = {"items": []} + mock_client_cls.return_value = mock_client + + evaluation = Evaluation(_make_config()) + result = evaluation.get_run_results("run-1") + assert result == {"items": []} + + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_get_run_results_returns_none_on_empty_id(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.api import Evaluation + + evaluation = Evaluation(_make_config()) + result = evaluation.get_run_results("") + assert result is None + + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_close_delegates_to_client(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.api import Evaluation + + mock_client = MagicMock() + mock_client_cls.return_value = mock_client + + evaluation = Evaluation(_make_config()) + evaluation.close() + mock_client.close.assert_called_once() + + @patch("netra.evaluation.api.SpanWrapper") + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_run_test_suite_success(self, mock_client_cls: MagicMock, mock_span_wrapper: MagicMock) -> None: + from netra.evaluation.api import Evaluation + + mock_span = MagicMock() + mock_span.__enter__ = MagicMock(return_value=mock_span) + mock_span.__exit__ = MagicMock(return_value=False) + mock_span.get_current_span.return_value = None + mock_span_wrapper.return_value = mock_span + + mock_client = MagicMock() + mock_client.create_run.return_value = {"id": "run-1"} + mock_client.post_run_item.return_value = "item-1" + mock_client.post_run_status.return_value = None + + async def mock_wait(*args: Any, **kwargs: Any) -> bool: + return True + + mock_client.wait_for_span_ingestion = mock_wait + mock_client_cls.return_value = mock_client + + evaluation = Evaluation(_make_config()) + dataset = Dataset(items=[DatasetItem(input="hello")]) + result = evaluation.run_test_suite( + name="test", + data=dataset, + task=lambda x: f"result: {x}", + ) + + assert result is not None + assert result["runId"] == "run-1" + assert len(result["items"]) == 1 + + @patch("netra.evaluation.api.SpanWrapper") + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_run_test_suite_marks_failed_on_exception( + self, mock_client_cls: MagicMock, mock_span_wrapper: MagicMock + ) -> None: + from netra.evaluation.api import Evaluation + + mock_span = MagicMock() + mock_span.__enter__ = MagicMock(return_value=mock_span) + mock_span.__exit__ = MagicMock(return_value=False) + mock_span.get_current_span.return_value = None + mock_span_wrapper.return_value = mock_span + + mock_client = MagicMock() + mock_client.create_run.return_value = {"id": "run-1"} + mock_client.post_run_item.side_effect = RuntimeError("backend down") + mock_client.post_run_status.return_value = None + mock_client_cls.return_value = mock_client + + evaluation = Evaluation(_make_config()) + dataset = Dataset(items=[DatasetItem(input="hello")]) + + with pytest.raises(RuntimeError, match="backend down"): + evaluation.run_test_suite( + name="test", + data=dataset, + task=lambda x: f"result: {x}", + ) + + mock_client.post_run_status.assert_called_with("run-1", "failed") + + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_run_test_suite_returns_none_when_create_run_fails(self, mock_client_cls: MagicMock) -> None: + from netra.evaluation.api import Evaluation + + mock_client = MagicMock() + mock_client.create_run.return_value = None + mock_client_cls.return_value = mock_client + + evaluation = Evaluation(_make_config()) + dataset = Dataset(items=[DatasetItem(input="hello")]) + result = evaluation.run_test_suite( + name="test", + data=dataset, + task=lambda x: x, + ) + assert result is None + + @patch("netra.evaluation.api.SpanWrapper") + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_run_test_suite_with_evaluators(self, mock_client_cls: MagicMock, mock_span_wrapper: MagicMock) -> None: + from netra.evaluation.api import Evaluation + + mock_span = MagicMock() + mock_span.__enter__ = MagicMock(return_value=mock_span) + mock_span.__exit__ = MagicMock(return_value=False) + mock_otel_span = MagicMock() + mock_otel_span.get_span_context.return_value = MagicMock(trace_id=123, span_id=456) + mock_span.get_current_span.return_value = mock_otel_span + mock_span_wrapper.return_value = mock_span + + async def mock_wait(*args: Any, **kwargs: Any) -> bool: + return True + + mock_client = MagicMock() + mock_client.create_run.return_value = {"id": "run-1"} + mock_client.post_run_item.return_value = "item-1" + mock_client.post_run_status.return_value = None + mock_client.wait_for_span_ingestion = mock_wait + mock_client.submit_local_evaluations.return_value = None + mock_client_cls.return_value = mock_client + + config = _make_evaluator_config() + evaluator = PassEvaluator(config) + + evaluation = Evaluation(_make_config()) + dataset = Dataset(items=[DatasetItem(input="hello")]) + result = evaluation.run_test_suite( + name="test", + data=dataset, + task=lambda x: f"result: {x}", + evaluators=[evaluator], + ) + + assert result is not None + assert result["runId"] == "run-1" + mock_client.submit_local_evaluations.assert_called_once() + + @patch("netra.evaluation.api.SpanWrapper") + @patch("netra.evaluation.api.EvaluationHttpClient") + def test_run_test_suite_handles_failed_task(self, mock_client_cls: MagicMock, mock_span_wrapper: MagicMock) -> None: + from netra.evaluation.api import Evaluation + + mock_span = MagicMock() + mock_span.__enter__ = MagicMock(return_value=mock_span) + mock_span.__exit__ = MagicMock(return_value=False) + mock_span.get_current_span.return_value = None + mock_span_wrapper.return_value = mock_span + + mock_client = MagicMock() + mock_client.create_run.return_value = {"id": "run-1"} + mock_client.post_run_item.return_value = "item-1" + mock_client.post_run_status.return_value = None + mock_client_cls.return_value = mock_client + + def failing_task(inp: Any) -> None: + raise ValueError("task failed") + + evaluation = Evaluation(_make_config()) + dataset = Dataset(items=[DatasetItem(input="hello")]) + result = evaluation.run_test_suite( + name="test", + data=dataset, + task=failing_task, + ) + + assert result is not None + assert result["items"][0]["status"] == "failed" + + +# --------------------------------------------------------------------------- +# Section 5: Evaluator +# --------------------------------------------------------------------------- + + +class TestBaseEvaluator: + """Tests for BaseEvaluator abstract class.""" + + def test_cannot_instantiate_directly(self) -> None: + with pytest.raises(TypeError): + BaseEvaluator(_make_evaluator_config()) # type: ignore[abstract] + + def test_sync_subclass(self) -> None: + config = _make_evaluator_config() + evaluator = PassEvaluator(config) + context = EvaluatorContext(input="x", task_output="y") + result = evaluator.evaluate(context) + assert isinstance(result, EvaluatorOutput) + assert result.is_passed is True + + def test_fail_evaluator(self) -> None: + config = _make_evaluator_config() + evaluator = FailEvaluator(config) + context = EvaluatorContext(input="x", task_output="y") + result = evaluator.evaluate(context) + assert result.is_passed is False + + def test_config_accessible(self) -> None: + config = _make_evaluator_config(name="my_eval") + evaluator = PassEvaluator(config) + assert evaluator.config.name == "my_eval" diff --git a/tests/test_simulation.py b/tests/test_simulation.py new file mode 100644 index 00000000..069e907b --- /dev/null +++ b/tests/test_simulation.py @@ -0,0 +1,783 @@ +""" +Unit tests for the netra/simulation/ module. + +Covers models, utils, client, api, and task layers with mocked +HTTP interactions and async helpers. +""" + +import asyncio +import base64 +from typing import Any, Optional +from unittest.mock import MagicMock, patch + +import httpx +import pytest + +from netra.simulation.models import ( + ConversationResponse, + ConversationStatus, + FileData, + ProcessedFile, + SimulationItem, + TaskResult, +) +from netra.simulation.task import BaseTask +from netra.simulation.utils import ( + execute_task, + format_trace_id, + parse_env_float, + process_files, + run_async_safely, + validate_simulation_inputs, +) + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +class SyncTask(BaseTask): + """Synchronous task that echoes the message.""" + + def run( + self, + message: str, + session_id: Optional[str] = None, + files: Optional[list[ProcessedFile]] = None, + ) -> TaskResult: + return TaskResult(message=f"echo: {message}", session_id=session_id or "sid-1") + + +class AsyncTask(BaseTask): + """Asynchronous task that echoes the message.""" + + async def run( + self, + message: str, + session_id: Optional[str] = None, + files: Optional[list[ProcessedFile]] = None, + ) -> TaskResult: + return TaskResult(message=f"async-echo: {message}", session_id=session_id or "sid-async") + + +class FileAwareTask(BaseTask): + """Task that uses the files parameter.""" + + def run( + self, + message: str, + session_id: Optional[str] = None, + files: Optional[list[ProcessedFile]] = None, + ) -> TaskResult: + count = len(files) if files else 0 + return TaskResult(message=f"files={count}", session_id=session_id or "sid-files") + + +# --------------------------------------------------------------------------- +# Section 1: Models +# --------------------------------------------------------------------------- + + +class TestConversationStatus: + """Tests for ConversationStatus enum.""" + + def test_continue_value(self) -> None: + assert ConversationStatus.CONTINUE.value == "continue" + + def test_stop_value(self) -> None: + assert ConversationStatus.STOP.value == "stop" + + def test_from_string(self) -> None: + assert ConversationStatus("continue") == ConversationStatus.CONTINUE + assert ConversationStatus("stop") == ConversationStatus.STOP + + +class TestFileData: + """Tests for the FileData frozen dataclass.""" + + def test_creation(self) -> None: + fd = FileData(file_name="a.txt", content_type="text/plain", description="desc", download_url="https://x") + assert fd.file_name == "a.txt" + assert fd.content_type == "text/plain" + assert fd.description == "desc" + assert fd.download_url == "https://x" + + def test_frozen(self) -> None: + fd = FileData(file_name="a.txt", content_type="text/plain", description=None, download_url="https://x") + with pytest.raises(AttributeError): + fd.file_name = "b.txt" # type: ignore[misc] + + +class TestProcessedFile: + """Tests for the ProcessedFile frozen dataclass.""" + + def test_creation(self) -> None: + pf = ProcessedFile(file_name="a.txt", content_type="text/plain", description=None, data="AAAA") + assert pf.data == "AAAA" + + def test_frozen(self) -> None: + pf = ProcessedFile(file_name="a.txt", content_type="text/plain", description=None, data="AAAA") + with pytest.raises(AttributeError): + pf.data = "BBBB" # type: ignore[misc] + + +class TestSimulationItem: + """Tests for the SimulationItem frozen dataclass.""" + + def test_defaults(self) -> None: + item = SimulationItem(run_item_id="r1", message="hi", turn_id="t1") + assert item.files == [] + + def test_with_files(self) -> None: + fd = FileData(file_name="a.txt", content_type="text/plain", description=None, download_url="https://x") + item = SimulationItem(run_item_id="r1", message="hi", turn_id="t1", files=[fd]) + assert len(item.files) == 1 + + +class TestConversationResponse: + """Tests for the ConversationResponse dataclass.""" + + def test_stop_decision(self) -> None: + resp = ConversationResponse(decision=ConversationStatus.STOP, reason="done") + assert resp.decision == ConversationStatus.STOP + assert resp.reason == "done" + + def test_continue_decision_defaults(self) -> None: + resp = ConversationResponse(decision=ConversationStatus.CONTINUE) + assert resp.next_turn_id is None + assert resp.next_user_message is None + assert resp.next_files == [] + + +class TestTaskResult: + """Tests for the TaskResult frozen dataclass.""" + + def test_creation(self) -> None: + tr = TaskResult(message="hello", session_id="s1") + assert tr.message == "hello" + assert tr.session_id == "s1" + + def test_frozen(self) -> None: + tr = TaskResult(message="hello", session_id="s1") + with pytest.raises(AttributeError): + tr.message = "bye" # type: ignore[misc] + + +# --------------------------------------------------------------------------- +# Section 2: Utils +# --------------------------------------------------------------------------- + + +class TestParseEnvFloat: + """Tests for parse_env_float.""" + + def test_returns_default_when_unset(self) -> None: + assert parse_env_float("_NETRA_TEST_NONEXISTENT_VAR_", 42.0) == 42.0 + + def test_parses_valid_value(self, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("_NETRA_TEST_FLOAT_", "3.14") + assert parse_env_float("_NETRA_TEST_FLOAT_", 1.0) == pytest.approx(3.14) + + def test_returns_default_on_invalid(self, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("_NETRA_TEST_FLOAT_", "not-a-number") + assert parse_env_float("_NETRA_TEST_FLOAT_", 7.0) == 7.0 + + +class TestFormatTraceId: + """Tests for format_trace_id.""" + + def test_zero(self) -> None: + assert format_trace_id(0) == "0" * 32 + + def test_known_value(self) -> None: + result = format_trace_id(255) + assert result == "0" * 30 + "ff" + assert len(result) == 32 + + +class TestValidateSimulationInputs: + """Tests for validate_simulation_inputs.""" + + def test_valid(self) -> None: + assert validate_simulation_inputs("ds-1", SyncTask()) is True + + def test_empty_dataset_id(self) -> None: + assert validate_simulation_inputs("", SyncTask()) is False + + def test_wrong_task_type(self) -> None: + assert validate_simulation_inputs("ds-1", "not a task") is False # type: ignore[arg-type] + + +class TestRunAsyncSafely: + """Tests for run_async_safely.""" + + def test_runs_coroutine(self) -> None: + async def coro() -> int: + return 42 + + assert run_async_safely(coro()) == 42 + + def test_propagates_exception(self) -> None: + async def coro() -> None: + raise ValueError("boom") + + with pytest.raises(ValueError, match="boom"): + run_async_safely(coro()) + + +class TestProcessFiles: + """Tests for process_files.""" + + def test_empty_list(self) -> None: + assert process_files([]) == [] + + @patch("netra.simulation.utils.httpx.get") + def test_downloads_and_encodes(self, mock_get: MagicMock) -> None: + raw_content = b"hello world" + mock_response = MagicMock(spec=httpx.Response) + mock_response.content = raw_content + mock_response.raise_for_status = MagicMock() + mock_get.return_value = mock_response + + fd = FileData(file_name="a.txt", content_type="text/plain", description=None, download_url="https://x/a.txt") + result = process_files([fd]) + + assert len(result) == 1 + assert result[0].file_name == "a.txt" + assert result[0].data == base64.b64encode(raw_content).decode("ascii") + + @patch("netra.simulation.utils.httpx.get") + def test_raises_on_download_failure(self, mock_get: MagicMock) -> None: + mock_get.side_effect = httpx.ConnectError("connection refused") + + fd = FileData(file_name="a.txt", content_type="text/plain", description=None, download_url="https://x/a.txt") + with pytest.raises(RuntimeError, match="Failed to download file 'a.txt'"): + process_files([fd]) + + @patch("netra.simulation.utils.httpx.get") + def test_concurrent_downloads(self, mock_get: MagicMock) -> None: + mock_response = MagicMock(spec=httpx.Response) + mock_response.content = b"data" + mock_response.raise_for_status = MagicMock() + mock_get.return_value = mock_response + + files = [ + FileData(file_name=f"f{i}.txt", content_type="text/plain", description=None, download_url=f"https://x/{i}") + for i in range(3) + ] + result = process_files(files) + assert len(result) == 3 + assert mock_get.call_count == 3 + + +class TestExecuteTaskFiles: + """Tests for file handling in execute_task.""" + + @patch("netra.simulation.utils.process_files", return_value=[]) + def test_files_downloaded_when_raw_files_present(self, mock_pf: MagicMock) -> None: + """Files are always downloaded and passed to the task.""" + fd = FileData(file_name="a.txt", content_type="text/plain", description=None, download_url="https://x/a.txt") + result = asyncio.run(execute_task(FileAwareTask(), "hi", None, raw_files=[fd])) + mock_pf.assert_called_once_with([fd]) + assert result[0] == "files=0" + + def test_no_files_passed_as_none(self) -> None: + """When no raw_files are provided, files=None is passed to the task.""" + msg, sid = asyncio.run(execute_task(SyncTask(), "hi", None, raw_files=None)) + assert msg == "echo: hi" + + @patch("netra.simulation.utils.process_files") + def test_empty_raw_files_skips_download(self, mock_pf: MagicMock) -> None: + """An empty raw_files list should not trigger downloads.""" + asyncio.run(execute_task(SyncTask(), "hi", None, raw_files=[])) + mock_pf.assert_not_called() + + +class TestExecuteTask: + """Tests for execute_task.""" + + def test_sync_task(self) -> None: + msg, sid = asyncio.run(execute_task(SyncTask(), "hello", None)) + assert msg == "echo: hello" + assert sid == "sid-1" + + def test_async_task(self) -> None: + msg, sid = asyncio.run(execute_task(AsyncTask(), "hello", None)) + assert msg == "async-echo: hello" + assert sid == "sid-async" + + def test_raises_on_bad_return_type(self) -> None: + class BadTask(BaseTask): + def run( + self, + message: str, + session_id: Optional[str] = None, + files: Optional[list[ProcessedFile]] = None, + ) -> Any: + return "not a TaskResult" + + with pytest.raises(ValueError, match="Task must return TaskResult"): + asyncio.run(execute_task(BadTask(), "x", None)) + + +# --------------------------------------------------------------------------- +# Section 3: Client +# --------------------------------------------------------------------------- + + +class TestSimulationHttpClient: + """Tests for SimulationHttpClient.""" + + def _make_config(self, endpoint: str = "https://api.getnetra.ai/telemetry", api_key: str = "key-1") -> MagicMock: + """Create a mock Config.""" + cfg = MagicMock() + cfg.otlp_endpoint = endpoint + cfg.api_key = api_key + cfg.headers = {} + return cfg + + def test_create_client_with_valid_config(self) -> None: + from netra.simulation.client import SimulationHttpClient + + client = SimulationHttpClient(self._make_config()) + assert client._client is not None + client.close() + + def test_create_client_strips_telemetry_suffix(self) -> None: + from netra.simulation.client import SimulationHttpClient + + client = SimulationHttpClient(self._make_config(endpoint="https://api.getnetra.ai/telemetry")) + assert client._client is not None + assert "/telemetry" not in str(client._client.base_url) + client.close() + + def test_create_client_returns_none_on_empty_endpoint(self) -> None: + from netra.simulation.client import SimulationHttpClient + + client = SimulationHttpClient(self._make_config(endpoint="")) + assert client._client is None + + def test_close_sets_client_to_none(self) -> None: + from netra.simulation.client import SimulationHttpClient + + client = SimulationHttpClient(self._make_config()) + assert client._client is not None + client.close() + assert client._client is None + + def test_close_idempotent(self) -> None: + from netra.simulation.client import SimulationHttpClient + + client = SimulationHttpClient(self._make_config()) + client.close() + client.close() + assert client._client is None + + def test_ensure_client_returns_none_when_not_initialized(self) -> None: + from netra.simulation.client import SimulationHttpClient + + client = SimulationHttpClient(self._make_config(endpoint="")) + assert client._ensure_client() is None + + def test_create_run_returns_none_without_client(self) -> None: + from netra.simulation.client import SimulationHttpClient + + client = SimulationHttpClient(self._make_config(endpoint="")) + assert client.create_run(name="test", dataset_id="ds-1") is None + + @patch("netra.simulation.client.httpx.Client") + def test_create_run_success(self, mock_client_cls: MagicMock) -> None: + from netra.simulation.client import SimulationHttpClient + + mock_response = MagicMock(spec=httpx.Response) + mock_response.json.return_value = { + "data": { + "id": "run-1", + "userMessages": [ + { + "testRunItemId": "item-1", + "userMessage": "hello", + "turnId": "turn-1", + "attachments": None, + } + ], + } + } + mock_response.raise_for_status = MagicMock() + + mock_instance = MagicMock() + mock_instance.post.return_value = mock_response + mock_client_cls.return_value = mock_instance + + client = SimulationHttpClient(self._make_config()) + result = client.create_run(name="test", dataset_id="ds-1") + + assert result is not None + assert result["run_id"] == "run-1" + assert len(result["simulation_items"]) == 1 + assert result["simulation_items"][0].message == "hello" + + @patch("netra.simulation.client.httpx.Client") + def test_create_run_returns_none_on_http_error(self, mock_client_cls: MagicMock) -> None: + from netra.simulation.client import SimulationHttpClient + + mock_instance = MagicMock() + mock_instance.post.side_effect = httpx.HTTPStatusError( + "Server Error", request=MagicMock(), response=MagicMock() + ) + mock_client_cls.return_value = mock_instance + + client = SimulationHttpClient(self._make_config()) + result = client.create_run(name="test", dataset_id="ds-1") + assert result is None + + @patch("netra.simulation.client.httpx.Client") + def test_trigger_conversation_stop(self, mock_client_cls: MagicMock) -> None: + from netra.simulation.client import SimulationHttpClient + + mock_response = MagicMock(spec=httpx.Response) + mock_response.json.return_value = { + "data": { + "decision": "stop", + "reason": "all done", + } + } + mock_response.raise_for_status = MagicMock() + + mock_instance = MagicMock() + mock_instance.post.return_value = mock_response + mock_client_cls.return_value = mock_instance + + client = SimulationHttpClient(self._make_config()) + resp = client.trigger_conversation(message="hi", turn_id="t1", session_id="s1", trace_id="trace") + + assert resp is not None + assert resp.decision == ConversationStatus.STOP + assert resp.reason == "all done" + + @patch("netra.simulation.client.httpx.Client") + def test_trigger_conversation_continue(self, mock_client_cls: MagicMock) -> None: + from netra.simulation.client import SimulationHttpClient + + mock_response = MagicMock(spec=httpx.Response) + mock_response.json.return_value = { + "data": { + "decision": "continue", + "userMessages": [ + { + "turnId": "turn-2", + "userMessage": "follow-up", + "testRunItemId": "item-2", + "attachments": None, + } + ], + } + } + mock_response.raise_for_status = MagicMock() + + mock_instance = MagicMock() + mock_instance.post.return_value = mock_response + mock_client_cls.return_value = mock_instance + + client = SimulationHttpClient(self._make_config()) + resp = client.trigger_conversation(message="hi", turn_id="t1", session_id="s1", trace_id="trace") + + assert resp is not None + assert resp.decision == ConversationStatus.CONTINUE + assert resp.next_turn_id == "turn-2" + assert resp.next_user_message == "follow-up" + + def test_trigger_conversation_returns_none_without_client(self) -> None: + from netra.simulation.client import SimulationHttpClient + + client = SimulationHttpClient(self._make_config(endpoint="")) + resp = client.trigger_conversation(message="hi", turn_id="t1", session_id="s1", trace_id="trace") + assert resp is None + + @patch("netra.simulation.client.httpx.Client") + def test_report_failure(self, mock_client_cls: MagicMock) -> None: + from netra.simulation.client import SimulationHttpClient + + mock_response = MagicMock(spec=httpx.Response) + mock_response.raise_for_status = MagicMock() + + mock_instance = MagicMock() + mock_instance.patch.return_value = mock_response + mock_client_cls.return_value = mock_instance + + client = SimulationHttpClient(self._make_config()) + client.report_failure(run_id="run-1", run_item_id="item-1", error="boom") + mock_instance.patch.assert_called_once() + + @patch("netra.simulation.client.httpx.Client") + def test_post_run_status_success(self, mock_client_cls: MagicMock) -> None: + from netra.simulation.client import SimulationHttpClient + + mock_response = MagicMock(spec=httpx.Response) + mock_response.json.return_value = {"data": {"status": "completed"}} + mock_response.raise_for_status = MagicMock() + + mock_instance = MagicMock() + mock_instance.post.return_value = mock_response + mock_client_cls.return_value = mock_instance + + client = SimulationHttpClient(self._make_config()) + result = client.post_run_status(run_id="run-1", status="completed") + assert result == {"status": "completed"} + + @patch("netra.simulation.client.httpx.Client") + def test_post_run_status_returns_error_on_failure(self, mock_client_cls: MagicMock) -> None: + from netra.simulation.client import SimulationHttpClient + + mock_instance = MagicMock() + mock_instance.post.side_effect = httpx.ConnectError("timeout") + mock_client_cls.return_value = mock_instance + + client = SimulationHttpClient(self._make_config()) + result = client.post_run_status(run_id="run-1", status="completed") + assert result == {"success": False} + + def test_parse_files_none(self) -> None: + from netra.simulation.client import SimulationHttpClient + + assert SimulationHttpClient._parse_files(None) == [] + + def test_parse_files_valid(self) -> None: + from netra.simulation.client import SimulationHttpClient + + raw = [{"fileName": "a.txt", "downloadUrl": "https://x/a", "contentType": "text/plain"}] + result = SimulationHttpClient._parse_files(raw) + assert len(result) == 1 + assert result[0].file_name == "a.txt" + + def test_parse_files_skips_malformed(self) -> None: + from netra.simulation.client import SimulationHttpClient + + raw = [{"fileName": "", "downloadUrl": "https://x/a"}] + result = SimulationHttpClient._parse_files(raw) + assert result == [] + + def test_extract_error_message_from_response(self) -> None: + from netra.simulation.client import SimulationHttpClient + + cfg = MagicMock() + cfg.otlp_endpoint = "" + cfg.api_key = "" + cfg.headers = {} + client = SimulationHttpClient(cfg) + + mock_response = MagicMock(spec=httpx.Response) + mock_response.json.return_value = {"error": {"message": "custom error"}} + result = client._extract_error_message(mock_response, ValueError("fallback")) + assert result == "custom error" + + def test_extract_error_message_fallback(self) -> None: + from netra.simulation.client import SimulationHttpClient + + cfg = MagicMock() + cfg.otlp_endpoint = "" + cfg.api_key = "" + cfg.headers = {} + client = SimulationHttpClient(cfg) + + result = client._extract_error_message(None, ValueError("fallback")) + assert result == "fallback" + + +# --------------------------------------------------------------------------- +# Section 4: API (Simulation class) +# --------------------------------------------------------------------------- + + +class TestSimulation: + """Tests for the Simulation public API.""" + + def _make_config(self) -> MagicMock: + cfg = MagicMock() + cfg.otlp_endpoint = "https://api.getnetra.ai/telemetry" + cfg.api_key = "key-1" + cfg.headers = {} + return cfg + + @patch("netra.simulation.api.SimulationHttpClient") + def test_run_simulation_returns_none_on_invalid_inputs(self, mock_client_cls: MagicMock) -> None: + from netra.simulation.api import Simulation + + sim = Simulation(self._make_config()) + result = sim.run_simulation(name="test", dataset_id="", task=SyncTask()) + assert result is None + + @patch("netra.simulation.api.SimulationHttpClient") + def test_run_simulation_returns_none_when_create_run_fails(self, mock_client_cls: MagicMock) -> None: + from netra.simulation.api import Simulation + + mock_client_cls.return_value.create_run.return_value = None + sim = Simulation(self._make_config()) + result = sim.run_simulation(name="test", dataset_id="ds-1", task=SyncTask()) + assert result is None + + @patch("netra.simulation.api.SpanWrapper") + @patch("netra.simulation.api.SimulationHttpClient") + def test_run_simulation_success(self, mock_client_cls: MagicMock, mock_span_wrapper: MagicMock) -> None: + from netra.simulation.api import Simulation + + mock_span = MagicMock() + mock_span.__enter__ = MagicMock(return_value=mock_span) + mock_span.__exit__ = MagicMock(return_value=False) + mock_span.get_current_span.return_value = None + mock_span_wrapper.return_value = mock_span + + stop_response = ConversationResponse( + decision=ConversationStatus.STOP, + reason="done", + ) + + mock_client = MagicMock() + mock_client.create_run.return_value = { + "run_id": "run-1", + "simulation_items": [ + SimulationItem(run_item_id="item-1", message="hello", turn_id="turn-1"), + ], + } + mock_client.trigger_conversation.return_value = stop_response + mock_client.post_run_status.return_value = {"status": "completed"} + mock_client_cls.return_value = mock_client + + sim = Simulation(self._make_config()) + result = sim.run_simulation(name="test", dataset_id="ds-1", task=SyncTask()) + + assert result is not None + assert result["total_items"] == 1 + assert len(result["completed"]) == 1 + assert len(result["failed"]) == 0 + + @patch("netra.simulation.api.SpanWrapper") + @patch("netra.simulation.api.SimulationHttpClient") + def test_run_simulation_marks_failed_on_exception( + self, mock_client_cls: MagicMock, mock_span_wrapper: MagicMock + ) -> None: + from netra.simulation.api import Simulation + + mock_span = MagicMock() + mock_span.__enter__ = MagicMock(return_value=mock_span) + mock_span.__exit__ = MagicMock(return_value=False) + mock_span.get_current_span.return_value = None + mock_span_wrapper.return_value = mock_span + + mock_client = MagicMock() + mock_client.create_run.return_value = { + "run_id": "run-1", + "simulation_items": [ + SimulationItem(run_item_id="item-1", message="hello", turn_id="turn-1"), + ], + } + mock_client.trigger_conversation.side_effect = RuntimeError("backend down") + mock_client.post_run_status.return_value = {} + mock_client_cls.return_value = mock_client + + sim = Simulation(self._make_config()) + result = sim.run_simulation(name="test", dataset_id="ds-1", task=SyncTask()) + + assert result is not None + assert len(result["failed"]) == 1 + assert result["failed"][0]["error"] == "backend down" + + @patch("netra.simulation.api.SpanWrapper") + @patch("netra.simulation.api.SimulationHttpClient") + def test_max_turns_guard(self, mock_client_cls: MagicMock, mock_span_wrapper: MagicMock) -> None: + from netra.simulation.api import Simulation + + mock_span = MagicMock() + mock_span.__enter__ = MagicMock(return_value=mock_span) + mock_span.__exit__ = MagicMock(return_value=False) + mock_span.get_current_span.return_value = None + mock_span_wrapper.return_value = mock_span + + continue_response = ConversationResponse( + decision=ConversationStatus.CONTINUE, + next_turn_id="turn-next", + next_user_message="keep going", + ) + + mock_client = MagicMock() + mock_client.create_run.return_value = { + "run_id": "run-1", + "simulation_items": [ + SimulationItem(run_item_id="item-1", message="hello", turn_id="turn-1"), + ], + } + mock_client.trigger_conversation.return_value = continue_response + mock_client.post_run_status.return_value = {} + mock_client_cls.return_value = mock_client + + sim = Simulation(self._make_config()) + result = sim.run_simulation(name="test", dataset_id="ds-1", task=SyncTask(), max_turns=3) + + assert result is not None + assert len(result["failed"]) == 1 + assert "Exceeded maximum turns (3)" in result["failed"][0]["error"] + + @patch("netra.simulation.api.SimulationHttpClient") + def test_close_delegates_to_client(self, mock_client_cls: MagicMock) -> None: + from netra.simulation.api import Simulation + + mock_client = MagicMock() + mock_client_cls.return_value = mock_client + + sim = Simulation(self._make_config()) + sim.close() + mock_client.close.assert_called_once() + + @patch("netra.simulation.api.SpanWrapper") + @patch("netra.simulation.api.SimulationHttpClient") + def test_trigger_conversation_none_response(self, mock_client_cls: MagicMock, mock_span_wrapper: MagicMock) -> None: + from netra.simulation.api import Simulation + + mock_span = MagicMock() + mock_span.__enter__ = MagicMock(return_value=mock_span) + mock_span.__exit__ = MagicMock(return_value=False) + mock_span.get_current_span.return_value = None + mock_span_wrapper.return_value = mock_span + + mock_client = MagicMock() + mock_client.create_run.return_value = { + "run_id": "run-1", + "simulation_items": [ + SimulationItem(run_item_id="item-1", message="hello", turn_id="turn-1"), + ], + } + mock_client.trigger_conversation.return_value = None + mock_client.post_run_status.return_value = {} + mock_client_cls.return_value = mock_client + + sim = Simulation(self._make_config()) + result = sim.run_simulation(name="test", dataset_id="ds-1", task=SyncTask()) + + assert result is not None + assert len(result["failed"]) == 1 + assert "Failed to get conversation response" in result["failed"][0]["error"] + + +# --------------------------------------------------------------------------- +# Section 5: BaseTask +# --------------------------------------------------------------------------- + + +class TestBaseTask: + """Tests for BaseTask abstract class.""" + + def test_cannot_instantiate_directly(self) -> None: + with pytest.raises(TypeError): + BaseTask() # type: ignore[abstract] + + def test_sync_subclass(self) -> None: + task = SyncTask() + result = task.run(message="hi") + assert isinstance(result, TaskResult) + assert result.message == "echo: hi" + + def test_async_subclass(self) -> None: + task = AsyncTask() + result = asyncio.run(task.run(message="hi")) # type: ignore[arg-type] + assert isinstance(result, TaskResult) + assert result.message == "async-echo: hi"