SPB Git

spb/wp27_uqo

Public
3.5 KB · 71 lines python
1"""OpenAI adapter (official `openai` SDK, Responses API with a JSON-schema text format).23Reads OPENAI_API_KEY from the environment only. Model IDs come from the run config. Reasoning effort4is passed only for models that accept it (gpt-5 family); temperature only when the config sets it."""5from __future__ import annotations67import os8import time910from . import ProviderResult11from ..schema import DECISION_JSON_SCHEMA121314class OpenAIProvider:15    name = "openai"1617    def __init__(self, timeout_s: float = 60.0, max_retries: int = 3, **_: object) -> None:18        import openai19        if not os.environ.get("OPENAI_API_KEY"):20            raise RuntimeError("OPENAI_API_KEY is not set")21        self._openai = openai22        self.client = openai.OpenAI(timeout=timeout_s, max_retries=max_retries)2324    def complete(self, system: str, user: str, model: str, reasoning_effort: str | None = "low",25                 temperature: float | None = None, max_output_tokens: int = 400, cache_key: str | None = None,26                 **_: object) -> ProviderResult:27        t0 = time.perf_counter()28        kwargs: dict = dict(29            model=model,30            instructions=system,31            input=[{"role": "user", "content": user}],32            max_output_tokens=max_output_tokens,33            text={"format": {"type": "json_schema", "name": "trading_decision", "schema": DECISION_JSON_SCHEMA, "strict": True}},34            store=False,35        )36        if reasoning_effort and (model.startswith("gpt-5") or model.startswith("o")):37            kwargs["reasoning"] = {"effort": reasoning_effort}38        if temperature is not None and not (model.startswith("gpt-5") or model.startswith("o")):39            kwargs["temperature"] = temperature40        if cache_key:41            kwargs["prompt_cache_key"] = cache_key42        try:43            resp = self.client.responses.create(**kwargs)44        except self._openai.RateLimitError as e:45            return ProviderResult(text="", error=f"rate_limit: {e}", latency_s=time.perf_counter() - t0, model=model)46        except self._openai.APIStatusError as e:47            return ProviderResult(text="", error=f"status_{e.status_code}: {str(e)[:200]}", latency_s=time.perf_counter() - t0, model=model)48        except self._openai.APIConnectionError as e:49            return ProviderResult(text="", error=f"connection: {str(e)[:200]}", latency_s=time.perf_counter() - t0, model=model)50        except Exception as e:  # noqa: BLE00151            return ProviderResult(text="", error=f"other: {type(e).__name__}: {str(e)[:200]}", latency_s=time.perf_counter() - t0, model=model)52        text = getattr(resp, "output_text", "") or ""53        u = getattr(resp, "usage", None)54        inp = int(getattr(u, "input_tokens", 0) or 0)55        out = int(getattr(u, "output_tokens", 0) or 0)56        cached = 057        reasoning = 058        try:59            cached = int(u.input_tokens_details.cached_tokens or 0)60        except Exception:  # noqa: BLE00161            pass62        try:63            reasoning = int(u.output_tokens_details.reasoning_tokens or 0)64        except Exception:  # noqa: BLE00165            pass66        status = getattr(resp, "status", "")67        err = "" if text else f"empty_output(status={status})"68        return ProviderResult(text=text, input_tokens=inp, cached_input_tokens=cached, output_tokens=out,69                              reasoning_tokens=reasoning, latency_s=time.perf_counter() - t0, model=model,70                              request_id=getattr(resp, "id", "") or "", error=err)71
Files · main
Menu

Theme