diff --git a/.gitignore b/.gitignore index 01fe8e22..2d298400 100644 --- a/.gitignore +++ b/.gitignore @@ -229,3 +229,5 @@ local_settings.py Dockerfile CLAUDE.md .omc/ +.deepeval/ +e2e_tests/ diff --git a/pyproject.toml b/pyproject.toml index e9a2dadf..1a700b76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -150,7 +150,7 @@ dev = [ "websockets>=14.1", "wheel>=0.45.1", "strands-agents>=1.20.0", - "strands-agents-evals>=0.1.0", + "strands-agents-evals>=1.0.3,<2.0.0", "langchain>=1.0.0", "langgraph>=1.0.0", "langchain-mcp-adapters>=0.1.0", @@ -174,15 +174,23 @@ langgraph = [ "httpx>=0.27.0", ] strands-agents-evals = [ - "strands-agents-evals>=0.1.0" + "strands-agents-evals>=1.0.3,<2.0.0" ] simulation = [ "jinja2>=3.1.0", - "strands-agents-evals>=0.1.0", + "strands-agents-evals>=1.0.3,<2.0.0", ] datasets = [ "requests>=2.31.0", ] +deepeval = [ + "deepeval>=2.0.0", + "strands-agents-evals>=1.0.3,<2.0.0", +] +autoevals = [ + "autoevals>=0.0.50", + "strands-agents-evals>=1.0.3,<2.0.0", +] [tool.uv] conflicts = [ diff --git a/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/models.py b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/models.py index c876b145..7d17b1cf 100644 --- a/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/models.py +++ b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/models.py @@ -82,6 +82,7 @@ class EvaluatorOutput(BaseModel): def _require_label_or_error_code(self) -> "EvaluatorOutput": if not self.errorCode and self.label is None: raise ValueError( - "label is required for success responses; set errorCode to return an error response without a label" + "Either label, value, or errorCode must be set; " + "set errorCode to return an error response without a label" ) return self diff --git a/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/__init__.py b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/__init__.py new file mode 100644 index 00000000..06ba3d0a --- /dev/null +++ b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/__init__.py @@ -0,0 +1,5 @@ +"""Third-party evaluation adapters for AgentCore code-based evaluators.""" + +from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.base import BaseAdapter + +__all__ = ["BaseAdapter"] diff --git a/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/autoevals/__init__.py b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/autoevals/__init__.py new file mode 100644 index 00000000..40337027 --- /dev/null +++ b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/autoevals/__init__.py @@ -0,0 +1,5 @@ +"""AutoEvals adapter for AgentCore code-based evaluators.""" + +from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.autoevals.adapter import AutoEvalsAdapter + +__all__ = ["AutoEvalsAdapter"] diff --git a/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/autoevals/adapter.py b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/autoevals/adapter.py new file mode 100644 index 00000000..5b606753 --- /dev/null +++ b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/autoevals/adapter.py @@ -0,0 +1,97 @@ +"""Autoevals adapter for AgentCore code-based evaluators.""" + +import logging +from typing import Any, Callable, Dict, Optional + +from bedrock_agentcore.evaluation.custom_code_based_evaluators.models import EvaluatorInput, EvaluatorOutput +from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.base import BaseAdapter + +logger = logging.getLogger(__name__) + + +class AutoEvalsAdapter(BaseAdapter): + """Adapter that runs an Autoevals scorer against AgentCore evaluation events. + + Example (default span mapping):: + + from autoevals import Factuality + from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.autoevals import AutoEvalsAdapter + + scorer = Factuality() + adapter = AutoEvalsAdapter(metric=scorer) + + Example (custom mapper returning eval kwargs):: + + from typing import Dict, Any + + def my_mapper(ev: EvaluatorInput) -> Dict[str, Any]: + return { + "input": ev.session_spans[0]["attributes"]["question"], + "output": ev.session_spans[0]["attributes"]["answer"], + "expected": "the expected answer", + } + + adapter = AutoEvalsAdapter( + metric=Factuality(), + custom_mapper=my_mapper, + ) + """ + + def __init__( + self, + metric: Any, + custom_mapper: Optional[Callable[[EvaluatorInput], Dict[str, Any]]] = None, + threshold: Optional[float] = None, + ): + """Initialize the adapter. + + Args: + metric: An Autoevals scorer instance (e.g. Factuality(), ClosedQA()). + custom_mapper: Optional callable that receives the EvaluatorInput and + returns a dict of kwargs for metric.eval(). Bypasses default span + mapping when provided. Expected keys: input, output, expected (optional). + threshold: Optional score threshold for Pass/Fail label. If None, label + is omitted from the output. + """ + self.metric = metric + self.custom_mapper = custom_mapper + self.threshold = threshold + + def _run(self, evaluator_input: EvaluatorInput) -> EvaluatorOutput: + """Run the Autoevals scorer pipeline.""" + if self.custom_mapper is not None: + kwargs = self.custom_mapper(evaluator_input) + else: + result = self._default_extract(evaluator_input) + if not result.input or not result.actual_output: + missing = [] + if not result.input: + missing.append("input") + if not result.actual_output: + missing.append("actual_output") + metric_name = type(self.metric).__name__ + return EvaluatorOutput( + errorCode="MISSING_REQUIRED_FIELD", + errorMessage=f"Field(s) {missing} required by {metric_name} but not found in evaluation event. " + f"Provide a custom_mapper or ensure spans contain the necessary data.", + ) + kwargs: Dict[str, Any] = { + "input": result.input, + "output": result.actual_output, + } + if result.expected_output: + kwargs["expected"] = result.expected_output + elif result.assertions: + kwargs["expected"] = "\n".join(result.assertions) + if result.retrieval_context: + kwargs["context"] = "\n".join(result.retrieval_context) + + eval_result = self.metric.eval(**kwargs) + + score = eval_result.score + threshold = self.threshold if self.threshold is not None else 0.5 + label = "Pass" if score is not None and score >= threshold else "Fail" + metadata = getattr(eval_result, "metadata", None) + explanation = metadata.get("rationale", "") if isinstance(metadata, dict) else "" + + return EvaluatorOutput(value=score, label=label, explanation=explanation) diff --git a/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/base.py b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/base.py new file mode 100644 index 00000000..4b362d09 --- /dev/null +++ b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/base.py @@ -0,0 +1,93 @@ +"""Base adapter for third-party evaluation framework integrations.""" + +import abc +import logging +from typing import Any, Dict, List + +from bedrock_agentcore.evaluation.custom_code_based_evaluators.models import EvaluatorInput, EvaluatorOutput +from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.span_mappers import ( + SpanMapResult, + map_spans, +) +from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.span_mappers.common import ( + FieldExtractionError, +) + +logger = logging.getLogger(__name__) + + +class BaseAdapter(abc.ABC): + """Base adapter for third-party evaluation framework integrations. + + Accepts an EvaluatorInput (from the code_based_evaluators flow), + extracts fields from spans using the built-in mapper layer, runs the + evaluation via execute(), and returns an EvaluatorOutput. + + Never raises unhandled exceptions — always returns a valid EvaluatorOutput. + """ + + def __call__(self, evaluator_input: EvaluatorInput, context: Any = None) -> EvaluatorOutput: + """Handle an evaluation invocation. + + Args: + evaluator_input: Parsed EvaluatorInput from the code-based evaluator flow. + context: Lambda context object (unused). + + Returns: + EvaluatorOutput with score, label, and explanation or error fields. + """ + try: + return self._run(evaluator_input) + except FieldExtractionError as e: + logger.error("Field extraction failed: %s", e) + return EvaluatorOutput( + errorCode="FIELD_EXTRACTION_ERROR", + errorMessage=str(e), + ) + except Exception as e: + logger.exception("Execution failed: %s", e) + return EvaluatorOutput( + errorCode="METRIC_ERROR", + errorMessage=f"{type(self).__name__} failed: {e}", + ) + + @abc.abstractmethod + def _run(self, evaluator_input: EvaluatorInput) -> EvaluatorOutput: + """Run the full evaluation pipeline. Subclasses implement this.""" + + def _default_extract(self, evaluator_input: EvaluatorInput) -> SpanMapResult: + """Extract fields using the built-in span mapper layer.""" + spans = self._filter_spans_by_target(evaluator_input) + return map_spans(spans, evaluator_input.reference_inputs, evaluator_input.target_trace_id) + + def _filter_spans_by_target(self, evaluator_input: EvaluatorInput) -> List[Dict]: + """Filter session spans based on evaluationLevel and evaluationTarget. + + The service passes ALL session spans in every Lambda invocation without + pre-filtering. It fans out one Lambda call per evaluation target (one per + trace at TRACE level, one per span at TOOL_CALL level) and provides the + target ID so the Lambda can scope its evaluation. We filter here because + the service-side _invoke_single_eval_target passes session_spans directly + into the payload without filtering. + + Levels: + - SESSION: all spans (no filtering) — one call for the entire session + - TRACE: only spans matching target_trace_id (service sends exactly one) + - TOOL_CALL: only the span matching target_span_id (service sends exactly one) + """ + spans = evaluator_input.session_spans + + if evaluator_input.evaluation_level == "TRACE" and evaluator_input.target_trace_id: + spans = [s for s in spans if s.get("traceId") == evaluator_input.target_trace_id] + elif evaluator_input.evaluation_level == "TOOL_CALL" and evaluator_input.target_span_id: + # Include both the target tool span AND its parent agent span. + # ToolCorrectness needs input/actual_output from the agent span + # plus tools_called from the tool span. + target_span = next((s for s in spans if s.get("spanId") == evaluator_input.target_span_id), None) + if target_span: + parent_id = target_span.get("parentSpanId") or target_span.get("parent_span_id") + spans = [s for s in spans if s.get("spanId") in (evaluator_input.target_span_id, parent_id)] + else: + spans = [s for s in spans if s.get("spanId") == evaluator_input.target_span_id] + + return spans diff --git a/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/deepeval/__init__.py b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/deepeval/__init__.py new file mode 100644 index 00000000..99cf10d5 --- /dev/null +++ b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/deepeval/__init__.py @@ -0,0 +1,5 @@ +"""DeepEval adapter for AgentCore code-based evaluators.""" + +from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.deepeval.adapter import DeepEvalAdapter + +__all__ = ["DeepEvalAdapter"] diff --git a/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/deepeval/adapter.py b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/deepeval/adapter.py new file mode 100644 index 00000000..d75ecd9d --- /dev/null +++ b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/deepeval/adapter.py @@ -0,0 +1,159 @@ +"""DeepEval adapter for AgentCore code-based evaluators.""" + +import logging +from typing import Any, Callable, Dict, List, Optional + +from deepeval.errors import MissingTestCaseParamsError +from deepeval.metrics import BaseConversationalMetric, BaseMetric +from deepeval.test_case import ConversationalTestCase, LLMTestCase, ToolCall +from deepeval.test_case.conversational_test_case import Turn + +from bedrock_agentcore.evaluation.custom_code_based_evaluators.models import EvaluatorInput, EvaluatorOutput +from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.base import BaseAdapter + +logger = logging.getLogger(__name__) + + +class DeepEvalAdapter(BaseAdapter): + """Adapter that runs a DeepEval metric against AgentCore evaluation events. + + Example (default span mapping):: + + from deepeval.metrics import AnswerRelevancyMetric + from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.deepeval import DeepEvalAdapter + + metric = AnswerRelevancyMetric(threshold=0.7) + adapter = DeepEvalAdapter(metric=metric) + + Example (custom mapper returning LLMTestCase):: + + from deepeval.test_case import LLMTestCase + + def my_mapper(ev: EvaluatorInput) -> LLMTestCase: + return LLMTestCase( + input=ev.session_spans[0]["attributes"]["user_query"], + actual_output=ev.session_spans[0]["attributes"]["response"], + ) + + adapter = DeepEvalAdapter( + metric=AnswerRelevancyMetric(threshold=0.7), + custom_mapper=my_mapper, + ) + """ + + def __init__( + self, + metric: BaseMetric, + custom_mapper: Optional[Callable[[EvaluatorInput], LLMTestCase]] = None, + ): + """Initialize the adapter. + + Args: + metric: A DeepEval BaseMetric instance (e.g. AnswerRelevancyMetric). + custom_mapper: Optional callable that receives the EvaluatorInput and + returns a LLMTestCase. Bypasses default span mapping when provided. + """ + self.metric = metric + self.custom_mapper = custom_mapper + + def _run(self, evaluator_input: EvaluatorInput) -> EvaluatorOutput: + """Run the DeepEval metric pipeline.""" + if self.custom_mapper is not None: + test_case = self.custom_mapper(evaluator_input) + elif isinstance(self.metric, BaseConversationalMetric): + test_case = self._build_conversational_test_case(evaluator_input) + else: + result = self._default_extract(evaluator_input) + if not result.input or not result.actual_output: + missing = [] + if not result.input: + missing.append("input") + if not result.actual_output: + missing.append("actual_output") + metric_name = type(self.metric).__name__ + return EvaluatorOutput( + errorCode="MISSING_REQUIRED_FIELD", + errorMessage=f"Field(s) {missing} required by {metric_name} but not found in evaluation event. " + f"Provide a custom_mapper or ensure spans contain the necessary data.", + ) + # context = assertions if provided, otherwise fall back to retrieval_context + # (HallucinationMetric uses context as ground truth to check against) + context = result.assertions if result.assertions else result.retrieval_context + test_case = LLMTestCase( + input=result.input, + actual_output=result.actual_output, + expected_output=result.expected_output, + context=context, + retrieval_context=result.retrieval_context, + tools_called=self._build_tool_calls(result.tools_called) if result.tools_called else None, + expected_tools=self._build_tool_calls(result.expected_tools) if result.expected_tools else None, + ) + + try: + self.metric.measure(test_case) + except MissingTestCaseParamsError as e: + return EvaluatorOutput( + errorCode="MISSING_REQUIRED_FIELD", + errorMessage=f"{type(self.metric).__name__} requires fields not extracted from spans: {e}. " + f"Provide a custom_mapper to supply custom fields from your trace data.", + ) + except Exception: + raise + + score = self.metric.score + reason = getattr(self.metric, "reason", None) or "" + threshold = getattr(self.metric, "threshold", 0.5) + success = getattr(self.metric, "success", score is not None and score >= threshold) + label = "Pass" if success else "Fail" + + return EvaluatorOutput(value=score, label=label, explanation=reason) + + def _build_conversational_test_case(self, evaluator_input: EvaluatorInput) -> ConversationalTestCase: + """Build a ConversationalTestCase for multi-turn metrics. + + Extracts all agent invocation turns from the session spans and + constructs alternating user/assistant Turn objects. + """ + from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.span_mappers.common import ( + FieldExtractionError, + ) + + result = self._default_extract(evaluator_input) + if not result.turns: + raise FieldExtractionError( + "Multi-turn metric requires multiple conversation turns but only a single turn was found. " + "Ensure the session contains multiple traces/invocations, or use a single-turn metric instead." + ) + turns = [Turn(role=t["role"], content=t["content"]) for t in result.turns] + + # Build optional fields for conversational metrics + chatbot_role = result.system_prompt or "A helpful AI assistant" + expected_outcome = None + if evaluator_input.reference_inputs: + ref = evaluator_input.reference_inputs[0] + expected = getattr(ref, "expected_response_text", None) + if expected: + expected_outcome = expected + + return ConversationalTestCase( + turns=turns, + chatbot_role=chatbot_role, + expected_outcome=expected_outcome, + ) + + @staticmethod + def _build_tool_calls(tools_called: List[Dict[str, Any]]) -> List[ToolCall]: + """Convert extracted tool call dicts to DeepEval ToolCall objects.""" + result: List[ToolCall] = [] + for tc in tools_called: + name = tc.get("name", "") + if not name: + continue + result.append( + ToolCall( + name=name, + input_parameters=tc.get("input_parameters"), + output=tc.get("output"), + ) + ) + return result diff --git a/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/span_mappers/__init__.py b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/span_mappers/__init__.py new file mode 100644 index 00000000..2b3c7b3f --- /dev/null +++ b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/span_mappers/__init__.py @@ -0,0 +1,17 @@ +"""Span mappers for extracting evaluation fields from Agent SDK trace formats. + +Uses strands-evals mappers for span format auto-detection and extraction, +bridged to SpanMapResult for adapter consumption. +""" + +from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.span_mappers.common import ( + SpanMapResult, +) +from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.span_mappers.registry import ( + map_spans, +) + +__all__ = [ + "SpanMapResult", + "map_spans", +] diff --git a/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/span_mappers/common.py b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/span_mappers/common.py new file mode 100644 index 00000000..a0c8b0e6 --- /dev/null +++ b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/span_mappers/common.py @@ -0,0 +1,24 @@ +"""Common span mapping types and utilities.""" + +from dataclasses import dataclass +from typing import Any, Dict, List, Optional + + +class FieldExtractionError(ValueError): + """Raised when span field extraction fails (no valid AgentInvocationSpan, unsupported scope, etc.).""" + + +@dataclass +class SpanMapResult: + """Extraction result from spans — only what metrics consume.""" + + input: Optional[str] = None + actual_output: Optional[str] = None + retrieval_context: Optional[List[str]] = None + context: Optional[List[str]] = None + system_prompt: Optional[str] = None + expected_output: Optional[str] = None + tools_called: Optional[List[Dict[str, Any]]] = None + expected_tools: Optional[List[Dict[str, Any]]] = None + assertions: Optional[List[str]] = None + turns: Optional[List[Dict[str, Any]]] = None diff --git a/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/span_mappers/registry.py b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/span_mappers/registry.py new file mode 100644 index 00000000..7db36009 --- /dev/null +++ b/src/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/span_mappers/registry.py @@ -0,0 +1,230 @@ +"""Span mapping orchestration — uses strands-evals mappers with auto-detection.""" + +import logging +import warnings +from typing import Any, Dict, List, Optional + +from strands_evals.mappers import detect_otel_mapper +from strands_evals.types.trace import AgentInvocationSpan, Session, ToolExecutionSpan + +from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.span_mappers.common import ( + FieldExtractionError, + SpanMapResult, +) + +logger = logging.getLogger(__name__) + + +def _extract_session_id(session_spans: List[Dict[str, Any]]) -> str: + """Extract session ID from span attributes.""" + for span in session_spans: + attrs = span.get("attributes", {}) + if isinstance(attrs, dict): + session_id = attrs.get("session.id") + if session_id: + return session_id + return "default" + + +def _detect_mapper(session_spans: List[Dict[str, Any]]): + """Detect the appropriate mapper using strands-evals auto-detection. + + When the service sends normalized dict spans (InMemory format without body), + StrandsInMemorySessionMapper expects ReadableSpan objects and fails on dicts. + In this case, fall back to CloudWatchSessionMapper which handles dicts. + """ + from strands_evals.mappers import CloudWatchSessionMapper, StrandsInMemorySessionMapper + + mapper = detect_otel_mapper(session_spans) + if isinstance(mapper, StrandsInMemorySessionMapper) and session_spans and isinstance(session_spans[0], dict): + return CloudWatchSessionMapper() + return mapper + + +def map_spans( + session_spans: List[Dict[str, Any]], + reference_inputs: Optional[List[Any]] = None, + target_trace_id: Optional[str] = None, +) -> SpanMapResult: + """Map session spans to evaluation fields using strands-evals mappers. + + Auto-detects the span format (Strands, OpenInference, OpenTelemetry LangChain) + and delegates to the appropriate strands-evals mapper, then bridges the result + to SpanMapResult for adapter consumption. + + Args: + session_spans: Raw ADOT span dicts from the evaluation service. + reference_inputs: Optional ReferenceInput list for expected_output/tools/assertions. + target_trace_id: Optional trace ID to match reference_inputs by spanContext. + + Returns: + SpanMapResult with extracted fields. + + Raises: + ValueError: If no mapper can extract data from the spans. + """ + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning, module="strands_evals") + mapper = _detect_mapper(session_spans) + + session_id = _extract_session_id(session_spans) + + try: + session = mapper.map_to_session(session_spans, session_id=session_id) + except Exception as e: + raise FieldExtractionError( + f"Could not extract evaluation fields from spans using {type(mapper).__name__}: {e}. " + f"Provide a custom_mapper for custom or unsupported span formats." + ) from e + + try: + result = _session_to_span_map_result(session) + except FieldExtractionError: + # Mapper couldn't find AgentInvocationSpan — try service format fallback + result = None + + # Fallback: extract from service-normalized format (gen_ai events) + if result is None or not result.input or not result.actual_output: + service_result = _extract_from_service_format(session_spans) + if service_result: + result = service_result + elif result is None: + raise FieldExtractionError( + "No AgentInvocationSpan found in session and service format extraction failed. " + "Provide a custom_mapper for custom or unsupported span formats." + ) + + if reference_inputs: + # Combine all relevant reference_inputs (session-level + trace-level). + # A reference is relevant if it has no traceId (session-scoped) or its + # traceId matches the target trace. + for ref in reference_inputs: + ctx = getattr(ref, "context", None) + if isinstance(ctx, dict): + span_ctx = ctx.get("spanContext", {}) + ref_trace = span_ctx.get("traceId") if isinstance(span_ctx, dict) else None + else: + ref_trace = None + if ref_trace and target_trace_id and ref_trace != target_trace_id: + continue + + expected = getattr(ref, "expected_response_text", None) + if expected and not result.expected_output: + result.expected_output = expected + trajectory = getattr(ref, "expected_trajectory", None) + if isinstance(trajectory, dict) and not result.expected_tools: + tool_names = trajectory.get("toolNames") + if isinstance(tool_names, list) and tool_names: + result.expected_tools = [{"name": name} for name in tool_names if isinstance(name, str)] + assertions = getattr(ref, "assertions", None) + if isinstance(assertions, list) and assertions and not result.assertions: + assertion_texts = [a.get("text") for a in assertions if isinstance(a, dict) and a.get("text")] + if assertion_texts: + result.assertions = assertion_texts + + return result + + +def _extract_from_service_format(session_spans: List[Dict[str, Any]]) -> Optional[SpanMapResult]: + """Extract fields from service-normalized span format. + + The AgentCore evaluation service sends spans with gen_ai semantic convention + events (gen_ai.user.message, gen_ai.choice) instead of body with input/output. + This handles that format as a fallback when strands-evals mappers can't parse it. + """ + import json as _json + + for span in session_spans: + scope = span.get("scope", {}).get("name", "") + events = span.get("events", []) + if "strands" not in scope or not events: + continue + + user_input = None + assistant_output = None + system_prompt = None + + for event in events: + event_name = event.get("name", "") + attrs = event.get("attributes", {}) + content = attrs.get("content", "") + + if event_name == "gen_ai.user.message" and content: + try: + parts = _json.loads(content) + user_input = " ".join(p.get("text", "") for p in parts if isinstance(p, dict)).strip() + except (ValueError, TypeError): + user_input = content + elif event_name == "gen_ai.choice" and attrs.get("message"): + try: + parts = _json.loads(attrs["message"]) + assistant_output = " ".join(p.get("text", "") for p in parts if isinstance(p, dict)).strip() + except (ValueError, TypeError): + assistant_output = attrs["message"] + elif event_name == "gen_ai.system.message" and content: + try: + parts = _json.loads(content) + system_prompt = " ".join(p.get("text", "") for p in parts if isinstance(p, dict)).strip() + except (ValueError, TypeError): + system_prompt = content + + if user_input and assistant_output: + return SpanMapResult( + input=user_input, + actual_output=assistant_output, + system_prompt=system_prompt, + ) + + return None + + +def _session_to_span_map_result(session: Session) -> SpanMapResult: + """Bridge strands-evals Session to SpanMapResult. + + Extracts the last AgentInvocationSpan for input/output (single-turn), + all ToolExecutionSpans for retrieval_context and tools_called, and + all AgentInvocationSpans as turns (multi-turn / session-level). + """ + agent_span = None + agent_spans: List[AgentInvocationSpan] = [] + tool_spans: List[ToolExecutionSpan] = [] + + for trace in session.traces: + for span in trace.spans: + if isinstance(span, AgentInvocationSpan): + agent_span = span + agent_spans.append(span) + elif isinstance(span, ToolExecutionSpan): + tool_spans.append(span) + + if agent_span is None: + raise FieldExtractionError( + "No AgentInvocationSpan found in session. Provide a custom_mapper for custom or unsupported span formats." + ) + + retrieval_context = [ts.tool_result.content for ts in tool_spans if ts.tool_result and ts.tool_result.content] + tools_called = [ + { + "name": ts.tool_call.name, + "input_parameters": ts.tool_call.arguments if ts.tool_call.arguments else None, + "output": ts.tool_result.content if ts.tool_result else None, + } + for ts in tool_spans + if ts.tool_call and ts.tool_call.name + ] + + # Build turns for multi-turn / session-level evaluation + turns = [] + for span in agent_spans: + turns.append({"role": "user", "content": span.user_prompt}) + turns.append({"role": "assistant", "content": span.agent_response}) + + return SpanMapResult( + input=agent_span.user_prompt, + actual_output=agent_span.agent_response, + retrieval_context=retrieval_context if retrieval_context else None, + context=None, + system_prompt=agent_span.system_prompt, + tools_called=tools_called if tools_called else None, + turns=turns if len(turns) > 2 else None, + ) diff --git a/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/__init__.py b/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/autoevals/__init__.py b/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/autoevals/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/autoevals/test_adapter.py b/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/autoevals/test_adapter.py new file mode 100644 index 00000000..a0abc954 --- /dev/null +++ b/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/autoevals/test_adapter.py @@ -0,0 +1,229 @@ +"""Tests for AutoEvalsAdapter.""" + +from unittest.mock import MagicMock + +from bedrock_agentcore.evaluation.custom_code_based_evaluators.models import EvaluatorInput, EvaluatorOutput +from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.autoevals.adapter import AutoEvalsAdapter + + +def _make_evaluator_input(spans=None): + """Build an EvaluatorInput with agent-level spans (CloudWatch split format).""" + if spans is None: + spans = [ + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "name": "invoke_agent", + "kind": "INTERNAL", + "startTimeUnixNano": 1000000000, + "endTimeUnixNano": 2000000000, + "attributes": {"gen_ai.operation.name": "invoke_agent", "session.id": "test-session"}, + "status": {"code": "UNSET"}, + }, + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "timeUnixNano": 2000000000, + "observedTimeUnixNano": 2000000001, + "severityNumber": 9, + "body": { + "input": {"messages": [{"role": "user", "content": {"content": '[{"text": "What is AI?"}]'}}]}, + "output": { + "messages": [{"role": "assistant", "content": {"message": "AI is artificial intelligence."}}] + }, + }, + }, + ] + return EvaluatorInput( + evaluation_level="TRACE", + session_spans=spans, + target_trace_id="t1", + ) + + +def _mock_scorer(score=0.9, rationale="Good answer"): + """Create a mock Autoevals scorer.""" + scorer = MagicMock() + type(scorer).__name__ = "MockScorer" + + result = MagicMock() + result.score = score + result.metadata = {"rationale": rationale} + + scorer.eval = MagicMock(return_value=result) + return scorer + + +class TestAutoEvalsAdapterSuccess: + def test_returns_pass_when_score_above_threshold(self): + scorer = _mock_scorer(score=0.8) + adapter = AutoEvalsAdapter(metric=scorer, threshold=0.5) + + result = adapter(_make_evaluator_input()) + + assert isinstance(result, EvaluatorOutput) + assert result.value == 0.8 + assert result.label == "Pass" + assert result.explanation == "Good answer" + + def test_returns_fail_when_score_below_threshold(self): + scorer = _mock_scorer(score=0.3) + adapter = AutoEvalsAdapter(metric=scorer, threshold=0.5) + + result = adapter(_make_evaluator_input()) + + assert result.value == 0.3 + assert result.label == "Fail" + + def test_custom_threshold(self): + scorer = _mock_scorer(score=0.6) + adapter = AutoEvalsAdapter(metric=scorer, threshold=0.7) + + result = adapter(_make_evaluator_input()) + + assert result.label == "Fail" + + def test_custom_threshold_pass(self): + scorer = _mock_scorer(score=0.8) + adapter = AutoEvalsAdapter(metric=scorer, threshold=0.7) + + result = adapter(_make_evaluator_input()) + + assert result.label == "Pass" + + def test_default_threshold_is_none(self): + scorer = _mock_scorer() + adapter = AutoEvalsAdapter(metric=scorer) + + assert adapter.threshold is None + + def test_no_threshold_defaults_to_half(self): + scorer = _mock_scorer(score=0.85) + adapter = AutoEvalsAdapter(metric=scorer) + + result = adapter(_make_evaluator_input()) + + assert result.value == 0.85 + assert result.label == "Pass" # defaults to threshold=0.5 + + def test_scorer_eval_called_with_input_and_output(self): + scorer = _mock_scorer() + adapter = AutoEvalsAdapter(metric=scorer) + + adapter(_make_evaluator_input()) + + scorer.eval.assert_called_once() + call_kwargs = scorer.eval.call_args[1] + assert call_kwargs["input"] == "What is AI?" + assert call_kwargs["output"] == "AI is artificial intelligence." + + def test_custom_custom_mapper(self): + scorer = _mock_scorer() + adapter = AutoEvalsAdapter( + metric=scorer, + custom_mapper=lambda ev: { + "input": "custom input", + "output": "custom output", + }, + ) + + adapter(_make_evaluator_input()) # result unused; we check call_args + + call_kwargs = scorer.eval.call_args[1] + assert call_kwargs["input"] == "custom input" + assert call_kwargs["output"] == "custom output" + + +class TestAutoEvalsAdapterErrors: + def test_no_agent_spans_returns_error(self): + spans = [ + { + "traceId": "t1", + "spanId": "s1", + "attributes": {"gen_ai.operation.name": "chat"}, + "span_events": [], + } + ] + scorer = _mock_scorer() + adapter = AutoEvalsAdapter(metric=scorer) + + result = adapter(_make_evaluator_input(spans=spans)) + + assert result.errorCode == "FIELD_EXTRACTION_ERROR" + + def test_missing_input_returns_error(self): + spans = [ + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer", "version": ""}, + "attributes": {"gen_ai.operation.name": "invoke_agent"}, + "span_events": [ + { + "body": { + "output": {"messages": [{"role": "assistant", "content": "answer"}]}, + } + } + ], + } + ] + scorer = _mock_scorer() + adapter = AutoEvalsAdapter(metric=scorer) + + result = adapter(_make_evaluator_input(spans=spans)) + + assert result.errorCode in ("MISSING_REQUIRED_FIELD", "FIELD_EXTRACTION_ERROR") + assert result.errorMessage # error message present + + def test_scorer_exception_returns_error(self): + scorer = _mock_scorer() + scorer.eval = MagicMock(side_effect=RuntimeError("API error")) + adapter = AutoEvalsAdapter(metric=scorer) + + result = adapter(_make_evaluator_input()) + + assert result.errorCode == "METRIC_ERROR" + assert "API error" in result.errorMessage + + def test_never_raises(self): + scorer = _mock_scorer() + scorer.eval = MagicMock(side_effect=Exception("unexpected")) + adapter = AutoEvalsAdapter(metric=scorer) + + result = adapter(_make_evaluator_input()) + + assert isinstance(result, EvaluatorOutput) + assert result.errorCode is not None + + +class TestAutoEvalsAdapterEdgeCases: + def test_score_none_without_threshold_returns_fail(self): + scorer = _mock_scorer(score=None) + adapter = AutoEvalsAdapter(metric=scorer) + + result = adapter(_make_evaluator_input()) + + assert result.label == "Fail" # defaults to threshold=0.5, None < 0.5 + + def test_score_none_with_threshold_returns_fail(self): + scorer = _mock_scorer(score=None) + adapter = AutoEvalsAdapter(metric=scorer, threshold=0.5) + + result = adapter(_make_evaluator_input()) + + assert result.label == "Fail" + + def test_no_metadata_returns_empty_explanation(self): + scorer = MagicMock() + type(scorer).__name__ = "MockScorer" + result_obj = MagicMock(spec=[]) + result_obj.score = 0.9 + scorer.eval = MagicMock(return_value=result_obj) + + adapter = AutoEvalsAdapter(metric=scorer) + + result = adapter(_make_evaluator_input()) + + assert result.explanation == "" diff --git a/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/autoevals/test_error_handling.py b/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/autoevals/test_error_handling.py new file mode 100644 index 00000000..731aae67 --- /dev/null +++ b/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/autoevals/test_error_handling.py @@ -0,0 +1,305 @@ +"""Error-handling tests for AutoEvalsAdapter — covers all error paths. + +Validates that error responses return only errorCode + errorMessage (no label, no explanation) +per the service contract: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/code-based-evaluators.html +""" + +from unittest.mock import MagicMock + +from bedrock_agentcore.evaluation.custom_code_based_evaluators.models import EvaluatorInput, EvaluatorOutput +from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.autoevals.adapter import AutoEvalsAdapter + +# --- Helpers --- + + +def _make_evaluator_input( + spans=None, + evaluation_level="TRACE", + target_trace_id="t1", + target_span_id=None, + reference_inputs=None, +): + return EvaluatorInput( + evaluation_level=evaluation_level, + session_spans=spans if spans is not None else [], + target_trace_id=target_trace_id, + target_span_id=target_span_id, + reference_inputs=reference_inputs or [], + ) + + +def _valid_strands_spans(): + """Single-turn Strands spans (valid for extraction).""" + return [ + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "name": "invoke_agent", + "kind": "INTERNAL", + "startTimeUnixNano": 1000000000, + "endTimeUnixNano": 2000000000, + "attributes": {"gen_ai.operation.name": "invoke_agent", "session.id": "test-session"}, + "status": {"code": "UNSET"}, + }, + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "timeUnixNano": 2000000000, + "observedTimeUnixNano": 2000000001, + "severityNumber": 9, + "body": { + "input": {"messages": [{"role": "user", "content": {"content": '[{"text": "What is AI?"}]'}}]}, + "output": { + "messages": [{"role": "assistant", "content": {"message": "AI is artificial intelligence."}}] + }, + }, + }, + ] + + +def _mock_scorer(name="Factuality"): + scorer = MagicMock() + type(scorer).__name__ = name + result = MagicMock() + result.score = 0.9 + result.metadata = {"rationale": "Good"} + scorer.eval = MagicMock(return_value=result) + return scorer + + +def _assert_error_response(result, error_code): + """Assert the response follows the error contract.""" + assert isinstance(result, EvaluatorOutput) + assert result.errorCode == error_code + assert result.errorMessage is not None and len(result.errorMessage) > 0 + assert result.label is None + assert result.explanation is None + + +# ============================================================================= +# FIELD_EXTRACTION_ERROR — Span parsing failures (6 scenarios) +# ============================================================================= + + +class TestFieldExtractionError: + """Scenarios where spans cannot be parsed into evaluation fields.""" + + def test_01_empty_spans(self): + adapter = AutoEvalsAdapter(metric=_mock_scorer()) + result = adapter(_make_evaluator_input(spans=[])) + _assert_error_response(result, "FIELD_EXTRACTION_ERROR") + + def test_02_unrecognized_scope(self): + spans = [ + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "unknown.framework"}, + "attributes": {"gen_ai.operation.name": "invoke_agent"}, + "name": "invoke_agent", + "kind": "INTERNAL", + "startTimeUnixNano": 1000000000, + "endTimeUnixNano": 2000000000, + "status": {"code": "UNSET"}, + } + ] + adapter = AutoEvalsAdapter(metric=_mock_scorer()) + result = adapter(_make_evaluator_input(spans=spans)) + _assert_error_response(result, "FIELD_EXTRACTION_ERROR") + + def test_03_spans_missing_body_input(self): + spans = [ + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "name": "invoke_agent", + "kind": "INTERNAL", + "startTimeUnixNano": 1000000000, + "endTimeUnixNano": 2000000000, + "attributes": {"gen_ai.operation.name": "invoke_agent", "session.id": "s"}, + "status": {"code": "UNSET"}, + }, + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "timeUnixNano": 2000000000, + "observedTimeUnixNano": 2000000001, + "severityNumber": 9, + "body": { + "output": {"messages": [{"role": "assistant", "content": {"message": "answer"}}]}, + }, + }, + ] + adapter = AutoEvalsAdapter(metric=_mock_scorer()) + result = adapter(_make_evaluator_input(spans=spans)) + assert result.errorCode in ("MISSING_REQUIRED_FIELD", "FIELD_EXTRACTION_ERROR") + assert result.label is None + + def test_04_spans_missing_body_output(self): + spans = [ + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "name": "invoke_agent", + "kind": "INTERNAL", + "startTimeUnixNano": 1000000000, + "endTimeUnixNano": 2000000000, + "attributes": {"gen_ai.operation.name": "invoke_agent", "session.id": "s"}, + "status": {"code": "UNSET"}, + }, + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "timeUnixNano": 2000000000, + "observedTimeUnixNano": 2000000001, + "severityNumber": 9, + "body": { + "input": {"messages": [{"role": "user", "content": {"content": '[{"text": "hi"}]'}}]}, + }, + }, + ] + adapter = AutoEvalsAdapter(metric=_mock_scorer()) + result = adapter(_make_evaluator_input(spans=spans)) + assert result.errorCode in ("MISSING_REQUIRED_FIELD", "FIELD_EXTRACTION_ERROR") + assert result.label is None + + def test_05_tool_call_invalid_span_id(self): + spans = _valid_strands_spans() + adapter = AutoEvalsAdapter(metric=_mock_scorer()) + result = adapter( + _make_evaluator_input( + spans=spans, + evaluation_level="TOOL_CALL", + target_span_id="nonexistent", + ) + ) + assert result.errorCode in ("FIELD_EXTRACTION_ERROR", "MISSING_REQUIRED_FIELD") + assert result.label is None + + def test_06_trace_mismatched_trace_id(self): + spans = _valid_strands_spans() + adapter = AutoEvalsAdapter(metric=_mock_scorer()) + result = adapter( + _make_evaluator_input( + spans=spans, + evaluation_level="TRACE", + target_trace_id="nonexistent", + ) + ) + assert result.errorCode in ("FIELD_EXTRACTION_ERROR", "MISSING_REQUIRED_FIELD") + assert result.label is None + + +# ============================================================================= +# MISSING_REQUIRED_FIELD — Scorer needs expected but not provided (4 scenarios) +# ============================================================================= + + +class TestMissingRequiredField: + """Autoevals metrics that need expected_output from reference_inputs.""" + + def test_07_factuality_no_expected(self): + scorer = _mock_scorer("Factuality") + scorer.eval = MagicMock(side_effect=TypeError("missing required argument: 'expected'")) + adapter = AutoEvalsAdapter(metric=scorer) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + + def test_08_closed_qa_no_expected(self): + scorer = _mock_scorer("ClosedQA") + scorer.eval = MagicMock(side_effect=TypeError("missing required argument: 'expected'")) + adapter = AutoEvalsAdapter(metric=scorer) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + + def test_09_answer_correctness_no_expected(self): + scorer = _mock_scorer("AnswerCorrectness") + scorer.eval = MagicMock(side_effect=TypeError("missing required argument: 'expected'")) + adapter = AutoEvalsAdapter(metric=scorer) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + + def test_10_exact_match_no_expected(self): + scorer = _mock_scorer("ExactMatch") + scorer.eval = MagicMock(side_effect=TypeError("missing required argument: 'expected'")) + adapter = AutoEvalsAdapter(metric=scorer) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + + +# ============================================================================= +# METRIC_ERROR — Runtime failures (8 scenarios) +# ============================================================================= + + +class TestMetricError: + """Scenarios where scorer execution fails at runtime.""" + + def test_11_bedrock_throttling(self): + scorer = _mock_scorer() + scorer.eval = MagicMock(side_effect=Exception("ThrottlingException: Rate exceeded")) + adapter = AutoEvalsAdapter(metric=scorer) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + assert "ThrottlingException" in result.errorMessage + + def test_12_bedrock_access_denied(self): + scorer = _mock_scorer() + scorer.eval = MagicMock( + side_effect=Exception("AccessDeniedException: not authorized to perform bedrock:InvokeModel") + ) + adapter = AutoEvalsAdapter(metric=scorer) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + + def test_13_bedrock_model_not_found(self): + scorer = _mock_scorer() + scorer.eval = MagicMock(side_effect=Exception("ResourceNotFoundException: Model not found")) + adapter = AutoEvalsAdapter(metric=scorer) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + + def test_14_custom_mapper_raises_exception(self): + def bad_mapper(ev): + raise ValueError("custom mapper crashed") + + scorer = _mock_scorer() + adapter = AutoEvalsAdapter(metric=scorer, custom_mapper=bad_mapper) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + assert "custom mapper crashed" in result.errorMessage + + def test_15_custom_mapper_returns_none(self): + scorer = _mock_scorer() + scorer.eval = MagicMock(side_effect=TypeError("argument of type 'NoneType'")) + adapter = AutoEvalsAdapter(metric=scorer, custom_mapper=lambda ev: None) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + + def test_16_custom_mapper_returns_wrong_type(self): + scorer = _mock_scorer() + scorer.eval = MagicMock(side_effect=TypeError("expected dict kwargs")) + adapter = AutoEvalsAdapter(metric=scorer, custom_mapper=lambda ev: "not a dict") + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + + def test_17_scorer_eval_network_error(self): + scorer = _mock_scorer() + scorer.eval = MagicMock(side_effect=ConnectionError("network unreachable")) + adapter = AutoEvalsAdapter(metric=scorer) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + + def test_18_scorer_eval_unexpected_exception(self): + scorer = _mock_scorer() + scorer.eval = MagicMock(side_effect=RuntimeError("unexpected internal error")) + adapter = AutoEvalsAdapter(metric=scorer) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + assert "unexpected internal error" in result.errorMessage diff --git a/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/deepeval/__init__.py b/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/deepeval/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/deepeval/test_adapter.py b/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/deepeval/test_adapter.py new file mode 100644 index 00000000..ede7fb7a --- /dev/null +++ b/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/deepeval/test_adapter.py @@ -0,0 +1,398 @@ +"""Tests for DeepEvalAdapter.""" + +from unittest.mock import MagicMock + +from bedrock_agentcore.evaluation.custom_code_based_evaluators.models import EvaluatorInput, EvaluatorOutput +from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.deepeval.adapter import DeepEvalAdapter + + +def _make_evaluator_input(spans=None): + """Build an EvaluatorInput with agent-level spans (CloudWatch split format).""" + if spans is None: + spans = [ + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "name": "invoke_agent", + "kind": "INTERNAL", + "startTimeUnixNano": 1000000000, + "endTimeUnixNano": 2000000000, + "attributes": {"gen_ai.operation.name": "invoke_agent", "session.id": "test-session"}, + "status": {"code": "UNSET"}, + }, + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "timeUnixNano": 2000000000, + "observedTimeUnixNano": 2000000001, + "severityNumber": 9, + "body": { + "input": {"messages": [{"role": "user", "content": {"content": '[{"text": "What is AI?"}]'}}]}, + "output": { + "messages": [{"role": "assistant", "content": {"message": "AI is artificial intelligence."}}] + }, + }, + }, + ] + return EvaluatorInput( + evaluation_level="TRACE", + session_spans=spans, + target_trace_id="t1", + ) + + +def _mock_metric(score=0.85, reason="Looks good", threshold=0.7, name="MockMetric"): + """Create a mock metric that returns a fixed score on measure().""" + metric = MagicMock() + type(metric).__name__ = name + metric.threshold = threshold + metric.score = score + metric.reason = reason + del metric.success + + def measure_side_effect(test_case): + metric.score = score + metric.reason = reason + + metric.measure = MagicMock(side_effect=measure_side_effect) + return metric + + +class TestDeepEvalAdapterSuccess: + def test_returns_pass_when_score_above_threshold(self): + metric = _mock_metric(score=0.9, threshold=0.7) + adapter = DeepEvalAdapter(metric=metric) + + result = adapter(_make_evaluator_input()) + + assert isinstance(result, EvaluatorOutput) + assert result.value == 0.9 + assert result.label == "Pass" + assert result.explanation == "Looks good" + + def test_returns_fail_when_score_below_threshold(self): + metric = _mock_metric(score=0.3, threshold=0.7) + adapter = DeepEvalAdapter(metric=metric) + + result = adapter(_make_evaluator_input()) + + assert result.value == 0.3 + assert result.label == "Fail" + + def test_returns_pass_at_exact_threshold(self): + metric = _mock_metric(score=0.7, threshold=0.7) + adapter = DeepEvalAdapter(metric=metric) + + result = adapter(_make_evaluator_input()) + + assert result.label == "Pass" + + def test_metric_measure_called_with_test_case(self): + metric = _mock_metric() + adapter = DeepEvalAdapter(metric=metric) + + adapter(_make_evaluator_input()) + + metric.measure.assert_called_once() + test_case = metric.measure.call_args[0][0] + assert test_case.input == "What is AI?" + assert test_case.actual_output == "AI is artificial intelligence." + + def test_custom_custom_mapper(self): + from deepeval.test_case import LLMTestCase + + metric = _mock_metric() + adapter = DeepEvalAdapter( + metric=metric, + custom_mapper=lambda ev: LLMTestCase( + input="mapped input", + actual_output="mapped output", + ), + ) + + result = adapter(_make_evaluator_input()) + + assert result.value == 0.85 + test_case = metric.measure.call_args[0][0] + assert test_case.input == "mapped input" + assert test_case.actual_output == "mapped output" + + def test_reference_inputs_populates_expected_output(self): + metric = _mock_metric() + adapter = DeepEvalAdapter(metric=metric) + + evaluator_input = EvaluatorInput( + evaluation_level="TRACE", + session_spans=[ + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "name": "invoke_agent", + "kind": "INTERNAL", + "startTimeUnixNano": 1000000000, + "endTimeUnixNano": 2000000000, + "attributes": {"gen_ai.operation.name": "invoke_agent", "session.id": "test-session"}, + "status": {"code": "UNSET"}, + }, + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "timeUnixNano": 2000000000, + "observedTimeUnixNano": 2000000001, + "severityNumber": 9, + "body": { + "input": {"messages": [{"role": "user", "content": {"content": '[{"text": "What is AI?"}]'}}]}, + "output": { + "messages": [ + {"role": "assistant", "content": {"message": "AI is artificial intelligence."}} + ] + }, + }, + }, + ], + target_trace_id="t1", + reference_inputs=[{"expectedResponse": {"text": "AI stands for artificial intelligence."}}], + ) + + adapter(evaluator_input) # result unused; we check test_case + + test_case = metric.measure.call_args[0][0] + assert test_case.expected_output == "AI stands for artificial intelligence." + + def test_label_uses_metric_success_true(self): + metric = _mock_metric(score=0.3, threshold=0.7) + metric.success = True + adapter = DeepEvalAdapter(metric=metric) + + result = adapter(_make_evaluator_input()) + + assert result.value == 0.3 + assert result.label == "Pass" + + def test_label_uses_metric_success_false(self): + metric = _mock_metric(score=0.9, threshold=0.7) + metric.success = False + adapter = DeepEvalAdapter(metric=metric) + + result = adapter(_make_evaluator_input()) + + assert result.value == 0.9 + assert result.label == "Fail" + + +class TestDeepEvalAdapterErrors: + def test_no_agent_spans_returns_error(self): + spans = [ + { + "traceId": "t1", + "spanId": "s1", + "attributes": {"gen_ai.operation.name": "chat"}, + "span_events": [], + } + ] + metric = _mock_metric() + adapter = DeepEvalAdapter(metric=metric) + + result = adapter(_make_evaluator_input(spans=spans)) + + assert isinstance(result, EvaluatorOutput) + assert result.errorCode == "FIELD_EXTRACTION_ERROR" + assert result.label is None + + def test_missing_input_returns_error(self): + spans = [ + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer", "version": ""}, + "attributes": {"gen_ai.operation.name": "invoke_agent"}, + "span_events": [ + { + "body": { + "output": {"messages": [{"role": "assistant", "content": "answer"}]}, + } + } + ], + } + ] + metric = _mock_metric() + adapter = DeepEvalAdapter(metric=metric) + + result = adapter(_make_evaluator_input(spans=spans)) + + assert result.errorCode in ("MISSING_REQUIRED_FIELD", "FIELD_EXTRACTION_ERROR") + assert result.errorMessage # error message present + assert "custom_mapper" in result.errorMessage + metric.measure.assert_not_called() + + def test_metric_measure_exception_returns_error(self): + metric = _mock_metric() + metric.measure = MagicMock(side_effect=RuntimeError("LLM timeout")) + adapter = DeepEvalAdapter(metric=metric) + + result = adapter(_make_evaluator_input()) + + assert result.errorCode == "METRIC_ERROR" + assert "LLM timeout" in result.errorMessage + + def test_missing_params_error_caught(self): + from deepeval.errors import MissingTestCaseParamsError + + metric = _mock_metric() + metric.measure = MagicMock(side_effect=MissingTestCaseParamsError("retrieval_context is required")) + adapter = DeepEvalAdapter(metric=metric) + + result = adapter(_make_evaluator_input()) + + assert result.errorCode == "MISSING_REQUIRED_FIELD" + assert "retrieval_context" in result.errorMessage + assert "custom_mapper" in result.errorMessage + + def test_never_raises(self): + metric = _mock_metric() + metric.measure = MagicMock(side_effect=Exception("unexpected")) + adapter = DeepEvalAdapter(metric=metric) + + result = adapter(_make_evaluator_input()) + + assert isinstance(result, EvaluatorOutput) + assert result.errorCode is not None + + +class TestDeepEvalAdapterEdgeCases: + def test_metric_with_no_reason(self): + metric = _mock_metric(score=0.8, reason=None) + adapter = DeepEvalAdapter(metric=metric) + + result = adapter(_make_evaluator_input()) + + assert result.explanation == "" + + def test_metric_score_zero(self): + metric = _mock_metric(score=0.0, threshold=0.5) + adapter = DeepEvalAdapter(metric=metric) + + result = adapter(_make_evaluator_input()) + + assert result.value == 0.0 + assert result.label == "Fail" + + def test_default_threshold_when_missing(self): + metric = _mock_metric(score=0.6) + del metric.threshold + adapter = DeepEvalAdapter(metric=metric) + + result = adapter(_make_evaluator_input()) + + assert result.label == "Pass" + + +class TestDeepEvalAdapterConversational: + """Tests for ConversationalTestCase support.""" + + def test_conversational_metric_with_multi_turn_spans(self): + """Test that BaseConversationalMetric gets ConversationalTestCase.""" + from deepeval.metrics import BaseConversationalMetric + from deepeval.test_case import ConversationalTestCase + + metric = MagicMock(spec=BaseConversationalMetric) + type(metric).__name__ = "KnowledgeRetentionMetric" + metric.threshold = 0.5 + metric.score = 0.8 + metric.reason = "Good retention" + del metric.success + + def measure_side_effect(test_case): + assert isinstance(test_case, ConversationalTestCase) + assert len(test_case.turns) == 4 + metric.score = 0.8 + metric.reason = "Good retention" + + metric.measure = MagicMock(side_effect=measure_side_effect) + + adapter = DeepEvalAdapter(metric=metric) + + # Multi-turn session: 2 traces, each with an invoke_agent span + spans = [ + # Trace 1 - metadata + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "name": "invoke_agent", + "kind": "INTERNAL", + "startTimeUnixNano": 1000000000, + "endTimeUnixNano": 2000000000, + "attributes": {"gen_ai.operation.name": "invoke_agent", "session.id": "test"}, + "status": {"code": "UNSET"}, + }, + # Trace 1 - body + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "timeUnixNano": 2000000000, + "observedTimeUnixNano": 2000000001, + "severityNumber": 9, + "body": { + "input": {"messages": [{"role": "user", "content": {"content": '[{"text": "Hello"}]'}}]}, + "output": {"messages": [{"role": "assistant", "content": {"message": "Hi there!"}}]}, + }, + }, + # Trace 2 - metadata + { + "traceId": "t2", + "spanId": "s2", + "scope": {"name": "strands.telemetry.tracer"}, + "name": "invoke_agent", + "kind": "INTERNAL", + "startTimeUnixNano": 3000000000, + "endTimeUnixNano": 4000000000, + "attributes": {"gen_ai.operation.name": "invoke_agent", "session.id": "test"}, + "status": {"code": "UNSET"}, + }, + # Trace 2 - body + { + "traceId": "t2", + "spanId": "s2", + "scope": {"name": "strands.telemetry.tracer"}, + "timeUnixNano": 4000000000, + "observedTimeUnixNano": 4000000001, + "severityNumber": 9, + "body": { + "input": {"messages": [{"role": "user", "content": {"content": '[{"text": "What is AI?"}]'}}]}, + "output": { + "messages": [{"role": "assistant", "content": {"message": "AI is artificial intelligence."}}] + }, + }, + }, + ] + + evaluator_input = EvaluatorInput( + evaluation_level="SESSION", + session_spans=spans, + ) + + result = adapter(evaluator_input) + + assert result.value == 0.8 + assert result.label == "Pass" + metric.measure.assert_called_once() + + def test_conversational_metric_single_turn_returns_error(self): + """Test that single-turn spans return error for conversational metric.""" + from deepeval.metrics import BaseConversationalMetric + + metric = MagicMock(spec=BaseConversationalMetric) + type(metric).__name__ = "KnowledgeRetentionMetric" + + adapter = DeepEvalAdapter(metric=metric) + + result = adapter(_make_evaluator_input()) + + assert result.errorCode == "FIELD_EXTRACTION_ERROR" + assert "multi-turn" in result.errorMessage.lower() or "Multiple" in result.errorMessage diff --git a/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/deepeval/test_error_handling.py b/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/deepeval/test_error_handling.py new file mode 100644 index 00000000..572c1a16 --- /dev/null +++ b/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/deepeval/test_error_handling.py @@ -0,0 +1,569 @@ +"""Error-handling tests for DeepEvalAdapter — 56 scenarios covering all error paths. + +Validates that error responses return only errorCode + errorMessage (no label, no explanation) +per the service contract: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/code-based-evaluators.html +""" + +from unittest.mock import MagicMock + +from deepeval.errors import MissingTestCaseParamsError +from deepeval.metrics import BaseConversationalMetric, BaseMetric +from deepeval.test_case import LLMTestCase + +from bedrock_agentcore.evaluation.custom_code_based_evaluators.models import EvaluatorInput, EvaluatorOutput +from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.deepeval.adapter import DeepEvalAdapter + +# --- Helpers --- + + +def _make_evaluator_input( + spans=None, + evaluation_level="TRACE", + target_trace_id="t1", + target_span_id=None, + reference_inputs=None, +): + return EvaluatorInput( + evaluation_level=evaluation_level, + session_spans=spans if spans is not None else [], + target_trace_id=target_trace_id, + target_span_id=target_span_id, + reference_inputs=reference_inputs or [], + ) + + +def _valid_strands_spans(): + """Single-turn Strands spans (valid for LLMTestCase metrics).""" + return [ + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "name": "invoke_agent", + "kind": "INTERNAL", + "startTimeUnixNano": 1000000000, + "endTimeUnixNano": 2000000000, + "attributes": {"gen_ai.operation.name": "invoke_agent", "session.id": "test-session"}, + "status": {"code": "UNSET"}, + }, + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "timeUnixNano": 2000000000, + "observedTimeUnixNano": 2000000001, + "severityNumber": 9, + "body": { + "input": {"messages": [{"role": "user", "content": {"content": '[{"text": "What is AI?"}]'}}]}, + "output": { + "messages": [{"role": "assistant", "content": {"message": "AI is artificial intelligence."}}] + }, + }, + }, + ] + + +def _mock_metric(name="AnswerRelevancyMetric"): + metric = MagicMock(spec=BaseMetric) + type(metric).__name__ = name + metric.threshold = 0.5 + metric.score = 0.9 + metric.reason = "Good" + del metric.success + return metric + + +def _mock_conversational_metric(name="ConversationalGEval"): + metric = MagicMock() + metric.__class__ = type(name, (BaseConversationalMetric,), {}) + type(metric).__name__ = name + metric.threshold = 0.5 + return metric + + +def _assert_error_response(result, error_code): + """Assert the response follows the error contract.""" + assert isinstance(result, EvaluatorOutput) + assert result.errorCode == error_code + assert result.errorMessage is not None and len(result.errorMessage) > 0 + assert result.label is None + assert result.explanation is None + + +# ============================================================================= +# FIELD_EXTRACTION_ERROR — Span parsing failures (22 scenarios) +# ============================================================================= + + +class TestFieldExtractionError: + """Scenarios where spans cannot be parsed into evaluation fields.""" + + def test_01_empty_spans_deepeval(self): + adapter = DeepEvalAdapter(metric=_mock_metric()) + result = adapter(_make_evaluator_input(spans=[])) + _assert_error_response(result, "FIELD_EXTRACTION_ERROR") + + def test_02_empty_spans_with_trace_target(self): + adapter = DeepEvalAdapter(metric=_mock_metric()) + result = adapter(_make_evaluator_input(spans=[], target_trace_id="nonexistent")) + _assert_error_response(result, "FIELD_EXTRACTION_ERROR") + + def test_03_conversational_geval_single_turn(self): + adapter = DeepEvalAdapter(metric=_mock_conversational_metric("ConversationalGEval")) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans(), evaluation_level="SESSION")) + _assert_error_response(result, "FIELD_EXTRACTION_ERROR") + assert "multiple conversation turns" in result.errorMessage + + def test_04_knowledge_retention_single_turn(self): + adapter = DeepEvalAdapter(metric=_mock_conversational_metric("KnowledgeRetentionMetric")) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans(), evaluation_level="SESSION")) + _assert_error_response(result, "FIELD_EXTRACTION_ERROR") + + def test_05_conversation_completeness_single_turn(self): + adapter = DeepEvalAdapter(metric=_mock_conversational_metric("ConversationCompletenessMetric")) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans(), evaluation_level="SESSION")) + _assert_error_response(result, "FIELD_EXTRACTION_ERROR") + + def test_06_goal_accuracy_single_turn(self): + adapter = DeepEvalAdapter(metric=_mock_conversational_metric("GoalAccuracyMetric")) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans(), evaluation_level="SESSION")) + _assert_error_response(result, "FIELD_EXTRACTION_ERROR") + + def test_07_role_adherence_single_turn(self): + adapter = DeepEvalAdapter(metric=_mock_conversational_metric("RoleAdherenceMetric")) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans(), evaluation_level="SESSION")) + _assert_error_response(result, "FIELD_EXTRACTION_ERROR") + + def test_08_turn_relevancy_single_turn(self): + adapter = DeepEvalAdapter(metric=_mock_conversational_metric("TurnRelevancyMetric")) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans(), evaluation_level="SESSION")) + _assert_error_response(result, "FIELD_EXTRACTION_ERROR") + + def test_09_turn_faithfulness_single_turn(self): + adapter = DeepEvalAdapter(metric=_mock_conversational_metric("TurnFaithfulnessMetric")) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans(), evaluation_level="SESSION")) + _assert_error_response(result, "FIELD_EXTRACTION_ERROR") + + def test_10_tool_use_single_turn(self): + adapter = DeepEvalAdapter(metric=_mock_conversational_metric("ToolUseMetric")) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans(), evaluation_level="SESSION")) + _assert_error_response(result, "FIELD_EXTRACTION_ERROR") + + def test_11_topic_adherence_single_turn(self): + adapter = DeepEvalAdapter(metric=_mock_conversational_metric("TopicAdherenceMetric")) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans(), evaluation_level="SESSION")) + _assert_error_response(result, "FIELD_EXTRACTION_ERROR") + + def test_12_turn_contextual_precision_single_turn(self): + adapter = DeepEvalAdapter(metric=_mock_conversational_metric("TurnContextualPrecisionMetric")) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans(), evaluation_level="SESSION")) + _assert_error_response(result, "FIELD_EXTRACTION_ERROR") + + def test_13_turn_contextual_recall_single_turn(self): + adapter = DeepEvalAdapter(metric=_mock_conversational_metric("TurnContextualRecallMetric")) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans(), evaluation_level="SESSION")) + _assert_error_response(result, "FIELD_EXTRACTION_ERROR") + + def test_14_turn_contextual_relevancy_single_turn(self): + adapter = DeepEvalAdapter(metric=_mock_conversational_metric("TurnContextualRelevancyMetric")) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans(), evaluation_level="SESSION")) + _assert_error_response(result, "FIELD_EXTRACTION_ERROR") + + def test_15_unrecognized_scope_deepeval(self): + spans = [ + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "unknown.framework"}, + "attributes": {"gen_ai.operation.name": "invoke_agent"}, + "name": "invoke_agent", + "kind": "INTERNAL", + "startTimeUnixNano": 1000000000, + "endTimeUnixNano": 2000000000, + "status": {"code": "UNSET"}, + } + ] + adapter = DeepEvalAdapter(metric=_mock_metric()) + result = adapter(_make_evaluator_input(spans=spans)) + _assert_error_response(result, "FIELD_EXTRACTION_ERROR") + + def test_16_spans_missing_body_input(self): + spans = [ + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "name": "invoke_agent", + "kind": "INTERNAL", + "startTimeUnixNano": 1000000000, + "endTimeUnixNano": 2000000000, + "attributes": {"gen_ai.operation.name": "invoke_agent", "session.id": "s"}, + "status": {"code": "UNSET"}, + }, + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "timeUnixNano": 2000000000, + "observedTimeUnixNano": 2000000001, + "severityNumber": 9, + "body": { + "output": {"messages": [{"role": "assistant", "content": {"message": "answer"}}]}, + }, + }, + ] + adapter = DeepEvalAdapter(metric=_mock_metric()) + result = adapter(_make_evaluator_input(spans=spans)) + assert result.errorCode in ("MISSING_REQUIRED_FIELD", "FIELD_EXTRACTION_ERROR") + assert result.label is None + + def test_17_spans_missing_body_output(self): + spans = [ + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "name": "invoke_agent", + "kind": "INTERNAL", + "startTimeUnixNano": 1000000000, + "endTimeUnixNano": 2000000000, + "attributes": {"gen_ai.operation.name": "invoke_agent", "session.id": "s"}, + "status": {"code": "UNSET"}, + }, + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "timeUnixNano": 2000000000, + "observedTimeUnixNano": 2000000001, + "severityNumber": 9, + "body": { + "input": {"messages": [{"role": "user", "content": {"content": '[{"text": "hi"}]'}}]}, + }, + }, + ] + adapter = DeepEvalAdapter(metric=_mock_metric()) + result = adapter(_make_evaluator_input(spans=spans)) + assert result.errorCode in ("MISSING_REQUIRED_FIELD", "FIELD_EXTRACTION_ERROR") + assert result.label is None + + def test_18_malformed_body_structure(self): + spans = [ + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "name": "invoke_agent", + "kind": "INTERNAL", + "startTimeUnixNano": 1000000000, + "endTimeUnixNano": 2000000000, + "attributes": {"gen_ai.operation.name": "invoke_agent", "session.id": "s"}, + "status": {"code": "UNSET"}, + }, + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "timeUnixNano": 2000000000, + "observedTimeUnixNano": 2000000001, + "severityNumber": 9, + "body": "not a dict", + }, + ] + adapter = DeepEvalAdapter(metric=_mock_metric()) + result = adapter(_make_evaluator_input(spans=spans)) + assert result.errorCode in ("FIELD_EXTRACTION_ERROR", "MISSING_REQUIRED_FIELD") + assert result.label is None + + def test_19_service_format_empty_events(self): + spans = [ + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "name": "invoke_agent", + "kind": "INTERNAL", + "startTimeUnixNano": 1000000000, + "endTimeUnixNano": 2000000000, + "attributes": {"gen_ai.operation.name": "invoke_agent", "session.id": "s"}, + "status": {"code": "UNSET"}, + "events": [], + }, + ] + adapter = DeepEvalAdapter(metric=_mock_metric()) + result = adapter(_make_evaluator_input(spans=spans)) + assert result.errorCode in ("FIELD_EXTRACTION_ERROR", "MISSING_REQUIRED_FIELD") + assert result.label is None + + def test_20_tool_call_level_invalid_span_id(self): + spans = _valid_strands_spans() + adapter = DeepEvalAdapter(metric=_mock_metric()) + result = adapter( + _make_evaluator_input( + spans=spans, + evaluation_level="TOOL_CALL", + target_span_id="nonexistent-span-id", + ) + ) + assert result.errorCode in ("FIELD_EXTRACTION_ERROR", "MISSING_REQUIRED_FIELD") + assert result.label is None + + def test_21_trace_level_mismatched_trace_id(self): + spans = _valid_strands_spans() + adapter = DeepEvalAdapter(metric=_mock_metric()) + result = adapter( + _make_evaluator_input( + spans=spans, + evaluation_level="TRACE", + target_trace_id="nonexistent-trace-id", + ) + ) + assert result.errorCode in ("FIELD_EXTRACTION_ERROR", "MISSING_REQUIRED_FIELD") + assert result.label is None + + def test_22_spans_only_log_records(self): + spans = [ + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "strands.telemetry.tracer"}, + "timeUnixNano": 2000000000, + "observedTimeUnixNano": 2000000001, + "severityNumber": 9, + "body": {"some": "log data"}, + }, + ] + adapter = DeepEvalAdapter(metric=_mock_metric()) + result = adapter(_make_evaluator_input(spans=spans)) + assert result.errorCode in ("FIELD_EXTRACTION_ERROR", "MISSING_REQUIRED_FIELD") + assert result.label is None + + +# ============================================================================= +# MISSING_REQUIRED_FIELD — Metric needs data not in spans (15 scenarios) +# ============================================================================= + + +class TestMissingRequiredField: + """Scenarios where spans are valid but metric requires fields not present.""" + + def test_23_faithfulness_no_retrieval_context(self): + metric = _mock_metric("FaithfulnessMetric") + metric.measure = MagicMock(side_effect=MissingTestCaseParamsError("retrieval_context is required")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "MISSING_REQUIRED_FIELD") + + def test_24_contextual_precision_no_expected_output(self): + metric = _mock_metric("ContextualPrecisionMetric") + metric.measure = MagicMock(side_effect=MissingTestCaseParamsError("expected_output is required")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "MISSING_REQUIRED_FIELD") + + def test_25_contextual_recall_no_expected_output(self): + metric = _mock_metric("ContextualRecallMetric") + metric.measure = MagicMock(side_effect=MissingTestCaseParamsError("expected_output is required")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "MISSING_REQUIRED_FIELD") + + def test_26_contextual_relevancy_no_retrieval_context(self): + metric = _mock_metric("ContextualRelevancyMetric") + metric.measure = MagicMock(side_effect=MissingTestCaseParamsError("retrieval_context is required")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "MISSING_REQUIRED_FIELD") + + def test_27_tool_correctness_no_expected_trajectory(self): + metric = _mock_metric("ToolCorrectnessMetric") + metric.measure = MagicMock(side_effect=MissingTestCaseParamsError("expected_tools is required")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "MISSING_REQUIRED_FIELD") + + def test_28_tool_correctness_empty_tool_names(self): + metric = _mock_metric("ToolCorrectnessMetric") + metric.measure = MagicMock(side_effect=MissingTestCaseParamsError("expected_tools is required")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "MISSING_REQUIRED_FIELD") + + def test_29_argument_correctness_no_tool_calls(self): + metric = _mock_metric("ArgumentCorrectnessMetric") + metric.measure = MagicMock(side_effect=MissingTestCaseParamsError("tools_called is required")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "MISSING_REQUIRED_FIELD") + + def test_30_hallucination_no_context(self): + metric = _mock_metric("HallucinationMetric") + metric.measure = MagicMock(side_effect=MissingTestCaseParamsError("context is required")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "MISSING_REQUIRED_FIELD") + + def test_31_prompt_alignment_no_instructions(self): + metric = _mock_metric("PromptAlignmentMetric") + metric.measure = MagicMock(side_effect=MissingTestCaseParamsError("prompt_instructions is required")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "MISSING_REQUIRED_FIELD") + + def test_32_geval_no_evaluation_params(self): + metric = _mock_metric("GEval") + metric.measure = MagicMock(side_effect=MissingTestCaseParamsError("evaluation_params is required")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "MISSING_REQUIRED_FIELD") + + def test_33_topic_adherence_no_relevant_topics(self): + metric = _mock_metric("TopicAdherenceMetric") + metric.measure = MagicMock(side_effect=MissingTestCaseParamsError("relevant_topics is required")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "MISSING_REQUIRED_FIELD") + + def test_34_tool_use_no_available_tools(self): + metric = _mock_metric("ToolUseMetric") + metric.measure = MagicMock(side_effect=MissingTestCaseParamsError("available_tools is required")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "MISSING_REQUIRED_FIELD") + + def test_35_non_advice_no_advice_types(self): + metric = _mock_metric("NonAdviceMetric") + metric.measure = MagicMock(side_effect=MissingTestCaseParamsError("advice_types is required")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "MISSING_REQUIRED_FIELD") + + def test_36_misuse_no_domain(self): + metric = _mock_metric("MisuseMetric") + metric.measure = MagicMock(side_effect=MissingTestCaseParamsError("domain is required")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "MISSING_REQUIRED_FIELD") + + def test_37_summarization_empty_input(self): + metric = _mock_metric("SummarizationMetric") + metric.measure = MagicMock(side_effect=MissingTestCaseParamsError("input is required but empty")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "MISSING_REQUIRED_FIELD") + + +# ============================================================================= +# METRIC_ERROR — Runtime failures (13 scenarios) +# ============================================================================= + + +class TestMetricError: + """Scenarios where metric execution fails at runtime.""" + + def test_38_bedrock_throttling(self): + metric = _mock_metric("AnswerRelevancyMetric") + metric.measure = MagicMock(side_effect=Exception("ThrottlingException: Rate exceeded")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + assert "ThrottlingException" in result.errorMessage + + def test_39_bedrock_access_denied(self): + metric = _mock_metric("AnswerRelevancyMetric") + metric.measure = MagicMock( + side_effect=Exception("AccessDeniedException: User is not authorized to perform bedrock:InvokeModel") + ) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + assert "AccessDeniedException" in result.errorMessage + + def test_40_bedrock_model_not_found(self): + metric = _mock_metric("AnswerRelevancyMetric") + metric.measure = MagicMock( + side_effect=Exception("ResourceNotFoundException: Model not found: invalid-model-id") + ) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + + def test_41_bedrock_timeout(self): + metric = _mock_metric("AnswerRelevancyMetric") + metric.measure = MagicMock(side_effect=TimeoutError("Read timeout on endpoint")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + + def test_42_bedrock_region_mismatch(self): + metric = _mock_metric("AnswerRelevancyMetric") + metric.measure = MagicMock( + side_effect=Exception("Could not resolve the foundation model from the model identifier") + ) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + + def test_43_custom_mapper_raises_exception(self): + def bad_mapper(ev): + raise ValueError("custom mapper crashed") + + metric = _mock_metric() + adapter = DeepEvalAdapter(metric=metric, custom_mapper=bad_mapper) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + assert "custom mapper crashed" in result.errorMessage + + def test_44_custom_mapper_returns_none(self): + metric = _mock_metric() + metric.measure = MagicMock(side_effect=AttributeError("'NoneType' object has no attribute 'input'")) + adapter = DeepEvalAdapter(metric=metric, custom_mapper=lambda ev: None) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + + def test_45_custom_mapper_returns_wrong_type(self): + metric = _mock_metric() + metric.measure = MagicMock(side_effect=TypeError("expected LLMTestCase, got dict")) + adapter = DeepEvalAdapter(metric=metric, custom_mapper=lambda ev: {"input": "hi"}) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + + def test_46_metric_measure_unexpected_exception(self): + metric = _mock_metric("BiasMetric") + metric.measure = MagicMock(side_effect=RuntimeError("unexpected internal error")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + assert "unexpected internal error" in result.errorMessage + + def test_47_metric_measure_network_error(self): + metric = _mock_metric("ToxicityMetric") + metric.measure = MagicMock(side_effect=ConnectionError("network unreachable")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + + def test_48_metric_measure_json_decode_error(self): + metric = _mock_metric("AnswerRelevancyMetric") + metric.measure = MagicMock(side_effect=ValueError("Expecting value: line 1")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + + def test_49_custom_mapper_returns_test_case_with_none_input(self): + metric = _mock_metric() + metric.measure = MagicMock(side_effect=Exception("input cannot be None")) + adapter = DeepEvalAdapter( + metric=metric, + custom_mapper=lambda ev: LLMTestCase(input="", actual_output=""), + ) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") + + def test_50_metric_measure_keyboard_interrupt_still_caught(self): + """Even unusual exceptions are caught — adapter never raises.""" + metric = _mock_metric("AnswerRelevancyMetric") + metric.measure = MagicMock(side_effect=Exception("something very unusual")) + adapter = DeepEvalAdapter(metric=metric) + result = adapter(_make_evaluator_input(spans=_valid_strands_spans())) + _assert_error_response(result, "METRIC_ERROR") diff --git a/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/span_mappers/__init__.py b/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/span_mappers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/span_mappers/test_span_mappers.py b/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/span_mappers/test_span_mappers.py new file mode 100644 index 00000000..5a025cf0 --- /dev/null +++ b/tests/bedrock_agentcore/evaluation/custom_code_based_evaluators/third_party/span_mappers/test_span_mappers.py @@ -0,0 +1,114 @@ +"""Tests for span mappers using strands-evals integration.""" + +import pytest + +from bedrock_agentcore.evaluation.custom_code_based_evaluators.models import ReferenceInput +from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.span_mappers import ( + SpanMapResult, + map_spans, +) + + +def _make_strands_cloudwatch_spans(): + """Build Strands CloudWatch format spans (merged: span metadata + body).""" + return [ + { + "traceId": "trace1", + "spanId": "span1", + "scope": {"name": "strands.telemetry.tracer"}, + "name": "invoke_agent", + "kind": "INTERNAL", + "startTimeUnixNano": 1000000000, + "endTimeUnixNano": 2000000000, + "attributes": {"gen_ai.operation.name": "invoke_agent"}, + "status": {"code": "UNSET"}, + "span_events": [ + { + "event_name": "strands.telemetry.tracer", + "body": { + "input": { + "messages": [ + {"role": "system", "content": "You are helpful."}, + {"role": "user", "content": {"content": '[{"text": "What is AI?"}]'}}, + ] + }, + "output": { + "messages": [ + {"role": "assistant", "content": {"message": "AI is artificial intelligence."}} + ] + }, + }, + } + ], + } + ] + + +class TestMapSpans: + def test_strands_cloudwatch_extraction(self): + spans = _make_strands_cloudwatch_spans() + result = map_spans(spans) + + assert isinstance(result, SpanMapResult) + assert result.input is not None + assert result.actual_output is not None + + def test_raises_on_empty_spans(self): + with pytest.raises(ValueError): + map_spans([]) + + def test_raises_on_unsupported_scope(self): + spans = [ + { + "traceId": "t1", + "spanId": "s1", + "scope": {"name": "unknown.scope"}, + "attributes": {}, + } + ] + with pytest.raises(ValueError): + map_spans(spans) + + def test_reference_inputs_expected_output(self): + spans = _make_strands_cloudwatch_spans() + ref = ReferenceInput( + context={}, + expected_response={"text": "AI stands for artificial intelligence."}, + ) + result = map_spans(spans, reference_inputs=[ref]) + + assert result.expected_output == "AI stands for artificial intelligence." + + def test_reference_inputs_expected_tools(self): + spans = _make_strands_cloudwatch_spans() + ref = ReferenceInput( + context={}, + expected_trajectory={"toolNames": ["search", "calculate"]}, + ) + result = map_spans(spans, reference_inputs=[ref]) + + assert result.expected_tools == [{"name": "search"}, {"name": "calculate"}] + + def test_reference_inputs_assertions(self): + spans = _make_strands_cloudwatch_spans() + ref = ReferenceInput( + context={}, + assertions=[{"text": "Fact 1"}, {"text": "Fact 2"}], + ) + result = map_spans(spans, reference_inputs=[ref]) + + assert result.assertions == ["Fact 1", "Fact 2"] + + def test_span_map_result_fields(self): + result = SpanMapResult( + input="hello", + actual_output="world", + retrieval_context=["ctx1"], + tools_called=[{"name": "tool1", "input_parameters": {"a": 1}, "output": "result"}], + ) + assert result.input == "hello" + assert result.actual_output == "world" + assert result.retrieval_context == ["ctx1"] + assert result.tools_called == [{"name": "tool1", "input_parameters": {"a": 1}, "output": "result"}] + assert result.expected_output is None + assert result.system_prompt is None diff --git a/tests_integ/evaluation/test_third_party_adapters.py b/tests_integ/evaluation/test_third_party_adapters.py new file mode 100644 index 00000000..9ae5dd8b --- /dev/null +++ b/tests_integ/evaluation/test_third_party_adapters.py @@ -0,0 +1,172 @@ +"""Integration tests for third-party evaluation adapters. + +These tests require `deepeval` and `autoevals` packages to be installed. +They verify the full adapter flow from EvaluatorInput through span parsing +to metric execution, using real library metrics (not mocks). + +SETUP: + pip install deepeval autoevals + +RUN: + pytest tests_integ/evaluation/test_third_party_adapters.py -v +""" + +import pytest + +from bedrock_agentcore.evaluation.custom_code_based_evaluators.models import EvaluatorInput, EvaluatorOutput + + +def _make_agent_evaluator_input( + user_prompt="What is the capital of France?", + agent_response="The capital of France is Paris.", + tool_messages=None, +): + """Build an EvaluatorInput with agent-level spans.""" + output_messages = [] + if tool_messages: + for msg in tool_messages: + output_messages.append({"role": "tool", "content": msg}) + output_messages.append({"role": "assistant", "content": agent_response}) + + spans = [ + { + "traceId": "integ-trace-1", + "spanId": "integ-span-1", + "attributes": {"gen_ai.operation.name": "invoke_agent"}, + "span_events": [ + { + "body": { + "input": {"messages": [{"role": "user", "content": user_prompt}]}, + "output": {"messages": output_messages}, + } + } + ], + } + ] + return EvaluatorInput( + evaluation_level="TRACE", + session_spans=spans, + target_trace_id="integ-trace-1", + ) + + +class TestDeepEvalAdapterIntegration: + """Integration tests for DeepEvalAdapter with real DeepEval metrics.""" + + @pytest.fixture(autouse=True) + def check_deepeval(self): + """Verify deepeval is installed.""" + import deepeval # noqa: F401 + + def test_bias_metric_passes(self): + from deepeval.metrics import BiasMetric + + from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.deepeval import DeepEvalAdapter + + metric = BiasMetric(threshold=0.5) + adapter = DeepEvalAdapter(metric=metric) + + result = adapter(_make_agent_evaluator_input()) + + assert isinstance(result, EvaluatorOutput) + assert result.value is not None + assert result.label in ("Pass", "Fail") + + def test_missing_retrieval_context_returns_error(self): + from deepeval.metrics import FaithfulnessMetric + + from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.deepeval import DeepEvalAdapter + + metric = FaithfulnessMetric(threshold=0.7) + adapter = DeepEvalAdapter(metric=metric) + + result = adapter( + _make_agent_evaluator_input( + user_prompt="Is the sky blue?", + agent_response="Yes, the sky is blue.", + ) + ) + + assert isinstance(result, EvaluatorOutput) + assert result.errorCode == "MISSING_REQUIRED_FIELD" or result.value is not None + + def test_with_custom_mapper(self): + from deepeval.metrics import BiasMetric + from deepeval.test_case import LLMTestCase + + from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.deepeval import DeepEvalAdapter + + metric = BiasMetric(threshold=0.5) + adapter = DeepEvalAdapter( + metric=metric, + custom_mapper=lambda ev: LLMTestCase( + input="Is Python a good language?", + actual_output="Python is a versatile programming language used widely.", + ), + ) + + result = adapter(_make_agent_evaluator_input()) + + assert isinstance(result, EvaluatorOutput) + assert result.value is not None + + +class TestAutoEvalsAdapterIntegration: + """Integration tests for AutoEvalsAdapter with real Autoevals scorers.""" + + @pytest.fixture(autouse=True) + def check_autoevals(self): + """Verify autoevals is installed.""" + import autoevals # noqa: F401 + + def test_factuality_scorer(self): + from autoevals import Factuality + + from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.autoevals import AutoEvalsAdapter + + scorer = Factuality() + adapter = AutoEvalsAdapter(metric=scorer) + + evaluator_input = _make_agent_evaluator_input() + evaluator_input.session_spans[0]["span_events"][0]["body"]["output"]["messages"] = [ + {"role": "assistant", "content": "The capital of France is Paris."} + ] + + result = adapter(evaluator_input) + + assert isinstance(result, EvaluatorOutput) + assert result.value is not None + assert result.label in ("Pass", "Fail") + + def test_custom_threshold(self): + from autoevals import Factuality + + from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.autoevals import AutoEvalsAdapter + + scorer = Factuality() + adapter = AutoEvalsAdapter(metric=scorer, threshold=0.9) + + result = adapter(_make_agent_evaluator_input()) + + assert isinstance(result, EvaluatorOutput) + assert result.value is not None + + def test_with_custom_mapper(self): + from autoevals import Factuality + + from bedrock_agentcore.evaluation.custom_code_based_evaluators.third_party.autoevals import AutoEvalsAdapter + + scorer = Factuality() + adapter = AutoEvalsAdapter( + metric=scorer, + custom_mapper=lambda ev: { + "input": "What is 2+2?", + "output": "4", + "expected": "4", + }, + ) + + result = adapter(_make_agent_evaluator_input()) + + assert isinstance(result, EvaluatorOutput) + assert result.value is not None diff --git a/uv.lock b/uv.lock index 103b65d7..cb467767 100644 --- a/uv.lock +++ b/uv.lock @@ -339,6 +339,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fb/95/adcb68e20c34162e9135f370d6e31737719c2b6f94bc953fe7ed1f10fe21/authlib-1.7.2-py2.py3-none-any.whl", hash = "sha256:3e1faedc9d87e7d56a164eca3ccb6ace0d61b94abe83e92242f8dc8bba9b4a9f", size = 259548, upload-time = "2026-05-06T08:10:21.436Z" }, ] +[[package]] +name = "autoevals" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "chevron" }, + { name = "jsonschema" }, + { name = "polyleven" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/bd/cbb2d2d53e991341fa63355248ed32870e2e60686fe11c066613f0df3a3c/autoevals-0.3.0.tar.gz", hash = "sha256:8def35d33146f80f09d24fa11d446c44d0be9471cce2836de437d82e88c6538a", size = 66952, upload-time = "2026-06-09T17:29:51.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/7d/142140e894c67bd69c9dbde15b2e929b4495647b5907e73e87f28d0b82ed/autoevals-0.3.0-py3-none-any.whl", hash = "sha256:c2292ab059d4617ae706658c5df51969eb8c6435a65fc930c61abb918ac2eb2d", size = 73778, upload-time = "2026-06-09T17:29:50.278Z" }, +] + [[package]] name = "aws-requests-auth" version = "0.4.3" @@ -392,6 +407,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/85/a12515514de8969b9e7fa40bb782501a336a8a985cc3093309120a80b627/awscrt-0.32.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a6782c19a00f354c7b232b675f09cde94d1ca37bcf29009b8779b3f6395b27b4", size = 4366435, upload-time = "2026-04-24T22:59:25.53Z" }, ] +[[package]] +name = "backoff" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, +] + [[package]] name = "backports-asyncio-runner" version = "1.2.0" @@ -457,9 +481,17 @@ a2a-v1 = [ ag-ui = [ { name = "ag-ui-protocol" }, ] +autoevals = [ + { name = "autoevals" }, + { name = "strands-agents-evals" }, +] datasets = [ { name = "requests" }, ] +deepeval = [ + { name = "deepeval" }, + { name = "strands-agents-evals" }, +] langgraph = [ { name = "httpx" }, { name = "langchain" }, @@ -507,8 +539,10 @@ requires-dist = [ { name = "a2a-sdk", extras = ["http-server"], marker = "extra == 'a2a'", specifier = ">=0.3,<0.4" }, { name = "a2a-sdk", extras = ["http-server"], marker = "extra == 'a2a-v1'", specifier = ">=1.0.1,<2.0" }, { name = "ag-ui-protocol", marker = "extra == 'ag-ui'", specifier = ">=0.1.10" }, + { name = "autoevals", marker = "extra == 'autoevals'", specifier = ">=0.0.50" }, { name = "boto3", specifier = ">=1.43.31" }, { name = "botocore", specifier = ">=1.43.31" }, + { name = "deepeval", marker = "extra == 'deepeval'", specifier = ">=2.0.0" }, { name = "httpx", marker = "extra == 'langgraph'", specifier = ">=0.27.0" }, { name = "jinja2", marker = "extra == 'simulation'", specifier = ">=3.1.0" }, { name = "langchain", marker = "extra == 'langgraph'", specifier = ">=1.0.0" }, @@ -519,14 +553,16 @@ requires-dist = [ { name = "requests", marker = "extra == 'datasets'", specifier = ">=2.31.0" }, { name = "starlette", specifier = ">=0.46.2" }, { name = "strands-agents", marker = "extra == 'strands-agents'", specifier = ">=1.20.0" }, - { name = "strands-agents-evals", marker = "extra == 'simulation'", specifier = ">=0.1.0" }, - { name = "strands-agents-evals", marker = "extra == 'strands-agents-evals'", specifier = ">=0.1.0" }, + { name = "strands-agents-evals", marker = "extra == 'autoevals'", specifier = ">=1.0.3,<2.0.0" }, + { name = "strands-agents-evals", marker = "extra == 'deepeval'", specifier = ">=1.0.3,<2.0.0" }, + { name = "strands-agents-evals", marker = "extra == 'simulation'", specifier = ">=1.0.3,<2.0.0" }, + { name = "strands-agents-evals", marker = "extra == 'strands-agents-evals'", specifier = ">=1.0.3,<2.0.0" }, { name = "typing-extensions", specifier = ">=4.13.2,<5.0.0" }, { name = "urllib3", specifier = ">=1.26.0" }, { name = "uvicorn", specifier = ">=0.34.2" }, { name = "websockets", specifier = ">=13.0" }, ] -provides-extras = ["a2a", "a2a-v1", "ag-ui", "strands-agents", "langgraph", "strands-agents-evals", "simulation", "datasets"] +provides-extras = ["a2a", "a2a-v1", "ag-ui", "strands-agents", "langgraph", "strands-agents-evals", "simulation", "datasets", "deepeval", "autoevals"] [package.metadata.requires-dev] dev = [ @@ -547,7 +583,7 @@ dev = [ { name = "pytest-rerunfailures", specifier = ">=15.0" }, { name = "ruff", specifier = ">=0.12.0" }, { name = "strands-agents", specifier = ">=1.20.0" }, - { name = "strands-agents-evals", specifier = ">=0.1.0" }, + { name = "strands-agents-evals", specifier = ">=1.0.3,<2.0.0" }, { name = "websockets", specifier = ">=14.1" }, { name = "wheel", specifier = ">=0.45.1" }, ] @@ -784,6 +820,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, ] +[[package]] +name = "chevron" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/1f/ca74b65b19798895d63a6e92874162f44233467c9e7c1ed8afd19016ebe9/chevron-0.14.0.tar.gz", hash = "sha256:87613aafdf6d77b6a90ff073165a61ae5086e21ad49057aa0e53681601800ebf", size = 11440, upload-time = "2021-01-02T22:47:59.233Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/93/342cc62a70ab727e093ed98e02a725d85b746345f05d2b5e5034649f4ec8/chevron-0.14.0-py3-none-any.whl", hash = "sha256:fbf996a709f8da2e745ef763f482ce2d311aa817d287593a5b990d6d6e4f0443", size = 11595, upload-time = "2021-01-02T22:47:57.847Z" }, +] + [[package]] name = "click" version = "8.2.1" @@ -964,6 +1009,46 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/00/8d/7f362c2fb8ef4decd2160bc24d4292c6ca658cc6d9a161b89ca5122bbdbf/cyclopts-4.16.1-py3-none-any.whl", hash = "sha256:617795392c4113a2c2cc7af716f20244900e87f23daa05442d1268d81472a592", size = 219020, upload-time = "2026-05-25T15:29:09.646Z" }, ] +[[package]] +name = "deepeval" +version = "4.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "click" }, + { name = "grpcio" }, + { name = "jinja2" }, + { name = "nest-asyncio" }, + { name = "openai" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, + { name = "portalocker" }, + { name = "posthog" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyfiglet" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-repeat" }, + { name = "pytest-rerunfailures" }, + { name = "pytest-xdist" }, + { name = "python-dotenv" }, + { name = "questionary" }, + { name = "requests" }, + { name = "rich" }, + { name = "sentry-sdk" }, + { name = "setuptools" }, + { name = "tabulate" }, + { name = "tenacity" }, + { name = "tqdm" }, + { name = "typer" }, + { name = "wheel" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/9e/5c9c403f25656dbffdfcd11a093ce13dabaea493e8b2568fd34781d86bf9/deepeval-4.1.4.tar.gz", hash = "sha256:cd8a393cb11cb6254b0e1da57be78bc5f927117f5edb9da938a63375dbc85967", size = 770881, upload-time = "2026-07-27T11:33:12.838Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/06/817d4d1f826ae478d79e6b188f75c2ba2499c476dc3a41a459d085029188/deepeval-4.1.4-py3-none-any.whl", hash = "sha256:86afbcd811e5a99eed370d14783dad5e990a8976c9e3e3b1690b22003f909a78", size = 1102921, upload-time = "2026-07-27T11:33:14.944Z" }, +] + [[package]] name = "dill" version = "0.4.0" @@ -1034,6 +1119,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, ] +[[package]] +name = "execnet" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, +] + [[package]] name = "fastapi" version = "0.139.2" @@ -1290,6 +1384,67 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1", size = 142357, upload-time = "2026-03-27T11:34:46.275Z" }, ] +[[package]] +name = "grpcio" +version = "1.83.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/98/304898ac4e04e2d5e4e4c2eadc178b1f2a16d5f4bc2f91306c87d64680b9/grpcio-1.83.0.tar.gz", hash = "sha256:7674587248fbbb2ac6e4eecf83a8a0f3d91a928f941de571acfd3a2f007fbc24", size = 13428824, upload-time = "2026-07-23T15:20:37.759Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/fd/655c8a773d728bc3c93fb4713ae4bf79ffc75996f86fb78b2974c8e1dfbd/grpcio-1.83.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:fba099b716e73512d61b97f71ea3c31a72abb36904036e316bf4dd148ca8dcc8", size = 6334247, upload-time = "2026-07-23T15:18:53.099Z" }, + { url = "https://files.pythonhosted.org/packages/d4/9a/1ce5760d35a04a992006dd2f79afff2db548f93ee7426fa95c9f1fc90c61/grpcio-1.83.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6755ed67cc3e454d51ae9f6e1915b80d3942fa4de956ef48dacd45ab7f40b727", size = 12168650, upload-time = "2026-07-23T15:18:56.348Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ab/bbcb5be0a1a6cb21f036e2afdd4f7a70147cfb7a7b42648a310d7c43acfc/grpcio-1.83.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5882c1a721b50ce0123ee5e839e1ab059ad72a7ade76cdf2d5bd833b56791acf", size = 6916899, upload-time = "2026-07-23T15:18:58.339Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d2/4c27977ecb3b3f9f363b93f570e001cb24ef264a9a907d7fd0f949ed59f0/grpcio-1.83.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4e3eedfc92b6b9f2960115e7e620cf0cbf80bb7849a51ce3820dc54dfd88b6b9", size = 7648761, upload-time = "2026-07-23T15:19:00.071Z" }, + { url = "https://files.pythonhosted.org/packages/23/49/0c823a7627ff2e69a61e4a53c4edf215272892fc2c47c6431f033d46f4cc/grpcio-1.83.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4fcaa7c45c45b4a89e2867d1f1785d9481a788399d915e341ed2eb49aeef9dd4", size = 7074920, upload-time = "2026-07-23T15:19:02.293Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ce/963f01ff7c789a76909c9691b704112e02ca1e11c10405cd99c2bd7c40f1/grpcio-1.83.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6b6c666a1d5613ff360c9e90f44665e3a88b25a815209ddbc0917eec281931cb", size = 7598046, upload-time = "2026-07-23T15:19:03.921Z" }, + { url = "https://files.pythonhosted.org/packages/eb/de/1ce6bdefc847a7973040d10cebc8996c653a2a687c0a4da8d05dcab4e397/grpcio-1.83.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6be5c807b717be3dd649446f021301fd7907e376318675d2147823071034112a", size = 8634792, upload-time = "2026-07-23T15:19:05.633Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8b/7fe6a73895e3bdd788101d1276e48e0d262ebb165afacec1ec4efebcd785/grpcio-1.83.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c834e86d8fd2f03d7e4db49a027f7c5b89c5b88eed305543a5295bd6fee61e40", size = 8000286, upload-time = "2026-07-23T15:19:07.739Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b0/9a779de2bcda8722501a056fad1bec3d1117977af0c080ab1fc0655fdf35/grpcio-1.83.0-cp310-cp310-win32.whl", hash = "sha256:35a5b1c192496b6c25956eebfa963468935612206fd2543ac3ce981e6a5e0f03", size = 4404616, upload-time = "2026-07-23T15:19:09.988Z" }, + { url = "https://files.pythonhosted.org/packages/f0/8e/ce9a23590cac33a6c24e6386cc0ffc55821cc13212acc822e98f00a67161/grpcio-1.83.0-cp310-cp310-win_amd64.whl", hash = "sha256:8f6c395e493d20c39b29392ca200e9aaeb78d0bc2f04db0c0a7da7ddc939aa57", size = 5162304, upload-time = "2026-07-23T15:19:11.467Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f6/3b781cd07a715ea5f5125ae264226e7fc4d87603d6d3955022cabfdc5da2/grpcio-1.83.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:8ff0b8767ddd62704e0d9571c1890af08d84a3a689ebba1807e62519d0b3277f", size = 6338720, upload-time = "2026-07-23T15:19:13.177Z" }, + { url = "https://files.pythonhosted.org/packages/21/cc/d14833d15d5984e366f1b027fa78bd038c9b028c66880bffb0f5a4d25ee2/grpcio-1.83.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:4772402f43517b4824980be4b3b2274a81eec0004a70009473c31b340d43e223", size = 12178773, upload-time = "2026-07-23T15:19:15.401Z" }, + { url = "https://files.pythonhosted.org/packages/6b/98/8acbb416544e7871132d8e42a07ed70c802d70e6a16c6009e505a34d32a4/grpcio-1.83.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f4cee5fc86e84a0cf7ad1574b454c3320e087c07f55b7df5dc0ac6a873fb90c0", size = 6921203, upload-time = "2026-07-23T15:19:17.824Z" }, + { url = "https://files.pythonhosted.org/packages/45/9c/0fdbfaf4fc54e5c88f6bce4008a065092fe7fbc4460eb5617ae8b20fd505/grpcio-1.83.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f5e822a7e7d03282f6ad225e710493c48b9057a353358344a5f7c42b2b37618d", size = 7648508, upload-time = "2026-07-23T15:19:19.685Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ea/107b9dbb2ed3ad14dd774fd3dde7d29ff9938a6c198654becb2c3a0e9a6a/grpcio-1.83.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5f410d7c2903eabb34789dfd6342eef04af1ad459943936b7e09a9f5bd417b9", size = 7079466, upload-time = "2026-07-23T15:19:21.478Z" }, + { url = "https://files.pythonhosted.org/packages/3b/06/9fa9941089e6fae83b060b6ce61c1e81053e52decae43197245f45e07d36/grpcio-1.83.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee94a4016fdf8699fb1fd8a38652475ff677f1c72074cee44deeeb9a7e95e745", size = 7605583, upload-time = "2026-07-23T15:19:23.74Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/f10fb56062dc2771c630827a82d9ad0ecd05cad572ea3b08d49f6631680a/grpcio-1.83.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c6444666317338e903093c7c756e6cc88eee59f798cb8dd41e87725bf54e1617", size = 8637810, upload-time = "2026-07-23T15:19:25.536Z" }, + { url = "https://files.pythonhosted.org/packages/99/55/f84927258f6a1b6ea6dea661fdc6de859b35e560c96f3012d15ccd39f85e/grpcio-1.83.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aa074041231f03959cb097dd5517b0677b8ea49215bae01d5710a7b69dd59969", size = 8008021, upload-time = "2026-07-23T15:19:27.863Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6b/cdf72161397ccd29d4ca2192f641524536c9cf54ad948c9dd0e0e01138fa/grpcio-1.83.0-cp311-cp311-win32.whl", hash = "sha256:cb056f6e171c42639a50460b2929c82241fda51f71cf3dcdd68090fe45095a45", size = 4404376, upload-time = "2026-07-23T15:19:30.137Z" }, + { url = "https://files.pythonhosted.org/packages/df/ed/e0ffeb4c848699c194dc9fb6a29ab29bcb2b6aac8c416bf18c51bfe8242c/grpcio-1.83.0-cp311-cp311-win_amd64.whl", hash = "sha256:7416952ca770477990257206276999056f8316d79196f2f25942393e58a20b49", size = 5164469, upload-time = "2026-07-23T15:19:31.941Z" }, + { url = "https://files.pythonhosted.org/packages/15/2b/51e32514a4e9b715375c99721aadff0f24164cc2049b8269eda4de82a814/grpcio-1.83.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:28f6c35ac8fcf10e4594f138e468f194360089dde40d126a7033e863fc479930", size = 6303167, upload-time = "2026-07-23T15:19:33.78Z" }, + { url = "https://files.pythonhosted.org/packages/39/33/b5b50fc2c6fbe350e04814047bb2d409feec7b36ef8b170254c050e06bc0/grpcio-1.83.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:33898e6a28e4ae598f1577cb1c4fec2a15c033d0ec52b9b45a09610dd045b9da", size = 12160538, upload-time = "2026-07-23T15:19:35.958Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5f/734e72e7b9f79bcf0b2c270b8d3bca0e4ebb97a27a50d06240b145f6d41e/grpcio-1.83.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6fb8a1dd0c6f0f931e69e9d0dc6d1c406ed2a44fa963414eafba07b7fb685d16", size = 6869310, upload-time = "2026-07-23T15:19:38.607Z" }, + { url = "https://files.pythonhosted.org/packages/a4/17/a1735f215b2a5cd43c38b79eac072ad197e61be9829905b6b29550abd0db/grpcio-1.83.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2b5e75c34842cd9c1b95285ca395c6a569664b81e3ffa6b714125922942abaaf", size = 7613472, upload-time = "2026-07-23T15:19:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/b2/78/c9e81f806ac704b6b145cb01628db398985b1f8dfdc10e23b55fb0902b3d/grpcio-1.83.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeb339838db07600481ef869507279b75326c75eac6d10f7afa62a0da1d2bcdd", size = 7040616, upload-time = "2026-07-23T15:19:42.349Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ba/94cd5af859876049d340480acbb61a959096c84b567f215534faa78d0424/grpcio-1.83.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f47d62808b4c0a97b78bff88a6d4ca283a2a492b9a04a87d814af95ca3b9c19c", size = 7570491, upload-time = "2026-07-23T15:19:44.357Z" }, + { url = "https://files.pythonhosted.org/packages/3e/15/108d30d5a5c964312ae8b9cb0e8cc5b3c1cc68d8f757cca52b3565534d26/grpcio-1.83.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62003babc444a606dcd1f009cd16391ce23669ae4ad6ec267a873da7937a69f5", size = 8605036, upload-time = "2026-07-23T15:19:46.454Z" }, + { url = "https://files.pythonhosted.org/packages/ea/23/3828ae13c3db8233d123ad612747665817b952d8a954f32390230b582336/grpcio-1.83.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1aa567f8c3f19850ffd5d2858c9a8ea7c80f0db6c01186b71eb31e923ec984f5", size = 7981587, upload-time = "2026-07-23T15:19:48.913Z" }, + { url = "https://files.pythonhosted.org/packages/17/5b/77af31228f55f55a2a5112bb0077ad0a1c4d23dbb0c2853a62475bbdcc14/grpcio-1.83.0-cp312-cp312-win32.whl", hash = "sha256:cb2906c61db4f9c64cc360054b5df70eeb81846228e9e56a4944bd415a63dadc", size = 4394004, upload-time = "2026-07-23T15:19:50.618Z" }, + { url = "https://files.pythonhosted.org/packages/c0/da/f706e39550e7a3732ce2b9c5926107a93d74a802775b19b642a6df27dc96/grpcio-1.83.0-cp312-cp312-win_amd64.whl", hash = "sha256:1c699bbb20f143c8f2bff219de578aa2dc1f919399d67dc702b038b986ee62df", size = 5158525, upload-time = "2026-07-23T15:19:52.246Z" }, + { url = "https://files.pythonhosted.org/packages/56/eb/135daaa713f32d33b8f99b4153b3f8dc3b2a124996ac15581bf9ebdad3c3/grpcio-1.83.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:6662f3b1e07cc7493d437351860dc867bddc6a93c83ecf33bbfdaf0c217ab2d0", size = 6304480, upload-time = "2026-07-23T15:19:53.962Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a1/121806ce69f23138dabe06aa595b0e5f1ae051a37e4c1954eed7d692c800/grpcio-1.83.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:74fe6f9e8a35c7dbf32255ee154d15e3e5338a81ed39173d079d594d2e544cd1", size = 12154419, upload-time = "2026-07-23T15:19:56.3Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e8/d0389e09cd6b4c4d3089b92967ae4e3ffd64795bd349bf2f85cd6656d3da/grpcio-1.83.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10b3fa0475eb572c9a81a6fe37fa16a9c500c0c91cfc148cac15692b7e3c2867", size = 6873200, upload-time = "2026-07-23T15:19:58.701Z" }, + { url = "https://files.pythonhosted.org/packages/f8/51/f464c1d211fa50d5adbabe1b2e519948d99c13757052bfc9ea7afa28e284/grpcio-1.83.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:5f20a988480b0f28207f057f7f7ae1313393c3cef0adcfeae8248f9947eaf881", size = 7618811, upload-time = "2026-07-23T15:20:00.733Z" }, + { url = "https://files.pythonhosted.org/packages/e8/c0/539fe0832f2dd6500a28f5263071623fb34e8d4867aec632ccf81bd21156/grpcio-1.83.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bd82671b39065ba18cd536e9cd45b27ff649053f81ddd2c6a966d595067080f", size = 7042310, upload-time = "2026-07-23T15:20:02.675Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ca/ccf617d37ffa72567fa8e005ec7090c99da922799be2fb9847c8b21ca18c/grpcio-1.83.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc60215b5cb9fc8ca72942c498b551ac2305bd08f6ef8d4e3f0d21b64fbecd61", size = 7575412, upload-time = "2026-07-23T15:20:04.712Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b9/fd8d5245f823a8e0fd35d90e20ea3aa4acd47f8d5318fa8df307df52dec6/grpcio-1.83.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f1c3e5689d4b90987b1d72022bcfe866a9a3dc66197484cf856d96b6150e7f45", size = 8604248, upload-time = "2026-07-23T15:20:06.77Z" }, + { url = "https://files.pythonhosted.org/packages/14/1e/f37632fc11db72dfa4bba86c3a43e54358e53030df111ecae5e91a733ad6/grpcio-1.83.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a21cb4eeeba124443f399be2e8b624943cde864dcbe588cb42e5c483a52a906c", size = 7977458, upload-time = "2026-07-23T15:20:09.109Z" }, + { url = "https://files.pythonhosted.org/packages/93/b6/d70b69ae5c0cfc341b9ba474980e4ed99cbf05c0e4a14e9eee8cb73db0a5/grpcio-1.83.0-cp313-cp313-win32.whl", hash = "sha256:8fe04f1050a59f875601eb55d42b4f66946fe89817f967e34db1462ccd07dadf", size = 4393993, upload-time = "2026-07-23T15:20:11.017Z" }, + { url = "https://files.pythonhosted.org/packages/0f/13/45d4cccb555cf4c476226979bf3d2fd0b0254216f7564c3a053e35117efc/grpcio-1.83.0-cp313-cp313-win_amd64.whl", hash = "sha256:6e01ecd9d8ef280abe1365138a4dc318f9a5287f4cb1b41d07816f796653f735", size = 5159650, upload-time = "2026-07-23T15:20:12.979Z" }, + { url = "https://files.pythonhosted.org/packages/9c/60/f2cca8147ea213d3e43ae9158d03ad04e020fdf32ff027253e1fe93f921d/grpcio-1.83.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3f351629f6ae16ecc0ec3553e586a6763ffd9f6114044286d0cbec3e09241bfa", size = 6305607, upload-time = "2026-07-23T15:20:15.353Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ab/d3874931d123a95e83a3ebf8aa04537988fb62425cedb8bf3cefc5ad41b2/grpcio-1.83.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d05ff664100d429335b93c91b8b34ddf9e94a112205e7fa06dede309e44a4e4c", size = 12166617, upload-time = "2026-07-23T15:20:17.435Z" }, + { url = "https://files.pythonhosted.org/packages/92/ff/6f18f9426b69306f4e00a9add3b0ee2748da8aad53836ef80cab0d62d04f/grpcio-1.83.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7936f2a56cf04f6514705c0fedf400971de01b6aa1719327e4718f410a765e2b", size = 6880213, upload-time = "2026-07-23T15:20:19.98Z" }, + { url = "https://files.pythonhosted.org/packages/70/21/706d1147c6b93b98f179240c13991fbcc56880eba0c868abb1ad40d8a0a6/grpcio-1.83.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b0a0be840e51b6b7ee9df9269770faf77bdf4b771053c257c21d12bad607714c", size = 7618335, upload-time = "2026-07-23T15:20:22.161Z" }, + { url = "https://files.pythonhosted.org/packages/74/04/1a8443c889115ec9e213a213e86bc93a71ee9088027e5befa09aaa0edd9d/grpcio-1.83.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:009667eaf3dcd5224c713589cdc98e7ca4ed0ff0b61132c6b276e930eb83a2df", size = 7043416, upload-time = "2026-07-23T15:20:24.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/c6/94e0fee5b12bc1da1370185b680988db6f739d19b42d9959db01a7ea50bf/grpcio-1.83.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bb669918fd88936b15599caff4160a77ab74bdeb25f2231f6e45b61282d6107b", size = 7583253, upload-time = "2026-07-23T15:20:26.313Z" }, + { url = "https://files.pythonhosted.org/packages/a0/97/de1ccb671fb85575bc5192faedf9ecdbdf5b390d2e6584dcf552bcbd370e/grpcio-1.83.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c19b454d3d3f28db81f2c7c4dbaee96e7f6fd149721733ffe79d6bc530f17404", size = 8605102, upload-time = "2026-07-23T15:20:28.437Z" }, + { url = "https://files.pythonhosted.org/packages/17/0f/0e0ec749a7034ffcbaa050e39779872950ead90c22e7e0116be3f28b2b46/grpcio-1.83.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:61007cd08640abc5c54547ee32505474c482cd733a53cb87551ea81faa6350af", size = 7979826, upload-time = "2026-07-23T15:20:31.182Z" }, + { url = "https://files.pythonhosted.org/packages/83/fa/c3fda157287f64bc65acee6c5aa90c41acf9e0d3a8e69a265eecff6d00a1/grpcio-1.83.0-cp314-cp314-win32.whl", hash = "sha256:32e11c37f5285b0c6fa3042c05fe06903696689749833fc64e67dec71b9bbe33", size = 4471765, upload-time = "2026-07-23T15:20:33.195Z" }, + { url = "https://files.pythonhosted.org/packages/a1/00/b1b26431c9d54eee11724fd6e5585473a2ed47fbc1fb95e5204906a642ce/grpcio-1.83.0-cp314-cp314-win_amd64.whl", hash = "sha256:2bb48cb5e6dd005ca12b89ce4b6ac0b48ff3112c747542ee7986ef611a8ca6d9", size = 5298932, upload-time = "2026-07-23T15:20:35.48Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -1432,6 +1587,105 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "jiter" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/1f/10936e16d8860c70698a1aa939a46aa0224813b782bce4e000e637da0b2d/jiter-0.16.0.tar.gz", hash = "sha256:7b24c3492c5f4f84a37946ad9cf504910cf6a782d6a4e0689b6673c5894b4a1c", size = 176431, upload-time = "2026-06-29T13:05:13.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/d8/b959609e44012a42b1f3e5ba98ea3b33c7e41e6d4b77cd8f00fd19b1d3ad/jiter-0.16.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c5fc4f8def331036a7b8e981b4347ebe409981edbc8308a5ea842b8c3614fa6c", size = 310082, upload-time = "2026-06-29T13:02:31.356Z" }, + { url = "https://files.pythonhosted.org/packages/c6/3d/4d7f5667ea0e0548534ba880b84bb3d12924fd133aa83ad6c6c80fca3d76/jiter-0.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a71d0d2014c3275043e1170bf3d4e771493cb0dcf07be54c567155f4d8ee64b", size = 315643, upload-time = "2026-06-29T13:02:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/9b/83/bed2dcb5c9f3e1ccfcbc67dda48265fe7d5ad0c9cadda5fe95f6e3b87f94/jiter-0.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:741eed508c233a76313a1c7b001f8f21b82f14327e9196ae8bd29a2cc164ae84", size = 341363, upload-time = "2026-06-29T13:02:34.853Z" }, + { url = "https://files.pythonhosted.org/packages/f4/2f/6bb3c3dda668ebc0445689c81a2b0f26a82b10843d67ed9c9b2c3edc177f/jiter-0.16.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3fb7bc819187b56dc48aa5c833aaf92257da8e07efdb9306156667bd2eeb491c", size = 365483, upload-time = "2026-06-29T13:02:36.295Z" }, + { url = "https://files.pythonhosted.org/packages/92/35/8a045ccb39164e70dcdae696413b661771f148b68b12b175c3a04d901937/jiter-0.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c9610fd25ebccb43fca584136f5c2fbb26802447eccd430dfdbab95a0fd5126", size = 461219, upload-time = "2026-06-29T13:02:38.116Z" }, + { url = "https://files.pythonhosted.org/packages/e7/99/22292dbbf0ed0c610cfe5ddc7f3bd67237a412f121318f865196e62a07bd/jiter-0.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4a1d68ff7ca1d3b5dee20a97a3decda7d5f15003823bf6d140c81f8561d3bc5c", size = 374905, upload-time = "2026-06-29T13:02:40.357Z" }, + { url = "https://files.pythonhosted.org/packages/29/ac/2f55ccb1f0eeafa6d89d24caf52f6f0944a59290ee199e9ade62177dca42/jiter-0.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb08c276dd02dac3a284acdd02cacc630d2e3cd6572a4b85519f35cbd133c3de", size = 348320, upload-time = "2026-06-29T13:02:41.923Z" }, + { url = "https://files.pythonhosted.org/packages/50/e3/7d88b9174c40064fabc07c84a9b62e6b10f5644562ec0e0a29392edbe978/jiter-0.16.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:8fc4d94713c4697347e38faf7d6ef91547c142219bdcfc7220c4870879974244", size = 356519, upload-time = "2026-06-29T13:02:43.436Z" }, + { url = "https://files.pythonhosted.org/packages/27/57/c4a33aeef513a9d5e26e31534e0bcc752d6ea0e54c94ddb7b68bade669c2/jiter-0.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a0f05e229edb29e68cdd0ccb83cea13b64263416120cf943767a6fd72e6787f", size = 394204, upload-time = "2026-06-29T13:02:44.987Z" }, + { url = "https://files.pythonhosted.org/packages/9d/70/c6c23e76ebb3766b111bc399437bbc9f870a76e2a92e10b2a5f561d57372/jiter-0.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2c842cbf374a8daf50b2c04212995bee34ca2ac2cdc29a901b4cdb072c9c4131", size = 521477, upload-time = "2026-06-29T13:02:46.724Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d3/0001c8c0c5976af2625bb1cfb1895e8ec693b6589fe4574b8e6fc2c85501/jiter-0.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5ed466aee31294d7cdcd4d37dfe5c42c97bc29d9a5f00eacf24504358309cb9b", size = 552187, upload-time = "2026-06-29T13:02:48.144Z" }, + { url = "https://files.pythonhosted.org/packages/f6/76/311b718e07e85740e48619c0632b36f7e0b8d113984499e436452ed13a9a/jiter-0.16.0-cp310-cp310-win32.whl", hash = "sha256:b42e9ff5376819c053da25809a8d4b6fa6e473b4856ebe42e298ac958be3d7f9", size = 206513, upload-time = "2026-06-29T13:02:49.515Z" }, + { url = "https://files.pythonhosted.org/packages/db/7f/ac680eeb0777dc0eb7dc824800ba27880d7f6bc712e362d34ad8ee559f36/jiter-0.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:10438939205546132189c8e74a2d536a707841f3a25cd7c74ee91fe503407a26", size = 199505, upload-time = "2026-06-29T13:02:50.829Z" }, + { url = "https://files.pythonhosted.org/packages/4e/3f/fae6cc967d120ec89e31c5418a51176d8278b3087fbb384a9176754f353c/jiter-0.16.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:67fddeda1688f0cce2d2ae83ccf8a80f79936f2d2997d6cc2261f82fdb54a4d3", size = 309289, upload-time = "2026-06-29T13:02:52.301Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e3/97c6c3562c077f6247d6e6ce5c82562500b6316c0d928e97e106b7a1321a/jiter-0.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c90c0f63df322be920eda6ce622e3083d8906ba267f8220fe7873213b8b4430e", size = 315181, upload-time = "2026-06-29T13:02:53.964Z" }, + { url = "https://files.pythonhosted.org/packages/7b/89/d8d073f8aa2667e46c6c0873f86fe4a512bba4293cc730f626a076211a62/jiter-0.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64c0203212098470032aabcde9356fc168f377aade3e43def61dfe17e92f2037", size = 340939, upload-time = "2026-06-29T13:02:55.412Z" }, + { url = "https://files.pythonhosted.org/packages/87/c9/db4fda3ed73fb864139305e935e5b8b38a5a24692a5a9dd356c22f1b9c8d/jiter-0.16.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12288303c9844e61e1651d02a9a6f6633e47d39f897d6991d1427161ce6b746e", size = 364932, upload-time = "2026-06-29T13:02:57.28Z" }, + { url = "https://files.pythonhosted.org/packages/a2/74/52b5e86241057f52ddd7c9a580f90effb51f9d06239f6fc612279b91a838/jiter-0.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cf109d010b4b05a105afb3d43be36a21322d345ad3111e13d15f680afef0e5b", size = 461132, upload-time = "2026-06-29T13:02:58.994Z" }, + { url = "https://files.pythonhosted.org/packages/a9/87/544a700f7447c1f31c5d7833821a4daa5683165c2d5a094fbf5b5800c3dc/jiter-0.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62c1b7fe1f77925acf5af68b6140b8810fa87dfd4dc0a9c8568ec2fa2a10429c", size = 374857, upload-time = "2026-06-29T13:03:00.455Z" }, + { url = "https://files.pythonhosted.org/packages/40/cd/0fcc3f7d39183674d5bfa9ec640faaeb506c60be7c8f94625dfba366e37c/jiter-0.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8597d23c87f59294f83bcb6229b9ed1fccee13dbba967b46930d2f1759466fee", size = 347053, upload-time = "2026-06-29T13:03:02.045Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ae/c7e64e7932ad597fa395b61440b249ada6366716e25c6e08dd2afbd021e6/jiter-0.16.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:3126a5dbad56401989ac769aca0cb56005bfb3e2366eea0ca99d1a91c3c1ee03", size = 356153, upload-time = "2026-06-29T13:03:03.706Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/1c719044f14da814e1a060191ab19b96f3e99207bc5b4bfc6d6be34b3f80/jiter-0.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c4b4717bdb35ae456f831a6b08d01880fff399887a6bbc526a583a406e484eea", size = 393956, upload-time = "2026-06-29T13:03:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/3b/dc/7b2f303a2847207e265503853a2d964a55354cffd62a5f2936c155486798/jiter-0.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:adff21bc78edfe086c15eb495b900306076de378dc2337c132401fc39bd79c91", size = 521081, upload-time = "2026-06-29T13:03:06.886Z" }, + { url = "https://files.pythonhosted.org/packages/c2/5f/501cf6e1e09caeb420195179ffc6f62aca603f1220ec53fd80d0d70b3e56/jiter-0.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dab907db06fc593645e73109acf4581ba5b548897d28b9348dc41ddc8343b2d3", size = 552085, upload-time = "2026-06-29T13:03:08.339Z" }, + { url = "https://files.pythonhosted.org/packages/79/54/aa5be86520113b79455c3877f3d1f07a348098df4083ba3688e9537e52dd/jiter-0.16.0-cp311-cp311-win32.whl", hash = "sha256:560b2cf3fb03240cd34f27409a238547488708f05b7c3924f571a60422251ec7", size = 206755, upload-time = "2026-06-29T13:03:09.653Z" }, + { url = "https://files.pythonhosted.org/packages/64/ec/2feb893eb330bd69b413866f4d5daada33c3962f1c6f270c91ca2d87fdf9/jiter-0.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:e431cfc9caf44c1d5459ff77d4e64cbf85fddb6a35dad836a15c6a9ec23087c1", size = 199155, upload-time = "2026-06-29T13:03:10.979Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9c/ca040d94415048a3666fc237774df8151c96f8d2b661cbe3b184acc95876/jiter-0.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:2a8e9e39cf083016137aa5cadafe3188adc2ba6ba1fbf1e5d18889ad3e9ad056", size = 194403, upload-time = "2026-06-29T13:03:12.341Z" }, + { url = "https://files.pythonhosted.org/packages/83/2b/52ace16ed031354f0539749a49e4bf33797d82bea5137910835fa4b09793/jiter-0.16.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:67c3bc1760f8c99d805dcab4e644027142a53b1d5d861f18780ebdbd5d40b72a", size = 306943, upload-time = "2026-06-29T13:03:14.035Z" }, + { url = "https://files.pythonhosted.org/packages/94/2e/34957c2c1b661c252ba9bcc60ae0bddc27e0f7202c6073326a13c5390eec/jiter-0.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5af7780e4a26bd7d0d989592bf9ef12ebf806b74ab709223ecca37c749872ea9", size = 307779, upload-time = "2026-06-29T13:03:15.418Z" }, + { url = "https://files.pythonhosted.org/packages/88/6c/59bd309cab4460c54cf1079f3eb7fe7af6a4c895c5c957a53378693bad2b/jiter-0.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5bf78d0e05e45cfdd66558893938d59afe3d1b1a824a202039b20e607d25a72", size = 335826, upload-time = "2026-06-29T13:03:17.11Z" }, + { url = "https://files.pythonhosted.org/packages/3b/8c/f5ef7b65f0df47afa16596969defb281ebb86e96df346d62be6fd853d620/jiter-0.16.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4444a83f946605990c98f625cdd3d2725bfb818158760c5748c653170a20e0e", size = 362573, upload-time = "2026-06-29T13:03:18.781Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0b/ace4354da061ee38844a0c27dc2c21eecd27aea119e8da324bea987522d0/jiter-0.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a23f0e4f957e1be65752d2dfac9a5a06b1917af8dc85deb639c3b9d02e31290", size = 457979, upload-time = "2026-06-29T13:03:20.293Z" }, + { url = "https://files.pythonhosted.org/packages/55/40/c0253d3772eb9dcd8e6606ee9b2d53ec8e5b814589c47f140aa585f21eaa/jiter-0.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c22a488f7b9218e245a0025a9ba6b100e2e54700831cf4cf16833a27fba3ad01", size = 372302, upload-time = "2026-06-29T13:03:21.739Z" }, + { url = "https://files.pythonhosted.org/packages/a8/d2/4839422241aa12860ce597b20068727094ba0bc480723c74924ca5bad483/jiter-0.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46add52f4ad47a08bfb1219f3e673da972191489a33016edefdb5ea55bfa8c48", size = 343805, upload-time = "2026-06-29T13:03:23.384Z" }, + { url = "https://files.pythonhosted.org/packages/e2/59/e196888a05befdda7dbe299b722d56f2f6eec65402bc34c0a3306d595feb/jiter-0.16.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:9c8a956fd72c2cf1e730d01ea080341f13aa0a97a4a33b51abebe725b7ae9ca9", size = 351107, upload-time = "2026-06-29T13:03:24.815Z" }, + { url = "https://files.pythonhosted.org/packages/ec/74/4cd9e0fca65232136400354b630fbfcd2de634e22ccbb96567725981b548/jiter-0.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:561926e0573ffe4a32498420a76d64b16c513e1ab413b9d28158a8764ac701e5", size = 388441, upload-time = "2026-06-29T13:03:26.266Z" }, + { url = "https://files.pythonhosted.org/packages/d9/8c/554691e48bc711299c0a293dd8a6179e24b2d66a54dc295421fcf64569c0/jiter-0.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:44d019fa8cdaf89bf29c71b39e3712143fdd0ac76725c6ef954f9957a5ea8730", size = 516354, upload-time = "2026-06-29T13:03:28.02Z" }, + { url = "https://files.pythonhosted.org/packages/a4/cb/01e9d69dc2cc6759d4f91e230b34489c4fdb2518992650633f9e20bece89/jiter-0.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0df91907609837f33341b8e6fe73b95991fdaa57caf1a0fbd343dffe826f386f", size = 547880, upload-time = "2026-06-29T13:03:29.534Z" }, + { url = "https://files.pythonhosted.org/packages/79/70/2953195f1c6ad00f49fa67e13df7e60acb3dd4f387101bc15abccddd905e/jiter-0.16.0-cp312-cp312-win32.whl", hash = "sha256:51d7b836acb0108d7c77df1742332cac2a1fa04a74d6dacec46e7091f0e91274", size = 203473, upload-time = "2026-06-29T13:03:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/2d/05/2909a8b10699a4d560f8c502b6b2c5f3991b682b1922c1eedda242b225bd/jiter-0.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:1878349266f8ee36ecb1375cc5ba2f115f35fd9f0a1a4119e725e379126647f7", size = 196905, upload-time = "2026-06-29T13:03:32.472Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a9/6b82bb1c8d7790d602489b967b982a909e5d092875a6c2ade96444c8dfc5/jiter-0.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:2ed5738ae4af18271a51a528b8811b0cbfa4a1858de9d83359e4169855d6a331", size = 190618, upload-time = "2026-06-29T13:03:34.672Z" }, + { url = "https://files.pythonhosted.org/packages/91/c0/555fc60473d30d66894ba825e63615e3be7524fac23858356afa7a38906c/jiter-0.16.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:41977aa5654023948c2dae2a81cbf9c43343954bef1cd59a154dd15a4d84c195", size = 306203, upload-time = "2026-06-29T13:03:36.243Z" }, + { url = "https://files.pythonhosted.org/packages/d0/2b/c3eaf16f5d7c9bad66ea32f40a95bd169b29a91217fcc7f081375157e99c/jiter-0.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d28bb3c26762358dadf3e5bf0bccd29ae987d65e6988d2e6f49829c76b003c09", size = 306489, upload-time = "2026-06-29T13:03:37.846Z" }, + { url = "https://files.pythonhosted.org/packages/96/3f/02fdfc6705cad96127d883af5c34e4867f554f29ec7705ec1a46156400a9/jiter-0.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0542a7189c26920778658fc8fcf2af8bae05bae9924577f71804acef37996536", size = 335453, upload-time = "2026-06-29T13:03:39.221Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a6/e4bda5920d4b0d7c5dfb7174ce4a6b2e4d3e11c9162c452ef0eab4cdbdbd/jiter-0.16.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8fb8de1e23a0cb2a7f53c335049c7b72b6db41aa6227cdcc0972a1de5cb39450", size = 361625, upload-time = "2026-06-29T13:03:40.597Z" }, + { url = "https://files.pythonhosted.org/packages/b7/97/4e6b59b2c6e55cbb3e183595f81ad65dcfb21c915fee5e19e335df21bc55/jiter-0.16.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b72d0b2990ca754a9102779ac98d8597b7cb31678958562214a007f909eab78e", size = 456958, upload-time = "2026-06-29T13:03:42.074Z" }, + { url = "https://files.pythonhosted.org/packages/15/e0/97e9557686d2f94f4b93786eccb7eed28e9228ad132ea8237f44727314a7/jiter-0.16.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f91b1c27fc22a57993d5a5cb8a627cb8ed4b10502716fac1ffbfe1d19d84e8", size = 372017, upload-time = "2026-06-29T13:03:43.658Z" }, + { url = "https://files.pythonhosted.org/packages/0f/94/db768b6938e0df35c86beeba3dfbbb025c9ee5c19e1aa271f2396e50864d/jiter-0.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c682bea068a90b764577bdb78a60a4c1d1606daf9cd4c893832a37c7cc9d9026", size = 343320, upload-time = "2026-06-29T13:03:45.226Z" }, + { url = "https://files.pythonhosted.org/packages/c1/d6/5a59d938244a30735fe62d9433fd325f9021ea29d89780ea4596ea93bc89/jiter-0.16.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:8d031aabecc4f1b6276adfb42e3aabb77c89d468bf616600e8d3a11328929053", size = 350520, upload-time = "2026-06-29T13:03:46.671Z" }, + { url = "https://files.pythonhosted.org/packages/67/f8/c4a857f49c9af125f6bbcac7e3eee7f7978ed89682833062e2dbf62576b1/jiter-0.16.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eab2cd170150e70153de16896a1774e3a1dca80154c56b54d7a812c479a7165e", size = 387550, upload-time = "2026-06-29T13:03:48.361Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d6/5fbc2f7d6b67b754caa61a993a2e626e815dec47ffc2f9e35f01adfebec7/jiter-0.16.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6edb63a46e65a82c26800a868e49b2cac30dd5a4218b88d74bc2c848c8ad60bb", size = 515424, upload-time = "2026-06-29T13:03:49.881Z" }, + { url = "https://files.pythonhosted.org/packages/ed/54/284f0164b64a5fed915fea6ba7e9ba9b3d8d37c67d59cf2e3bb99d45cdfe/jiter-0.16.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:659039cc50b5addcc35fcc87ae2c1833b7c0a8e5326ef631a75e4478447bcf84", size = 546981, upload-time = "2026-06-29T13:03:51.363Z" }, + { url = "https://files.pythonhosted.org/packages/13/c5/2a467585a576594384e1d2c43e1224deaafc085f24e243529cf98beef8e1/jiter-0.16.0-cp313-cp313-win32.whl", hash = "sha256:c9c53be232c2e206ef9cdbad81a48bfa74c3d3f08bcf8124630a8a748aad993e", size = 202853, upload-time = "2026-06-29T13:03:53.015Z" }, + { url = "https://files.pythonhosted.org/packages/88/6a/de61d04b9eec69c71719968d2f716532a3bc121170c44a39e14979c6be81/jiter-0.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:baad945ed47f163ad833314f8e3288c396118934f94e7bbb9e243ce4b341a4fd", size = 196160, upload-time = "2026-06-29T13:03:54.447Z" }, + { url = "https://files.pythonhosted.org/packages/19/4b/b390ed59bafb3f31d008d1218578f10327714484b334439947f7e5b11e7f/jiter-0.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:3c1fd2dbe1b0af19e987f03fe66c5f5bd105a2229c1aff4ab14890b24f41d21a", size = 189862, upload-time = "2026-06-29T13:03:55.754Z" }, + { url = "https://files.pythonhosted.org/packages/a7/89/bc4f1b57d5da938fd344a466396541e586d161320d70bffd929aaafcd8f4/jiter-0.16.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b2c61484666ad42726029af0c00ef4541f0f3b5cdc550221f56c2343208018ee", size = 308239, upload-time = "2026-06-29T13:03:57.205Z" }, + { url = "https://files.pythonhosted.org/packages/65/7a/c415453e5213001bf3b411ff65dec3d303b0e76a4a2cfea9768cd4960994/jiter-0.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:63efadc657488f45db1c676d81e704cac2abf3fdb892def1faea61db053127e2", size = 308928, upload-time = "2026-06-29T13:03:58.643Z" }, + { url = "https://files.pythonhosted.org/packages/11/fc/1f4fb7ebf9a724c7741994f4aae18fba1e2f3133df14521a79194952c34a/jiter-0.16.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf0d73f50e7b6935677854f6e8e31d499ca7064dd24734f703e060f5b237d883", size = 336998, upload-time = "2026-06-29T13:04:00.071Z" }, + { url = "https://files.pythonhosted.org/packages/a0/8d/72cadaac05ccfa7cc3a0a2232862e6c72443ca40cf300ba8b57f9f18b69b/jiter-0.16.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3ea07d9bc8e7d03a9fbc051295462e6dbc295b894fd72457c3136e3e43d898", size = 362112, upload-time = "2026-06-29T13:04:01.52Z" }, + { url = "https://files.pythonhosted.org/packages/58/4a/c4b0d5f651fda90a24ffce9f8d56cde462a2e09d31ae3de3c68cef34c04e/jiter-0.16.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26798522707abb47d767db536e4148ceac1b14446bf028ee85e579a2e043cfe5", size = 459807, upload-time = "2026-06-29T13:04:03.214Z" }, + { url = "https://files.pythonhosted.org/packages/80/58/ef77879ea9aa56b50824edc5a445e226422c7a8d211f3fd2a56bcb9493cf/jiter-0.16.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc837c1b9631be10abfe0191537fe8009838204cec7e44827401ace390ddb567", size = 373181, upload-time = "2026-06-29T13:04:04.629Z" }, + { url = "https://files.pythonhosted.org/packages/49/2e/ffbc3f254e4d8a66da3062c624a7df4b7c2b2cf9e1fe43cf394b3e104041/jiter-0.16.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49060fd70737fad59d33ba9dcc0d83247dc9e77187de26053a19c16c9f32bd69", size = 344927, upload-time = "2026-06-29T13:04:06.067Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f6/0be5dc6d64a89f80aa8fec984f94dedb2973e251edcae55841d60786d578/jiter-0.16.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:adbb8edeadd431bc4477879d5d371ece7cb1334486584e0f252656dd7ffada29", size = 352754, upload-time = "2026-06-29T13:04:07.477Z" }, + { url = "https://files.pythonhosted.org/packages/da/6e/7d31243b3b91cd261dd19e9d3557fc3251a80883d3d8049c86174e7ab7af/jiter-0.16.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:31aaee5b80f672c1dc21272bcfb9cbdcfc1ea04ff50f00ed5af500b80c44fa93", size = 390553, upload-time = "2026-06-29T13:04:08.92Z" }, + { url = "https://files.pythonhosted.org/packages/25/33/51ae371fde3c88897520f62b4d5f8b27ad7103e2bb10812ff52195609853/jiter-0.16.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:6722bcef4ffc86c835574b1b2fac6b33b9fb4a889c781e67950e891591f3c55a", size = 516900, upload-time = "2026-06-29T13:04:10.407Z" }, + { url = "https://files.pythonhosted.org/packages/a0/45/6449b3d123ea439ba79507c657288f461d55049e7bcbdc2cf8eb8210f491/jiter-0.16.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:5ab4f50ff971b611d656554ea10b75f80097392c827bc32923c6eeb6386c8b00", size = 548754, upload-time = "2026-06-29T13:04:12.046Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e7/fd2fb11ae3e2649333da3aa170d04d7b3000bbdc3b270f6513382fdf4e04/jiter-0.16.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:710cc51d4ebdcd3c1f70b232c1db1ea1344a075770422bbd4bede5708335acbe", size = 122381, upload-time = "2026-06-29T13:04:13.413Z" }, + { url = "https://files.pythonhosted.org/packages/26/80/f0b147a62c315a164ed2168908286ca302310824c218d3aae52b06c0c9a9/jiter-0.16.0-cp314-cp314-win32.whl", hash = "sha256:57b37fc887a32d44798e4d8ebfa7c9683ff3da1d5bf38f08d1bb3573ccb39106", size = 204578, upload-time = "2026-06-29T13:04:14.813Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e6/4758a14304b4523a6f5adb2419340086aa3593bd4327c2b25b5948a90548/jiter-0.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:cbd18dd5e2df96b580487b5745adf57ef64ad89ba2d9662fc3c19386acce7db8", size = 198154, upload-time = "2026-06-29T13:04:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/26/be/41fa54a2e7ea41d6c99f1dc5b1f0fd4cb474680304b5d268dd518e81da3a/jiter-0.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:a32d2027a9fa67f109ff245a3252ece3ccc32cc56703e1deab6cc846a59e0585", size = 191458, upload-time = "2026-06-29T13:04:17.707Z" }, + { url = "https://files.pythonhosted.org/packages/81/6b/59127338b86d9fe4d99418f5a15118bea778103ee0fe9d9dd7e0af174e95/jiter-0.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2577196f4474ef3fc4779a088a23b0897bbf86f9ea3679c372d45b8383b43207", size = 316739, upload-time = "2026-06-29T13:04:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/2d/95/49461034d5388196d3dabf98748935f017b7785d8f3f5349f834bcc4ed0d/jiter-0.16.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e89e008a93c01104161c75b4988e58716b01d62307ebfe161e52a56d2a818", size = 340911, upload-time = "2026-06-29T13:04:21.257Z" }, + { url = "https://files.pythonhosted.org/packages/cd/97/a4369f2fb82cb3dda13b98622f31249b2e014b223fe64ee534413ad72294/jiter-0.16.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2e9efbe042210df657bade597f66d6d75723e3d8f45a12ea6d8167ff8bbce3", size = 361747, upload-time = "2026-06-29T13:04:22.677Z" }, + { url = "https://files.pythonhosted.org/packages/28/51/49b6ed456261646e1906016a6760367a28aacd3c24805e4e5fe64116c1db/jiter-0.16.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f4d9e473a5ce7d27fef8b848df4dc16e283893d3f53b4a585e72c9595f3c284", size = 460225, upload-time = "2026-06-29T13:04:24.441Z" }, + { url = "https://files.pythonhosted.org/packages/33/b5/5689aff4f66c5b60be63106e591dbfcba2190df97d2c9c7cf052361ddb98/jiter-0.16.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d30a4a1c87713060c8d1cc59a7b6c8fb6b8ef0a6900368014c76c87922a2929", size = 373169, upload-time = "2026-06-29T13:04:25.884Z" }, + { url = "https://files.pythonhosted.org/packages/a2/96/3ae1b85ee0d6d6cab254fb7f8da018272b932bbf2d69b07e98aa2a96c746/jiter-0.16.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae96332410f866e5900d809298b1ed82735932986c672495f9701daacd80620", size = 350332, upload-time = "2026-06-29T13:04:27.302Z" }, + { url = "https://files.pythonhosted.org/packages/15/32/c99d7bafd78986556c95bf60ce84c6cc98786eac56066c12d7f828bb6747/jiter-0.16.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:da3d7ec75dc83bb18bca888b5edfae0656a26849056c59e05a7728badd17e7af", size = 353377, upload-time = "2026-06-29T13:04:28.731Z" }, + { url = "https://files.pythonhosted.org/packages/0e/4b/f99a8e571287c3dec766bcc18528bbe8e8fb5365522ab5e6d64c93e87066/jiter-0.16.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ee6162b77d49a9939229df666dfa8af3e656b6701b54c4c84966d740e189264e", size = 387746, upload-time = "2026-06-29T13:04:30.319Z" }, + { url = "https://files.pythonhosted.org/packages/75/69/c78a5b3f71040e34eb5917df26fb7ae9a2174cad1ccbf277512507c53a6e/jiter-0.16.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:63ffdbdae7d4499f4cda14eadc12ddcabef0fc0c081191bdc2247489cb698077", size = 517292, upload-time = "2026-06-29T13:04:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f7/095b38eda4c70d03651c403f29a5590f16d12ddc5d544aac9f9cddf72277/jiter-0.16.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a111256a7193bea0759267b10385e5870949c239ed7b6ddbaaf57573edb38734", size = 549259, upload-time = "2026-06-29T13:04:33.721Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c5/6a0207d90e5f656d95af98ebd0934f382d37674416f215aeda2ff8063e51/jiter-0.16.0-cp314-cp314t-win32.whl", hash = "sha256:de5ba8763e56b793561f43bed197c9ea55776daa5e9a6b91eed68a909bc9cdbf", size = 206523, upload-time = "2026-06-29T13:04:35.068Z" }, + { url = "https://files.pythonhosted.org/packages/a5/31/c757d5f30a8980fd945ce7b98be10be9e4ff59c7c42f5fd86804c2e87db8/jiter-0.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b8a3f9a6008048fe9def7bf465180564a6e458047d2ce499149cfbe73c3ae9db", size = 200366, upload-time = "2026-06-29T13:04:36.61Z" }, + { url = "https://files.pythonhosted.org/packages/7c/a2/d88de6d313d734a544a7901353ad5db67cb38dcfcd91713b7979dafc345d/jiter-0.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0fa25b09b13075c46f5bc174f2690525a925a4fc2f7c82969a2bbabff22386ce", size = 190516, upload-time = "2026-06-29T13:04:38.004Z" }, + { url = "https://files.pythonhosted.org/packages/06/d3/8e278946d43eeca2585b4dd0834a887cd71136329b837f3a16ed86a8b4b0/jiter-0.16.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:850ccb1d7eedb4200f4014b1c0e8a577de114fc3cd88faad646dcc9bc4bb12ad", size = 304518, upload-time = "2026-06-29T13:05:00.172Z" }, + { url = "https://files.pythonhosted.org/packages/72/43/28d4ef495028bf0506a413d4db3f4eb3e7288a382e0f065f306a17bbeb5e/jiter-0.16.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:e34e97bda77eb63242a410243c071e28ac7e0d8c0948c5ee658498690a4b2f2f", size = 310207, upload-time = "2026-06-29T13:05:02.123Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ca/c366b1012da1d640de975d9683acd44e4d150d9068845d0ca2610435253f/jiter-0.16.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7dc85ea77d4abbae8bad0d3538678aedee75bceec4e2f6c8dfb1c74772e5aa5", size = 342771, upload-time = "2026-06-29T13:05:03.55Z" }, + { url = "https://files.pythonhosted.org/packages/16/52/50cc4056fc1ae02e7154704e7ecc89df0afb8300222cfe8a52d3f67e4730/jiter-0.16.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17ca7fae79f6d99cd9a042b75f917eaada7b895cfc7dd2ee3a16089dcaec7a85", size = 346468, upload-time = "2026-06-29T13:05:05.452Z" }, + { url = "https://files.pythonhosted.org/packages/98/ab/664fd8c4be028b2bedd3d2ff08769c4ede23d0dbc87a77c62384a0515b5d/jiter-0.16.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:f17d61a28b4b3e0e3e2ba98490c70501403b4d196f78732439160e7fd3678127", size = 303106, upload-time = "2026-06-29T13:05:07.118Z" }, + { url = "https://files.pythonhosted.org/packages/1a/07/421f1d5b65493a76e16027b848aba6a7d28073ae75944fa4289cc914d39f/jiter-0.16.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:96e38eea538c8ddf853a35727c7be0741c76c13f04148ac5c116222f50ece3b3", size = 304658, upload-time = "2026-06-29T13:05:08.708Z" }, + { url = "https://files.pythonhosted.org/packages/0a/db/bba1155f01a01c3c37a89425d571da751bbedf5c54247b831a04cb971798/jiter-0.16.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d284fb8d94d5855d60c44fefcab4bf966f1da6fada73992b01f6f0c9bc0c6702", size = 339719, upload-time = "2026-06-29T13:05:10.41Z" }, + { url = "https://files.pythonhosted.org/packages/78/f7/18a1afcd64f35314b68c1f23afcd9994d0bc13e65cc77517afff4e83986d/jiter-0.16.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64d613743df53199b1aa256a7d328340da6d7078aac7705a7db9d7a791e9cfd2", size = 343885, upload-time = "2026-06-29T13:05:12.087Z" }, +] + [[package]] name = "jmespath" version = "1.0.1" @@ -2051,6 +2305,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -2060,6 +2323,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] +[[package]] +name = "openai" +version = "2.50.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/f5/e7735f2af272ee179a287911a698b3cbdb59d7a4ac4874571363adf1e4de/openai-2.50.0.tar.gz", hash = "sha256:5128f7caf4a6b01aefd6e7e93efe170a2c3427b8de286b9af5cdff3aa47e02c8", size = 1081965, upload-time = "2026-07-28T22:16:31.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/ca/db315b3bb748c26c644a3f85b7d509e774354d6518d47080b1446005ee41/openai-2.50.0-py3-none-any.whl", hash = "sha256:90bdddcc5a2fa529b350fac9c5780d87e5c361dcc6090ab57b0d470b0d7af7fa", size = 1650721, upload-time = "2026-07-28T22:16:29.48Z" }, +] + [[package]] name = "openapi-pydantic" version = "0.5.1" @@ -2421,6 +2703,95 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "polyleven" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/c7/e0b3bbe72e0003e5d02726e0d406ea47d523a2aec9c41d831817a8e0bce1/polyleven-0.11.0.tar.gz", hash = "sha256:d74d348387cf340051711c0dd6af993b4c264daa78470098de16f4a2b725785c", size = 6407, upload-time = "2026-02-09T09:41:49.87Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/6c/34c6189c80adf7575fb2daee38c4b836c154e416ab3c14d17afa7f88b9c3/polyleven-0.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ccf87f6ac8d76aa4c48a4828becc0c19fd1589b14b20affe23e5e012be4fa64f", size = 7421, upload-time = "2026-02-09T09:40:33.243Z" }, + { url = "https://files.pythonhosted.org/packages/e6/21/b20d3c9f9b6bded43a0388037044f2bcb1add20fa9a758d1144d79e09d10/polyleven-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1a02e3f0acfd1164cbaea25192398bc943ee9b93b9883a1fba9b2613d3616b0", size = 7514, upload-time = "2026-02-09T09:40:34.514Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d8/60290fd8d8298671edb4ce221d8ee4d81156b3c21b1154a615da7ea8e57d/polyleven-0.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6526d2516b439065864722069de6fcc418a4135696990dad66b81ddb18863bd9", size = 19556, upload-time = "2026-02-09T09:40:35.868Z" }, + { url = "https://files.pythonhosted.org/packages/c1/cd/17f1f6009a344c18f4213edcc97a9e7dae22e9a26e722aae10052378a43e/polyleven-0.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65fc01fe6cfe287f2f20170b35687a436ab36b882db568a55d81d6e0acd8379d", size = 20303, upload-time = "2026-02-09T09:40:36.99Z" }, + { url = "https://files.pythonhosted.org/packages/4e/18/30c7da8056adc4b9a81775f5b1810a2c9b6cc87fc34c8cf4c0280d28cab6/polyleven-0.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4b8e5ac8faecc6daa7b3d325436a3f23f8c33dec7bfca5d22df3fbe00f92ddd9", size = 19565, upload-time = "2026-02-09T09:40:38.471Z" }, + { url = "https://files.pythonhosted.org/packages/de/14/86e9c33ff9fda84297556373a6376100cbe9bb5d917fc3421dce4ada441b/polyleven-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dc4f17007b07fde292ad33ec43a3ae8febe27a5bd92462b920736fd81d774fce", size = 19365, upload-time = "2026-02-09T09:40:39.893Z" }, + { url = "https://files.pythonhosted.org/packages/be/18/1341f7860bbe2287f6cb8a540c3435c504cfc58659b67106ab60f695175e/polyleven-0.11.0-cp310-cp310-win32.whl", hash = "sha256:cae70197d545a09bfab8d7e506eed66ef314fa6c4e7a5e2c402c2febc31db74b", size = 11672, upload-time = "2026-02-09T09:40:40.822Z" }, + { url = "https://files.pythonhosted.org/packages/6d/09/ed2cb3dbec7a925d80107516b06dfe10dd368c6abfb43765df6feb6cd551/polyleven-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:bf47079b6dc62e6af2bd6ecb45a6087efd9a27b61666b98d0326c246a22ea991", size = 10828, upload-time = "2026-02-09T09:40:42.224Z" }, + { url = "https://files.pythonhosted.org/packages/13/7a/cb74c2ffe4e35935d80ef6f180f9e0987b8000917d52050a3563b32e1e73/polyleven-0.11.0-cp310-cp310-win_arm64.whl", hash = "sha256:4c78b4d3e7d7b74315d5422178118963374c0cf3d7a9532a955f446ed365320a", size = 9389, upload-time = "2026-02-09T09:40:43.172Z" }, + { url = "https://files.pythonhosted.org/packages/6c/7a/27ea9a78b617ddb14c2f5d2416df2fbf07fa5e52685f2968686a0308c8af/polyleven-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a28860fe33a7f907bc5f86e55a0b9faea80047d1677fa23b4d6c631ccf91ef2f", size = 7420, upload-time = "2026-02-09T09:40:44.505Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9c/fea309d41502aa5a344a6d4d6e5b8bdabb1df1e28f1af52bb53180f6c956/polyleven-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:47a3fb5b8cb60f647d2832d38b7d87cda27da8622b27c1292bceb9a04954c189", size = 7514, upload-time = "2026-02-09T09:40:46.44Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/c7d3bb6c66050304c3fe3cae1a716f62fea947ac3f14d02ef71e24422f76/polyleven-0.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:209fa669ca23ac453a7e9fbf07695350d5cbe61d71a6226b861757ccab28e664", size = 20887, upload-time = "2026-02-09T09:40:47.304Z" }, + { url = "https://files.pythonhosted.org/packages/77/1b/aeaf38075c7e0225fd7ba89db3bd11c0e65b50907242d82d57c4804941d9/polyleven-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3bfce4689b6aaacf7c5296b8ed11ada07ccf046a01097ba1681e10f9caabbf6f", size = 21376, upload-time = "2026-02-09T09:40:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/bd/96/10f01f8ab883a51ef7bed610933ba88b7cf5b0a0e3fd059c94f1db8414d4/polyleven-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:83e59c8590a06ea6a959a3c55e6d28544b8d11a51aac2a318c1b74f92575dd28", size = 20436, upload-time = "2026-02-09T09:40:50.059Z" }, + { url = "https://files.pythonhosted.org/packages/b1/4e/5cedf4cfde32eddab388435463a0f8c2e322449c61ef4765db62ca7c0d13/polyleven-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2eb8f6778f3073dce041805a09f0753cc441b0219253d7b933aad234a954f30a", size = 20685, upload-time = "2026-02-09T09:40:51.03Z" }, + { url = "https://files.pythonhosted.org/packages/a0/40/d88e50b60d0a731d9fb7e71268a91fcf8e290eaa5c043715d8f6ad158fe2/polyleven-0.11.0-cp311-cp311-win32.whl", hash = "sha256:248b9f645d8c6e337091498ed5c7d4a796d9d51df98458be25b1d76d962954e2", size = 11613, upload-time = "2026-02-09T09:40:52.468Z" }, + { url = "https://files.pythonhosted.org/packages/33/67/a52aeeb5200ea4c6d1642cd9e86827feee619e56792d1795465532a9d6f8/polyleven-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:5c9ccb2f327d49a7566b0192e0d426f7772b38e247dc4e809c0b1cdf23e2ecc2", size = 10814, upload-time = "2026-02-09T09:40:53.355Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e1/857fa37a1d4cca74cb2a144cd2962d265fd2d705f971c146d6ee6cdab546/polyleven-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:9b97b9260730deda4cdd5878fd6ce128b970497da0fbefeeacc0b1ed4c59ebb7", size = 9420, upload-time = "2026-02-09T09:40:54.59Z" }, + { url = "https://files.pythonhosted.org/packages/9b/31/a9b7aa80589d54bde7d894a9fb118a766833def3ded11739e70b1f19d951/polyleven-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a33728df4708c9370f5e65fdb8de7e15f01d5ae8530eedf507d182fe63afb0e", size = 7437, upload-time = "2026-02-09T09:40:55.459Z" }, + { url = "https://files.pythonhosted.org/packages/e7/5e/71ee3cb252fd6abdf19dd40c11f87ad3adffa06cc0b8098b98ec355573a4/polyleven-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58c11ce44466f6d833fd90f77ccd0c44accce41c9e80dea4c2817d5c124a61d5", size = 7510, upload-time = "2026-02-09T09:40:56.296Z" }, + { url = "https://files.pythonhosted.org/packages/ba/1b/4c41947cfb2f7c4348d60d53d45418e47c305e33b1ce78218b84d636fca8/polyleven-0.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ce2e782f8fae812c7ad960c4fd17a58ada183b89ae220cacfe6b3234179872f4", size = 20982, upload-time = "2026-02-09T09:40:57.152Z" }, + { url = "https://files.pythonhosted.org/packages/65/4d/07e4f90873e4c0c764f6081ad44c17308d7d18976284d80bca47c98e4282/polyleven-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a472fccba89ffb44b10481760c7351c79855b0ff654ec9d28966bfb111a71748", size = 21476, upload-time = "2026-02-09T09:41:00.3Z" }, + { url = "https://files.pythonhosted.org/packages/67/26/31871030852e62e705d060c1dafaddd2f9a4b3664a8a6866ad7521e7e07f/polyleven-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:758c5fcb9d8556720fb51c1de5f3a7b39bb8dce9510a1f40e5f287951b901010", size = 20497, upload-time = "2026-02-09T09:41:01.252Z" }, + { url = "https://files.pythonhosted.org/packages/71/8a/8e1b60f5fc905c0437a063414eee58fcc775b8ae6229e85439b6c477b331/polyleven-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:367faf0e1898c79624f46a894ebe5b69bf1782318ec3e3331676ce5b24352882", size = 20768, upload-time = "2026-02-09T09:41:02.191Z" }, + { url = "https://files.pythonhosted.org/packages/42/00/7f9bab45279828b9d70a0674d92fc78e858dcbfa79b1e0669f93b039249e/polyleven-0.11.0-cp312-cp312-win32.whl", hash = "sha256:71bbb17919548d4e162444c918b1acf864f84150197087c757975606cbb99e43", size = 11625, upload-time = "2026-02-09T09:41:03.189Z" }, + { url = "https://files.pythonhosted.org/packages/e5/35/47a019878909f80526567347379ba73aff114c5ee6d6c6efb7a0b6d4573b/polyleven-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:e7b6c8cfa13114bc2b17b51503a4db0cbc358c3c96197d6d7283bd686c0fd8fb", size = 10834, upload-time = "2026-02-09T09:41:04.065Z" }, + { url = "https://files.pythonhosted.org/packages/3e/40/4e80a66231052693328fc866a932e07b636cf8bb7ddf0eb54aa475f792bf/polyleven-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:561f028c9535223c78cd58f6b546dd15ce69a6e268e651ae1377644845fae639", size = 9424, upload-time = "2026-02-09T09:41:05.225Z" }, + { url = "https://files.pythonhosted.org/packages/ce/16/5aec69609adc373f10087eb69b0b9d177ae721632715a86348b429030514/polyleven-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0cb8ed97b536f9aada3ad45169ee7768c426498bf3fa608a4eabd055dfef795e", size = 7425, upload-time = "2026-02-09T09:41:06.542Z" }, + { url = "https://files.pythonhosted.org/packages/bd/5b/0542c723aa83833a5090114bc4e5a8e60293873fe60ee8221a5888d87370/polyleven-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2f975ab8cb81fd8eb5a647a3cefb0bb80bc307920a9307f66ab4019d88370ed2", size = 7505, upload-time = "2026-02-09T09:41:07.445Z" }, + { url = "https://files.pythonhosted.org/packages/4a/8d/c317217734a5bd2011f1128c1a9056477a5148d8d95527fcab2fe3955876/polyleven-0.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16986fd58911d6075b5f63ea001197141145b7a6df48bc4ce4530e79227e74a2", size = 21035, upload-time = "2026-02-09T09:41:08.32Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8f/8a3e6e4a68dbd9de564fd3d16eee90e3f807a4380fd7192f40af4be47175/polyleven-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6a814629cc0468f9800b1333414a3be08fda9c5ce6b63e97154a9d21732e590", size = 21509, upload-time = "2026-02-09T09:41:09.285Z" }, + { url = "https://files.pythonhosted.org/packages/6b/da/4097998bea845f0b3a67112200aa08c19d4da0a17d761b35484d695c21e2/polyleven-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:88a35ec93ec3d81a7347fd49db314a914798a144dca3d22946d18bba9b597dec", size = 20536, upload-time = "2026-02-09T09:41:10.211Z" }, + { url = "https://files.pythonhosted.org/packages/a1/71/67b7679ede99589ec749290d938693b87cdb6bb327b062c46d2129a5e6ec/polyleven-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:50bb7d68b790194d552ee1256a02e205486b27eb22ab333eeb0003e0271c4846", size = 20775, upload-time = "2026-02-09T09:41:11.692Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3e/6f7fad4fee748ba365cb3e1ba2e061a74e18d987eb554ead4757127df2ab/polyleven-0.11.0-cp313-cp313-win32.whl", hash = "sha256:ce264f6a9daa3265299d8ffcb180d8256517a8d9235613a3b267172da0bc1e06", size = 11629, upload-time = "2026-02-09T09:41:12.652Z" }, + { url = "https://files.pythonhosted.org/packages/2f/cc/4877913dec8fb4f968a070c894254db5811b62128d3a69b05bcd1305b5c3/polyleven-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:4648732c8ad3955c8d7b1aa015d92936a150475aaa97ce704fe0c8e7fa7e0c4f", size = 10841, upload-time = "2026-02-09T09:41:13.682Z" }, + { url = "https://files.pythonhosted.org/packages/59/e2/039cc477ce73d6184e12cf6341ac200bc9f4c5428254c399015ec30392e1/polyleven-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:166f6c9b161c6af92ff201c734d6437bc7ef74a32dab306c5d47a0bdb7a82d9f", size = 9424, upload-time = "2026-02-09T09:41:14.545Z" }, + { url = "https://files.pythonhosted.org/packages/a9/cf/a02d74f965127adb6a8fbd5030e2c98335ef2f8e7452b12a882883b2053a/polyleven-0.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3c18b8e44e5d04f1ffa7d41eb68da553833ab8663b7cfb1a505d85676db5c797", size = 7482, upload-time = "2026-02-09T09:41:15.431Z" }, + { url = "https://files.pythonhosted.org/packages/fe/74/dfa9e9891cd85e679f230c5e740cba11b0bb11bd9fb298657ccf048ff70e/polyleven-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7ab547adc0ac72a2852d37337a4a839d4e2f713940b0e8a944d45c528e5e6538", size = 7508, upload-time = "2026-02-09T09:41:16.365Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ef/399ae8d21f7b348514b7ad3bd7b9d530bf195fb0a8ec63cf7af7d17a4071/polyleven-0.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5808f62874187dfd4e30de5dd5f42a660562ec95a87cc64d5455ba0f4be8f175", size = 21056, upload-time = "2026-02-09T09:41:17.226Z" }, + { url = "https://files.pythonhosted.org/packages/21/60/7eb97286a6171dd794a0e5b261175e8bfeb99a2b566bd9b8848ebc97f6df/polyleven-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9deb75346b4177d5e69496791e6156f705d9059961ce8f9520a0dc96532f10f2", size = 21535, upload-time = "2026-02-09T09:41:18.137Z" }, + { url = "https://files.pythonhosted.org/packages/a2/bc/6fa59257c2138e33a858f10236a2a6b381b87f61251c1df468be7c666338/polyleven-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ef28c4c6cdc71a32f0478772d2f07b2cd412fe7950182033b1c36c8a481b0834", size = 20560, upload-time = "2026-02-09T09:41:19.04Z" }, + { url = "https://files.pythonhosted.org/packages/5a/2d/85be9c91d05cb0127586640108f3110f6a3a98c9478f84713d4771c49761/polyleven-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94832ff5d04022ba6038c2ca0c9ea6906330cde3a3b1761739d772647d01da33", size = 20814, upload-time = "2026-02-09T09:41:20.001Z" }, + { url = "https://files.pythonhosted.org/packages/da/91/5a99ae6cf16ff55a94c5686871ed20b816ad1690f823494c76dc3ce0f54b/polyleven-0.11.0-cp314-cp314-win32.whl", hash = "sha256:e6182ea6142904ea50cf82e2955d922156b5fcf9a8279925f312961f16710a58", size = 11966, upload-time = "2026-02-09T09:41:20.946Z" }, + { url = "https://files.pythonhosted.org/packages/48/ec/9c6fcdeb1dd436523f8e2275407f588d6a66a524d7a793f554957373769c/polyleven-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:bf82bb8601582da8f2248293c1e6f4cce2025c79fd64fccddf67dd8538655b55", size = 11100, upload-time = "2026-02-09T09:41:21.863Z" }, + { url = "https://files.pythonhosted.org/packages/42/7f/1e59881a56a4963b4546c7b558ab7979daddff586001f18b80f1f66cece9/polyleven-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:45487a1e4a8415e4ed45e6720b2a3ad9d240336f7afa136a625b8f802a1880c2", size = 9624, upload-time = "2026-02-09T09:41:22.749Z" }, + { url = "https://files.pythonhosted.org/packages/47/5a/5eaa75427f17d4cdf8e2139988a3ec6b841b6e077ebc1fccb754c1f8b55e/polyleven-0.11.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c518ced3e7c05de4efbd12fd7b61d6d574eb170f431e0415689d9f143fe552ee", size = 7490, upload-time = "2026-02-09T09:41:23.677Z" }, + { url = "https://files.pythonhosted.org/packages/50/47/5dd5fa13d315e0d5dc3e41bbaa16306ea56e74929ad29df54d5c24a84dcc/polyleven-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fa49732cdecd985241db9f78d5fdba7170ba6375d2bf9ad040b05127dc96b877", size = 7514, upload-time = "2026-02-09T09:41:24.55Z" }, + { url = "https://files.pythonhosted.org/packages/75/aa/838f1bc632144f4f5820b9dbd31e0c64de41a7b0970b5cbe6fc02746090f/polyleven-0.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b2aada9dd04e84389d90790f359447447a499d6d86807697d80732ed45547a43", size = 21123, upload-time = "2026-02-09T09:41:25.401Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a9/d6f32263b863dfffeed9a67e80b53476cd0089f202b0510a80eb07f7425b/polyleven-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94311ee39e2db957415eacb36b96ae26dcc427c260465324de45fb8c870d4661", size = 21627, upload-time = "2026-02-09T09:41:27.219Z" }, + { url = "https://files.pythonhosted.org/packages/ae/68/4dee05a4217a3eb1f85cbc915f5fa269d79b86d2a8384be68bcd21de37cc/polyleven-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:45cfb234fece0c9df73276788fa529a25f91abf97dd0d9aed4f1b713b6d530e3", size = 20635, upload-time = "2026-02-09T09:41:28.137Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c2/8486bdaebf47e6b764e8be227a7d2898463f2b4d91443ecdeee9ebeca6bc/polyleven-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9aaed455f498172769fd88f83c27bb8f43e0583d7b27d6b343154d471ec2145e", size = 20870, upload-time = "2026-02-09T09:41:29.07Z" }, + { url = "https://files.pythonhosted.org/packages/b3/13/b827188b55108bd816110a6f60b78aee0db045a98bf7b1f2e7bfb60f4039/polyleven-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2a59849c327279902e8b396666f6998234aa82aacc47abc103d93babaad46203", size = 11917, upload-time = "2026-02-09T09:41:29.997Z" }, + { url = "https://files.pythonhosted.org/packages/ab/18/c909bde1d1db7ead33329b941b0050c93cab9b811e44b49d04adb8c5f0f8/polyleven-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6ba2dcf3aff2909bbf3bdd9c1749f8de207f023fbb2c0b1d681c6bf3e78ceef1", size = 11073, upload-time = "2026-02-09T09:41:31.371Z" }, + { url = "https://files.pythonhosted.org/packages/78/cf/51f7a0fab2d65c2b6908872f26bb03bb7e2357d195f2a59aec1a27489106/polyleven-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:05207bb66da15a2dc5c530e2f5cb5f0588d0a7e79b3bd542965f9e06e3fb14fe", size = 9601, upload-time = "2026-02-09T09:41:32.235Z" }, +] + +[[package]] +name = "portalocker" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32' or (extra == 'extra-17-bedrock-agentcore-a2a' and extra == 'extra-17-bedrock-agentcore-a2a-v1') or (extra == 'extra-17-bedrock-agentcore-a2a' and extra == 'group-17-bedrock-agentcore-dev')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/77/65b857a69ed876e1951e88aaba60f5ce6120c33703f7cb61a3c894b8c1b6/portalocker-3.2.0.tar.gz", hash = "sha256:1f3002956a54a8c3730586c5c77bf18fae4149e07eaf1c29fc3faf4d5a3f89ac", size = 95644, upload-time = "2025-06-14T13:20:40.03Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/a6/38c8e2f318bf67d338f4d629e93b0b4b9af331f455f0390ea8ce4a099b26/portalocker-3.2.0-py3-none-any.whl", hash = "sha256:3cdc5f565312224bc570c49337bd21428bba0ef363bbcf58b9ef4a9f11779968", size = 22424, upload-time = "2025-06-14T13:20:38.083Z" }, +] + +[[package]] +name = "posthog" +version = "7.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backoff" }, + { name = "distro" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/8e/d664a1f9d51871412853702e39ddb23806807b14cb21d7744b1b95f4e2af/posthog-7.33.0.tar.gz", hash = "sha256:963b18cee5315eae51830f7cb2a0bba217d1131ca636a19c45d7c34e69e9ae0e", size = 389275, upload-time = "2026-07-29T07:06:25.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/b5/a62d308f749a86112029641392a68d8765b48cc5e925a61bdcee86041980/posthog-7.33.0-py3-none-any.whl", hash = "sha256:4ba7a87ec06447b03effea44dbf584761eafc7e72e01dc1044d0428b92d462d6", size = 462236, upload-time = "2026-07-29T07:06:23.32Z" }, +] + [[package]] name = "pre-commit" version = "4.2.0" @@ -2767,6 +3138,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, ] +[[package]] +name = "pyfiglet" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/e3/0a86276ad2c383ce08d76110a8eec2fe22e7051c4b8ba3fa163a0b08c428/pyfiglet-1.0.4.tar.gz", hash = "sha256:db9c9940ed1bf3048deff534ed52ff2dafbbc2cd7610b17bb5eca1df6d4278ef", size = 1560615, upload-time = "2025-08-15T18:32:47.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/5c/fe9f95abd5eaedfa69f31e450f7e2768bef121dbdf25bcddee2cd3087a16/pyfiglet-1.0.4-py3-none-any.whl", hash = "sha256:65b57b7a8e1dff8a67dc8e940a117238661d5e14c3e49121032bd404d9b2b39f", size = 1806118, upload-time = "2025-08-15T18:32:45.556Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -2807,13 +3187,13 @@ name = "pytest" version = "8.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-17-bedrock-agentcore-a2a' and extra == 'extra-17-bedrock-agentcore-a2a-v1') or (extra == 'extra-17-bedrock-agentcore-a2a' and extra == 'group-17-bedrock-agentcore-dev')" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11' or (extra == 'extra-17-bedrock-agentcore-a2a' and extra == 'extra-17-bedrock-agentcore-a2a-v1') or (extra == 'extra-17-bedrock-agentcore-a2a' and extra == 'group-17-bedrock-agentcore-dev')" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, { name = "pygments" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'extra-17-bedrock-agentcore-a2a' and extra == 'extra-17-bedrock-agentcore-a2a-v1') or (extra == 'extra-17-bedrock-agentcore-a2a' and extra == 'group-17-bedrock-agentcore-dev')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } wheels = [ @@ -2825,7 +3205,7 @@ name = "pytest-asyncio" version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, + { name = "backports-asyncio-runner", marker = "python_full_version < '3.11' or (extra == 'extra-17-bedrock-agentcore-a2a' and extra == 'extra-17-bedrock-agentcore-a2a-v1') or (extra == 'extra-17-bedrock-agentcore-a2a' and extra == 'group-17-bedrock-agentcore-dev')" }, { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4e/51/f8794af39eeb870e87a8c8068642fc07bce0c854d6865d7dd0f2a9d338c2/pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea", size = 46652, upload-time = "2025-07-16T04:29:26.393Z" } @@ -2859,6 +3239,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1b/73/59b038d1aafca89f8e9936eaa8ffa6bb6138d00459d13a32ce070be4f280/pytest_order-1.3.0-py3-none-any.whl", hash = "sha256:2cd562a21380345dd8d5774aa5fd38b7849b6ee7397ca5f6999bbe6e89f07f6e", size = 14609, upload-time = "2024-08-22T12:29:53.156Z" }, ] +[[package]] +name = "pytest-repeat" +version = "0.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/d4/69e9dbb9b8266df0b157c72be32083403c412990af15c7c15f7a3fd1b142/pytest_repeat-0.9.4.tar.gz", hash = "sha256:d92ac14dfaa6ffcfe6917e5d16f0c9bc82380c135b03c2a5f412d2637f224485", size = 6488, upload-time = "2025-04-07T14:59:53.077Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/d4/8b706b81b07b43081bd68a2c0359fe895b74bf664b20aca8005d2bb3be71/pytest_repeat-0.9.4-py3-none-any.whl", hash = "sha256:c1738b4e412a6f3b3b9e0b8b29fcd7a423e50f87381ad9307ef6f5a8601139f3", size = 4180, upload-time = "2025-04-07T14:59:51.492Z" }, +] + [[package]] name = "pytest-rerunfailures" version = "16.1" @@ -2872,6 +3264,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/54/60eabb34445e3db3d3d874dc1dfa72751bfec3265bd611cb13c8b290adea/pytest_rerunfailures-16.1-py3-none-any.whl", hash = "sha256:5d11b12c0ca9a1665b5054052fcc1084f8deadd9328962745ef6b04e26382e86", size = 14093, upload-time = "2025-10-10T07:06:00.019Z" }, ] +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -2977,6 +3382,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, ] +[[package]] +name = "questionary" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "prompt-toolkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/45/eafb0bba0f9988f6a2520f9ca2df2c82ddfa8d67c95d6625452e97b204a5/questionary-2.1.1.tar.gz", hash = "sha256:3d7e980292bb0107abaa79c68dd3eee3c561b83a0f89ae482860b181c8bd412d", size = 25845, upload-time = "2025-08-28T19:00:20.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl", hash = "sha256:a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59", size = 36753, upload-time = "2025-08-28T19:00:19.56Z" }, +] + [[package]] name = "referencing" version = "0.36.2" @@ -3234,6 +3651,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, ] +[[package]] +name = "sentry-sdk" +version = "2.66.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7f/6f/d59cad0889d15fde85254cf58e701484de3f3f0406003b3197746910b19b/sentry_sdk-2.66.1.tar.gz", hash = "sha256:f882fb08710c5f8bfc603aafa3e901b384009a19cc3f76a572b863392ee81cdc", size = 940543, upload-time = "2026-07-22T12:26:54.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/d3/726bd88f0eece09ddf431bea4c9191c18e7a8d070b854eb0014d447712ee/sentry_sdk-2.66.1-py3-none-any.whl", hash = "sha256:86002793161d9a95ef04bdd8d442e9bfece5d989b755f05d6360215094a7aff6", size = 505555, upload-time = "2026-07-22T12:26:52.71Z" }, +] + +[[package]] +name = "setuptools" +version = "83.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -3309,12 +3757,13 @@ wheels = [ [[package]] name = "strands-agents" -version = "1.38.0" +version = "1.50.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "boto3" }, { name = "botocore" }, { name = "docstring-parser" }, + { name = "httpx" }, { name = "jsonschema" }, { name = "mcp" }, { name = "opentelemetry-api" }, @@ -3325,14 +3774,14 @@ dependencies = [ { name = "typing-extensions" }, { name = "watchdog" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/11/89/3e722f4b5bd913531bc32a23bf88aaa77a434774f294bba5bfa88690ec46/strands_agents-1.38.0.tar.gz", hash = "sha256:02a68ec321ad457f9137dfd6a99cf72cf0e86081fee35de85fbe29b9ac0af2b2", size = 858950, upload-time = "2026-04-30T16:57:43.244Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/7a/b5e20f5ee859c71e451e2e06de8767aab73d850018167c436e3a96446caf/strands_agents-1.50.2.tar.gz", hash = "sha256:7b45c41593e41f22383962a233b6616360b8c3686ae8b902059b6a6fab3deacf", size = 1224067, upload-time = "2026-07-27T20:38:48.238Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/06/de8d8ab14a2e92dcb0fa82db0a4cb102418a1eda139412bbe5b5725e28df/strands_agents-1.38.0-py3-none-any.whl", hash = "sha256:9dc3de17e25d70e367d37f9151f2a4c7b3ac8fc9f6237e9e1f34d00bfbfd001b", size = 422354, upload-time = "2026-04-30T16:57:41.094Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d6/2fd27147f53c45e546c0c542c6ea4a24b93c1f3908dcbad164624e5ddfa6/strands_agents-1.50.2-py3-none-any.whl", hash = "sha256:39c6b755e579e0b631ea01af78a0a201019320670b87adcf0b5c8dd1e5a2cef8", size = 638411, upload-time = "2026-07-27T20:38:46.12Z" }, ] [[package]] name = "strands-agents-evals" -version = "0.1.0" +version = "1.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "boto3" }, @@ -3343,11 +3792,12 @@ dependencies = [ { name = "rich" }, { name = "strands-agents" }, { name = "strands-agents-tools" }, + { name = "tenacity" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/12/30/094087a522b253660ec5ca73cf981af968f714260d4326c8e1f0e77e770c/strands_agents_evals-0.1.0.tar.gz", hash = "sha256:a2948704d909ecc10967cc1a57bf3af7bda3108a3803fafb7f34809afe297213", size = 103152, upload-time = "2025-12-03T17:42:16.189Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/b8/935c2be22bb5d6270e31dc3bf1e6947e19b9ea865c7a86b8109f92140608/strands_agents_evals-1.0.3.tar.gz", hash = "sha256:0d3d6b717399d247be4a038131eec30d0087d67b6448fec36c2765ad5036619a", size = 477066, upload-time = "2026-07-23T21:04:05.007Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/78/8bdde902bb67bfe876db0731bb554c5d54cc40e9d2b9bd6cd64ea1b87af8/strands_agents_evals-0.1.0-py3-none-any.whl", hash = "sha256:26e0ef45655bc20691af293027c04cfc9a0963aaea70c3400defd3b007c4930c", size = 95455, upload-time = "2025-12-03T17:42:14.677Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9f/317c912da542af792c7eaad0b247d352bed001fc204518c6dbb5a1797e40/strands_agents_evals-1.0.3-py3-none-any.whl", hash = "sha256:10550f4cc7676b0d7df64cc7618dd2ee248d2289e1eae873548bf9057164a6d1", size = 355342, upload-time = "2026-07-23T21:04:02.915Z" }, ] [[package]] @@ -3390,6 +3840,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, ] +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, +] + [[package]] name = "tenacity" version = "9.1.2" @@ -3438,6 +3897,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] +[[package]] +name = "tqdm" +version = "4.70.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-17-bedrock-agentcore-a2a' and extra == 'extra-17-bedrock-agentcore-a2a-v1') or (extra == 'extra-17-bedrock-agentcore-a2a' and extra == 'group-17-bedrock-agentcore-dev')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/3b/6c24bec5be5e743ffd99576daa5cc077722fc7d5bbc00bd133fa0c698dc6/tqdm-4.70.0.tar.gz", hash = "sha256:55b0b0dbd97462d06ebee91e4dac24ed4d4702be82b24f07e6c1d27e08cea220", size = 795438, upload-time = "2026-07-27T11:33:15.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/1c/01bfd571a64e7f270e6bab5e33777debe0edc56759233ce84f27dec92d14/tqdm-4.70.0-py3-none-any.whl", hash = "sha256:7f585706bfddbdebf89daac705b2dfcc16890130727d3197ca62c732b4310953", size = 80184, upload-time = "2026-07-27T11:33:13.167Z" }, +] + +[[package]] +name = "typer" +version = "0.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-17-bedrock-agentcore-a2a' and extra == 'extra-17-bedrock-agentcore-a2a-v1') or (extra == 'extra-17-bedrock-agentcore-a2a' and extra == 'group-17-bedrock-agentcore-dev')" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/78/fda3361b56efc27944f24225f6ecd13d96d6fcfe37bd0eb34e2f4c63f9fc/typer-0.27.0.tar.gz", hash = "sha256:629bd12ea5d13a17148125d9a264f949eb171fb3f120f9b04d85873cab054fa5", size = 203430, upload-time = "2026-07-15T19:21:07.007Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/03/26a383c9e58c213199d1aad1c3d353cfc22d4444ec6d2c0bf8ad02523843/typer-0.27.0-py3-none-any.whl", hash = "sha256:6f4b27631e47f077871b7dc30e933ec0131c1390fbe0e387ea5574b5bac9ccf1", size = 122716, upload-time = "2026-07-15T19:21:05.553Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0"