Spaces:
Paused
Paused
| """ | |
| Shared OpenAI HTTP client — single implementation of the chat-completions call. | |
| Replaces duplicated urllib boilerplate in gpt_reasoning, relevance, | |
| mission_parser, and threat_chat. | |
| """ | |
| import json | |
| import logging | |
| import os | |
| import urllib.request | |
| import urllib.error | |
| from typing import Dict, Optional, Tuple | |
| logger = logging.getLogger(__name__) | |
| _API_URL = "https://api.openai.com/v1/chat/completions" | |
| class OpenAIAPIError(Exception): | |
| """Raised when the OpenAI API call fails (HTTP or network error).""" | |
| def __init__(self, message: str, status_code: Optional[int] = None): | |
| self.status_code = status_code | |
| super().__init__(message) | |
| def get_api_key() -> Optional[str]: | |
| """Return the OpenAI API key from the environment, or None.""" | |
| return os.environ.get("OPENAI_API_KEY") | |
| def chat_completion(payload: Dict, *, timeout: int = 30) -> Dict: | |
| """Send a chat-completion request and return the parsed JSON response. | |
| Args: | |
| payload: Full request body (model, messages, etc.). | |
| timeout: HTTP timeout in seconds. | |
| Returns: | |
| Parsed response dict. | |
| Raises: | |
| OpenAIAPIError: On HTTP or network failure. | |
| """ | |
| api_key = get_api_key() | |
| if not api_key: | |
| raise OpenAIAPIError("OPENAI_API_KEY not set") | |
| headers = { | |
| "Content-Type": "application/json", | |
| "Authorization": f"Bearer {api_key}", | |
| } | |
| try: | |
| req = urllib.request.Request( | |
| _API_URL, | |
| data=json.dumps(payload).encode("utf-8"), | |
| headers=headers, | |
| method="POST", | |
| ) | |
| with urllib.request.urlopen(req, timeout=timeout) as response: | |
| return json.loads(response.read().decode("utf-8")) | |
| except urllib.error.HTTPError as e: | |
| raise OpenAIAPIError( | |
| f"HTTP {e.code}: {e.reason}", status_code=e.code | |
| ) from e | |
| except urllib.error.URLError as e: | |
| raise OpenAIAPIError(f"URL error: {e.reason}") from e | |
| def extract_content(resp_data: Dict) -> Tuple[Optional[str], Optional[str]]: | |
| """Safely extract content and refusal from a chat-completion response. | |
| Returns: | |
| (content, refusal) — either may be None. | |
| """ | |
| choice = resp_data.get("choices", [{}])[0] | |
| message = choice.get("message", {}) | |
| return message.get("content"), message.get("refusal") | |