diff --git a/docs/suts-how-to.md b/docs/suts-how-to.md
index 02df7ff4..e62c8751 100644
--- a/docs/suts-how-to.md
+++ b/docs/suts-how-to.md
@@ -57,48 +57,6 @@ Llama-4-Maverick-17B-128E-Instruct on sambanova via Huggingface:
`meta-llama/Llama-4-Maverick-17B-128E-Instruct:sambanova:hfrelay`
-### OpenAI-Compatible Dynamic SUTs
-
-If your SUT has an OpenAI-compatible API, you can add it with minimal code. VLLM and models hosted by OpenAI
-(like the chatgpt family) support the OpenAI API. Other providers offer that option too. This is a good option
-if you self-host a model using VLLM.
-
-The UID for an OpenAI-compatible SUT works the same way as above, with "openai" as the `driver` section and a string of your choice as the `provider` section of the UID, e.g.:
-
-`my/big_model:my_host:openai`
-
-Because these SUTs need a base URL for the API, you do need to write a little code as follows:
-
-1. Create a subclass of `OpenAIGenericSUTFactory` in [openai_sut_factory.py](../src/modelgauge/suts/openai_sut_factory.py):
- * `base_url` is the base URL of your API server.
- * `provider` is a string of your choice. It must be a valid TOML section identifier. We strongly recommend lowercase ASCII letters.
-2. Add your new class to the `OPENAI_SUT_FACTORIES` dict in [openai_sut_factory.py](../src/modelgauge/suts/openai_sut_factory.py). The dict key must be the same as the value set for `provider`.
-
-```python
-class MySUTFactory(OpenAIGenericSUTFactory):
- def __init__(self, raw_secrets, **kwargs):
- super().__init__(raw_secrets)
- self.provider = "my_host"
- self.base_url = "https://example.net/v1/"
-
-OPENAI_SUT_FACTORIES: dict = {"my_host": MySUTFactory}
-```
-
-3. Add a scope containing the `api_key` secret to your API in [config/secrets.toml](../config/secrets.toml). The scope must be named the same as the `provider` in your SUT factory class.
-
-```toml
-[my_host]
-api_key=
-```
-
-Your SUT UID will look like `my/big_model:my_host:openai`, and you can use it with modelgauge and modelbench like this:
-
-```bash
-uv run modelgauge run-sut --sut my/big_model:my_host:openai --prompt "Why did the chicken cross the road?"
-
-uv run modelbench benchmark general --sut my/big_model:my_host:openai --prompt-set practice --evaluator default -m 10
-```
-
### Dynamic SUTs With New Drivers
If your SUT provider requires custom client code that isn't available in this repo, you will need to write some driver code. Details are in [add-a-new-sut-driver.md](./add-a-new-sut-driver.md).
diff --git a/src/modelgauge/sut_factory.py b/src/modelgauge/sut_factory.py
index 653111cb..ae5c6059 100644
--- a/src/modelgauge/sut_factory.py
+++ b/src/modelgauge/sut_factory.py
@@ -1,5 +1,5 @@
from enum import Enum
-from typing import Optional
+from typing import Optional, Any
from modelgauge.config import load_secrets_from_config
from modelgauge.dynamic_sut_factory import DynamicDriverSUTFactory, UnknownSUTMakerError
@@ -212,4 +212,26 @@ def get_missing_dependencies(self, uid: str, *, secrets: RawSecrets):
return self.sut_registry.get_missing_dependencies(uid, secrets=secrets)
-SUT_FACTORY = SUTFactory(SUTS)
+# Stop SUTFactory from initializing on startup. Yes, this is kooky; the right solution
+# is to make this not be a global variable
+
+_GLOBAL_VARIABLES: dict[str, Any] = {}
+
+
+def __getattr__(name: str):
+ if name == "SUT_FACTORY":
+ _GLOBAL_VARIABLES["SUT_FACTORY"] = SUTFactory(SUTS)
+
+ if name in _GLOBAL_VARIABLES:
+ return _GLOBAL_VARIABLES[name]
+ if name in globals():
+ return globals()[name]
+ if name in vars():
+ return vars()[name]
+ if name == "__path__":
+ return None # this works, but I'm not sure it's correct in all cases
+ raise (Exception(f"unknown: {name}"))
+
+
+def __setattr__(name: str, value: str):
+ _GLOBAL_VARIABLES[name] = value
diff --git a/src/modelgauge/suts/aws_bedrock_client.py b/src/modelgauge/suts/aws_bedrock_client.py
deleted file mode 100644
index 060468ee..00000000
--- a/src/modelgauge/suts/aws_bedrock_client.py
+++ /dev/null
@@ -1,178 +0,0 @@
-# as defined here:
-# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-runtime/client/converse.html
-
-import os
-from typing import Any, Dict, List, Optional
-
-import boto3
-
-from modelgauge.general import APIException
-from modelgauge.prompt import TextPrompt
-from modelgauge.retry_decorator import retry
-from modelgauge.secret_values import InjectSecret, RequiredSecret, SecretDescription
-from modelgauge.sut import PromptResponseSUT, SUTResponse
-from modelgauge.model_options import ModelOptions
-from modelgauge.sut_capabilities import AcceptsTextPrompt
-from modelgauge.sut_decorator import modelgauge_sut
-from modelgauge.sut_registry import SUTS
-from pydantic import BaseModel
-
-
-class AwsAccessKeyId(RequiredSecret):
- @classmethod
- def description(cls) -> SecretDescription:
- return SecretDescription(
- scope="aws",
- key="access_key_id",
- instructions="See https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html",
- )
-
-
-class AwsSecretAccessKey(RequiredSecret):
- @classmethod
- def description(cls) -> SecretDescription:
- return SecretDescription(
- scope="aws",
- key="secret_access_key",
- instructions="See https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html",
- )
-
-
-class BedrockRequest(BaseModel):
- class BedrockMessage(BaseModel):
- role: str = "user"
- content: List[Dict[str, str]] = []
-
- class InferenceConfig(BaseModel):
- maxTokens: Optional[int] = None
- temperature: Optional[float] = None
- topP: Optional[float] = None
- stopSequences: Optional[list[str]] = None
-
- class GuardrailConfig(BaseModel):
- guardrailIdentifier: str = None
- guardrailVersion: str = None
- trace: Optional[str] = None
-
- modelId: str = None
- messages: List[BedrockMessage] = []
- system: Optional[List[Dict]] = None
- inferenceConfig: Optional[InferenceConfig] = None
- toolConfig: Optional[Dict] = None
- guardrailConfig: Optional[GuardrailConfig] = None
- additionalModelRequestFields: Optional[Any] = None
- promptVariables: Optional[Dict] = None
- additionalModelResponseFieldPaths: Optional[List[str]] = None
- requestMetadata: Optional[Dict] = None
- performanceConfig: Optional[Dict] = None
-
-
-class BedrockResponse(BaseModel):
- class BedrockResponseMetadata(BaseModel):
- RequestId: str = None
- HTTPStatusCode: Optional[int] = None
- HTTPHeaders: Dict[str, str] = None
- RetryAttempts: int = None
-
- class BedrockResponseOutput(BaseModel):
- class BedrockResponseMessage(BaseModel):
- class BedrockResponseContent(BaseModel):
- text: Optional[str] = None
- image: Optional[dict] = None
- document: Optional[dict] = None
- video: Optional[dict] = None
- toolUse: Optional[dict] = None
- toolResult: Optional[dict] = None
- guardContent: Optional[dict] = None
-
- role: str = None
- content: List[BedrockResponseContent] = []
-
- message: BedrockResponseMessage = None
- HTTPStatusCode: Optional[int] = None
-
- class BedrockResponseUsage(BaseModel):
- inputTokens: Optional[int] = None
- outputTokens: Optional[int] = None
- totalTokens: Optional[int] = None
-
- ResponseMetadata: BedrockResponseMetadata = None
- output: BedrockResponseOutput = None
- stopReason: str = None
- usage: BedrockResponseUsage = None
- metrics: dict = None
- additionalModelResponseFields: Optional[Any] = None
- trace: Optional[dict] = None
- performanceConfig: Optional[dict] = None
-
-
-@modelgauge_sut(capabilities=[AcceptsTextPrompt])
-class AmazonBedrockSut(PromptResponseSUT):
-
- def __init__(self, uid: str, model_id: str, access_key_id: AwsAccessKeyId, secret_access_key: AwsSecretAccessKey):
- super().__init__(uid)
- self.model_id = model_id
- self.access_key_id = access_key_id.value
- self.secret_access_key = secret_access_key.value
- self.client = None
-
- def _load_client(self):
- return boto3.client(
- service_name="bedrock-runtime",
- region_name=os.getenv("AWS_REGION", "us-east-1"),
- aws_access_key_id=self.access_key_id,
- aws_secret_access_key=self.secret_access_key,
- )
-
- def translate_text_prompt(self, prompt: TextPrompt, options: ModelOptions) -> BedrockRequest:
- inference_config = BedrockRequest.InferenceConfig(
- maxTokens=options.max_tokens,
- temperature=options.temperature,
- topP=options.top_p,
- stopSequences=options.stop_sequences,
- )
-
- return BedrockRequest(
- modelId=self.model_id,
- messages=[
- BedrockRequest.BedrockMessage(content=[{"text": prompt.text}]),
- ],
- inferenceConfig=inference_config,
- )
-
- @retry()
- def evaluate(self, request: BedrockRequest) -> BedrockResponse:
- if self.client is None:
- # Lazy load the client.
- self.client = self._load_client()
- response = self.client.converse(**request.model_dump(exclude_none=True))
- return BedrockResponse(**response)
-
- def translate_response(self, request: BedrockRequest, response: BedrockResponse) -> SUTResponse:
- if response.output is None:
- raise APIException(f"No output in response: {response}")
- content = response.output.message.content
- assert len(content) == 1, f"Expected a single response message, got {len(content)}."
- return SUTResponse(text=content[0].text)
-
-
-BEDROCK_MODELS = ["micro", "lite", "pro"]
-
-for model in BEDROCK_MODELS:
- SUTS.register(
- AmazonBedrockSut,
- f"amazon-nova-1.0-{model}",
- f"amazon.nova-{model}-v1:0",
- InjectSecret(AwsAccessKeyId),
- InjectSecret(AwsSecretAccessKey),
- )
-
-BEDROCK_INFERENCE_PROFILES = ["premier"]
-for model in BEDROCK_INFERENCE_PROFILES:
- SUTS.register(
- AmazonBedrockSut,
- f"amazon-nova-1.0-{model}",
- f"us.amazon.nova-{model}-v1:0",
- InjectSecret(AwsAccessKeyId),
- InjectSecret(AwsSecretAccessKey),
- )
diff --git a/src/modelgauge/suts/aws_bedrock_sut_factory.py b/src/modelgauge/suts/aws_bedrock_sut_factory.py
deleted file mode 100644
index a2cfb535..00000000
--- a/src/modelgauge/suts/aws_bedrock_sut_factory.py
+++ /dev/null
@@ -1,63 +0,0 @@
-import os
-
-import boto3
-
-from modelgauge.dynamic_sut_factory import DynamicDriverSUTFactory, ModelNotSupportedError
-from modelgauge.secret_values import InjectSecret, RawSecrets
-from modelgauge.sut import SUT
-from modelgauge.sut_definition import SUTDefinition
-from modelgauge.suts.aws_bedrock_client import AmazonBedrockSut, AwsAccessKeyId, AwsSecretAccessKey
-
-
-class AWSBedrockSUTFactory(DynamicDriverSUTFactory):
- DRIVER_NAME = "aws"
-
- def __init__(self, raw_secrets: RawSecrets):
- super().__init__(raw_secrets)
- self._client = None # Lazy load.
-
- @property
- def client(self):
- if self._client is None:
- self._client = boto3.client(
- service_name="bedrock",
- region_name=os.getenv("AWS_REGION", "us-east-1"),
- aws_access_key_id=self.injected_secrets()[0].value,
- aws_secret_access_key=self.injected_secrets()[1].value,
- )
- return self._client
-
- def list_suts(self) -> list[SUTDefinition]:
- return list(self._get_available_models().values())
-
- def _get_available_models(self) -> dict[str, SUTDefinition]:
- response = self.client.list_foundation_models()
- models = {}
- for m in response["modelSummaries"]:
- if m.get("modelLifecycle", {}).get("status") != "ACTIVE":
- continue
- models[m["modelId"]] = self._convert_model_id(m["modelId"])
- return models
-
- def _convert_model_id(self, model_id: str) -> SUTDefinition:
- """Convert AWS model IDs (maker.model[:version?]) to our standard format."""
- maker, model_name = model_id.split(".", maxsplit=1)
- model_name = model_name.replace(":", ".")
- return SUTDefinition({"maker": maker, "model": model_name, "driver": self.DRIVER_NAME})
-
- def _get_model_id(self, sut_definition: SUTDefinition):
- models = self._get_available_models()
- for model_id, model_definition in models.items():
- if str(model_definition.to_dynamic_sut_metadata()) == str(sut_definition.to_dynamic_sut_metadata()):
- return model_id
- supported_models = [model_def.to_dynamic_sut_metadata().external_model_name() for model_def in models.values()]
- raise ModelNotSupportedError(
- f"Model {sut_definition.external_model_name()} not found among AWS Bedrock models. AWS carries the following models from maker {sut_definition.get("maker")}: {supported_models} "
- )
-
- def get_secrets(self) -> list[InjectSecret]:
- return [InjectSecret(AwsAccessKeyId), InjectSecret(AwsSecretAccessKey)]
-
- def make_sut(self, sut_definition: SUTDefinition) -> SUT:
- model_id = self._get_model_id(sut_definition)
- return AmazonBedrockSut(sut_definition.dynamic_uid, model_id, *self.injected_secrets())
diff --git a/src/modelgauge/suts/mistral_client.py b/src/modelgauge/suts/mistral_client.py
deleted file mode 100644
index f8c2e5a5..00000000
--- a/src/modelgauge/suts/mistral_client.py
+++ /dev/null
@@ -1,80 +0,0 @@
-from mistralai.client import Mistral
-from mistralai.client.errors import HTTPValidationError, SDKError
-from mistralai.client.utils import BackoffStrategy, RetryConfig
-
-from modelgauge.secret_values import RequiredSecret, SecretDescription
-
-BACKOFF_INITIAL_MILLIS = 1000
-BACKOFF_MAX_INTERVAL_MILLIS = 100_000
-BACKOFF_EXPONENT = 1.9
-BACKOFF_MAX_ELAPSED_MILLIS = 86_400_000 # 1 day
-
-
-class MistralAIAPIKey(RequiredSecret):
- @classmethod
- def description(cls) -> SecretDescription:
- return SecretDescription(
- scope="mistralai",
- key="api_key",
- instructions="MistralAI API key. See https://docs.mistral.ai/getting-started/quickstart/",
- )
-
-
-class MistralAIClient:
- def __init__(self, api_key: MistralAIAPIKey):
- self.api_key = api_key.value
- self._client = None
-
- @property
- def client(self) -> Mistral:
- if not self._client:
- self._client = Mistral(
- api_key=self.api_key,
- timeout_ms=BACKOFF_MAX_ELAPSED_MILLIS * 3,
- retry_config=RetryConfig(
- "backoff",
- BackoffStrategy(
- BACKOFF_INITIAL_MILLIS,
- BACKOFF_MAX_INTERVAL_MILLIS,
- BACKOFF_EXPONENT,
- BACKOFF_MAX_INTERVAL_MILLIS,
- ),
- True,
- ),
- )
- return self._client
-
- @staticmethod
- def _make_request(endpoint, kwargs: dict):
- try:
- response = endpoint(**kwargs)
- return response
- # TODO check if this actually happens
- except HTTPValidationError as exc:
- raise (exc)
- # TODO check if the retry strategy takes care of this
- except SDKError as exc:
- raise (exc)
- # TODO what else can happen?
- except Exception as exc:
- raise (exc)
-
- def model_info(self, model):
- return self._make_request(self.client.models.retrieve, {"model_id": model})
-
- def request(self, req: dict):
- if self.client.chat.sdk_configuration._hooks.before_request_hooks:
- # work around bug in client
- self.client.chat.sdk_configuration._hooks.before_request_hooks = []
- return self._make_request(self.client.chat.complete, req)
-
- def score_conversation(self, model, prompt, response):
- """Returns moderation object for a conversation."""
- req = {
- "model": model,
- "inputs": [
- {"role": "user", "content": prompt},
- {"role": "assistant", "content": response},
- ],
- }
- return self._make_request(self.client.classifiers.moderate_chat, req)
diff --git a/src/modelgauge/suts/mistral_sut.py b/src/modelgauge/suts/mistral_sut.py
deleted file mode 100644
index 0450c76b..00000000
--- a/src/modelgauge/suts/mistral_sut.py
+++ /dev/null
@@ -1,85 +0,0 @@
-from typing import Optional
-
-from mistralai.client.errors import SDKError
-from mistralai.client.models import ChatCompletionResponse
-from modelgauge.model_options import ModelOptions
-from modelgauge.prompt import TextPrompt
-from modelgauge.retry_decorator import retry
-from modelgauge.secret_values import InjectSecret
-from modelgauge.sut import PromptResponseSUT, SUTResponse
-from modelgauge.sut_capabilities import AcceptsTextPrompt
-from modelgauge.sut_decorator import modelgauge_sut
-from modelgauge.sut_registry import SUTS
-from modelgauge.suts.mistral_client import MistralAIAPIKey, MistralAIClient
-from pydantic import BaseModel
-
-_USER_ROLE = "user"
-
-
-class MistralAIRequest(BaseModel):
- model: str
- messages: list[dict]
- temperature: Optional[float] = None
- max_tokens: Optional[int]
- n: int = 1 # Number of completions to generate.
-
-
-class MistralAIResponse(ChatCompletionResponse):
- """The ChatCompletionResponse class from Mistral matches our Response
- objects now, but we subclass it for consistency and so we can adjust it
- in case the upstream object changes."""
-
- pass
-
-
-@modelgauge_sut(capabilities=[AcceptsTextPrompt])
-class MistralAISut(PromptResponseSUT):
- """A MistralAI SUT hosted on MistralAI."""
-
- def __init__(
- self,
- uid: str,
- model_name: str,
- api_key: MistralAIAPIKey,
- ):
- super().__init__(uid)
- self.model_name = model_name
- self._api_key = api_key
- self._client = None
-
- @property
- def client(self):
- if not self._client:
- self._client = MistralAIClient(self._api_key)
- return self._client
-
- def translate_text_prompt(self, prompt: TextPrompt, options: ModelOptions) -> MistralAIRequest:
- args = {"model": self.model_name, "messages": [{"role": _USER_ROLE, "content": prompt.text}]}
- if options.temperature is not None:
- args["temperature"] = options.temperature
- if options.max_tokens is not None:
- args["max_tokens"] = options.max_tokens
- return MistralAIRequest(**args)
-
- @retry(transient_exceptions=[SDKError])
- def evaluate(self, request: MistralAIRequest) -> ChatCompletionResponse:
- response = self.client.request(request.model_dump(exclude_none=True)) # type: ignore
- return response
-
- def translate_response(self, request: MistralAIRequest, response: MistralAIResponse) -> SUTResponse:
- assert len(response.choices) == 1, f"Expected 1 completion, got {len(response.choices)}."
- text = response.choices[0].message.content
- assert text is not None
- return SUTResponse(text=str(text))
-
-
-def register_suts_for_model(model_name):
- MISTRAL_API_KEY = InjectSecret(MistralAIAPIKey)
- # Register standard SUT.
- model_uid = f"mistralai-{model_name}"
- SUTS.register(MistralAISut, model_uid, model_name, MISTRAL_API_KEY)
-
-
-register_suts_for_model("ministral-8b-2410")
-register_suts_for_model("mistral-large-2411")
-register_suts_for_model("mistral-large-2402")
diff --git a/src/modelgauge/suts/mistral_sut_factory.py b/src/modelgauge/suts/mistral_sut_factory.py
deleted file mode 100644
index 9f9936f6..00000000
--- a/src/modelgauge/suts/mistral_sut_factory.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from mistralai.client.models import ModelList
-
-from modelgauge.dynamic_sut_factory import DynamicDriverSUTFactory, ModelNotSupportedError
-from modelgauge.secret_values import InjectSecret, RawSecrets
-from modelgauge.sut import SUT
-from modelgauge.sut_definition import SUTDefinition
-from modelgauge.suts.mistral_client import MistralAIAPIKey, MistralAIClient
-from modelgauge.suts.mistral_sut import MistralAISut
-
-
-class MistralSUTFactory(DynamicDriverSUTFactory):
- DRIVER_NAME = "mistral"
-
- def __init__(self, raw_secrets: RawSecrets):
- super().__init__(raw_secrets)
- self._client = None # Lazy load.
-
- @property
- def client(self) -> MistralAIClient:
- if self._client is None:
- api_key = self.injected_secrets()[0]
- self._client = MistralAIClient(api_key)
- return self._client
-
- def get_secrets(self) -> list[InjectSecret]:
- api_key = InjectSecret(MistralAIAPIKey)
- return [api_key]
-
- def list_suts(self) -> list[SUTDefinition]:
- model_list: ModelList = self.client.client.models.list()
- return [SUTDefinition(maker=self.DRIVER_NAME, model=m.id, driver=self.DRIVER_NAME) for m in model_list.data]
-
- def make_sut(self, sut_definition: SUTDefinition) -> SUT:
- model_name = sut_definition.to_dynamic_sut_metadata().external_model_name()
-
- try:
- self.client.model_info(model_name)
- except Exception as e:
- raise ModelNotSupportedError(f"Model {model_name} not found or not available on mistral: {e}")
-
- return MistralAISut(sut_definition.dynamic_uid, model_name, *self.injected_secrets())
diff --git a/src/modelgauge/suts/nvidia_nim_api_client.py b/src/modelgauge/suts/nvidia_nim_api_client.py
deleted file mode 100644
index 78a66eaa..00000000
--- a/src/modelgauge/suts/nvidia_nim_api_client.py
+++ /dev/null
@@ -1,67 +0,0 @@
-from typing import Optional
-
-from modelgauge.secret_values import (
- InjectSecret,
- RequiredSecret,
- SecretDescription,
-)
-from modelgauge.suts.openai_client import OpenAIChatSUT, OpenAIChatRequest
-from modelgauge.model_options import ModelOptions
-from modelgauge.sut_capabilities import (
- AcceptsChatPrompt,
- AcceptsTextPrompt,
-)
-from modelgauge.sut_decorator import modelgauge_sut
-from modelgauge.sut_registry import SUTS
-
-BASE_URL = "https://integrate.api.nvidia.com/v1"
-
-
-class NvidiaNIMApiKey(RequiredSecret):
- @classmethod
- def description(cls) -> SecretDescription:
- return SecretDescription(
- scope="nvidia-nim-api",
- key="api_key",
- instructions="See https://build.nvidia.com/",
- )
-
-
-class NIMOpenAIChatRequest(OpenAIChatRequest):
- max_tokens: Optional[int] = (
- 256 # NVIDIA NIM uses the deprecated "max_tokens" param name instead of "max_completion_tokens"
- )
-
-
-@modelgauge_sut(
- capabilities=[
- AcceptsTextPrompt,
- AcceptsChatPrompt,
- ]
-)
-class NvidiaNIMApiClient(OpenAIChatSUT):
- """
- Documented at https://https://docs.api.nvidia.com/
- """
-
- def __init__(self, uid: str, model: str, api_key: NvidiaNIMApiKey):
- super().__init__(uid, model, api_key=api_key, base_url=BASE_URL)
-
- def _translate_request_with_temperature(
- self, messages, options: ModelOptions, temperature: float | None
- ) -> NIMOpenAIChatRequest:
- request = super()._translate_request_with_temperature(messages, options, temperature)
- request_json = request.model_dump(exclude_none=True)
- del request_json["max_completion_tokens"] # NIM API doesn't allow extra inputs
- return NIMOpenAIChatRequest(
- max_tokens=options.max_tokens,
- **request_json,
- )
-
-
-SUTS.register(
- NvidiaNIMApiClient,
- "nvidia-nemotron-mini-4b-instruct",
- "nvidia/nemotron-mini-4b-instruct",
- InjectSecret(NvidiaNIMApiKey),
-)
diff --git a/src/modelgauge/suts/nvidia_nim_sut_factory.py b/src/modelgauge/suts/nvidia_nim_sut_factory.py
deleted file mode 100644
index 1e89358a..00000000
--- a/src/modelgauge/suts/nvidia_nim_sut_factory.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from openai import OpenAI
-
-from modelgauge.dynamic_sut_factory import DynamicDriverSUTFactory, ModelNotSupportedError
-from modelgauge.secret_values import InjectSecret
-from modelgauge.sut_definition import SUTDefinition
-from modelgauge.suts.nvidia_nim_api_client import BASE_URL, NvidiaNIMApiKey, NvidiaNIMApiClient
-
-
-class NvidiaNIMSUTFactory(DynamicDriverSUTFactory):
- DRIVER_NAME = "nvidia-nim"
-
- def __init__(self, raw_secrets):
- super().__init__(raw_secrets)
- self._client = None
-
- @property
- def client(self) -> OpenAI:
- if self._client is None:
- self._client = OpenAI(api_key=self.injected_secrets()[0].value, base_url=BASE_URL)
- return self._client
-
- def list_suts(self) -> list[SUTDefinition]:
- model_list = self.client.models.list().data
- return [self._definition_for(m.id, self.DRIVER_NAME) for m in model_list]
-
- def get_secrets(self) -> list[InjectSecret]:
- return [InjectSecret(NvidiaNIMApiKey)]
-
- def _model_exists(self, model_name: str) -> bool:
- try:
- self.client.models.retrieve(model_name) # type: ignore
- return True
- except:
- return False
-
- def make_sut(self, sut_definition: SUTDefinition) -> NvidiaNIMApiClient:
- model_name = sut_definition.external_model_name()
- if not self._model_exists(model_name):
- raise ModelNotSupportedError(f"Model {model_name} not found or not available on nvidia NIM.")
- return NvidiaNIMApiClient(sut_definition.uid, model_name, *self.injected_secrets())
diff --git a/src/modelgauge/suts/openai_sut_factory.py b/src/modelgauge/suts/openai_sut_factory.py
index b88bb97f..55db9450 100644
--- a/src/modelgauge/suts/openai_sut_factory.py
+++ b/src/modelgauge/suts/openai_sut_factory.py
@@ -1,11 +1,9 @@
-from openai import OpenAI, NotFoundError
+from openai import OpenAI
from modelgauge.auth.openai_compatible_secrets import OpenAICompatibleApiKey
from modelgauge.dynamic_sut_factory import (
DynamicSUTFactory,
- DynamicDriverSUTFactory,
ModelNotSupportedError,
- ProviderNotFoundError,
)
from modelgauge.secret_values import InjectSecret, RawSecrets
from modelgauge.sut_definition import SUTDefinition
@@ -36,31 +34,6 @@ def _make_client(self) -> OpenAI:
return _client
-class OpenAICompatibleSUTFactory(BaseOpenAISUTFactory, DynamicDriverSUTFactory):
- DRIVER_NAME = "openai"
-
- def make_sut(self, sut_definition: SUTDefinition) -> OpenAIResponsesSUT:
- factory = factory_class = None
- self.provider = sut_definition.get("provider") # type: ignore
-
- if not self.provider or self.provider == "openai":
- factory = OpenAISUTFactory(self.raw_secrets)
- else:
- factory_class = OPENAI_SUT_FACTORIES.get(self.provider, None)
- # we don't have a prebuilt factory...
- if not factory_class:
- # ... but maybe we have credentials and a base url, and we can try to make a SUT
- base_url = sut_definition.get("base_url", None)
- has_secret = self.provider in self.raw_secrets
- if base_url and has_secret:
- factory_class = OpenAIGenericSUTFactory
- if factory_class:
- factory = factory_class(self.raw_secrets)
- else:
- raise ProviderNotFoundError(f"I don't know how to make a {self.provider} SUT with the OpenAI client")
- return factory.make_sut(sut_definition)
-
-
class OpenAISUTFactory(BaseOpenAISUTFactory):
"""OpenAI SUT hosted by OpenAI"""
@@ -81,42 +54,3 @@ def make_sut(self, sut_definition: SUTDefinition) -> OpenAIResponsesSUT:
f"Model {sut_definition.external_model_name()} not found or not available on openai."
)
return OpenAIResponsesSUT(sut_definition.uid, sut_definition.get("model"), client=self.client) # type: ignore
-
-
-class OpenAIGenericSUTFactory(BaseOpenAISUTFactory):
- """A SUT that uses the OpenAI client, not hosted by OpenAI"""
-
- def __init__(self, raw_secrets: RawSecrets, base_url: str | None = None):
- super().__init__(raw_secrets)
- self.base_url = base_url
-
- def _make_client(self):
- assert self.base_url
- [api_key] = self.injected_secrets()
- _client = OpenAI(api_key=api_key.value, base_url=self.base_url, max_retries=NUM_RETRIES)
- return _client
-
- def make_sut(self, sut_definition: SUTDefinition, base_url: str | None = None) -> OpenAIResponsesSUT:
- the_base_url = sut_definition.get("base_url", None)
- if base_url:
- the_base_url = base_url
- self.provider = sut_definition.get("provider") # type: ignore
- if the_base_url:
- self.base_url = the_base_url
- return OpenAIResponsesSUT(sut_definition.uid, sut_definition.get("model"), client=self.client) # type: ignore
-
-
-# this is how you add a new OpenAI-compatible SUT
-class DemoOpenAICompatibleSUTFactory(OpenAIGenericSUTFactory):
- def __init__(self, raw_secrets, **kwargs):
- super().__init__(raw_secrets)
- self.provider = "demo"
- self.base_url = "https://example.net/v1/"
-
- # the SUT UID is maker/model:demo:openai
- # the credentials in secrets.toml must be:
- # [demo]
- # api_key = "abcd"
-
-
-OPENAI_SUT_FACTORIES: dict = {"demo": DemoOpenAICompatibleSUTFactory}
diff --git a/tests/modelgauge_tests/sut_tests/test_aws_bedrock_client.py b/tests/modelgauge_tests/sut_tests/test_aws_bedrock_client.py
deleted file mode 100644
index 61a05336..00000000
--- a/tests/modelgauge_tests/sut_tests/test_aws_bedrock_client.py
+++ /dev/null
@@ -1,88 +0,0 @@
-import pytest
-from unittest.mock import patch
-
-from modelgauge.prompt import TextPrompt
-from modelgauge.sut import SUTResponse
-from modelgauge.model_options import ModelOptions
-from modelgauge.typed_data import is_typeable
-
-from modelgauge.suts.aws_bedrock_client import (
- AmazonBedrockSut,
- AwsAccessKeyId,
- AwsSecretAccessKey,
- BedrockRequest,
- BedrockResponse,
-)
-
-FAKE_MODEL_ID = "fake-model"
-
-
-@pytest.fixture
-def fake_sut():
- return AmazonBedrockSut(
- "fake-sut", FAKE_MODEL_ID, AwsAccessKeyId("fake-api-key"), AwsSecretAccessKey("fake-secret-key")
- )
-
-
-def _make_request(model_id, prompt_text, **inference_params):
- inference_config = BedrockRequest.InferenceConfig(**inference_params)
- return BedrockRequest(
- modelId=model_id,
- messages=[
- BedrockRequest.BedrockMessage(content=[{"text": prompt_text}]),
- ],
- inferenceConfig=inference_config,
- )
-
-
-def _make_response(response_text):
- return BedrockResponse(
- output=BedrockResponse.BedrockResponseOutput(
- message=BedrockResponse.BedrockResponseOutput.BedrockResponseMessage(content=[{"text": response_text}])
- )
- )
-
-
-def test_translate_text_prompt(fake_sut):
- default_options = ModelOptions()
- prompt = TextPrompt(text="some-text")
- request = fake_sut.translate_text_prompt(prompt, default_options)
-
- assert isinstance(request, BedrockRequest)
- assert request.modelId == FAKE_MODEL_ID
- assert len(request.messages) == 1
- message = request.messages[0]
- assert message.content == [{"text": "some-text"}]
- assert request.inferenceConfig.maxTokens == default_options.max_tokens # Default ModelOptions value
-
-
-def test_can_cache_request():
- request = _make_request(FAKE_MODEL_ID, "some-text", maxTokens=100)
- assert is_typeable(request)
-
-
-def test_can_cache_response():
- response = _make_response("response")
- assert is_typeable(response)
-
-
-@patch("modelgauge.suts.aws_bedrock_client.boto3.client")
-def test_evaluate_sends_correct_params(mock_client, fake_sut):
- fake_sut.client = mock_client
- request = _make_request(FAKE_MODEL_ID, "some-text", maxTokens=100, topP=0.5)
- fake_sut.evaluate(request)
-
- mock_client.converse.assert_called_with(
- modelId=FAKE_MODEL_ID,
- messages=[{"content": [{"text": "some-text"}], "role": "user"}],
- inferenceConfig={"maxTokens": 100, "topP": 0.5},
- )
-
-
-def test_translate_response(fake_sut):
- request = _make_request(FAKE_MODEL_ID, "some-text")
- response = _make_response("response")
-
- translated_response = fake_sut.translate_response(request, response)
-
- assert translated_response == SUTResponse(text="response")
diff --git a/tests/modelgauge_tests/sut_tests/test_aws_bedrock_sut_factory.py b/tests/modelgauge_tests/sut_tests/test_aws_bedrock_sut_factory.py
deleted file mode 100644
index ff4f790c..00000000
--- a/tests/modelgauge_tests/sut_tests/test_aws_bedrock_sut_factory.py
+++ /dev/null
@@ -1,74 +0,0 @@
-from unittest.mock import patch
-
-import pytest
-
-from modelgauge.dynamic_sut_factory import ModelNotSupportedError
-from modelgauge.sut_definition import SUTDefinition
-from modelgauge.suts.aws_bedrock_client import AmazonBedrockSut
-from modelgauge.suts.aws_bedrock_sut_factory import AWSBedrockSUTFactory
-
-
-@pytest.fixture
-def factory():
- return AWSBedrockSUTFactory({"aws": {"access_key_id": "value", "secret_access_key": "value"}})
-
-
-@pytest.fixture
-def mock_list_foundation_models():
- models = {
- "modelSummaries": [
- {"modelId": "amazon.nova-1.0-micro-v1:0", "modelLifecycle": {"status": "ACTIVE"}},
- {"modelId": "old_model", "modelLifecycle": {"status": "LEGACY"}},
- ]
- }
-
- with patch("boto3.client") as mock_client:
-
- mock_client.return_value.list_foundation_models.return_value = models
-
- yield mock_client
-
-
-def test_convert_model_id(factory):
- definition = factory._convert_model_id("amazon.nova-v1")
- assert definition.get("maker") == "amazon"
- assert definition.get("model") == "nova-v1"
- assert definition.get("driver") == "aws"
-
- # Sometimes they have colons
- definition = factory._convert_model_id("amazon.nova-v1:0")
- assert definition.get("maker") == "amazon"
- assert definition.get("model") == "nova-v1.0"
- assert definition.get("driver") == "aws"
-
- # "." in the model name
- definition = factory._convert_model_id("moonshotai.kimi-k2.5")
- assert definition.get("maker") == "moonshotai"
- assert definition.get("model") == "kimi-k2.5"
- assert definition.get("driver") == "aws"
-
-
-def test_make_sut(factory, mock_list_foundation_models):
- sut_definition = SUTDefinition(model="nova-1.0-micro-v1.0", maker="amazon", driver="aws")
- sut = factory.make_sut(sut_definition)
-
- assert isinstance(sut, AmazonBedrockSut)
- assert sut.uid == "amazon/nova-1.0-micro-v1.0:aws"
- assert sut.model_id == "amazon.nova-1.0-micro-v1:0"
-
-
-def test_make_sut_no_model(factory, mock_list_foundation_models):
- sut_definition = SUTDefinition(model="unknown", maker="amazon", driver="aws")
- with pytest.raises(ModelNotSupportedError):
- factory.make_sut(sut_definition)
-
-
-def test_make_sut_legacy_model(factory, mock_list_foundation_models):
- sut_definition = SUTDefinition(model="old_model", maker="amazon", driver="aws")
- with pytest.raises(ModelNotSupportedError):
- factory.make_sut(sut_definition)
-
-
-def test_list_suts(factory, mock_list_foundation_models):
- suts = factory.list_suts()
- assert "amazon/nova-1.0-micro-v1.0:aws" in [s.uid for s in suts]
diff --git a/tests/modelgauge_tests/sut_tests/test_mistral_sut.py b/tests/modelgauge_tests/sut_tests/test_mistral_sut.py
deleted file mode 100644
index 6d01a442..00000000
--- a/tests/modelgauge_tests/sut_tests/test_mistral_sut.py
+++ /dev/null
@@ -1,66 +0,0 @@
-import pytest
-from mistralai.client.models import (
- AssistantMessage,
- ChatCompletionChoice,
- UsageInfo,
-)
-from modelgauge.model_options import ModelOptions
-from modelgauge.prompt import TextPrompt
-from modelgauge.sut import SUTResponse
-from modelgauge.suts.mistral_client import MistralAIAPIKey
-from modelgauge.suts.mistral_sut import (
- MistralAIResponse,
- MistralAISut,
-)
-
-
-@pytest.fixture
-def req():
- return {
- "model": "ministral-8b-latest",
- "messages": [{"role": "user", "content": "Why did the chicken cross the road?"}],
- "max_tokens": 91,
- "temperature": 0.3,
- "n": 1,
- }
-
-
-@pytest.fixture
-def response():
- return MistralAIResponse(
- id="ed6c8eccd53e4b319a7bc566f6a53357",
- object="chat.completion",
- model="ministral-8b-latest",
- created=1731977771,
- usage=UsageInfo(prompt_tokens=11, completion_tokens=22, total_tokens=33),
- choices=[
- ChatCompletionChoice(
- index=0,
- message=AssistantMessage(
- content="The classic joke has several variations",
- tool_calls=None,
- prefix=False,
- role="assistant",
- ),
- finish_reason="stop",
- )
- ],
- )
-
-
-@pytest.fixture
-def sut():
- return MistralAISut("ministral-8b-latest", "ministral-8b-latest", MistralAIAPIKey("fake"))
-
-
-class TestMistralAISut:
-
- def test_request(self, sut, req):
- translated_req = sut.translate_text_prompt(
- TextPrompt(text="Why did the chicken cross the road?"), ModelOptions(temperature=0.3, max_tokens=91)
- )
- assert translated_req.model_dump(exclude_none=True) == req
-
- def test_response(self, sut, req, response):
- resp = sut.translate_response(request=req, response=response)
- assert resp == SUTResponse(text="The classic joke has several variations")
diff --git a/tests/modelgauge_tests/sut_tests/test_mistral_sut_factory.py b/tests/modelgauge_tests/sut_tests/test_mistral_sut_factory.py
deleted file mode 100644
index ceca94f2..00000000
--- a/tests/modelgauge_tests/sut_tests/test_mistral_sut_factory.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from unittest.mock import patch, MagicMock
-
-import pytest
-
-from modelgauge.dynamic_sut_factory import ModelNotSupportedError
-from modelgauge.sut_definition import SUTDefinition
-from modelgauge.suts.mistral_sut import MistralAISut
-from modelgauge.suts.mistral_sut_factory import MistralSUTFactory
-from modelgauge_tests.utilities import FakeObject
-
-
-@pytest.fixture
-def factory():
- return MistralSUTFactory({"mistralai": {"api_key": "value"}})
-
-
-def test_make_sut(factory):
- with patch("modelgauge.suts.mistral_client.MistralAIClient.model_info", return_value="model exists"):
- sut_definition = SUTDefinition(model="bar", maker="foo", driver="mistral")
- sut = factory.make_sut(sut_definition)
-
- assert isinstance(sut, MistralAISut)
- assert sut.uid == "foo/bar:mistral"
- assert sut.model_name == "foo/bar"
- assert sut._api_key.value == "value"
-
-
-def test_make_sut_bad_model(factory):
- sut_definition = SUTDefinition(model="bogus", maker="fake", driver="mistral")
- with patch("modelgauge.suts.mistral_client.MistralAIClient.model_info", side_effect=Exception()):
- with pytest.raises(ModelNotSupportedError):
- factory.make_sut(sut_definition)
-
-
-def test_list_suts(factory):
- model_list = MagicMock()
- model_list.data = [FakeObject(id="thingy-1.0")]
- factory._client = MagicMock()
- factory._client.client.models.list.return_value = model_list
- assert "mistral/thingy-1.0:mistral" in [s.uid for s in factory.list_suts()]
diff --git a/tests/modelgauge_tests/sut_tests/test_nvidia_nim_api_client.py b/tests/modelgauge_tests/sut_tests/test_nvidia_nim_api_client.py
deleted file mode 100644
index 2da3b7bd..00000000
--- a/tests/modelgauge_tests/sut_tests/test_nvidia_nim_api_client.py
+++ /dev/null
@@ -1,57 +0,0 @@
-from openai.types.chat import ChatCompletion
-
-from modelgauge.prompt import TextPrompt
-from modelgauge.sut import SUTResponse
-from modelgauge.suts.nvidia_nim_api_client import NIMOpenAIChatRequest, NvidiaNIMApiKey, NvidiaNIMApiClient
-from modelgauge.suts.openai_client import OpenAIChatMessage
-from modelgauge.model_options import ModelOptions
-
-
-def _make_client():
- return NvidiaNIMApiClient(uid="test-model", model="some-model", api_key=NvidiaNIMApiKey("some-value"))
-
-
-def test_openai_chat_translate_request():
- client = _make_client()
- prompt = TextPrompt(text="some-text")
- request = client.translate_text_prompt(prompt, ModelOptions(max_tokens=100))
- assert request == NIMOpenAIChatRequest(
- model="some-model",
- messages=[OpenAIChatMessage(content="some-text", role="user")],
- max_tokens=100,
- n=1,
- )
-
-
-def test_openai_chat_translate_response():
- client = _make_client()
- request = NIMOpenAIChatRequest(
- model="some-model",
- messages=[],
- )
- # response is base on openai request: https://platform.openai.com/docs/api-reference/chat/create
- response = ChatCompletion.model_validate_json("""\
-{
- "id": "chatcmpl-123",
- "object": "chat.completion",
- "created": 1677652288,
- "model": "nvidia/nemotron-mini-4b-instruct",
- "system_fingerprint": "fp_44709d6fcb",
- "choices": [{
- "index": 0,
- "message": {
- "role": "assistant",
- "content": "Hello there, how may I assist you today?"
- },
- "logprobs": null,
- "finish_reason": "stop"
- }],
- "usage": {
- "prompt_tokens": 9,
- "completion_tokens": 12,
- "total_tokens": 21
- }
-}
-""")
- result = client.translate_response(request, response)
- assert result == SUTResponse(text="Hello there, how may I assist you today?", top_logprobs=None)
diff --git a/tests/modelgauge_tests/sut_tests/test_nvidia_nim_sut_factory.py b/tests/modelgauge_tests/sut_tests/test_nvidia_nim_sut_factory.py
deleted file mode 100644
index 665f344d..00000000
--- a/tests/modelgauge_tests/sut_tests/test_nvidia_nim_sut_factory.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from unittest.mock import MagicMock
-
-import pytest
-
-from modelgauge.dynamic_sut_factory import ModelNotSupportedError
-from modelgauge.sut_definition import SUTDefinition
-from modelgauge.suts.nvidia_nim_api_client import NvidiaNIMApiClient
-from modelgauge.suts.nvidia_nim_sut_factory import NvidiaNIMSUTFactory
-from modelgauge_tests.utilities import FakeObject
-
-
-@pytest.fixture
-def factory():
- return NvidiaNIMSUTFactory({"nvidia-nim-api": {"api_key": "value"}})
-
-
-def test_make_sut(factory):
- factory._client = MagicMock()
- factory._client.models.retrieve.return_value = "model exists"
-
- sut_definition = SUTDefinition(model="bar", maker="foo", driver="nvidia-nim")
- sut = factory.make_sut(sut_definition)
-
- assert isinstance(sut, NvidiaNIMApiClient)
- assert sut.uid == "foo/bar:nvidia-nim"
- assert sut.model == "foo/bar"
-
-
-def test_make_sut_bad_model(factory):
- sut_definition = SUTDefinition(model="bogus", maker="fake", driver="nvidia-nim")
- factory._client = MagicMock()
- factory._client.models.retrieve.side_effect = Exception()
- with pytest.raises(ModelNotSupportedError):
- factory.make_sut(sut_definition)
-
-
-def test_list_suts(factory):
- m = FakeObject(id="fnord/thingy-1.0")
- model_list = MagicMock()
- model_list.data = [m]
- factory._client = MagicMock()
- factory._client.models.list.return_value = model_list
- assert "fnord/thingy-1.0:nvidia-nim" in [s.uid for s in factory.list_suts()]
diff --git a/tests/modelgauge_tests/sut_tests/test_openai_sut_factory.py b/tests/modelgauge_tests/sut_tests/test_openai_sut_factory.py
index dcfd8987..f8067937 100644
--- a/tests/modelgauge_tests/sut_tests/test_openai_sut_factory.py
+++ b/tests/modelgauge_tests/sut_tests/test_openai_sut_factory.py
@@ -4,10 +4,10 @@
from openai import OpenAI
from modelgauge.config import load_secrets_from_config
-from modelgauge.dynamic_sut_factory import ModelNotSupportedError, ProviderNotFoundError
+from modelgauge.dynamic_sut_factory import ModelNotSupportedError
from modelgauge.sut_definition import SUTDefinition
from modelgauge.suts.openai_client import OpenAIResponsesSUT
-from modelgauge.suts.openai_sut_factory import OpenAICompatibleSUTFactory, OpenAIGenericSUTFactory, OpenAISUTFactory
+from modelgauge.suts.openai_sut_factory import OpenAISUTFactory
from modelgauge_tests.utilities import expensive_tests
@@ -16,21 +16,6 @@ def openai_factory():
return OpenAISUTFactory(raw_secrets={"openai": {"api_key": "some_key"}})
-@pytest.fixture
-def openai_generic_factory():
- return OpenAIGenericSUTFactory(raw_secrets={"demo": {"api_key": "some_key"}}, base_url="some_url")
-
-
-@pytest.fixture
-def factory():
- return OpenAICompatibleSUTFactory(
- raw_secrets={
- "openai": {"api_key": "some_key", "organization": "some_org"},
- "demo": {"api_key": "some_key"},
- }
- )
-
-
@pytest.fixture
def sut_definition():
return SUTDefinition(model="some_model", maker="some_maker", driver="openai", provider="demo")
@@ -72,61 +57,6 @@ def test_make_unknown_sut_raises_error(openai_factory):
openai_factory.make_sut(sut_definition)
-### SUTs using the OpenAI client running anywhere
-def test_make_generic_sut(openai_generic_factory, sut_definition):
- openai_generic_factory.base_url = "https://example.com"
- sut = openai_generic_factory.make_sut(sut_definition)
- assert isinstance(sut, OpenAIResponsesSUT)
- assert sut.uid == "some_maker/some_model:demo:openai"
- assert sut.model == "some_model"
- assert isinstance(sut.client, OpenAI)
-
-
-### SUTs using the OpenAI client running anywhere
-def test_make_generic_sut_with_late_base_url(openai_generic_factory, sut_definition):
- sut = openai_generic_factory.make_sut(sut_definition, base_url="https://example.com")
- assert isinstance(sut, OpenAIResponsesSUT)
- assert sut.uid == "some_maker/some_model:demo:openai"
- assert sut.model == "some_model"
- assert isinstance(sut.client, OpenAI)
-
-
-### Factory that decides which kind of OpenAI-compatible SUT you want
-def test_factory_makes_the_right_generic_sut(factory):
- sut_definition = SUTDefinition(
- model="some_model", maker="some_maker", driver="openai", provider="demo", base_url="https://example.org"
- )
- sut = factory.make_sut(sut_definition)
- assert isinstance(sut, OpenAIResponsesSUT)
- assert sut.uid == "some_maker/some_model:demo:openai;url=https://example.org"
- assert sut.model == "some_model"
- assert isinstance(sut.client, OpenAI)
-
-
-### Factory that tries to create a generic OpenAI-compatible SUT if you pass in the URL and secrets exist
-def test_factory_tries_to_make_a_generic_sut(factory, sut_definition):
- # there's a base URL but no secret for that provider, so we give up
- sut_definition = SUTDefinition(
- model="some_model", maker="some_maker", driver="openai", provider="unknown", base_url="https://www.example.net"
- )
- with pytest.raises(ProviderNotFoundError):
- _ = factory.make_sut(sut_definition)
-
- # there is a secret for the "unknown" provider, so we try to make a SUT
- new_factory = OpenAICompatibleSUTFactory(raw_secrets={"unknown": {"api_key": "some_key"}})
- sut = new_factory.make_sut(sut_definition)
- assert isinstance(sut, OpenAIResponsesSUT)
- assert isinstance(sut.client, OpenAI)
-
-
-def test_factory_makes_the_right_openai_sut(factory):
- with patch("modelgauge.suts.openai_sut_factory.BaseOpenAISUTFactory._make_client"):
- sut_definition = SUTDefinition(model="gpt-5", maker="openai", driver="openai")
- sut = factory.make_sut(sut_definition)
- assert sut.uid == "openai/gpt-5:openai"
- assert sut.model == "gpt-5"
-
-
@expensive_tests
def test_connection():
factory = OpenAISUTFactory(load_secrets_from_config(path="."))