# Author: Simon-Pierre Boucher — simon-pierre.boucher@uqo.ca """OpenAI adapter (official SDK, Responses API with JSON-schema structured output).""" from __future__ import annotations import os from typing import Any from pydantic import BaseModel from .base import LLMProvider, json_schema_for class OpenAIProvider(LLMProvider): name = "openai" def __init__(self, **kw): super().__init__(**kw) from openai import AsyncOpenAI if not os.environ.get("OPENAI_API_KEY"): raise RuntimeError("OPENAI_API_KEY not set") self.client = AsyncOpenAI(max_retries=self.max_retries, timeout=self.timeout_s) async def _raw_complete(self, *, system: str, user: str, schema: type[BaseModel], model: str, effort: str, max_tokens: int) -> tuple[str, dict[str, Any]]: kwargs: dict[str, Any] = dict( model=model, instructions=system, input=user, max_output_tokens=max_tokens, text={"format": {"type": "json_schema", "name": schema.__name__, "schema": json_schema_for(schema), "strict": True}}, ) if effort: kwargs["reasoning"] = {"effort": effort} resp = await self.client.responses.create(**kwargs) text = resp.output_text or "" u = resp.usage cached = 0 try: cached = int(u.input_tokens_details.cached_tokens or 0) except Exception: pass return text, { "input_tokens": int(u.input_tokens or 0), "output_tokens": int(u.output_tokens or 0), "cached_tokens": cached, "request_id": getattr(resp, "_request_id", None) or getattr(resp, "id", None), "status": getattr(resp, "status", None), }