Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Build and deploy AI applications on Azure AI Foundry using Microsoft's model catalog and AI services
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
finetuning/scripts/common.py
1"""2common.py — Shared Azure AI Foundry authentication and client setup.34Supports three connection methods in order of preference:51. /v1/ project endpoint (simplest, preferred)62. Foundry SDK with DefaultAzureCredential (no API key needed, cloud-native)73. Azure OpenAI endpoint (classic)89AAD tokens are auto-refreshed via azure.identity for long-running scripts10(monitor_training.py, generate_distillation_data.py, etc.).1112Usage:13from common import get_clients, upload_file1415# Method 1: Project /v1/ endpoint (preferred)16clients = get_clients(base_url="https://<resource>.services.ai.azure.com/api/projects/<project>/openai/v1/",17api_key="KEY")1819# Method 2: Foundry SDK (DefaultAzureCredential — no API key needed)20clients = get_clients(project_endpoint="https://<resource>.services.ai.azure.com/api/projects/<project>")2122# Method 3: Azure OpenAI endpoint23clients = get_clients(azure_endpoint="https://<resource>.openai.azure.com",24api_key="KEY")25"""26import argparse27import os28import sys29303132try:33sys.stdout.reconfigure(encoding="utf-8")34sys.stderr.reconfigure(encoding="utf-8")35except (AttributeError, OSError):36pass # Stream not reconfigurable (older Python or non-tty); default encoding is fine37_AZURE_COGSERVICES_SCOPE = "https://cognitiveservices.azure.com/.default"383940def _clamp_score(v, default=0):41"""Clamp a judge score to [1, 10]. Returns `default` for missing/non-numeric values.4243LLM judges occasionally return out-of-range integers (e.g., 15) or non-numeric44strings ("high"). Without clamping, these distort aggregate scores or crash45`int()`. We use 0 as a sentinel for "missing/failed" so callers can filter via46`score > 0`.47"""48if v is None:49return default50try:51return max(1, min(10, int(v)))52except (ValueError, TypeError):53return default545556class HelpOnErrorParser(argparse.ArgumentParser):57"""ArgumentParser that prints full help when arguments are invalid.5859Standard ArgumentParser only prints a one-line usage summary on error,60which isn't helpful for first-time users. This prints the full --help.61"""6263def error(self, message):64self.print_help(sys.stderr)65self.exit(2, f"\nerror: {message}\n")666768def _make_token_provider():69"""Create an auto-refreshing AAD token provider for long-running scripts.7071Returns a callable that the OpenAI SDK calls before each request to get72a fresh token. Tokens are cached and refreshed ~5 min before expiry.73"""74from azure.identity import DefaultAzureCredential75credential = DefaultAzureCredential()7677def get_token():78try:79token = credential.get_token(_AZURE_COGSERVICES_SCOPE)80return token.token81except Exception as e:82raise RuntimeError(83f"Azure AD authentication failed: {e}\n"84"Ensure you're logged in (az login) or have valid "85"AZURE_CLIENT_ID/AZURE_TENANT_ID/AZURE_CLIENT_SECRET set."86) from e8788return get_token899091def get_clients(base_url=None, azure_endpoint=None, project_endpoint=None, api_key=None):92"""Initialize and return OpenAI-compatible client.9394Tries in order:951. Project /v1/ endpoint with openai.OpenAI() (simplest, preferred)962. Foundry SDK with AIProjectClient.get_openai_client() (no API key needed)973. Azure OpenAI endpoint with openai.AzureOpenAI() (classic)9899When using DefaultAzureCredential (no API key), tokens are auto-refreshed100so long-running scripts won't fail with 401 after ~60 min.101102Returns: (openai_client, method_name)103"""104# Method 1: /v1/ project endpoint105base_url = base_url or os.environ.get("OPENAI_BASE_URL")106api_key = api_key or os.environ.get("AZURE_OPENAI_API_KEY")107108if base_url:109import openai110if not api_key:111try:112token_provider = _make_token_provider()113token_provider() # verify it works114# Use a custom httpx auth class that refreshes the token on each request115import httpx116117class _AzureADAuth(httpx.Auth):118def __init__(self, provider):119self._provider = provider120121def auth_flow(self, request):122request.headers["Authorization"] = f"Bearer {self._provider()}"123yield request124125client = openai.OpenAI(126base_url=base_url,127api_key="aad", # required by SDK but overridden by auth128http_client=httpx.Client(auth=_AzureADAuth(token_provider)),129)130print(f"✅ Connected via /v1/ project endpoint (DefaultAzureCredential, auto-refresh)")131return client, "project-v1-aad"132except Exception as e:133print(f"⚠️ No API key and DefaultAzureCredential failed: {e}")134else:135client = openai.OpenAI(base_url=base_url, api_key=api_key)136print(f"✅ Connected via /v1/ project endpoint")137return client, "project-v1"138139# Method 2: Foundry SDK140project_endpoint = project_endpoint or os.environ.get("AZURE_AI_PROJECT_ENDPOINT")141if project_endpoint:142try:143from azure.ai.projects import AIProjectClient144from azure.identity import DefaultAzureCredential145146credential = DefaultAzureCredential()147project_client = AIProjectClient(endpoint=project_endpoint, credential=credential)148openai_client = project_client.get_openai_client()149print(f"✅ Connected via Foundry SDK")150return openai_client, "foundry-sdk"151except Exception as e:152print(f"⚠️ Foundry SDK failed: {e}")153154# Method 3: Azure OpenAI endpoint155azure_endpoint = azure_endpoint or os.environ.get("AZURE_OPENAI_ENDPOINT")156if azure_endpoint:157import openai158if api_key:159client = openai.AzureOpenAI(160azure_endpoint=azure_endpoint,161api_key=api_key,162api_version="2025-04-01-preview",163)164print(f"✅ Connected via Azure OpenAI endpoint")165return client, "azure-openai"166else:167# No API key — use DefaultAzureCredential with auto-refresh168try:169token_provider = _make_token_provider()170token_provider() # verify it works171client = openai.AzureOpenAI(172azure_endpoint=azure_endpoint,173azure_ad_token_provider=token_provider,174api_version="2025-04-01-preview",175)176print(f"✅ Connected via Azure OpenAI endpoint (DefaultAzureCredential, auto-refresh)")177return client, "azure-openai-aad"178except Exception as e:179print(f"⚠️ DefaultAzureCredential failed for Azure endpoint: {e}")180181print("❌ No valid connection method. Set one of:")182print(" OPENAI_BASE_URL (preferred)")183print(" AZURE_AI_PROJECT_ENDPOINT (Foundry SDK)")184print(" AZURE_OPENAI_ENDPOINT + AZURE_OPENAI_API_KEY")185raise SystemExit(1)186187188def upload_file(openai_client, filepath: str, purpose: str = "fine-tune") -> str:189"""Upload a file to Azure AI Foundry and wait for processing."""190print(f"📤 Uploading {filepath}...")191with open(filepath, "rb") as f:192file_obj = openai_client.files.create(file=f, purpose=purpose)193print(f" File ID: {file_obj.id}")194print(f" Waiting for processing...")195openai_client.files.wait_for_processing(file_obj.id)196print(f" ✅ File ready")197return file_obj.id198