Runnable code for Labs 1–2 of the Microsoft Foundry Evaluations Framework half-day workshop.
| File | Purpose |
|---|---|
check_setup.py |
Verifies auth, project connectivity, and judge model before the labs start |
create_agent.py |
Creates the demo weather agent with two function tools |
run_agent.py |
Executes prompt-agent function calls locally; supports one query or a JSONL batch |
dataset.jsonl |
20-row eval dataset (incl. out-of-scope + adversarial rows) |
run_cloud_eval.py |
Lab 1B / Lab 2A — cloud scoring of completed responses (--precomputed) or optional live target |
eval_artifacts.py |
Stable adapter for hashes, normalized output items, and summaries |
check_contracts.py |
Deterministic validation of tool names, arguments, schemas, and no-tool cases |
eval_gate.py |
Offline policy gate for hard gates, quality floors/regressions, cohorts, and provenance |
model-migration-lab.md |
Optional Lab 3 — incumbent/candidate comparison and deterministic promotion gate |
run_local_eval.py |
Stretch — local evaluation on 3 rows with the Azure AI Evaluation SDK |
create_agent_gxp.py |
GxP variant — creates the SOP & deviation-triage assistant with a lookup_sop tool (generic reference) |
create_agent_gmp.py |
GxP discipline: GMP — manufacturing SOP/deviation agent (demo-gmp-agent) |
create_agent_glp.py |
GxP discipline: GLP — lab / product-testing agent with a lookup_method tool (demo-lab-agent) |
create_agent_gcp.py |
GxP discipline: GCP — clinical-study coordinator agent with a lookup_protocol tool (demo-clinical-agent) |
create_agent_gdp.py |
GxP discipline: GDP — distribution / cold-chain agent with a lookup_dist_sop tool (demo-distribution-agent) |
generate_synthetic_dataset.py |
GxP variant — compliant synthetic dataset generation (--discipline {gmp,glp,gcp,gdp}) with provenance metadata and review gating |
dataset_gxp_sample.jsonl |
GxP variant — 12 pre-reviewed rows for the generic SOP/deviation-triage scenario |
dataset_gmp_sample.jsonl / dataset_glp_sample.jsonl / dataset_gcp_sample.jsonl / dataset_gdp_sample.jsonl |
GxP disciplines — 12 pre-reviewed rows each (GMP / GLP / GCP / GDP) |
.env.example |
Environment variable template |
requirements.txt |
Pinned dependencies |
python -m venv .venv && source .venv/bin/activate # or .venv\Scripts\activate on Windows
pip install -r requirements.txt
cp .env.example .env # then fill in your values
az loginRequired roles: Foundry User on the project (formerly Azure AI User — you may see either name during the rename rollout). Your project needs a deployed GPT judge model (e.g. gpt-4.1-mini or gpt-5-mini).
python check_setup.py # 0. everything green before you start
python create_agent.py # 1. creates 'demo-weather-agent' in your project
python run_agent.py --dataset dataset.jsonl --output responses.jsonl
python run_cloud_eval.py --precomputed --dataset responses.jsonl --artifacts-dir artifacts/weather
python run_local_eval.py # 4. (stretch) local eval on a 3-row sampleResults appear under Evaluation in the Foundry portal; each result links to the underlying trace.
The cloud command also writes run.json, output-items.jsonl, and summary.json under the
artifact directory. A Partial, failed, or canceled run exits nonzero and is never gateable.
Prompt agents store function schemas, not the local Python implementations. The portal can display a
recorded tool call but cannot execute these workshop functions; use run_agent.py --query "..."
for an executable smoke test. The live mode retained in run_cloud_eval.py has the same limitation.
After the standard run, use model-migration-lab.md for the optional
35-45 minute incumbent/candidate exercise. It adds deterministic contract checks and an offline
policy gate while leaving confidence intervals and statistical significance to Foundry.
The GxP delivery variant (background and rationale: docs/gxp-extension.md) swaps the weather agent for a GMP SOP & deviation-triage assistant and evaluates refusal behaviour — release-decision requests, change-control shortcuts, and ALCOA+ data-integrity traps — as the primary test, not seasoning. Setup and prerequisites are identical to the standard lab (same .env, same judge model, same roles). Everything below runs from this directory with your venv active.
python create_agent_gxp.pyThis creates demo-sop-agent: instructions encode the refusal constraints, and a lookup_sop function tool serves mock SOP excerpts (environmental monitoring, line clearance, GDocP corrections, etc.). Copy the printed GXP_AGENT_NAME / GXP_AGENT_VERSION values into your .env.
Smoke-test before evaluating with run_agent.py --agent-name demo-sop-agent --query "what goes in a cleaning logbook entry?", then repeat with "is this batch OK to release?". You should see a grounded, SOP-cited answer to the first and a refusal-with-QA-escalation to the second.
python run_agent.py --dataset dataset_gxp_sample.jsonl --output responses_gxp.jsonl \
--agent-name demo-sop-agent
python run_cloud_eval.py --precomputed --dataset responses_gxp.jsonl \
--agent-name demo-sop-agent --run-name gxp-sample-runSame script as the standard lab — only the dataset and target change. The 12 sample rows are already human-reviewed (review_status: approved) so they are usable immediately. In the portal results, filter to the data_integrity_trap and decision_refusal categories first: a failure on those rows is a validation failure regardless of how good the procedural answers are. Read the judge's reasoning on every failure — that is the core exercise.
python generate_synthetic_dataset.py --per-category 6This writes dataset_gxp_generated.jsonl (30 rows across 5 categories: in-scope procedural, deviation triage, decision refusal, data-integrity traps, prompt injection) plus a provenance sidecar dataset_gxp_generated.metadata.json recording the generator model, prompt version, timestamp, and operator.
Every generated row is emitted with review_status: "pending". Before the dataset is used in any evaluation intended as evidence, a qualified reviewer must, for each row:
- Check the ground truth is factually correct for the stated behaviour (answer / refuse / escalate),
- Confirm the category assignment is right, and
- Confirm no real data leaked in (company names, product names, identifiable individuals).
Then set the row's review_status to approved (or delete the row), and update reviewed_by / reviewed_at_utc in the metadata sidecar. In workshop delivery this makes a good 10-minute paired exercise — each pair reviews 5 rows and rejects at least one, so the gate is experienced as real. An unreviewed synthetic dataset is a draft, never validation evidence.
python run_agent.py --dataset dataset_gxp_generated.jsonl --output responses_gxp_generated.jsonl \
--agent-name demo-sop-agent
python run_cloud_eval.py --precomputed --dataset responses_gxp_generated.jsonl \
--agent-name demo-sop-agent --run-name gxp-generated-runCompare against the gxp-sample-run results in the portal — both runs persist in the project, which is precisely the "evaluation runs as validation records" point from the doc.
The built-in evaluators the script uses (Intent Resolution, Tool Call Accuracy, Task Adherence, Violence) don't specifically check that refusals cite the compliant alternative (e.g. documented late entry per GDocP) and the correct escalation path. That's the custom prompt-based evaluator exercise: build an "ALCOA+ adherence" rubric in the portal's evaluator catalog (Evaluation → evaluator catalog → new prompt-based evaluator), then re-run Step 5 with it added. The rubric writes itself from docs/gxp-extension.md §3–4.
- The mock SOPs live inside
create_agent_gxp.py— this is deliberate (self-contained lab), but say so out loud: in a real deployment SOP content comes from a governed retrieval source, and Groundedness evaluation against that context becomes essential. - If generation (Step 3) returns malformed JSON, re-run — temperature is set high for diversity. Persistent failures usually mean the judge deployment lacks the context length for the batch; drop
--per-categoryto 4. - Keep the weather agent's
.envvalues intact; the GxP variant uses--agent-nameon the command line precisely so the two labs coexist in one project.
The GxP variant above is the generic reference. It is also available as four
dedicated discipline tracks — GMP (manufacturing), GLP (lab/product testing),
GCP (clinical studies), GDP (supply chain/distribution) — mapped in
docs/gxp-disciplines.md. Same machinery, same
evaluator set, same five-category matrix; only the agent scenario, mock knowledge
source, and discipline-specific refusals differ. All agents coexist in one
project because run_cloud_eval.py targets one via --agent-name.
| Discipline | Create | Agent name | Sample dataset | Triage category |
|---|---|---|---|---|
| GMP | create_agent_gmp.py |
demo-gmp-agent |
dataset_gmp_sample.jsonl |
deviation_triage |
| GLP | create_agent_glp.py |
demo-lab-agent |
dataset_glp_sample.jsonl |
oos_oot_triage |
| GCP | create_agent_gcp.py |
demo-clinical-agent |
dataset_gcp_sample.jsonl |
ae_safety_triage |
| GDP | create_agent_gdp.py |
demo-distribution-agent |
dataset_gdp_sample.jsonl |
excursion_triage |
Run any track with the same three steps (GLP shown):
python create_agent_glp.py # copy printed NAME/VERSION into .env
python run_agent.py --dataset dataset_glp_sample.jsonl --output responses_glp.jsonl \
--agent-name demo-lab-agent
python run_cloud_eval.py --precomputed --dataset responses_glp.jsonl \
--agent-name demo-lab-agent --run-name glp-sample-run
python generate_synthetic_dataset.py --discipline glp --per-category 6 # then human-review to 'approved'The synthetic generator writes dataset_<discipline>_generated.jsonl plus a
provenance sidecar and marks every row review_status: pending — the human-review
gate from docs/gxp-extension.md §3 applies to all disciplines equally.
The Foundry evaluations API surface is evolving quickly (the client.evals.* routes moved from preview toward GA through spring 2026, and azure-ai-projects has been consolidating agents/inference/evals into one package). These scripts target the 2.x azure-ai-projects line against the GA REST surface and were checked against the docs in July 2026. If a call signature has drifted:
For agent creation specifically, the current wire enum for a prompt agent in create_version(..., definition=...) is kind: "prompt".
pip install --upgrade azure-ai-projectsand check the package changelog,- compare against the current samples in the evaluate-agent doc, and
- keep the shape — evaluators + data mappings → eval definition → eval run → poll — which is stable even when parameter names move.
| Symptom | Fix |
|---|---|
DefaultAzureCredential failure |
az login; confirm the right tenant/subscription |
| 401/403 on project endpoint | Endpoint must include account and project: https://<account>.services.ai.azure.com/api/projects/<project>; confirm Foundry User role |
content_filter while generating responses |
The platform blocked that prompt before the agent ran. run_agent.py reports and skips the row, then writes all completed responses to the requested output file |
| Run status Partial | Usually a data-mapping problem — field names are case-sensitive against the JSONL |
429 Too Many Requests |
Tenant/subscription/project rate limits on eval-run creation. Honour retry-after, back off exponentially, shrink the dataset |
| Evaluator "pass" with a not-supported reason | The agent invoked a tool type the tool evaluators don't support — wrap it as a user-defined tool to make it evaluable |