from openai import APITimeoutError from apps.modelhub.crypto import encrypt_secret from apps.modelhub.models import ModelDeployment, ModelInvocation, ModelProvider from apps.modelhub.services import CompletionResult, execute_invocation def create_invocation(): provider = ModelProvider.objects.create( name="模型渠道", base_url="https://models.example.com/v1", api_key_encrypted=encrypt_secret("secret"), ) deployment = ModelDeployment.objects.create( provider=provider, display_name="支持模型", model_name="support-model", ) return ModelInvocation.objects.create( deployment=deployment, input_snapshot={"messages": [{"role": "user", "content": "hello"}]}, ) def test_transient_error_retries_once(db, monkeypatch): invocation = create_invocation() calls = [] def request(*args, **kwargs): calls.append(1) if len(calls) == 1: raise APITimeoutError(request=None) return CompletionResult("ok", 3, 1) monkeypatch.setattr("apps.modelhub.services._request_completion", request) monkeypatch.setattr("apps.modelhub.services.sleep", lambda *_: None) execute_invocation(invocation, invocation.input_snapshot["messages"]) invocation.refresh_from_db() assert len(calls) == 2 assert invocation.status == "succeeded" assert invocation.retry_count == 1 def test_non_transient_error_fails_without_exposing_raw_error(db, monkeypatch): invocation = create_invocation() def request(*args, **kwargs): raise ValueError("secret provider detail") monkeypatch.setattr("apps.modelhub.services._request_completion", request) execute_invocation(invocation, invocation.input_snapshot["messages"]) invocation.refresh_from_db() assert invocation.status == "failed" assert invocation.error_code == "unexpected_error" assert "secret provider detail" not in invocation.error_message