GCMD_Keyword_Classifier_MVP / tests /test_openai_provider.py
igerasimov's picture
Support GPT-5-family OpenAI temperature defaults
68cf134
Raw
History Blame Contribute Delete
6.82 kB
from __future__ import annotations
import importlib
import pytest
from gcmd_classifier.config import ModelSettings
from gcmd_classifier.errors import NonRetryableModelError, RetryableModelError
from gcmd_classifier.llm import FakeModelClient, ModelRequest, ModelStage
from gcmd_classifier.llm.schemas import TopicResponse
def test_openai_provider_module_import_does_not_require_openai() -> None:
module = importlib.import_module("gcmd_classifier.llm.openai_provider")
assert hasattr(module, "OpenAIModelClient")
def test_openai_provider_is_isolated_from_fake_model_tests() -> None:
client = FakeModelClient([{"selected": [], "ambiguous_alternatives": []}])
request = ModelRequest.from_settings(
stage=ModelStage.TOPIC,
prompt="prompt",
response_schema=TopicResponse,
settings=ModelSettings(),
)
response = client.generate_structured(request)
assert response.provider == "fake"
assert response.parsed.selected == []
def test_openai_provider_does_not_make_live_call_without_explicit_key(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
module = importlib.import_module("gcmd_classifier.llm.openai_provider")
client = module.OpenAIModelClient(ModelSettings(provider="openai", model_name="configured"))
request = ModelRequest.from_settings(
stage=ModelStage.TOPIC,
prompt="prompt",
response_schema=TopicResponse,
settings=ModelSettings(provider="openai", model_name="configured"),
)
with pytest.raises(NonRetryableModelError):
client.generate_structured(request)
class _FakeCompletions:
def __init__(self, exc: Exception) -> None:
self._exc = exc
def parse(self, **kwargs):
raise self._exc
class _FakeOpenAIClient:
def __init__(self, exc: Exception) -> None:
self.beta = type(
"Beta",
(),
{"chat": type("Chat", (), {"completions": _FakeCompletions(exc)})()},
)()
class _FakeOpenAIError(Exception):
status_code = 404
body = {
"error": {
"message": "The model `gpt-5.5` does not exist. api_key=sk-secret123",
"type": "invalid_request_error",
"code": "model_not_found",
}
}
def __init__(self) -> None:
super().__init__("raw fallback with authorization Bearer secret-token")
class _FakeTimeout(TimeoutError):
status_code = 504
code = "timeout"
def test_openai_provider_non_retryable_error_includes_sanitized_diagnostics() -> None:
module = importlib.import_module("gcmd_classifier.llm.openai_provider")
settings = ModelSettings(provider="openai", model_name="gpt-5.5")
client = module.OpenAIModelClient(settings, client=_FakeOpenAIClient(_FakeOpenAIError()))
request = ModelRequest.from_settings(
stage=ModelStage.TOPIC,
prompt="SECRET ABSTRACT SHOULD NOT APPEAR",
response_schema=TopicResponse,
settings=settings,
)
with pytest.raises(NonRetryableModelError) as exc_info:
client.generate_structured(request)
message = str(exc_info.value)
assert "OpenAI provider call failed." in message
assert "exception_class=_FakeOpenAIError" in message
assert "http_status=404" in message
assert "openai_error_type=invalid_request_error" in message
assert "openai_error_code=model_not_found" in message
assert "gpt-5.5" in message
assert "sk-secret123" not in message
assert "secret-token" not in message
assert "SECRET ABSTRACT SHOULD NOT APPEAR" not in message
assert "authorization" not in message.lower()
def test_openai_provider_retryable_error_includes_sanitized_diagnostics() -> None:
module = importlib.import_module("gcmd_classifier.llm.openai_provider")
settings = ModelSettings(provider="openai", model_name="gpt-4o")
client = module.OpenAIModelClient(settings, client=_FakeOpenAIClient(_FakeTimeout("timeout")))
request = ModelRequest.from_settings(
stage=ModelStage.TOPIC,
prompt="prompt should not appear",
response_schema=TopicResponse,
settings=settings,
)
with pytest.raises(RetryableModelError) as exc_info:
client.generate_structured(request)
message = str(exc_info.value)
assert "Temporary OpenAI provider failure." in message
assert "exception_class=_FakeTimeout" in message
assert "http_status=504" in message
assert "openai_error_code=timeout" in message
assert "prompt should not appear" not in message
class _CaptureCompletions:
def __init__(self) -> None:
self.kwargs = None
def parse(self, **kwargs):
self.kwargs = kwargs
parsed = TopicResponse(selected=[])
message = type("Message", (), {"parsed": parsed})()
choice = type("Choice", (), {"message": message})()
return type("Completion", (), {"choices": [choice], "usage": None})()
class _CaptureOpenAIClient:
def __init__(self) -> None:
self.completions = _CaptureCompletions()
self.beta = type(
"Beta",
(),
{"chat": type("Chat", (), {"completions": self.completions})()},
)()
def test_gpt5_family_request_omits_temperature_when_configured_zero() -> None:
module = importlib.import_module("gcmd_classifier.llm.openai_provider")
settings = ModelSettings(provider="openai", model_name="gpt-5.5", temperature=0.0)
fake_openai = _CaptureOpenAIClient()
client = module.OpenAIModelClient(settings, client=fake_openai)
request = ModelRequest.from_settings(
stage=ModelStage.TOPIC,
prompt="prompt",
response_schema=TopicResponse,
settings=settings,
)
response = client.generate_structured(request)
assert response.model_name == "gpt-5.5"
assert fake_openai.completions.kwargs is not None
assert fake_openai.completions.kwargs["model"] == "gpt-5.5"
assert "temperature" not in fake_openai.completions.kwargs
def test_gpt4o_request_preserves_temperature_behavior() -> None:
module = importlib.import_module("gcmd_classifier.llm.openai_provider")
settings = ModelSettings(provider="openai", model_name="gpt-4o", temperature=0.0)
fake_openai = _CaptureOpenAIClient()
client = module.OpenAIModelClient(settings, client=fake_openai)
request = ModelRequest.from_settings(
stage=ModelStage.TOPIC,
prompt="prompt",
response_schema=TopicResponse,
settings=settings,
)
response = client.generate_structured(request)
assert response.model_name == "gpt-4o"
assert fake_openai.completions.kwargs is not None
assert fake_openai.completions.kwargs["model"] == "gpt-4o"
assert fake_openai.completions.kwargs["temperature"] == 0.0