Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Deploy, evaluate, and manage AI agents end-to-end on Microsoft Azure AI Foundry
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