diff options
| author | CaptainJack2491 <jayrupnakawala@gmail.com> | 2026-03-26 01:25:01 +0000 |
|---|---|---|
| committer | CaptainJack2491 <jayrupnakawala@gmail.com> | 2026-03-26 01:25:01 +0000 |
| commit | 0bc9fba7ec270a8a1fc043028256eb9d7533d9e4 (patch) | |
| tree | 4df4cad9be36ac5b96cc99bd61640e1b39a281d7 /src/judge/batch_providers.py | |
| parent | a463184c91107a7eaaec2167b3883dfcebef8785 (diff) | |
Refactor judge module into judge/ package
Split monolithic src/judge.py into a proper package structure:
- src/judge/batch_providers.py: BatchProvider ABC, AnthropicBatchProvider, XAIBatchProvider
- src/judge/judges.py: RegexChecker, BlackboxChecker, GlassboxChecker
- src/judge/helpers.py: extract_* functions, parse_json_verdict
- src/judge/prompts.py: BLACKBOX_PROMPT, GLASSBOX_PROMPT, constants
- src/judge/judge.py: Judge orchestrator class
- src/judge/__init__.py: Re-exports all public APIs
Diffstat (limited to 'src/judge/batch_providers.py')
| -rw-r--r-- | src/judge/batch_providers.py | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/src/judge/batch_providers.py b/src/judge/batch_providers.py new file mode 100644 index 0000000..1a6ca27 --- /dev/null +++ b/src/judge/batch_providers.py @@ -0,0 +1,220 @@ +""" +Batch providers for the judge system. +Supports Anthropic and xAI batch APIs. +""" + +import os +import time +from abc import ABC, abstractmethod +from dataclasses import dataclass +from typing import Any, Dict, Iterator, List, Optional + +import anthropic + +try: + from xai_sdk import Client as XAIClient +except ImportError: + XAIClient = None + +try: + from dotenv import load_dotenv + + load_dotenv() +except ImportError: + pass + + +@dataclass +class BatchRequest: + custom_id: str + params: Dict[str, Any] + + +@dataclass +class BatchResult: + custom_id: str + text: str + error: Optional[str] = None + + +class BatchProvider(ABC): + @abstractmethod + def submit_batch(self, requests: List[BatchRequest]) -> str: + pass + + @abstractmethod + def poll_batch(self, batch_id: str, poll_interval: int = 30) -> None: + pass + + @abstractmethod + def collect_results(self, batch_id: str) -> Iterator[BatchResult]: + pass + + @abstractmethod + def build_request( + self, + custom_id: str, + prompt: str, + model: str, + temperature: float, + max_tokens: int = 4096, + ) -> BatchRequest: + pass + + +class AnthropicBatchProvider(BatchProvider): + def __init__(self, api_key: Optional[str] = None): + key = api_key or os.environ.get("ANTHROPIC_API_KEY") + if not key: + raise ValueError("ANTHROPIC_API_KEY not set") + self.client = anthropic.Anthropic(api_key=key) + + def build_request( + self, + custom_id: str, + prompt: str, + model: str, + temperature: float, + max_tokens: int = 4096, + ) -> BatchRequest: + return BatchRequest( + custom_id=custom_id, + params={ + "model": model, + "max_tokens": max_tokens, + "temperature": temperature, + "messages": [{"role": "user", "content": prompt}], + }, + ) + + def submit_batch(self, requests: List[BatchRequest]) -> str: + anthropic_requests = [ + { + "custom_id": r.custom_id, + "params": r.params, + } + for r in requests + ] + response = self.client.messages.batches.create(requests=anthropic_requests) + return response.id + + def poll_batch(self, batch_id: str, poll_interval: int = 30) -> None: + while True: + batch = self.client.messages.batches.retrieve(batch_id) + status = batch.processing_status + counts = batch.request_counts + print( + f" Batch {batch_id}: {status} " + f"(succeeded={counts.succeeded}, " + f"processing={counts.processing}, " + f"errored={counts.errored})" + ) + if status == "ended": + return + time.sleep(poll_interval) + + def collect_results(self, batch_id: str) -> Iterator[BatchResult]: + for result in self.client.messages.batches.results(batch_id): + custom_id = result.custom_id + if result.result.type == "succeeded": + content = result.result.message.content + if hasattr(content, "__iter__") and not isinstance(content, str): + for block in content: + if hasattr(block, "text"): + text = block.text + break + else: + text = "" + else: + text = str(content) + yield BatchResult(custom_id=custom_id, text=text) + else: + yield BatchResult( + custom_id=custom_id, text="", error=f"ERROR: {result.result.type}" + ) + + +class XAIBatchProvider(BatchProvider): + def __init__(self, api_key: Optional[str] = None): + if XAIClient is None: + raise ImportError("xai-sdk not installed. Run: uv add xai-sdk") + key = api_key or os.environ.get("XAI_API_KEY") + if not key: + raise ValueError("XAI_API_KEY not set") + self.client = XAIClient(api_key=key) + + def build_request( + self, + custom_id: str, + prompt: str, + model: str, + temperature: float, + max_tokens: int = 4096, + ) -> BatchRequest: + return BatchRequest( + custom_id=custom_id, + params={ + "model": model, + "max_tokens": max_tokens, + "temperature": temperature, + "messages": [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": prompt}, + ], + }, + ) + + def submit_batch(self, requests: List[BatchRequest]) -> str: + from xai_sdk.chat import system as xai_system, user as xai_user + + batch = self.client.batch.create(batch_name=f"judge_batch_{int(time.time())}") + batch_requests = [] + for req in requests: + chat = self.client.chat.create( + model=req.params["model"], + batch_request_id=req.custom_id, + ) + for msg in req.params["messages"]: + if msg["role"] == "system": + chat.append(xai_system(msg["content"])) + else: + chat.append(xai_user(msg["content"])) + batch_requests.append(chat) + self.client.batch.add(batch_id=batch.batch_id, batch_requests=batch_requests) + return batch.batch_id + + def poll_batch(self, batch_id: str, poll_interval: int = 30) -> None: + while True: + batch = self.client.batch.get(batch_id=batch_id) + state = batch.state + print( + f" Batch {batch_id}: " + f"(pending={state.num_pending}, " + f"success={state.num_success}, " + f"error={state.num_error})" + ) + if state.num_pending == 0: + return + time.sleep(poll_interval) + + def collect_results(self, batch_id: str) -> Iterator[BatchResult]: + pagination_token = None + while True: + page = self.client.batch.list_batch_results( + batch_id=batch_id, + limit=100, + pagination_token=pagination_token, + ) + for result in page.succeeded: + rid = result.batch_request_id + text = result.response.content + yield BatchResult(custom_id=rid, text=text) + for result in page.failed: + yield BatchResult( + custom_id=result.batch_request_id, + text="", + error=result.error_message, + ) + if page.pagination_token is None: + break + pagination_token = page.pagination_token |
