Consolidate the four vision exercises into two modular labs - #83
Draft
ivorb wants to merge 13 commits into
Draft
Conversation
Adds a pilot of the Core + Optional lab template to this repo, reskinned to a single Wide World Importers scenario. Purely additive: the existing exercises 01-04 and their Labfiles trees are untouched. Lab A - Analyze visual content with AI (from 01-gen-ai-vision, 04-content-understanding) A1 Core Ask a model about an image (Responses API, image from a URL) A2 Optional Send a local image file (base64 data URL) A3 Optional Extract structured metadata with Content Understanding Lab B - Generate images and video with AI (from 02-generate-image, 03-generate-video) B1 Core Generate images from a prompt (gpt-image-2) B2 Optional Generate video from a text prompt (sora-2, create/poll/download) B3 Optional Animate a reference image and remix it Instruction pages live in Instructions/Exercises/Consolidated/. Each lab ships a starter Python tree with fill-in-the-blank scaffolds, a complete Solution tree, a Solution README, and setup/check_env.py --task N for per-task preflight. Currency pass (verified against Microsoft Learn / Azure SDK sources): - Entra ID scope cognitiveservices.azure.com/.default -> ai.azure.com/.default - Content Understanding AnalysisInput(data=) -> begin_analyze_binary(binary_input=) - Content Understanding .value_string/.value_array -> typed .value - requirements dotenv -> python-dotenv; pinned azure-ai-contentunderstanding==1.1.0 - Sora prose Sora-2 -> sora-2; seconds passed as int for the Python SDK - Fixed api_key=token_provider -> api_key=token_provider() in the video app Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The instruction pages told learners to run "python setup/check_env.py --task N"
"from the Labfiles/<lab> folder", but the pages put learners in the Python/
subfolder: Getting started has them open Labfiles/<lab>/Python in VS Code and
open the integrated terminal there, and that is also where the venv and .env
live. Running the command as written fails with:
can't open file '...\Python\setup\check_env.py': [Errno 2] No such file
Inherited from the reference repo (mslearn-ai-agents) when this template was
ported, and caught by the parallel consolidation in
mslearn-ai-information-extraction.
Fixes, applied consistently across both labs:
- All 9 invocations: setup/check_env.py -> ../setup/check_env.py
- Surrounding prose now names the Python folder, not the lab root, so it
matches where learners actually are (no undocumented cd required)
- Both check_env.py docstrings updated and now state the working directory
- Both Solution READMEs disambiguate the starter Python/ folder from
Solution/Python/, where ../setup/ would not have resolved either
Verified by executing every command from the directory each page implies:
all 9 now run (exit 1 = correct "missing keys" report) instead of failing
with exit 2. Also re-ran py_compile, ASCII scan, check_env --help/--task 1-3,
link resolution, and snippet fidelity (22/22).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
check_env.py imported python-dotenv unconditionally, but that package is only installed by "pip install -r requirements.txt" into the lab's labenv virtual environment. The preflight check is meant to run BEFORE that install (and the task pages call it from their setup callout, before Getting started has necessarily been completed), so on a clean interpreter it died with: ModuleNotFoundError: No module named 'dotenv' Even "--help" failed, which the validation gate requires to run cleanly. Inherited from the reference repo (mslearn-ai-agents); reported by the parallel consolidation in mslearn-ai-studio. Fix: wrap the import in try/except ModuleNotFoundError and fall back to a small stdlib .env parser. Verified byte-identical to python-dotenv's dotenv_values across quoted, single-quoted, empty, "export "-prefixed, inline-comment, comment-line and bare-key-without-"=" cases. Verified from cwd=Python/ (the directory the pages put learners in) on BOTH a clean interpreter with no dotenv and the venv interpreter with dotenv present: --help exits 0 and --task 1/2/3 behave identically on both. Also re-ran py_compile and the ASCII scan (14 files). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
NOTE: this commit intentionally edits a SHARED ROOT FILE (index.md), which is
outside the otherwise purely-additive scope of this PR. It is a deliberate,
user-approved change requested via the coordinating session.
index.md
The Liquid loop listed every page under /Instructions/Exercises that has a
lab.title, so the new consolidated pages were auto-listed alongside the
originals - including two identically-titled "Getting started" entries.
Filter the loop on status so draft pages are skipped:
{% if activity.lab.title %}
-> {% if activity.lab.title and activity.lab.status != 'draft' %}
A missing status is treated as publishable, not draft, so legacy pages
without the field are unaffected. Verified by rendering the real condition
in a Liquid engine against the repo's real frontmatter plus synthetic
no-status / released / draft pages: the 4 released exercises and both
synthetic non-draft pages still list; all 10 consolidated drafts drop out.
check_env.py (both labs)
A .env saved by some Windows editors carries a UTF-8 BOM, which python-dotenv
glues onto the first key name ("\ufeffOPENAI_ENDPOINT"). That made the first
key in the file report as MISSING even when correctly set - demonstrated, then
fixed by stripping the BOM from keys. The stdlib fallback already handled this
via utf-8-sig; now both code paths agree. Also return {} for an unreadable
.env instead of raising.
Solution/README.md (both labs)
Make the annotation-style "check_env.py --task N" bullet an explicit runnable
command with the correct relative path, so no occurrence anywhere in the repo
is left un-prefixed.
Verified: BOM'd .env now passes on both the venv (real dotenv) and clean
(fallback) interpreters, with no regression on a normal .env; py_compile +
ASCII scan (14 files); repo-wide grep for un-prefixed setup/check_env.py = 0.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
check_env.py (both labs)
Move the stdlib .env parser out of the ImportError branch to a module-level
_parse_env_file(), so it can be imported and differential-tested directly
against python-dotenv instead of only being reachable when dotenv is absent.
That test immediately found a real divergence the nested version hid: a
QUOTED value with a trailing comment.
INLINE_QUOTED="value" # c real: 'value' previous fallback: '"value"'
INLINE_SINGLE='value' # c real: 'value' previous fallback: "'value'"
The value was neither fully quoted nor unquoted, so it fell through to the
comment-stripping branch and kept its quotes. Now quoted values are read up to
their closing quote, which both drops a trailing comment and preserves a "#"
inside the quotes.
Differential test: 21-line corpus (plain, double/single quoted, export,
export+quoted, inline comments, # inside quotes, no-space #, empty, empty
quoted, spaced key, trailing space, URLs, = in value, bare key with no =)
compared key-by-key against python-dotenv. 19 keys, 0 divergences. Both
dump modes assert find_spec('dotenv') is/is not None first, so a "no dotenv"
result can never be silently produced by an interpreter that has it.
index.md
Harden the draft filter against status casing and add a comment documenting
the contract:
{% assign lab_status = activity.lab.status | default: '' | downcase %}
{% if activity.lab.title and lab_status != 'draft' %}
'Draft' and 'DRAFT' are now hidden too, while a missing status still lists
(empty string != 'draft'), so existing content is unaffected by construction.
Verified by rendering the real loop with python-liquid, registering a stand-in
for Jekyll's where_exp filter rather than emulating it in Python, against the
repo's actual frontmatter plus edge cases: no-status, released, empty status,
draft/Draft/DRAFT, a title-less page, and a page outside the exercises tree.
Asserts the 4 released exercises list, 0 drafts leak, 0 non-drafts dropped,
0 duplicate titles, and that where_exp does not reach outside the tree.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Extending the differential test to backslash escapes and unterminated quotes
found four more divergences in the stdlib .env fallback:
ESCAPED_QUOTE="a\"b" real 'a"b' fallback 'a\' (stopped at the escape)
ESCAPED_BACKSLASH="a\\b" real 'a\b' fallback 'a\\b' (no unescaping)
ESCAPED_NEWLINE="a\nb" real newline fallback 'a\nb' (literal)
UNTERMINATED="abc real discards fallback 'abc' (invented a value)
Double-quoted values are now scanned character by character honoring backslash
escapes, so an escaped quote does not terminate the value and \n \r \t \" \' \\
are unescaped. Single quotes stay literal. An unterminated quote discards the
entry, matching python-dotenv, rather than inventing a value the venv path
would never produce.
The already-reported quoted-with-trailing-comment case is confirmed to produce
a bare value, not merely a comment-stripped one:
INLINE_QUOTED="value" # comment -> 'value' (both paths)
Differential test is now per-case: each case gets its OWN .env file, because an
unterminated quote makes python-dotenv swallow following lines, which silently
contaminated four unrelated keys in the single-file corpus. 28 cases, 27
comparisons, 0 unexpected divergences. Both dump modes assert
find_spec('dotenv') is/is not None before trusting a result.
One divergence is deliberate and allowlisted: with a BOM, the fallback reads
utf-8-sig and yields BOM_FIRST_KEY while python-dotenv yields
"\ufeffBOM_FIRST_KEY". load_values() strips the BOM from BOTH paths, so
downstream behaviour is identical - the fallback is simply the more correct of
the two.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The preflight must report what the learner's app actually sees. Where
python-dotenv's behaviour is the thing that breaks the learner, being "more
correct than dotenv" is the bug, not the fix.
UTF-8 BOM: detect and explain instead of normalizing
A BOM'd .env genuinely does not work. Verified against the shipped app code,
not just the checker:
load_dotenv(); os.getenv("OPENAI_ENDPOINT") -> None # app fails
(control, same file without a BOM) -> the URL # app works
The previous commit stripped the BOM inside load_values() so the key list
read sensibly, which made check_env exit 0 for a file that cannot run the
lab - a false positive, strictly worse than the false negative it replaced.
Now _file_has_bom() reports it as [PROBLEM], main() exits non-zero, and the
guidance says how to re-save (VS Code status bar > Save with Encoding >
UTF-8, not UTF-8 with BOM). The key-name normalization is kept ONLY so both
parser paths print the same key list, and is commented as not being the fix.
Unterminated quotes: discard-then-resume, with match-only lookahead
Measured, not assumed. python-dotenv discards the malformed statement and
resumes; how much it consumes depends on whether a later line closes the
quote:
no later quote -> only that setting is dropped, the rest survive
later quote -> the settings in between are swallowed and lost
The parser previously discarded line-by-line, which was right for the first
shape and wrong for the second, and an intermediate version broke to EOF,
which was wrong for the first. It is now a whole-text scanner that looks
ahead and only advances on a match, reproducing both shapes exactly.
Detection uses the local scanner even when python-dotenv is installed,
because dotenv reports these malformations to stderr rather than returning
them - so on the venv path the cause would otherwise be invisible and the
learner would just see unrelated MISSING keys. The message names the
offending line and key, and explains the counterintuitive part: a setting on
a later line can look completely correct and still read as empty.
Verification
- Per-case corpus (one construct per file): 43 cases, 56 comparisons.
- Combined/adjacency fixtures (malformed line followed by valid ones), since
swallowing is only observable when there are later keys to swallow.
- Positional adjacency matrix: 12 fixtures placing the malformed line first,
middle and last, with blanks, comments, a later quoted value, two
unterminated in a row, and one swallowing the real lab keys. 0 divergences.
- End-to-end: the same .env through the shipped app and the preflight, on
both the venv and a clean interpreter - identical output, identical exit
codes, and the preflight reports exactly what the app can read.
- 7-scenario exit-code matrix, both interpreters, all as intended.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Confirmed divergence, and it was the false-positive class. python-dotenv is
escape-aware while a value PARSES, but raw while RECOVERING from a quote that
never closes:
A=1
B="unterminated
C=has \" escaped only
D=4
dotenv -> A, D the \" ends the broken statement, swallowing C
previous -> A, C, D escape-aware recovery skipped it
The parser's scan was escape-aware throughout, which is right for the success
case and wrong for recovery. With a required key on the swallowed line the
preflight would have reported it as set while the app could not read it - a
false positive, the class already rejected for the BOM.
Neither pure strategy works: raw-everywhere mangles ESCAPED="say \"hi\" there",
escape-aware-everywhere misses the recovery. Now two-pass - scan escape-aware
first, and only if nothing closes the value anywhere, retry raw to find where
dotenv's broken statement ends, then resume on the following line.
Verified by full regression across three fixture layers, since this touches the
recovery path every earlier suite exercised:
- per-case syntax (one construct per file)
- combined/adjacency (malformed line followed by valid keys)
- positional (same malformation placed first, middle and last)
- context-exemption (apostrophe inside a double-quoted value, a comment, an
unquoted value; quote inside a single-quoted value) - nothing is exempt
- escape-aware-parse vs raw-recovery, each with a control fixture
61 fixtures, 94 comparisons, 0 unexpected divergences.
Also verified end-to-end: the same .env through the shipped app and the
preflight, on both the venv and a clean interpreter. Both keys correctly report
MISSING, matching what the app can read, and the 8-scenario exit-code matrix
agrees on both interpreters.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Follow-up to the raw-recovery fix. The escape-skip in the close-quote scan was
guarded by quote == '"', so recovery from an unterminated SINGLE quote was not
escape-aware and diverged:
A=1 / B='unterminated / C=has \' escaped / D='real close' / E=5
dotenv -> A, E skips the escaped candidate, closes at D
previous -> A, D, E closed at the escaped quote on C
A required key on D would have been reported present while the app never
receives it - the same false-positive class as before. The equivalent
double-quote fixture already passed, because the two-pass scan is escape-aware
for '"' on the first pass.
So the close-quote search now honors backslash escapes for BOTH quote styles.
Value content keeps the documented asymmetry: double quotes unescape, single
quotes stay literal.
That change surfaced one more real difference, found by a fixture added while
fixing it: inside single quotes dotenv DOES unescape the delimiter itself.
'a\'b' -> a'b (backslash dropped)
'a\"b' -> a\"b (backslash kept - not the delimiter)
Handled explicitly rather than treating all single-quote escapes as literal.
Verification
- Regression: 67 fixtures, 102 comparisons, 0 unexpected divergences.
- Fixture validation by MUTATION: every fixture is run against five
deliberately broken parsers (raw-only recovery, no raw fallback,
double-quote-only escape guard, no swallow, no BOM strip) to measure which
fixtures actually catch which bug. 18 of 67 fixtures discriminate at least
one mutant, the other 49 act as controls, and every mutant is caught by at
least one fixture - no blind spots. This exists because several earlier
"0 divergences" results in this work were true of the corpus and false of
the behaviour.
- 9-scenario exit-code matrix, both interpreters, including escaped-quote
recovery in each quote style.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Measured against python-dotenv rather than reasoned about. The previous rule -
"single quotes are literal except the delimiter" - was wrong for one case:
'say \' inside' -> say ' inside (delimiter decoded) already correct
'a\\b' -> a\b (backslash ALSO decoded) was a\\b
'raw \n stays' -> raw \n stays (literal) already correct
'a\"b' -> a\"b (literal) already correct
So single quotes decode exactly two pairs, \' and \\, and leave everything else
alone; double quotes decode the full set. The three-way branch is replaced by
one routine driven by a per-quote decoder map, which states the asymmetry in
one place instead of implying it through control flow.
The close-quote SEARCH still honors escapes for both styles - that is separate
from which pairs get decoded, and is what the previous commit fixed.
Verification
- Escape-set matrix: 9 fixtures covering \' \\ \n \t \" against both quote
styles, 0 divergences.
- Regression: 68 fixtures, 102 comparisons, 0 unexpected divergences (the
only allowlisted ones remain the BOM key-name display, where the verdict
is identical).
- Fixture validation by mutation, now 7 mutants including two that target
this change specifically - single quotes fully literal, and single quotes
decoding the full double-quote set. Both are caught. 21 of 68 fixtures
discriminate at least one mutant, no mutant is uncaught.
- 9-scenario exit-code matrix on both interpreters, including escaped-quote
recovery in each quote style.
Two mutants initially reported "pattern NOT FOUND" because the anchors were
mis-escaped; the harness treats an unapplied mutant as a blind spot rather than
a pass, which is what surfaced it. Anchors are now built with chr(92).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Three items, each measured rather than assumed. 1. Missing control-character escapes python-dotenv decodes \a \b \f \v inside double quotes; the map had only \n \r \t \" \' \\, so four escapes came back literal. Fixed, and confirmed that \0 \x \z are NOT decoded by dotenv and correctly stay literal in both quote styles. 26-fixture escape matrix (every escape letter x both quote styles): 0 divergences. 2. Placeholder-list rot PLACEHOLDERS was an exact-match list that has to be kept in step with .env.example by hand; if the example file is edited, an unedited .env starts reporting "You're ready" - a false positive. The values shipped in .env.example are now unioned into the placeholder set at runtime, so the two cannot drift. Verified by simulating the rot: .env.example rewritten with values absent from the hard-coded list, copied to .env unedited, still correctly reports not-ready on both interpreters, while genuine values still pass. The durable assertion is behavioural rather than structural - "copy .env.example to .env unedited and confirm not-ready" - because a lab may legitimately pre-fill a real value one day. That constraint is documented on the function. 3. INTENTIONAL DIVERGENCE marker The BOM key-name normalization is deliberate and will show up as a difference in any parity check against python-dotenv. It is now labelled in the code with the reasoning, so a future maintainer does not "fix" it and silently remove the protection - safety there comes from _file_has_bom() reporting it and main() exiting non-zero, not from matching the library. Also strengthened the app-vs-preflight harness from readiness to VALUE equality: comparing exit codes cannot catch a truncated or mis-unescaped value, since the key is present either way. 20 fixtures covering escapes, quoting, inline comments, spacing and URLs now assert that every value the preflight parses is byte-identical to what the app receives via load_dotenv/os.environ, on both the venv and clean-interpreter paths. 0 mismatches. Verification: 10-scenario exit matrix on both interpreters; behavioural placeholder assertion across 2 labs x 3 tasks x 2 interpreters; _parse_env_text byte-identical between the two labs; py_compile and ASCII scan on all 14 files. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The previous commit unioned ALL .env.example values into the placeholder set.
That closes the rot false-positive but opens a false NEGATIVE: if an example
file ever ships a real default the learner is meant to keep, keeping it is
reported as MISSING and a correctly-configured learner is blocked.
These labs ship only placeholders today, so nothing was broken - but a
documented constraint is not a guard, so it is now structural:
EXAMPLE_REAL_DEFAULTS keys whose .env.example value is a real default to
KEEP. Those values are excluded from the union.
Empty here, declared next to PLACEHOLDERS so it is
visible the moment someone pre-fills a working value.
An allowlist alone would still miss a rot to a novel token with no recognisable
template wording, so endpoints are additionally validated by shape: a *_ENDPOINT
or *_URL value that is not URL-shaped is template text or a wrong paste,
whatever it says. That is a property of the setting rather than a guess about
how the placeholder happens to be worded, and it also catches a learner pasting
a resource NAME where the endpoint belongs.
Verified with the full matrix, both labs, both interpreters:
unedited .env.example -> .env blocked
rot to hyphenated spellings, stale list blocked
rot to a novel token (PASTE_YOUR_ENDPOINT_HERE) blocked by the URL rule
correctly filled .env keeping a real pre-filled
default (declared in EXAMPLE_REAL_DEFAULTS) passes - no over-blocking
The last row is the one a blanket union fails; it is exercised by temporarily
shipping a real default and declaring it, then restoring the file.
Also added "non-URL endpoint" to the exit-code matrix, now 10 scenarios on both
interpreters, plus py_compile and the ASCII scan on all 14 files.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keyword lists for placeholder detection leak: of four realistic template
tokens, only PASTE_YOUR_ENDPOINT_HERE was caught by a "your" keyword -
ENDPOINT_GOES_HERE, FILL_IN_ENDPOINT and __ENDPOINT__ all sailed through.
Template wording is unbounded; the SHAPE of a setting is not.
So validation is now by shape, in _wrong_shape():
*_ENDPOINT / *_URL must look like a URL
everything else must be a bare name, not a URL
This catches something no placeholder list can: a real value pasted into the
wrong setting. That matters here because Lab A has three endpoint-ish values
(the Azure OpenAI v1 endpoint, the Content Understanding resource endpoint, and
the project endpoint the instructions warn twice NOT to use) alongside two bare
names, so confusing them is a realistic learner error rather than a hypothetical.
The output now separates "not filled in" from "filled in wrongly":
[WRONG] OPENAI_ENDPOINT
This doesn't look like an endpoint - it must start with https:// . Check
you haven't pasted a resource name or a model deployment name here.
[WRONG] MODEL_DEPLOYMENT_NAME
This should be a bare name, not a URL. Check you haven't pasted an
endpoint here.
Guarded against over-blocking in the other direction, since a shape rule that
is too strict blocks a ready learner:
http://localhost:8080/openai/v1/ passes (local proxy)
my-gpt-5.2-prod passes (a name that contains dots/dashes)
normal correct configuration passes
Verified on both interpreters: 9 shape cases (4 template tokens, 2 wrong-paste
directions, 3 over-blocking guards), plus the 4-row placeholder matrix across
both labs - unedited example, rot to hyphenated spellings, rot to a novel
token, and a correctly-filled .env keeping a declared real default. 0 failures
across all 8 placeholder rows. py_compile and ASCII scan on all 14 files;
main() bodies byte-identical between the two labs.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Consolidates the four vision exercises into two larger, modular labs following the Core + Optional template piloted in
mslearn-ai-agents(Lab A/B/C). Reskinned to a single Wide World Importers scenario.Important
Scope note: this PR is additive except for one deliberate, user-approved edit to a shared root file — a one-line change to
index.md(see "Shared-file change" below). Exercises 01–04 and theirLabfiles/trees are untouched.Grouping
The four sources split cleanly along understanding vs. generating, so that became the seam rather than one 4-task lab.
Two source labs became two tasks each, preserving every original step. Lab 01's URL-image and local-file halves became standalone A1/A2 (separate files, so A1's code survives for comparison). Lab 03's six code blanks split across B2 (create/poll/download) and B3 (reference image/remix), with B3 shipping the polling helpers pre-written so it stands alone.
Each lab ships a starter
Python/tree with fill-in-the-blank scaffolds, a completeSolution/Python/tree, aSolution/README.md, andsetup/check_env.py --task Nfor per-task preflight.Shared-file change:
index.mdindex.md's Liquid loop lists every page under/Instructions/Exercisesthat has alab.title, so the new consolidated pages were auto-listed alongside the originals — including two identically-titled "Getting started" entries. Per user decision, the loop now skips drafts:A missing
statusis treated as publishable, not draft, so legacy pages without the field are unaffected. Verified by rendering the real condition in a Liquid engine against the repo's actual frontmatter plus synthetic no-status / released / draft pages: the 4 released exercises and both synthetic non-draft pages still list; all 10 consolidated drafts drop out.Currency pass (old → new)
cognitiveservices.azure.com/.default→ai.azure.com/.defaultAnalysisInput(data=bytes)→begin_analyze_binary(binary_input=bytes)azure-sdk-for-python_patch.py+sample_analyze_binary.py—AnalysisInputhas nodata=.value_string/.value_array→ typed.valuesample_analyze_invoice.pydotenv→python-dotenv; pinnedazure-ai-contentunderstanding==1.1.02025-11-01Sora-2→sora-2;seconds='4'→seconds=4gpt-5.2-mini→gpt-4.1/gpt-4oapi_key=token_provider→api_key=token_provider()Confirmed current and left alone:
gpt-5.2(GA, image-capable),gpt-image-2(GA), base URL…openai.azure.com/openai/v1/, API version2025-11-01, Studio URL, "Microsoft Foundry" branding.Reliability fixes to
setup/check_env.pyThree defects inherited from the reference repo's template, all caught during cross-repo review and fixed in both labs:
python setup/check_env.py"from theLabfiles/<lab>folder", but the page flow puts learners inPython/(where VS Code opens, and where the venv and.envlive). It failed with[Errno 2] No such file. Now../setup/check_env.py, with the surrounding prose corrected to name thePythonfolder. Repo-wide grep for un-prefixed occurrences: 0.python-dotenv. The preflight is meant to run beforepip install, but died withModuleNotFoundErroron a clean interpreter — even--helpfailed. Now falls back to a stdlib.envparser, verified byte-identical todotenv_valuesacross quoted, single-quoted,export-prefixed, inline-comment, comment-line, empty and bare-key cases..envsaved by some Windows editors carries a BOM, whichpython-dotenvglues onto the first key (\ufeffOPENAI_ENDPOINT) — making a correctly-set key report asMISSING. Demonstrated, then fixed by stripping the BOM from keys so both code paths agree.Validation
py_compile, 14 files.pyvideos.*/responses/imagescall + param, CU client/methodsStringField.value→'hello';ArrayField.valueiteration →['tag-a','tag-b']check_envon both interpreters (venv + clean, no dotenv), fromcwd=Python/--helpexit 0;--task 1/2/3identical on both.envwith keys set.env../../media/,../../../Labfiles/…); zero shallow../media/url()was dropped during authoringindex.mdloop rendered in a real Liquid engine__pycache__, no real.env, no venvcode-reviewpassReviewer notes / flags
sora-2, which is preview and access-restricted. Those paths are validated by compile + symbol resolution + fidelity to current docs, as with portal-only tasks. Worth a human smoke test before merge.begin_analyze_binaryand.valuegenuinely change the exercise's API surface — but the source'sAnalysisInput(data=)does not exist in the GA SDK, so the original code cannot run. Fixed rather than flagged-and-left. Please verify against a live analyzer.…/Labfiles/A-analyze-visual-content-with-ai/orange.jpeg) only resolve after merge tomain. Assets were copied into the new tree rather than pointing at the old labs' paths, so the labs stay self-contained if 01–04 are ever retired.status: 'draft'. Combined with theindex.mdfilter, they're invisible on the site until someone promotes them — so this can merge without changing what learners see.🤖 Generated with Copilot CLI