From 792339200aa4c8f75bf42bc724bea26d2f05985c Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 13 Nov 2025 21:48:57 -0800 Subject: [PATCH] Migrate Add Model Fields to backend (#16620) --- .../provider_create_metadata.py | 744 ++++++++++++++++++ .../public_endpoints/public_endpoints.py | 21 +- .../public_endpoints/public_endpoints.py | 21 +- .../test_provider_create_metadata.py | 55 ++ .../public_endpoints/test_public_endpoints.py | 22 + 5 files changed, 861 insertions(+), 2 deletions(-) create mode 100644 litellm/proxy/public_endpoints/provider_create_metadata.py create mode 100644 tests/test_litellm/proxy/public_endpoints/test_provider_create_metadata.py diff --git a/litellm/proxy/public_endpoints/provider_create_metadata.py b/litellm/proxy/public_endpoints/provider_create_metadata.py new file mode 100644 index 0000000000..f2ef03e2a0 --- /dev/null +++ b/litellm/proxy/public_endpoints/provider_create_metadata.py @@ -0,0 +1,744 @@ +from __future__ import annotations + +from typing import Any, Dict, List + +from litellm.types.proxy.public_endpoints.public_endpoints import ( + ProviderCreateInfo, + ProviderCredentialField, +) + +DEFAULT_MODEL_PLACEHOLDER = "gpt-3.5-turbo" + +_FALLBACK_FIELDS: List[Dict[str, Any]] = [ + { + "key": "api_base", + "label": "API Base", + "field_type": "text", + "required": False, + }, + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": False, + }, +] + +PROVIDER_BASE_INFO: Dict[str, Dict[str, Any]] = { + "AIML": { + "provider_display_name": "AI/ML API", + "litellm_provider": "aiml", + "default_model_placeholder": "aiml/flux-pro/v1.1", + }, + "Anthropic": { + "provider_display_name": "Anthropic", + "litellm_provider": "anthropic", + "default_model_placeholder": "claude-3-opus", + }, + "AssemblyAI": { + "provider_display_name": "AssemblyAI", + "litellm_provider": "assemblyai", + }, + "Azure": { + "provider_display_name": "Azure", + "litellm_provider": "azure", + "default_model_placeholder": "azure/my-deployment", + }, + "Azure_AI_Studio": { + "provider_display_name": "Azure AI Foundry (Studio)", + "litellm_provider": "azure_ai", + "default_model_placeholder": "azure_ai/command-r-plus", + }, + "Bedrock": { + "provider_display_name": "Amazon Bedrock", + "litellm_provider": "bedrock", + "default_model_placeholder": "claude-3-opus", + }, + "Cerebras": { + "provider_display_name": "Cerebras", + "litellm_provider": "cerebras", + }, + "Cohere": { + "provider_display_name": "Cohere", + "litellm_provider": "cohere", + }, + "Dashscope": { + "provider_display_name": "Dashscope", + "litellm_provider": "dashscope", + }, + "Databricks": { + "provider_display_name": "Databricks (Qwen API)", + "litellm_provider": "databricks", + }, + "DeepInfra": { + "provider_display_name": "DeepInfra", + "litellm_provider": "deepinfra", + "default_model_placeholder": "deepinfra/", + }, + "Deepgram": { + "provider_display_name": "Deepgram", + "litellm_provider": "deepgram", + }, + "Deepseek": { + "provider_display_name": "Deepseek", + "litellm_provider": "deepseek", + }, + "ElevenLabs": { + "provider_display_name": "ElevenLabs", + "litellm_provider": "elevenlabs", + }, + "FalAI": { + "provider_display_name": "Fal AI", + "litellm_provider": "fal_ai", + "default_model_placeholder": "fal_ai/fal-ai/flux-pro/v1.1-ultra", + }, + "FireworksAI": { + "provider_display_name": "Fireworks AI", + "litellm_provider": "fireworks_ai", + }, + "Google_AI_Studio": { + "provider_display_name": "Google AI Studio", + "litellm_provider": "gemini", + "default_model_placeholder": "gemini-pro", + }, + "GradientAI": { + "provider_display_name": "GradientAI", + "litellm_provider": "gradient_ai", + }, + "Groq": { + "provider_display_name": "Groq", + "litellm_provider": "groq", + }, + "Hosted_Vllm": { + "provider_display_name": "vllm", + "litellm_provider": "hosted_vllm", + }, + "Infinity": { + "provider_display_name": "Infinity", + "litellm_provider": "infinity", + }, + "JinaAI": { + "provider_display_name": "Jina AI", + "litellm_provider": "jina_ai", + "default_model_placeholder": "jina_ai/", + }, + "MistralAI": { + "provider_display_name": "Mistral AI", + "litellm_provider": "mistral", + }, + "Ollama": { + "provider_display_name": "Ollama", + "litellm_provider": "ollama", + }, + "OpenAI": { + "provider_display_name": "OpenAI", + "litellm_provider": "openai", + }, + "OpenAI_Compatible": { + "provider_display_name": "OpenAI-Compatible Endpoints (Together AI, etc.)", + "litellm_provider": "openai", + }, + "OpenAI_Text": { + "provider_display_name": "OpenAI Text Completion", + "litellm_provider": "text-completion-openai", + }, + "OpenAI_Text_Compatible": { + "provider_display_name": "OpenAI-Compatible Text Completion Models (Together AI, etc.)", + "litellm_provider": "text-completion-openai", + }, + "Openrouter": { + "provider_display_name": "Openrouter", + "litellm_provider": "openrouter", + }, + "Oracle": { + "provider_display_name": "Oracle Cloud Infrastructure (OCI)", + "litellm_provider": "oci", + "default_model_placeholder": "oci/xai.grok-4", + }, + "Perplexity": { + "provider_display_name": "Perplexity", + "litellm_provider": "perplexity", + }, + "SageMaker": { + "provider_display_name": "AWS SageMaker", + "litellm_provider": "sagemaker_chat", + "default_model_placeholder": "sagemaker/jumpstart-dft-meta-textgeneration-llama-2-7b", + }, + "Sambanova": { + "provider_display_name": "Sambanova", + "litellm_provider": "sambanova", + }, + "Snowflake": { + "provider_display_name": "Snowflake", + "litellm_provider": "snowflake", + "default_model_placeholder": "snowflake/mistral-7b", + }, + "TogetherAI": { + "provider_display_name": "TogetherAI", + "litellm_provider": "together_ai", + }, + "Triton": { + "provider_display_name": "Triton", + "litellm_provider": "triton", + }, + "Vertex_AI": { + "provider_display_name": "Vertex AI (Anthropic, Gemini, etc.)", + "litellm_provider": "vertex_ai", + "default_model_placeholder": "gemini-pro", + }, + "VolcEngine": { + "provider_display_name": "VolcEngine", + "litellm_provider": "volcengine", + "default_model_placeholder": "volcengine/", + }, + "Voyage": { + "provider_display_name": "Voyage AI", + "litellm_provider": "voyage", + "default_model_placeholder": "voyage/", + }, + "xAI": { + "provider_display_name": "xAI", + "litellm_provider": "xai", + }, +} + +PROVIDER_CREDENTIAL_FIELDS: Dict[str, List[Dict[str, Any]]] = { + "OpenAI": [ + { + "key": "api_base", + "label": "API Base", + "field_type": "text", + "placeholder": "https://api.openai.com/v1", + "tooltip": "Common endpoints: https://api.openai.com/v1, https://eu.api.openai.com, https://us.api.openai.com", + "default_value": "https://api.openai.com/v1", + }, + { + "key": "organization", + "label": "OpenAI Organization ID", + "placeholder": "[OPTIONAL] my-unique-org", + }, + { + "key": "api_key", + "label": "OpenAI API Key", + "field_type": "password", + "required": True, + }, + ], + "OpenAI_Text": [ + { + "key": "api_base", + "label": "API Base", + "field_type": "text", + "placeholder": "https://api.openai.com/v1", + "tooltip": "Common endpoints: https://api.openai.com/v1, https://eu.api.openai.com, https://us.api.openai.com", + "default_value": "https://api.openai.com/v1", + }, + { + "key": "organization", + "label": "OpenAI Organization ID", + "placeholder": "[OPTIONAL] my-unique-org", + }, + { + "key": "api_key", + "label": "OpenAI API Key", + "field_type": "password", + "required": True, + }, + ], + "Vertex_AI": [ + { + "key": "vertex_project", + "label": "Vertex Project", + "placeholder": "adroit-cadet-1234..", + "required": True, + }, + { + "key": "vertex_location", + "label": "Vertex Location", + "placeholder": "us-east-1", + "required": True, + }, + { + "key": "vertex_credentials", + "label": "Vertex Credentials", + "field_type": "upload", + "required": True, + }, + ], + "AssemblyAI": [ + { + "key": "api_base", + "label": "API Base", + "field_type": "select", + "required": True, + "options": [ + "https://api.assemblyai.com", + "https://api.eu.assemblyai.com", + ], + }, + { + "key": "api_key", + "label": "AssemblyAI API Key", + "field_type": "password", + "required": True, + }, + ], + "Azure": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://...", + "required": True, + }, + { + "key": "api_version", + "label": "API Version", + "placeholder": "2023-07-01-preview", + "tooltip": "By default litellm will use the latest version. If you want to use a different version, you can specify it here", + }, + { + "key": "base_model", + "label": "Base Model", + "placeholder": "azure/gpt-3.5-turbo", + }, + { + "key": "api_key", + "label": "Azure API Key", + "field_type": "password", + "placeholder": "Enter your Azure API Key", + }, + { + "key": "azure_ad_token", + "label": "Azure AD Token", + "field_type": "password", + "placeholder": "Enter your Azure AD Token", + }, + ], + "Azure_AI_Studio": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-10-21", + "tooltip": "Enter your full Target URI from Azure Foundry here. Example: https://litellm8397336933.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-10-21", + "required": True, + }, + { + "key": "api_key", + "label": "Azure API Key", + "field_type": "password", + "required": True, + }, + ], + "OpenAI_Compatible": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://...", + "required": True, + }, + { + "key": "api_key", + "label": "OpenAI API Key", + "field_type": "password", + "required": True, + }, + ], + "Dashscope": [ + { + "key": "api_key", + "label": "Dashscope API Key", + "field_type": "password", + "required": True, + }, + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", + "default_value": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", + "required": True, + "tooltip": "The base URL for your Dashscope server. Defaults to https://dashscope-intl.aliyuncs.com/compatible-mode/v1 if not specified.", + }, + ], + "OpenAI_Text_Compatible": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://...", + "required": True, + }, + { + "key": "api_key", + "label": "OpenAI API Key", + "field_type": "password", + "required": True, + }, + ], + "Bedrock": [ + { + "key": "aws_access_key_id", + "label": "AWS Access Key ID", + "field_type": "password", + "tooltip": "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", + }, + { + "key": "aws_secret_access_key", + "label": "AWS Secret Access Key", + "field_type": "password", + "tooltip": "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", + }, + { + "key": "aws_session_token", + "label": "AWS Session Token", + "field_type": "password", + "tooltip": "Temporary credentials session token. You can provide the raw token or the environment variable (e.g. `os.environ/MY_SESSION_TOKEN`).", + }, + { + "key": "aws_region_name", + "label": "AWS Region Name", + "placeholder": "us-east-1", + "tooltip": "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", + }, + { + "key": "aws_session_name", + "label": "AWS Session Name", + "placeholder": "my-session", + "tooltip": "Name for the AWS session. You can provide the raw value or the environment variable (e.g. `os.environ/MY_SESSION_NAME`).", + }, + { + "key": "aws_profile_name", + "label": "AWS Profile Name", + "placeholder": "default", + "tooltip": "AWS profile name to use for authentication. You can provide the raw value or the environment variable (e.g. `os.environ/MY_PROFILE_NAME`).", + }, + { + "key": "aws_role_name", + "label": "AWS Role Name", + "placeholder": "MyRole", + "tooltip": "AWS IAM role name to assume. You can provide the raw value or the environment variable (e.g. `os.environ/MY_ROLE_NAME`).", + }, + { + "key": "aws_web_identity_token", + "label": "AWS Web Identity Token", + "field_type": "password", + "tooltip": "Web identity token for OIDC authentication. You can provide the raw token or the environment variable (e.g. `os.environ/MY_WEB_IDENTITY_TOKEN`).", + }, + { + "key": "aws_bedrock_runtime_endpoint", + "label": "AWS Bedrock Runtime Endpoint", + "placeholder": "https://bedrock-runtime.us-east-1.amazonaws.com", + "tooltip": "Custom Bedrock runtime endpoint URL. You can provide the raw value or the environment variable (e.g. `os.environ/MY_BEDROCK_ENDPOINT`).", + }, + ], + "SageMaker": [ + { + "key": "aws_access_key_id", + "label": "AWS Access Key ID", + "field_type": "password", + "tooltip": "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", + }, + { + "key": "aws_secret_access_key", + "label": "AWS Secret Access Key", + "field_type": "password", + "tooltip": "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", + }, + { + "key": "aws_region_name", + "label": "AWS Region Name", + "placeholder": "us-east-1", + "tooltip": "You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", + }, + ], + "Ollama": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "http://localhost:11434", + "default_value": "http://localhost:11434", + "tooltip": "The base URL for your Ollama server. Defaults to http://localhost:11434 if not specified.", + }, + ], + "Anthropic": [ + { + "key": "api_key", + "label": "API Key", + "placeholder": "sk-", + "field_type": "password", + "required": True, + }, + ], + "Deepgram": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "ElevenLabs": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Google_AI_Studio": [ + { + "key": "api_key", + "label": "API Key", + "placeholder": "aig-", + "field_type": "password", + "required": True, + }, + ], + "Groq": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "MistralAI": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Deepseek": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Cohere": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Databricks": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "xAI": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "AIML": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Cerebras": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Sambanova": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Perplexity": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "TogetherAI": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Openrouter": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "FireworksAI": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "GradientAI": [ + { + "key": "api_base", + "label": "GradientAI Endpoint", + "placeholder": "https://...", + }, + { + "key": "api_key", + "label": "GradientAI API Key", + "field_type": "password", + "required": True, + }, + ], + "Triton": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + }, + { + "key": "api_base", + "label": "API Base", + "placeholder": "http://localhost:8000/generate", + }, + ], + "Hosted_Vllm": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://...", + "required": True, + }, + { + "key": "api_key", + "label": "vLLM API Key", + "field_type": "password", + }, + ], + "Voyage": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "JinaAI": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "VolcEngine": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "DeepInfra": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Oracle": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], + "Snowflake": [ + { + "key": "api_key", + "label": "Snowflake API Key / JWT Key for Authentication", + "field_type": "password", + "required": True, + }, + { + "key": "api_base", + "label": "Snowflake API Endpoint", + "placeholder": "https://1234567890.snowflakecomputing.com/api/v2/cortex/inference:complete", + "tooltip": "Enter the full endpoint with path here. Example: https://1234567890.snowflakecomputing.com/api/v2/cortex/inference:complete", + "required": True, + }, + ], + "Infinity": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "http://localhost:7997", + }, + ], + "FalAI": [ + { + "key": "api_key", + "label": "API Key", + "field_type": "password", + "required": True, + }, + ], +} + + +def _normalize_field(field: Dict[str, Any]) -> ProviderCredentialField: + return ProviderCredentialField( + key=field["key"], + label=field["label"], + placeholder=field.get("placeholder"), + tooltip=field.get("tooltip"), + required=field.get("required", False), + field_type=field.get("field_type", "text"), + options=field.get("options"), + default_value=field.get("default_value"), + ) + + +def get_provider_create_metadata() -> List[ProviderCreateInfo]: + providers: List[ProviderCreateInfo] = [] + + for provider_key, base_info in PROVIDER_BASE_INFO.items(): + raw_fields = PROVIDER_CREDENTIAL_FIELDS.get(provider_key, _FALLBACK_FIELDS) + normalized_fields = [_normalize_field(field) for field in raw_fields] + + providers.append( + ProviderCreateInfo( + provider=provider_key, + provider_display_name=base_info["provider_display_name"], + litellm_provider=base_info["litellm_provider"], + default_model_placeholder=base_info.get( + "default_model_placeholder", DEFAULT_MODEL_PLACEHOLDER + ), + credential_fields=normalized_fields, + ) + ) + + providers.sort(key=lambda item: item.provider_display_name.lower()) + return providers + diff --git a/litellm/proxy/public_endpoints/public_endpoints.py b/litellm/proxy/public_endpoints/public_endpoints.py index 2cc3dd0ed4..8c1e6b74b3 100644 --- a/litellm/proxy/public_endpoints/public_endpoints.py +++ b/litellm/proxy/public_endpoints/public_endpoints.py @@ -3,11 +3,17 @@ from typing import List from fastapi import APIRouter, Depends, HTTPException from litellm.proxy._types import CommonProxyErrors +from litellm.proxy.public_endpoints.provider_create_metadata import ( + get_provider_create_metadata, +) from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.types.proxy.management_endpoints.model_management_endpoints import ( ModelGroupInfoProxy, ) -from litellm.types.proxy.public_endpoints.public_endpoints import PublicModelHubInfo +from litellm.types.proxy.public_endpoints.public_endpoints import ( + PublicModelHubInfo, + ProviderCreateInfo, +) from litellm.types.utils import LlmProviders router = APIRouter() @@ -74,3 +80,16 @@ async def get_supported_providers() -> List[str]: """ return sorted(provider.value for provider in LlmProviders) + + +@router.get( + "/public/providers/fields", + tags=["public", "providers"], + response_model=List[ProviderCreateInfo], +) +async def get_provider_fields() -> List[ProviderCreateInfo]: + """ + Return provider metadata required by the dashboard create-model flow. + """ + + return get_provider_create_metadata() diff --git a/litellm/types/proxy/public_endpoints/public_endpoints.py b/litellm/types/proxy/public_endpoints/public_endpoints.py index b2949a719e..7edf05dc94 100644 --- a/litellm/types/proxy/public_endpoints/public_endpoints.py +++ b/litellm/types/proxy/public_endpoints/public_endpoints.py @@ -1,4 +1,4 @@ -from typing import Dict, Optional +from typing import Dict, List, Literal, Optional from pydantic import BaseModel @@ -8,3 +8,22 @@ class PublicModelHubInfo(BaseModel): custom_docs_description: Optional[str] litellm_version: str useful_links: Optional[Dict[str, str]] + + +class ProviderCredentialField(BaseModel): + key: str + label: str + placeholder: Optional[str] = None + tooltip: Optional[str] = None + required: bool = False + field_type: Literal["text", "password", "select", "upload"] = "text" + options: Optional[List[str]] = None + default_value: Optional[str] = None + + +class ProviderCreateInfo(BaseModel): + provider: str + provider_display_name: str + litellm_provider: str + credential_fields: List[ProviderCredentialField] + default_model_placeholder: Optional[str] = None diff --git a/tests/test_litellm/proxy/public_endpoints/test_provider_create_metadata.py b/tests/test_litellm/proxy/public_endpoints/test_provider_create_metadata.py new file mode 100644 index 0000000000..6676720b7a --- /dev/null +++ b/tests/test_litellm/proxy/public_endpoints/test_provider_create_metadata.py @@ -0,0 +1,55 @@ +import os +import sys +from copy import deepcopy + +import pytest + +sys.path.insert(0, os.path.abspath("../../..")) + +import litellm.proxy.public_endpoints.provider_create_metadata as pcm # noqa: E402 +from litellm.proxy.public_endpoints.provider_create_metadata import ( # noqa: E402 + _normalize_field, + get_provider_create_metadata, +) + + +def test_get_provider_create_metadata_includes_openai_fields(): + metadata = get_provider_create_metadata() + + openai_info = next(item for item in metadata if item.provider == "OpenAI") + + assert openai_info.provider_display_name == "OpenAI" + assert openai_info.litellm_provider == "openai" + keys = {field.key for field in openai_info.credential_fields} + assert {"api_base", "api_key"}.issubset(keys) + + +def test_get_provider_create_metadata_returns_sorted_display_names(): + metadata = get_provider_create_metadata() + display_names = [item.provider_display_name for item in metadata] + + assert display_names == sorted(display_names, key=str.lower) + + +def test_get_provider_create_metadata_uses_fallback_fields(monkeypatch): + overridden_fields = deepcopy(pcm.PROVIDER_CREDENTIAL_FIELDS) + overridden_fields.pop("Azure", None) + monkeypatch.setattr(pcm, "PROVIDER_CREDENTIAL_FIELDS", overridden_fields) + + metadata = get_provider_create_metadata() + azure_info = next(item for item in metadata if item.provider == "Azure") + + fallback_keys = [field.key for field in azure_info.credential_fields] + assert fallback_keys == ["api_base", "api_key"] + assert all(field.required is False for field in azure_info.credential_fields) + + +def test_normalize_field_applies_defaults(): + normalized = _normalize_field({"key": "api_key", "label": "API Key"}) + + assert normalized.key == "api_key" + assert normalized.label == "API Key" + assert normalized.field_type == "text" + assert normalized.required is False + assert normalized.placeholder is None + assert normalized.options is None diff --git a/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py b/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py index 89f9dd0987..f433c64c58 100644 --- a/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py +++ b/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py @@ -23,3 +23,25 @@ def test_get_supported_providers_returns_enum_values(): expected_providers = sorted(provider.value for provider in LlmProviders) assert response.json() == expected_providers + +def test_get_provider_fields_returns_metadata(): + app = FastAPI() + app.include_router(router) + client = TestClient(app) + + response = client.get("/public/providers/fields") + + assert response.status_code == 200 + payload = response.json() + assert isinstance(payload, list) + + provider_lookup = {item["provider"]: item for item in payload} + assert "OpenAI" in provider_lookup + + openai_fields = provider_lookup["OpenAI"] + assert openai_fields["provider_display_name"] == "OpenAI" + assert openai_fields["litellm_provider"] == "openai" + + credential_keys = {field["key"] for field in openai_fields["credential_fields"]} + assert {"api_base", "api_key"}.issubset(credential_keys) +