Files
astral/scripts/diagnostics/precheck_azure_openai_availability.py
Tomas Kracmar 17d745bdac Sync from dev @ 252c1cf
Source: main (252c1cf)
Excluded: live tenant exports, generated artifacts, and dev-only tooling.
2026-04-17 15:57:35 +02:00

204 lines
7.7 KiB
Python

#!/usr/bin/env python3
"""
Lightweight Azure OpenAI availability precheck for pipeline diagnostics.
This script is intentionally non-blocking: it always exits 0.
"""
from __future__ import annotations
import json
import os
import sys
from urllib.error import HTTPError, URLError
from urllib.parse import quote, urlsplit
from urllib.request import Request, urlopen
def _env(name: str, default: str = "") -> str:
return os.environ.get(name, default).strip()
def _set_pipeline_var(name: str, value: str) -> None:
print(f"##vso[task.setvariable variable={name}]{value}")
def _normalize_aoai_endpoint(endpoint: str) -> str:
cleaned = endpoint.strip().rstrip("/")
if not cleaned:
return cleaned
parsed = urlsplit(cleaned)
if parsed.scheme and parsed.netloc:
cleaned = f"{parsed.scheme}://{parsed.netloc}"
marker = "/openai"
idx = cleaned.lower().find(marker)
if idx != -1:
return cleaned[:idx]
return cleaned
def _preferred_aoai_token_param(deployment_name: str) -> str:
override = _env("AZURE_OPENAI_TOKEN_PARAM", "").lower()
if override in {"max_tokens", "max_completion_tokens"}:
return override
if deployment_name.strip().lower().startswith("gpt-5"):
return "max_completion_tokens"
return "max_tokens"
def _aoai_token_param_candidates(deployment_name: str) -> list[str]:
preferred = _preferred_aoai_token_param(deployment_name)
alternate = "max_completion_tokens" if preferred == "max_tokens" else "max_tokens"
return [preferred, alternate]
def _preferred_aoai_temperature(deployment_name: str) -> float | None:
override = _env("AZURE_OPENAI_TEMPERATURE", "").lower()
if override in {"default", "none", "omit"}:
return None
if override:
try:
return float(override)
except ValueError:
return None
if deployment_name.strip().lower().startswith("gpt-5"):
return None
return 0.0
def _aoai_temperature_candidates(deployment_name: str) -> list[float | None]:
preferred = _preferred_aoai_temperature(deployment_name)
if preferred is None:
return [None]
return [preferred, None]
def main() -> int:
enabled = _env("ENABLE_PR_AI_SUMMARY", "true").lower() == "true"
if not enabled:
print("Azure OpenAI precheck skipped: ENABLE_PR_AI_SUMMARY=false")
_set_pipeline_var("AOAI_AVAILABLE", "0")
return 0
endpoint = _env("AZURE_OPENAI_ENDPOINT")
deployment = _env("AZURE_OPENAI_DEPLOYMENT")
api_key = _env("AZURE_OPENAI_API_KEY")
api_version = _env("AZURE_OPENAI_API_VERSION", "2024-12-01-preview")
if not endpoint or not deployment or not api_key:
print("Azure OpenAI precheck skipped: missing endpoint/deployment/api-key variable")
_set_pipeline_var("AOAI_AVAILABLE", "0")
return 0
endpoint_raw = endpoint
endpoint = _normalize_aoai_endpoint(endpoint_raw)
deployment_url = f"{endpoint}/openai/deployments/{quote(deployment)}/chat/completions?api-version={quote(api_version)}"
v1_url = f"{endpoint}/openai/v1/chat/completions"
print("Azure OpenAI precheck: starting")
print(f"- endpoint(raw): {endpoint_raw}")
print(f"- endpoint(normalized): {endpoint}")
print(f"- deployment: {deployment}")
print(f"- api_version: {api_version}")
prefer_v1 = endpoint.lower().endswith(".cognitiveservices.azure.com")
health_messages = [
{"role": "system", "content": "You are a health-check assistant."},
{"role": "user", "content": "Reply with: OK"},
]
for temperature in _aoai_temperature_candidates(deployment):
temperature_unsupported = False
for token_param in _aoai_token_param_candidates(deployment):
deployment_payload = {
"messages": health_messages,
token_param: 16,
}
v1_payload = {
"model": deployment,
"messages": health_messages,
token_param: 16,
}
if temperature is not None:
deployment_payload["temperature"] = temperature
v1_payload["temperature"] = temperature
routes = (
[("v1", v1_url, v1_payload), ("deployments", deployment_url, deployment_payload)]
if prefer_v1
else [("deployments", deployment_url, deployment_payload), ("v1", v1_url, v1_payload)]
)
token_param_unsupported = False
for route_name, route_url, payload in routes:
req = Request(
url=route_url,
method="POST",
data=json.dumps(payload).encode("utf-8"),
headers={
"Content-Type": "application/json",
"api-key": api_key,
},
)
try:
with urlopen(req, timeout=45) as resp:
_ = json.loads(resp.read().decode("utf-8"))
print(f"Azure OpenAI precheck: SUCCESS via {route_name} route")
_set_pipeline_var("AOAI_AVAILABLE", "1")
return 0
except HTTPError as exc:
raw = ""
try:
raw = exc.read().decode("utf-8", errors="replace")
except Exception:
raw = ""
print(f"Azure OpenAI precheck: HTTP {exc.code} via {route_name} route")
if raw:
print(raw)
if exc.code == 400:
raw_lower = raw.lower()
if "unsupported parameter" in raw_lower and f"'{token_param}'" in raw_lower:
token_param_unsupported = True
break
if "unsupported value" in raw_lower and "'temperature'" in raw_lower and temperature is not None:
temperature_unsupported = True
break
if exc.code == 404:
# Try fallback route first.
continue
if exc.code in (401, 403):
print("Hint: Check AZURE_OPENAI_API_KEY and endpoint/resource pairing.")
_set_pipeline_var("AOAI_AVAILABLE", "0")
return 0
if exc.code == 400:
print("Hint: Check model/deployment name and API version compatibility.")
_set_pipeline_var("AOAI_AVAILABLE", "0")
return 0
_set_pipeline_var("AOAI_AVAILABLE", "0")
return 0
except URLError as exc:
print(f"Azure OpenAI precheck: network error via {route_name} route: {exc}")
_set_pipeline_var("AOAI_AVAILABLE", "0")
return 0
except Exception as exc: # pragma: no cover
print(f"Azure OpenAI precheck: unexpected error via {route_name} route: {exc}")
_set_pipeline_var("AOAI_AVAILABLE", "0")
return 0
if temperature_unsupported:
break
if not token_param_unsupported:
break
if not temperature_unsupported:
break
print("Azure OpenAI precheck: no successful response from tested routes/token-params")
print("Hint: Verify AZURE_OPENAI_ENDPOINT points to the resource root, without /openai path suffix.")
print("Hint: Verify AZURE_OPENAI_DEPLOYMENT is the deployment name (for v1 this is passed as model).")
_set_pipeline_var("AOAI_AVAILABLE", "0")
return 0
if __name__ == "__main__":
sys.exit(main())