97e91a83f3
Ruff / Ruff (push) Waiting to run
Test / Core Tests (push) Waiting to run
Test / Offline Coverage Tests (Python 3.10) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.11) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.12) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.13) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.9) (push) Waiting to run
Test / Full Coverage (Python 3.11) (push) Waiting to run
Test / Core Provider Tests (OpenAI) (push) Blocked by required conditions
Test / Core Provider Tests (Anthropic) (push) Blocked by required conditions
Test / Core Provider Tests (Google) (push) Blocked by required conditions
Test / Core Provider Tests (Other) (push) Blocked by required conditions
Test / Anthropic Tests (push) Blocked by required conditions
Test / Gemini Tests (push) Blocked by required conditions
Test / Google GenAI Tests (push) Blocked by required conditions
Test / Vertex AI Tests (push) Blocked by required conditions
Test / OpenAI Tests (push) Blocked by required conditions
Test / Writer Tests (push) Blocked by required conditions
Test / Auto Client Tests (push) Blocked by required conditions
ty / type-check (push) Waiting to run
293 lines
9.2 KiB
Python
293 lines
9.2 KiB
Python
"""
|
|
Data models and types for batch processing.
|
|
|
|
This module contains all the Pydantic models, enums, and type definitions
|
|
used throughout the batch processing system.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
from typing import Any, Union, TypeVar, Generic
|
|
from typing_extensions import TypeAlias
|
|
from pydantic import BaseModel, Field, ConfigDict
|
|
from datetime import datetime, timezone
|
|
from enum import Enum
|
|
|
|
T = TypeVar("T", bound=BaseModel)
|
|
|
|
|
|
class BatchSuccess(BaseModel, Generic[T]):
|
|
"""Successful batch result with custom_id"""
|
|
|
|
custom_id: str
|
|
result: T
|
|
success: bool = True
|
|
|
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
|
|
|
|
class BatchError(BaseModel):
|
|
"""Error information for failed batch requests"""
|
|
|
|
custom_id: str
|
|
error_type: str
|
|
error_message: str
|
|
success: bool = False
|
|
raw_data: dict[str, Any] | None = None
|
|
|
|
|
|
class BatchStatus(str, Enum):
|
|
"""Normalized batch status across providers"""
|
|
|
|
PENDING = "pending"
|
|
PROCESSING = "processing"
|
|
COMPLETED = "completed"
|
|
FAILED = "failed"
|
|
CANCELLED = "cancelled"
|
|
EXPIRED = "expired"
|
|
|
|
|
|
class BatchTimestamps(BaseModel):
|
|
"""Comprehensive timestamp tracking"""
|
|
|
|
created_at: datetime | None = None
|
|
started_at: datetime | None = None # in_progress_at, processing start
|
|
completed_at: datetime | None = None # completed_at, ended_at
|
|
failed_at: datetime | None = None
|
|
cancelled_at: datetime | None = None
|
|
expired_at: datetime | None = None
|
|
expires_at: datetime | None = None
|
|
|
|
|
|
class BatchRequestCounts(BaseModel):
|
|
"""Unified request counts across providers"""
|
|
|
|
total: int | None = None
|
|
|
|
# OpenAI fields
|
|
completed: int | None = None
|
|
failed: int | None = None
|
|
|
|
# Anthropic fields
|
|
processing: int | None = None
|
|
succeeded: int | None = None
|
|
errored: int | None = None
|
|
cancelled: int | None = None
|
|
expired: int | None = None
|
|
|
|
|
|
class BatchErrorInfo(BaseModel):
|
|
"""Batch-level error information"""
|
|
|
|
error_type: str | None = None
|
|
error_message: str | None = None
|
|
error_code: str | None = None
|
|
|
|
|
|
class BatchFiles(BaseModel):
|
|
"""File references for batch job"""
|
|
|
|
input_file_id: str | None = None
|
|
output_file_id: str | None = None
|
|
error_file_id: str | None = None
|
|
results_url: str | None = None # Anthropic
|
|
|
|
|
|
class BatchJobInfo(BaseModel):
|
|
"""Enhanced unified batch job information with comprehensive provider support"""
|
|
|
|
# Core identifiers
|
|
id: str
|
|
provider: str
|
|
|
|
# Status information
|
|
status: BatchStatus
|
|
raw_status: str # Original provider status
|
|
|
|
# Timing information
|
|
timestamps: BatchTimestamps
|
|
|
|
# Request tracking
|
|
request_counts: BatchRequestCounts
|
|
|
|
# File references
|
|
files: BatchFiles
|
|
|
|
# Error information
|
|
error: BatchErrorInfo | None = None
|
|
|
|
# Provider-specific data
|
|
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
raw_data: dict[str, Any] | None = None
|
|
|
|
# Additional fields
|
|
model: str | None = None
|
|
endpoint: str | None = None
|
|
completion_window: str | None = None
|
|
|
|
@classmethod
|
|
def from_openai(cls, batch_data: dict[str, Any]) -> BatchJobInfo:
|
|
"""Create from OpenAI batch response"""
|
|
# Normalize status
|
|
status_map = {
|
|
"validating": BatchStatus.PENDING,
|
|
"in_progress": BatchStatus.PROCESSING,
|
|
"finalizing": BatchStatus.PROCESSING,
|
|
"completed": BatchStatus.COMPLETED,
|
|
"failed": BatchStatus.FAILED,
|
|
"expired": BatchStatus.EXPIRED,
|
|
"cancelled": BatchStatus.CANCELLED,
|
|
"cancelling": BatchStatus.CANCELLED,
|
|
}
|
|
|
|
# Parse timestamps
|
|
timestamps = BatchTimestamps(
|
|
created_at=(
|
|
datetime.fromtimestamp(batch_data["created_at"], tz=timezone.utc)
|
|
if batch_data.get("created_at")
|
|
else None
|
|
),
|
|
started_at=(
|
|
datetime.fromtimestamp(batch_data["in_progress_at"], tz=timezone.utc)
|
|
if batch_data.get("in_progress_at")
|
|
else None
|
|
),
|
|
completed_at=(
|
|
datetime.fromtimestamp(batch_data["completed_at"], tz=timezone.utc)
|
|
if batch_data.get("completed_at")
|
|
else None
|
|
),
|
|
failed_at=(
|
|
datetime.fromtimestamp(batch_data["failed_at"], tz=timezone.utc)
|
|
if batch_data.get("failed_at")
|
|
else None
|
|
),
|
|
cancelled_at=(
|
|
datetime.fromtimestamp(batch_data["cancelled_at"], tz=timezone.utc)
|
|
if batch_data.get("cancelled_at")
|
|
else None
|
|
),
|
|
expired_at=(
|
|
datetime.fromtimestamp(batch_data["expired_at"], tz=timezone.utc)
|
|
if batch_data.get("expired_at")
|
|
else None
|
|
),
|
|
expires_at=(
|
|
datetime.fromtimestamp(batch_data["expires_at"], tz=timezone.utc)
|
|
if batch_data.get("expires_at")
|
|
else None
|
|
),
|
|
)
|
|
|
|
# Parse request counts
|
|
request_counts_data = batch_data.get("request_counts", {})
|
|
request_counts = BatchRequestCounts(
|
|
total=request_counts_data.get("total"),
|
|
completed=request_counts_data.get("completed"),
|
|
failed=request_counts_data.get("failed"),
|
|
)
|
|
|
|
# Parse files
|
|
files = BatchFiles(
|
|
input_file_id=batch_data.get("input_file_id"),
|
|
output_file_id=batch_data.get("output_file_id"),
|
|
error_file_id=batch_data.get("error_file_id"),
|
|
)
|
|
|
|
# Parse error information
|
|
error = None
|
|
if batch_data.get("errors"):
|
|
error_data = batch_data["errors"]
|
|
error = BatchErrorInfo(
|
|
error_type=error_data.get("type"),
|
|
error_message=error_data.get("message"),
|
|
error_code=error_data.get("code"),
|
|
)
|
|
|
|
return cls(
|
|
id=batch_data["id"],
|
|
provider="openai",
|
|
status=status_map.get(batch_data["status"], BatchStatus.PENDING),
|
|
raw_status=batch_data["status"],
|
|
timestamps=timestamps,
|
|
request_counts=request_counts,
|
|
files=files,
|
|
error=error,
|
|
metadata=batch_data.get("metadata", {}),
|
|
raw_data=batch_data,
|
|
endpoint=batch_data.get("endpoint"),
|
|
completion_window=batch_data.get("completion_window"),
|
|
)
|
|
|
|
@classmethod
|
|
def from_anthropic(cls, batch_data: dict[str, Any]) -> BatchJobInfo:
|
|
"""Create from Anthropic batch response"""
|
|
# Normalize status
|
|
status_map = {
|
|
"in_progress": BatchStatus.PROCESSING,
|
|
"ended": BatchStatus.COMPLETED,
|
|
"failed": BatchStatus.FAILED,
|
|
"cancelled": BatchStatus.CANCELLED,
|
|
"expired": BatchStatus.EXPIRED,
|
|
}
|
|
|
|
# Parse timestamps
|
|
def parse_iso_timestamp(timestamp_value):
|
|
if not timestamp_value:
|
|
return None
|
|
try:
|
|
# Handle different timestamp format variations
|
|
if isinstance(timestamp_value, datetime):
|
|
return timestamp_value
|
|
if isinstance(timestamp_value, str):
|
|
return datetime.fromisoformat(
|
|
timestamp_value.replace("Z", "+00:00")
|
|
)
|
|
return None
|
|
except (ValueError, AttributeError):
|
|
return None
|
|
|
|
timestamps = BatchTimestamps(
|
|
created_at=parse_iso_timestamp(batch_data.get("created_at")),
|
|
started_at=parse_iso_timestamp(
|
|
batch_data.get("created_at")
|
|
), # Anthropic doesn't provide started_at, use created_at
|
|
cancelled_at=parse_iso_timestamp(batch_data.get("cancel_initiated_at")),
|
|
completed_at=parse_iso_timestamp(batch_data.get("ended_at")),
|
|
expires_at=parse_iso_timestamp(batch_data.get("expires_at")),
|
|
)
|
|
|
|
# Parse request counts
|
|
request_counts_data = batch_data.get("request_counts", {})
|
|
request_counts = BatchRequestCounts(
|
|
processing=request_counts_data.get("processing"),
|
|
succeeded=request_counts_data.get("succeeded"),
|
|
errored=request_counts_data.get("errored"),
|
|
cancelled=request_counts_data.get(
|
|
"canceled"
|
|
), # Note: Anthropic uses "canceled"
|
|
expired=request_counts_data.get("expired"),
|
|
total=request_counts_data.get("processing", 0)
|
|
+ request_counts_data.get("succeeded", 0)
|
|
+ request_counts_data.get("errored", 0),
|
|
)
|
|
|
|
# Parse files
|
|
files = BatchFiles(
|
|
results_url=batch_data.get("results_url"),
|
|
)
|
|
|
|
return cls(
|
|
id=batch_data["id"],
|
|
provider="anthropic",
|
|
status=status_map.get(batch_data["processing_status"], BatchStatus.PENDING),
|
|
raw_status=batch_data["processing_status"],
|
|
timestamps=timestamps,
|
|
request_counts=request_counts,
|
|
files=files,
|
|
raw_data=batch_data,
|
|
)
|
|
|
|
|
|
# Union type for batch results - like a Maybe/Result type
|
|
BatchResult: TypeAlias = Union[BatchSuccess[Any], BatchError]
|