""" Anthropic-specific batch processing implementation. This module contains the Anthropic batch processing provider class. """ import json from typing import Any, Optional, Union import io import logging from .base import BatchProvider from ..models import BatchJobInfo logger = logging.getLogger(__name__) class AnthropicProvider(BatchProvider): """Anthropic batch processing provider""" def submit_batch( self, file_path_or_buffer: Union[str, io.BytesIO], metadata: Optional[dict[str, Any]] = None, **kwargs, ) -> str: """Submit Anthropic batch job""" _ = kwargs # Unused but accepted for API consistency try: import anthropic client = anthropic.Anthropic() # Note: Anthropic doesn't support metadata in batch creation # but we accept it for API consistency if metadata: print( f"Note: Anthropic batches don't support metadata. Ignoring: {metadata}" ) # TODO(#batch-api-stable): Remove beta fallback when stable API is available try: batches_client = client.messages.batches except AttributeError: batches_client = client.beta.messages.batches if isinstance(file_path_or_buffer, str): with open(file_path_or_buffer) as f: requests = [json.loads(line) for line in f if line.strip()] elif isinstance(file_path_or_buffer, io.BytesIO): file_path_or_buffer.seek(0) content = file_path_or_buffer.read().decode("utf-8") requests = [ json.loads(line) for line in content.split("\n") if line.strip() ] else: raise ValueError( f"Unsupported file_path_or_buffer type: {type(file_path_or_buffer)}" ) batch = batches_client.create(requests=requests) return batch.id except (ValueError, TypeError) as e: # Re-raise validation errors as-is logger.error(f"Validation error in Anthropic batch submission: {e}") raise except Exception as e: raise RuntimeError(f"Failed to submit Anthropic batch: {e}") from e def get_status(self, batch_id: str) -> dict[str, Any]: """Get Anthropic batch status""" try: import anthropic client = anthropic.Anthropic() # TODO(#batch-api-stable): Remove beta fallback when stable API is available try: batches_client = client.messages.batches except AttributeError: batches_client = client.beta.messages.batches batch = batches_client.retrieve(batch_id) return { "id": batch.id, "status": batch.processing_status, "created_at": batch.created_at, "request_counts": getattr(batch, "request_counts", {}), } except Exception as e: raise Exception(f"Failed to get Anthropic batch status: {e}") from e def retrieve_results(self, batch_id: str) -> str: """Retrieve Anthropic batch results""" try: import anthropic client = anthropic.Anthropic() # TODO(#batch-api-stable): Remove beta fallback when stable API is available try: batches_client = client.messages.batches except AttributeError: batches_client = client.beta.messages.batches batch = batches_client.retrieve(batch_id) # Check for various terminal states if batch.processing_status in ["failed", "cancelled", "expired"]: raise Exception( f"Batch job failed with status: {batch.processing_status}" ) if batch.processing_status != "ended": raise Exception( f"Batch not completed, status: {batch.processing_status}" ) # Check if all requests failed request_counts = getattr(batch, "request_counts", None) if request_counts: succeeded = getattr(request_counts, "succeeded", 0) errored = getattr(request_counts, "errored", 0) total = getattr(request_counts, "total", 0) if errored > 0 and succeeded == 0: raise RuntimeError( f"All {total} batch requests failed. No results will be available." ) results = batches_client.results(batch_id) results_lines = [] for result in results: results_lines.append(result.model_dump_json()) return "\n".join(results_lines) except Exception as e: raise Exception(f"Failed to retrieve Anthropic results: {e}") from e def download_results(self, batch_id: str, file_path: str) -> None: """Download Anthropic batch results to a file""" try: import anthropic client = anthropic.Anthropic() # TODO(#batch-api-stable): Remove beta fallback when stable API is available try: batches_client = client.messages.batches except AttributeError: batches_client = client.beta.messages.batches batch = batches_client.retrieve(batch_id) # Check for various terminal states if batch.processing_status in ["failed", "cancelled", "expired"]: raise Exception( f"Batch job failed with status: {batch.processing_status}" ) if batch.processing_status != "ended": raise Exception( f"Batch not completed, status: {batch.processing_status}" ) # Check if all requests failed request_counts = getattr(batch, "request_counts", None) if request_counts: succeeded = getattr(request_counts, "succeeded", 0) errored = getattr(request_counts, "errored", 0) total = getattr(request_counts, "total", 0) if errored > 0 and succeeded == 0: raise RuntimeError( f"All {total} batch requests failed. No results will be available." ) results = batches_client.results(batch_id) with open(file_path, "w") as f: for result in results: f.write(result.model_dump_json() + "\n") except Exception as e: raise Exception(f"Failed to download Anthropic results: {e}") from e def cancel_batch(self, batch_id: str) -> dict[str, Any]: """Cancel Anthropic batch job""" try: import anthropic client = anthropic.Anthropic() # TODO(#batch-api-stable): Remove beta fallback when stable API is available try: batches_client = client.messages.batches except AttributeError: batches_client = client.beta.messages.batches batch = batches_client.cancel(batch_id) return batch.model_dump() except Exception as e: raise Exception(f"Failed to cancel Anthropic batch: {e}") from e def delete_batch(self, batch_id: str) -> dict[str, Any]: """Delete Anthropic batch job""" try: import anthropic client = anthropic.Anthropic() # TODO(#batch-api-stable): Remove beta fallback when stable API is available try: batches_client = client.messages.batches except AttributeError: batches_client = client.beta.messages.batches batch = batches_client.retrieve(batch_id) return { "id": batch.id, "status": batch.processing_status, "message": "Anthropic does not support batch deletion", } except Exception as e: raise Exception(f"Failed to delete Anthropic batch: {e}") from e def list_batches(self, limit: int = 10) -> list[BatchJobInfo]: """List Anthropic batch jobs""" try: import anthropic client = anthropic.Anthropic() # TODO(#batch-api-stable): Remove beta fallback when stable API is available try: batches_client = client.messages.batches except AttributeError: batches_client = client.beta.messages.batches batches = batches_client.list(limit=limit) return [ BatchJobInfo.from_anthropic(batch.model_dump()) for batch in batches.data ] except Exception as e: raise Exception(f"Failed to list Anthropic batches: {e}") from e