Support Azure OpenAI v1 model discovery

This commit is contained in:
Connor Bescos 2026-05-20 21:17:16 -07:00
parent fb16e28d28
commit 8cf604fdb7
2 changed files with 54 additions and 18 deletions

View file

@ -596,10 +596,29 @@ async def get_models(request: Request, url_idx: int | None = None, user=Depends(
headers, cookies = await get_headers_and_cookies(request, url, key, api_config, user=user)
if api_config.get('azure') or api_config.get('provider') == 'azure':
models = {
'data': api_config.get('model_ids', []) or [],
'object': 'list',
}
if is_azure_openai_v1_url(url):
async with session.get(
f'{url.rstrip("/")}/models',
headers=headers,
cookies=cookies,
ssl=AIOHTTP_CLIENT_SESSION_SSL,
) as r:
if r.status != 200:
error_detail = f'HTTP Error: {r.status}'
try:
res = await r.json()
if 'error' in res:
error_detail = f'External Error: {res["error"]}'
except Exception:
pass
raise Exception(error_detail)
models = await r.json()
else:
models = {
'data': api_config.get('model_ids', []) or [],
'object': 'list',
}
elif is_anthropic_url(url):
models = await get_anthropic_models(url, key, user=user)
if models is None:
@ -687,9 +706,14 @@ async def verify_connection(
if auth_type not in ('azure_ad', 'microsoft_entra_id'):
headers['api-key'] = key
api_version = api_config.get('api_version', '') or '2023-03-15-preview'
if is_azure_openai_v1_url(url):
models_url = f'{url.rstrip("/")}/models'
else:
api_version = api_config.get('api_version', '') or '2023-03-15-preview'
models_url = f'{url}/openai/models?api-version={api_version}'
async with session.get(
url=f'{url}/openai/models?api-version={api_version}',
url=models_url,
headers=headers,
cookies=cookies,
ssl=AIOHTTP_CLIENT_SESSION_SSL,
@ -809,6 +833,11 @@ def _sanitize_model_for_url(model: str) -> str:
return quote(model, safe='')
def is_azure_openai_v1_url(url: str) -> bool:
"""Return True when an Azure connection uses the OpenAI-compatible v1 API."""
return bool(re.search(r'/openai/v1(?:/|$)', url.rstrip('/')))
def convert_to_azure_payload(url, payload: dict, api_version: str):
model = payload.get('model', '')
@ -1159,7 +1188,7 @@ async def generate_chat_completion(
# Azure v1 format: base URL already ends with /openai/v1,
# model stays in the payload, no deployment URL rewriting.
is_azure_v1 = bool(re.search(r'/openai/v1(?:/|$)', url))
is_azure_v1 = is_azure_openai_v1_url(url)
if is_azure_v1:
if is_responses:
@ -1413,7 +1442,7 @@ async def responses(
if auth_type not in ('azure_ad', 'microsoft_entra_id'):
headers['api-key'] = key
is_azure_v1 = bool(re.search(r'/openai/v1(?:/|$)', url))
is_azure_v1 = is_azure_openai_v1_url(url)
if is_azure_v1:
request_url = f'{url.rstrip("/")}/responses'
@ -1525,7 +1554,7 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)):
if auth_type not in ('azure_ad', 'microsoft_entra_id'):
headers['api-key'] = key
is_azure_v1 = bool(re.search(r'/openai/v1(?:/|$)', url))
is_azure_v1 = is_azure_openai_v1_url(url)
if is_azure_v1:
qs = request.url.query

View file

@ -42,6 +42,7 @@
((url.includes('azure.') || url.includes('cognitive.microsoft.com')) &&
!direct &&
provider === '');
$: azureOpenAIV1 = azure && /\/openai\/v1\/?$/.test(url.replace(/\/$/, ''));
let prefixId = '';
let enable = true;
@ -103,7 +104,7 @@
auth_type,
...(provider ? { provider } : {}),
...(azure ? { azure: true } : {}),
api_version: apiVersion,
...(apiVersion ? { api_version: apiVersion } : {}),
...(_headers ? { headers: _headers } : {})
}
},
@ -142,7 +143,7 @@
}
if (azure) {
if (!apiVersion) {
if (!azureOpenAIV1 && !apiVersion) {
loading = false;
toast.error($i18n.t('API Version is required'));
@ -156,7 +157,7 @@
return;
}
if (modelIds.length === 0) {
if (!azureOpenAIV1 && modelIds.length === 0) {
loading = false;
toast.error($i18n.t('Deployment names are required for Azure OpenAI'));
return;
@ -192,7 +193,7 @@
headers: headers ? JSON.parse(headers) : undefined,
...(provider ? { provider } : {}),
...(!ollama && azure ? { azure: true } : {}),
...(azure ? { api_version: apiVersion } : {}),
...(azure && apiVersion ? { api_version: apiVersion } : {}),
...(apiType ? { api_type: apiType } : {})
}
};
@ -517,7 +518,7 @@
</div>
{/if}
{#if azure}
{#if azure && !azureOpenAIV1}
<div class="flex gap-2 mt-2">
<div class="flex flex-col w-full">
<label
@ -624,10 +625,16 @@
url: url
})}
{:else if azure}
{$i18n.t('Deployment names are required for Azure OpenAI')}
<!-- {$i18n.t('Leave empty to include all models from "{{url}}" endpoint', {
url: `${url}/openai/deployments`
})} -->
{#if azureOpenAIV1}
{$i18n.t('Leave empty to include all models from "{{url}}/models" endpoint', {
url: url
})}
{:else}
{$i18n.t('Deployment names are required for Azure OpenAI')}
<!-- {$i18n.t('Leave empty to include all models from "{{url}}" endpoint', {
url: `${url}/openai/deployments`
})} -->
{/if}
{:else}
{$i18n.t('Leave empty to include all models from "{{url}}/models" endpoint', {
url: url