ACT 1: HOOK & ANCHOR — Slides 1–3
_header: "" _footer: ""
_notes: (1) KEY CONCEPTS: Welcome to Module 0.2. M0.1 gave you the mental model — LLM output is probabilistic. This module gives you the first engineering response to that fact: forcing the model's output into a shape your code can rely on. The professional context is concrete: you are building LLM features that integrate with downstream systems (billing, ticketing, CRM, reconciliation). Those systems do not accept a paragraph of prose — they accept typed fields. Structured output is what turns an LLM call from "interesting demo" into "function I can compose." This is harder than it appears because LLMs are trained to be helpful prose writers, not schema-compliant JSON emitters. Left to its own devices, the model wraps JSON in markdown, adds an apology, or invents a field name. Schema enforcement moves error detection from "2 AM in production" to "call time." (2) EMPHASIZE: req-026 (structured output / Pydantic / JSON schema) is a Score-3 requirement at High JD frequency. Score 3 means this module produces direct portfolio evidence — the extract_contact function — not just conceptual grounding. (3) MISCONCEPTION: The anticipated pre-module misconception is the Prose-Prompt-JSON Misconception: the belief that telling the model "respond in JSON" is equivalent to enforcing a schema. We name and correct it in Act 4. (4) REFERENCES: - [Coverage Matrix — req-026 structured output/Pydantic/JSON schema](https://github.com/drdgreed/career-foundry/blob/main/backend/content/agentic-mastery/curriculum.json) Score 3, High frequency - [GitLab Senior AI Engineer JD](https://about.gitlab.com/jobs/) "practical fluency across the LLM ecosystem"
_notes: (1) KEY CONCEPTS: This scenario is the most common failure mode for engineers shipping their first LLM feature. The code is correct in traditional terms — it parses JSON, it handles the happy path. But it assumes the model will always return parseable JSON, and the model makes no such guarantee. At temperature 0 the model is near-deterministic, but "near-deterministic prose that mostly looks like JSON" is not the same as "schema-valid JSON every time." The 10% failure rate is not noise you can ignore; at production scale it is hundreds of paged incidents. The fix is not a better prompt — it is moving the format guarantee out of the prompt and into the API/validation layer. (2) EMPHASIZE: In production, unstructured LLM output is a reliability bug, not a feature gap. Schema enforcement moves error detection to call time, where it is cheap to handle, instead of downstream, where it is expensive to trace. (3) MISCONCEPTION: The Prose-Prompt-JSON Misconception — "I told it to respond in JSON, so it will." Telling is not enforcing. We correct this in Act 4. (4) REFERENCES: - [OpenAI structured outputs guide](https://platform.openai.com/docs/guides/structured-outputs) - [Anthropic tool use documentation](https://docs.anthropic.com/en/docs/build-with-claude/tool-use)
_notes: (1) KEY CONCEPTS: M0.2 is where coverage scores start to climb. M0.1 was a Score-1/2 foundation module; M0.2 produces a Score-3 artifact for req-026 because the homework — extract_contact — is direct, demonstrable evidence of structured-output skill. The GitLab Senior AI Engineer JD asks for "practical fluency across the LLM ecosystem"; this module is exactly that fluency: environment, SDKs, schema enforcement, and the client-vs-framework distinction. (2) EMPHASIZE: The architectural takeaway from Stripe's invoice-extraction example applies here: the LLM call becomes a transformation function, unstructured text -> validated typed object. Downstream systems depend on the typed contract, not on parsing prose. (3) MISCONCEPTION: A learner may read "Score 3" as "hardest module." Score is about portfolio-evidence strength, not difficulty. M0.2 is mechanically straightforward; its value is that the artifact is directly hireable. (4) REFERENCES: - [Coverage Matrix — req-026](https://github.com/drdgreed/career-foundry/blob/main/backend/content/agentic-mastery/curriculum.json) Score 3, High frequency - [Stripe docs — structured extraction use case](https://stripe.com/docs) - [GitLab AI Engineer JD](https://about.gitlab.com/jobs/) ecosystem fluency requirement
ACT 2: PREREQS & BRIDGE — Slides 4–6
_notes: (1) KEY CONCEPTS: M0.2 is not standalone. It assumes the mental model from M0.1 plus the practical ability to make an authenticated, controlled API call. If "set temperature=0" or "read finish_reason" is unfamiliar, the structured-output labs will feel like magic rather than engineering — and magic is exactly what we are trying to eliminate. The four prerequisites are binary gates, not nice-to-haves. (2) EMPHASIZE: The single most important carry-over from M0.1 is statelessness: the messages array is the memory. The retry loop in this module appends the validation error back into messages — that only makes sense if you already understand that the model has no memory between calls. (3) MISCONCEPTION: "I know Python, so I'm ready." The specific requirement is controlled API calls plus secret hygiene via .env — covered in Concept 1. A strong Python developer who has never managed API keys will still hit friction in Lab 1. (4) REFERENCES: - [M0.1 — How LLMs Actually Behave](https://career-forge.org/agentic-ai-mastery/module/m0-1-llm-mental-model) prerequisite module - [python-dotenv documentation](https://github.com/theskumar/python-dotenv) secret loading
_notes: (1) KEY CONCEPTS: Five terms, introduced before they are needed. Pay special attention to two distinctions. First, "tool schema" guarantees JSON syntax and field presence, but not semantic validity — that is why Pydantic exists on top of it. Second, the "client SDK vs agent SDK" distinction frames the entire rest of the course: Milestones 0 and 1 use the client SDK directly so you see every line of the loop; frameworks come later, after you understand what they abstract. (2) EMPHASIZE: instructor is not a separate way of calling the model — it is a thin wrapper that adds the validate-and-retry loop around the same tool-schema call you would write by hand. Understanding the manual loop first makes instructor legible. (3) MISCONCEPTION: Learners sometimes treat Pydantic as "just type hints." It is runtime validation: a field typed int that receives a string raises by default in Pydantic v2 — it does not silently coerce. We name this the Silent-Type-Coercion Misconception in Act 6. (4) REFERENCES: - [Pydantic documentation](https://docs.pydantic.dev) typed models and validation - [instructor library](https://python.useinstructor.com) validation + retry wrapper - [litellm documentation](https://docs.litellm.ai) provider-neutral client SDK
_notes: (1) KEY CONCEPTS: M0.1 plants the seed — LLM output is probabilistic. Every subsequent module is an engineering response to that fact. M0.2 is the first direct response: schema enforcement converts unbounded prose variance into a bounded, typed contract. If the model returns something off-shape, the validation layer rejects it. The variance still exists in the model, but it can no longer corrupt downstream systems silently. ALT TEXT: A linear diagram showing M0.1 Mental model -> M0.2 Structured output -> M1.1 Prompt reliability, with annotations "why variance" under M0.1 and "first tool to contain it" under M0.2. (2) EMPHASIZE: The bridge sentence: M0.1 gave you the mental model of what LLMs are; M0.2 gives you the first reliability tool: forcing the model's output into a shape your code can trust. (3) MISCONCEPTION: None on this orientation slide. But note that schema enforcement does not make the model more accurate — it makes the output more parseable. Accuracy is a separate concern (grounding, evals) addressed later. (4) REFERENCES: - [M1.1 — Prompting for Reliability](https://career-forge.org/agentic-ai-mastery/module/m1-1-prompting-reliability) downstream module - [Anthropic Building Effective Agents](https://www.anthropic.com/research/building-effective-agents) structured output as a reliability primitive
ACT 3: CONCEPT BUILD-UP — Slides 7–14
_notes: (1) KEY CONCEPTS: Professional Python-for-AI projects need four reproducibility guarantees: an isolated environment, secrets out of code, pinned dependencies, and a single model-call seam. The .env / .env.example split is the core secret-hygiene pattern: the example file documents which keys are needed without ever exposing a real value. In production you would swap .env for a secrets manager, but the code still reads from os.getenv(), so the interface is identical. The llm.py seam — every model call routed through one file — is what makes provider swaps a one-line env-var change rather than a twenty-file hunt. (2) EMPHASIZE: Pin your dependencies. The single most common "it worked yesterday" failure in AI projects is an unpinned transitive dependency bumping a major version overnight. (3) MISCONCEPTION: The "clean it up later" misconception around hardcoded keys. It almost never gets cleaned up, and the key almost always reaches git history. Use .env from the first commit. (4) REFERENCES: - [uv — fast Python package manager](https://github.com/astral-sh/uv) - [Twelve-Factor App — Config](https://12factor.net/config) configuration via environment variables - [python-dotenv documentation](https://github.com/theskumar/python-dotenv)
_notes: (1) KEY CONCEPTS: This is the authenticated-call scaffold. load_dotenv() pulls .env into the environment; the explicit key check converts a confusing downstream 401 into a clear, actionable startup error. The model default — claude-sonnet-4-6 — comes from LLM_MODEL with a fallback, so the same code runs in local dev and CI without edits. This is mLO-0.2a: a reproducible env that makes authenticated calls. (2) EMPHASIZE: Fail loudly, early. A missing-key EnvironmentError at line 8 is infinitely cheaper to debug than an AuthenticationError surfacing inside litellm after the program has already done work. (3) MISCONCEPTION: "If the key is wrong the SDK will tell me clearly." It will, but the message is provider-specific and often buried. An explicit pre-check gives you a message you wrote, in the place you expect it. (4) REFERENCES: - [litellm completion reference](https://docs.litellm.ai/docs/completion) - [python-dotenv documentation](https://github.com/theskumar/python-dotenv) - [Anthropic quickstart](https://docs.anthropic.com/en/api/getting-started)
_notes: (1) KEY CONCEPTS: The reliability ladder is the central mental model of this module. Rung 1 — prompt-only JSON — asks the model nicely to return JSON. It works often, but offers no enforcement: the model may wrap output in markdown fences, prepend prose, or emit invalid JSON. Rung 2 — tool/function schema (and provider JSON modes like response_format) — constrains the output at the API level; the model is forced to return arguments matching the schema's shape, and you get a dict. This is reliable for models that support tool calling. Rung 3 — Pydantic plus instructor — adds the semantic layer on top: typed fields, custom validators, and an automatic retry that feeds the validation error back to the model. Rung 3 is the strongest guarantee. The decision diamond is "Does the model support tool calling?" — if no, you are stuck on Rung 1 with a warning; if yes, climb to Rung 2, and to Rung 3 for any field your code reads programmatically. ALT TEXT: A left-to-right flowchart. "Need machine-readable output" leads to a yellow decision diamond "Model supports tool/function calling?" The "no" branch leads to a red node "Rung 1: Prompt-only JSON, no enforcement." The "yes" branch leads to a purple node "Rung 2: Tool schema, structure enforced at API level," then a second yellow diamond "Field read by downstream code?" whose "display only" branch stays at Rung 2 and whose "yes, programmatic" branch leads to a green node "Rung 3: Pydantic + instructor, validate types + auto-retry." (2) EMPHASIZE: Key quiz fact (Q-M0-06): the strongest guarantee is tool schema + Pydantic validation — NOT prompt-only JSON. Prompt-only JSON has zero enforcement. (3) MISCONCEPTION: The Prose-Prompt-JSON Misconception — believing Rung 1 is production-grade. It is not; it is a prototype rung. (4) REFERENCES: - [OpenAI structured outputs guide](https://platform.openai.com/docs/guides/structured-outputs) - [Anthropic tool use](https://docs.anthropic.com/en/docs/build-with-claude/tool-use) - [instructor library](https://python.useinstructor.com) validation + retry
_notes: (1) KEY CONCEPTS: Rung 1 is the rung everyone reaches for first because it requires no new API surface — just a prompt instruction. The code shows the reality: you end up writing markdown-fence stripping and a try/except around json.loads, because the model frequently adds a ```json fence or a sentence of preamble. None of that defensive code makes the output reliable; it just papers over the lack of enforcement. The takeaway: defensive parsing is a smell that you are on the wrong rung. (2) EMPHASIZE: Rung 1 fails open — bad output flows downstream as a parse error or, worse, as plausible-but-wrong data. Higher rungs fail closed at the boundary. (3) MISCONCEPTION: "Temperature 0 makes prompt-JSON reliable." Temperature 0 reduces variance but does not enforce structure; the model can still choose to add a fence or prose. (4) REFERENCES: - [OpenAI structured outputs guide](https://platform.openai.com/docs/guides/structured-outputs) why prompt-only JSON is fragile - [Anthropic tool use](https://docs.anthropic.com/en/docs/build-with-claude/tool-use)
_notes: (1) KEY CONCEPTS: Rung 2 moves the format guarantee from the prompt into the API. By passing a tool schema and forcing tool_choice, the model must return arguments matching the schema — the provider enforces the shape. You read the structured arguments off tool_calls[0].function.arguments and get a dict whose keys you specified. This is reliable for any model that supports tool calling. Its limit is the seam to Rung 3: tool schemas guarantee that customer_email is a present string, but not that it is a valid email. A model can return "not provided" or "n/a" and satisfy the schema. (2) EMPHASIZE: Syntax and presence are not the same as semantic validity. That gap is exactly what Pydantic validators close on Rung 3. (3) MISCONCEPTION: "Tool schema means the data is correct." It means the data has the right shape. Correctness of values still needs validation. (4) REFERENCES: - [Anthropic tool use](https://docs.anthropic.com/en/docs/build-with-claude/tool-use) - [OpenAI function calling](https://platform.openai.com/docs/guides/function-calling) - [litellm tool calling](https://docs.litellm.ai/docs/completion/function_call)
_notes: (1) KEY CONCEPTS: Rung 3 is the production rung. Pydantic adds typed fields and custom validators on top of the tool schema. The field_validator on customer_email rejects values without an "@" — closing exactly the semantic gap Rung 2 leaves open — and normalizes the value to lowercase, stripped. instructor.from_litellm() wraps the client so create() returns a validated Pydantic object directly and retries internally when validation fails. Note the conflict resolution: the lesson uses instructor.from_litellm(litellm.completion), not the brief's instructor.from_openai(); the lesson wins per the override clause, and the default model is claude-sonnet-4-6. (2) EMPHASIZE: The strongest guarantee in the ladder is tool schema PLUS Pydantic validation. Rung 3 is the answer to "any field your code reads programmatically." (3) MISCONCEPTION: The Silent-Type-Coercion Misconception — assuming Pydantic v2 will quietly coerce "3" into 3. By default it raises; you opt into coercion explicitly. We revisit this in Act 6. (4) REFERENCES: - [Pydantic field validators](https://docs.pydantic.dev/latest/concepts/validators/) - [instructor — from_litellm](https://python.useinstructor.com) - [litellm documentation](https://docs.litellm.ai)
_notes: (1) KEY CONCEPTS: This is mLO-0.2c, an Understand-level objective. A client SDK gives you raw API access: you call the API, you write the while loop, you check finish_reason, you accumulate messages, you dispatch tools. An agent SDK manages that loop for you: you register tools and agents, and the framework calls the model and routes results. Neither is "better" — they trade control for convenience. The course deliberately teaches the client SDK first (all of Milestones 0 and 1) so that when you reach frameworks in M2.1, you understand exactly what they are hiding. You will build the raw loop, re-implement it in a framework, and write five sentences on what the framework abstracted — that exercise builds real architectural judgment. (2) EMPHASIZE: Key quiz fact (Q-M0-07): client SDK = you own the loop; agent SDK/framework = the framework owns the loop and calls your functions. The boundary is loop ownership. (3) MISCONCEPTION: "Frameworks are the professional choice; raw SDKs are for beginners." Backwards. Senior engineers reach for the client SDK when the loop is simple and want full transparency; they adopt frameworks when orchestration complexity justifies the lost transparency. (4) REFERENCES: - [LangGraph documentation](https://langchain-ai.github.io/langgraph/) framework that owns the loop - [smolagents documentation](https://huggingface.co/docs/smolagents) minimal agent framework - [litellm documentation](https://docs.litellm.ai) client SDK
_notes: (1) KEY CONCEPTS: This is Lab 4 distilled. Version A is the client-SDK loop: explicit retry, explicit message accumulation, explicit empty-response handling — every control-flow decision visible. Version B is the framework: CodeAgent.run() wraps the same underlying litellm call inside a managed loop that handles the while-loop, the message accumulation, the tool dispatch, and step logging. The framework version is shorter, but the brevity is borrowed against debuggability: when the agent does something unexpected, you must understand the Version A control flow to diagnose it. (2) EMPHASIZE: The framework did not eliminate the loop — it hid it. "What smolagents hid": the stop-reason check, message accumulation across turns, tool dispatch, and step logging. Understanding A is the prerequisite for trusting B. (3) MISCONCEPTION: "The framework version is doing something fundamentally different / smarter." It is calling the same model through the same client underneath; the difference is who writes the loop. (4) REFERENCES: - [smolagents — CodeAgent](https://huggingface.co/docs/smolagents) managed agent loop - [litellm completion](https://docs.litellm.ai/docs/completion) the call both versions share
ACT 4: CHECK 1 (UNDERSTAND) — Slide 12
_notes: (1) KEY CONCEPTS: Pause before code. This Understand-level check (sourced from Q-M0-06/07/08) surfaces the Prose-Prompt-JSON Misconception before you embed it in code. The expected answer has two parts: name the flaw (no enforcement — the model can return prose or malformed JSON) and prescribe the fix (Rung 2 tool schema or Rung 3 Pydantic + instructor, which enforce at the API/validation layer and retry). It is a short-answer / scenario-correction question, not debug-first, because no code has been presented for debugging yet. (2) EMPHASIZE: The distinction between instructing and enforcing is the whole point of the module. A learner who cannot articulate it is not ready for the Apply-level worked example. (3) MISCONCEPTION: Prose-Prompt-JSON — the belief that a prompt instruction equals schema enforcement. The distractor explanations in the platform check tag this directly. (4) REFERENCES: - [Anderson & Krathwohl 2001 — Bloom's Revised Taxonomy](https://quincycollege.edu/wp-content/uploads/Anderson-and-Krathwohl_Revised-Blooms-Taxonomy.pdf) Understand level - [OpenAI structured outputs guide](https://platform.openai.com/docs/guides/structured-outputs)
ACT 5: WORKED EXAMPLE + LIVE CODE — Slides 13–17
_notes: (1) KEY CONCEPTS: This worked example builds extract_contact, the homework artifact, on Rung 3. The flow: raw email text goes into the instructor-wrapped client with response_model=Contact; the model fills the Contact tool schema; Pydantic validates the types and runs any field validators. On success you return a typed Contact object. On a ValidationError, instructor feeds the error back to the model and retries up to max_retries. If retries are exhausted, the function raises ExtractionError — never silently returns bad data. This is the typed-function contract: unstructured text -> validated typed object, or an explicit failure. ALT TEXT: A left-to-right flowchart. "Raw email text" flows to "instructor client (response_model=Contact)" to "LLM fills the Contact tool schema" to a yellow decision diamond "Pydantic validates types + validators." The "valid" branch goes to a green node "Return typed Contact object." The "ValidationError" branch goes to a purple node "Feed error back, retry (max_retries)" which loops back to the LLM node, and on retries exhausted goes to a red node "raise ExtractionError." (2) EMPHASIZE: The retry loop here is a micro-instance of the evaluator-optimizer pattern (M2.3): ask, check, repair. The same principle scales to entire agent outputs later. (3) MISCONCEPTION: The Silent-Type-Coercion Misconception — that the validate step quietly fixes bad types. It does not; it rejects them and triggers a retry, then raises. (4) REFERENCES: - [instructor — from_litellm](https://python.useinstructor.com) validate + retry - [Pydantic validators](https://docs.pydantic.dev/latest/concepts/validators/) - [Stripe docs — typed extraction](https://stripe.com/docs) LLM as a typed function
_notes: (1) KEY CONCEPTS: Step 1 is schema design — and it is the highest-leverage step. The Contact model declares two required fields (name, email) and two optional fields (phone, company) with None defaults. The Optional[str] = None typing is what lets the extractor succeed on emails that legitimately omit a phone number — absence of an optional field is not a failure, but absence of a required field is. Designing this schema correctly is most of the homework: the schema encodes your acceptance criteria. (2) EMPHASIZE: The schema is the spec. Once you have written the Contact model, "did the extraction succeed?" has a precise, machine-checkable answer. (3) MISCONCEPTION: "Make everything optional to avoid errors." That defeats the purpose — required fields are how you force the model to actually find the data. A Contact with no email is not a useful Contact; making email required is correct. (4) REFERENCES: - [Pydantic models](https://docs.pydantic.dev/latest/concepts/models/) required vs optional fields - [Python typing — Optional](https://docs.python.org/3/library/typing.html)
_notes: (1) KEY CONCEPTS: Step 2 is the call. instructor.from_litellm(litellm.completion) wraps the client so create() returns a Contact, not a raw response. response_model=Contact is the key argument — it tells instructor which Pydantic model to enforce and to return. max_retries=2 enables the repair loop: on a ValidationError instructor feeds the error back to the model and asks it to fix the output. temperature=0 keeps extraction deterministic. This whole function fits in about ten lines — small enough to show whole, which is the point: the production-grade path is not much more code than the fragile one, it just puts the guarantee in the right place. (2) EMPHASIZE: The return type is Contact, a typed object — not a dict. Downstream code gets static type-checking and IDE autocomplete, and a mistyped field is caught at the boundary. (3) MISCONCEPTION: "instructor is a different API client." It is a thin wrapper over the same litellm call; under the hood it is still tool-schema + validation + retry, exactly the manual loop from Lab 3. (4) REFERENCES: - [instructor — response_model](https://python.useinstructor.com) typed returns + retries - [litellm documentation](https://docs.litellm.ai) - [Pydantic models](https://docs.pydantic.dev/latest/concepts/models/)
_notes: (1) KEY CONCEPTS: Step 3 closes the contract. After instructor exhausts its retries it raises; we catch that and re-raise as ExtractionError, a named, specific exception. This matters because callers can catch ExtractionError specifically and handle the "no valid contact" case — log it, route to a human, return a default — rather than catching a generic exception that could mask unrelated bugs. The homework acceptance criteria require exactly this: raise ExtractionError (not a generic exception) when the model cannot find required fields after retry. The "from e" preserves the original traceback for debugging. (2) EMPHASIZE: Named exceptions are the professional pattern. ExtractionError tells the caller precisely what failed; a generic Exception tells them nothing actionable. (3) MISCONCEPTION: The Try-Except-All Misconception — wrapping in except Exception: pass and returning None. The None then propagates and crashes somewhere unrelated, far from the real cause. (4) REFERENCES: - [Python exceptions — raise from](https://docs.python.org/3/tutorial/errors.html) exception chaining - [instructor — error handling](https://python.useinstructor.com) - [Stripe docs — failure modes in typed extraction](https://stripe.com/docs)
_notes: (1) KEY CONCEPTS: The side-by-side is the synthesis slide. Reading down the "Rung 3" column: low glue code, full enforcement including semantics, failure surfaces as a retry then a named ExtractionError at call time, and you use it for any field your code reads. Compare the "cost of failure" row — this is the whole argument. Rung 1 pushes failure to 2 AM in production; Rung 2 pushes it to the parse site downstream; Rung 3 catches it at the call boundary where you have the most context and the cheapest fix. The clincher is the bottom insight: the three rungs cost roughly the same to write, so there is no engineering excuse for shipping Rung 1. (2) EMPHASIZE: Reliability is not about more code — it is about putting the guarantee at the boundary. Rung 3 is not meaningfully more code than Rung 1; it just fails in the right place. (3) MISCONCEPTION: "Higher rungs are slower / more expensive at runtime." The dominant cost is the model call, which all three rungs share; the validation overhead is negligible. (4) REFERENCES: - [instructor — reliability](https://python.useinstructor.com) - [Anthropic Building Effective Agents](https://www.anthropic.com/research/building-effective-agents) failure-mode placement - [OpenAI structured outputs guide](https://platform.openai.com/docs/guides/structured-outputs)
ACT 6: CHECK 2 (APPLY) — Slide 18
_notes: (1) KEY CONCEPTS: This is the Apply-level check — the learner writes a schema and the call, consistent with the module's Apply Bloom ceiling. The expected answer: an OrderLine(BaseModel) with quantity: int and unit_price: float, wrapped with instructor.from_litellm. The conceptual probe is the type-mismatch question: if the model returns quantity as "3" (string), Pydantic v2 raises a ValidationError by default rather than coercing — instructor catches it and retries, and on exhaustion the ValidationError propagates. This is Apply, not debug-first, consistent with the module Bloom ceiling and the design decision to keep this module at schema-design level. (2) EMPHASIZE: The fix-or-fail behavior is the point: a type mismatch is not silently coerced; it is rejected, retried, and finally surfaced. That is what makes the extracted object trustworthy. (3) MISCONCEPTION: Silent-Type-Coercion — the belief that Pydantic v2 turns "3" into 3 without telling you. It raises by default; coercion is opt-in. (4) REFERENCES: - [Pydantic v2 — strict mode and coercion](https://docs.pydantic.dev/latest/concepts/conversion_table/) - [instructor — retries on ValidationError](https://python.useinstructor.com) - [Anderson & Krathwohl 2001 — Bloom's Apply level](https://quincycollege.edu/wp-content/uploads/Anderson-and-Krathwohl_Revised-Blooms-Taxonomy.pdf)
ACT 7: PRACTICE BRIEF — Slides 19–20
_notes: (1) KEY CONCEPTS: The four labs map to the learning objectives: Lab 1 -> mLO-0.2a (reproducible env, authenticated call); Labs 2 and 3 -> mLO-0.2b (schema-valid structured output and validation); Lab 4 -> mLO-0.2c (client vs agent SDK comparison). The homework, extract_contact, is the Score-3 portfolio artifact for req-026. Its acceptance criteria are precise: required name and email, optional phone and company, email lowercase-stripped, and a named ExtractionError after one retry — not a generic exception. Both submission paths are supported: the platform AI-grader is primary; the GitHub fork production-track runs the same grader in CI on the PR. (2) EMPHASIZE: "Done" looks like: all four labs pass; extract_contact returns a typed Contact; ExtractionError raised on unrecoverable input; and no API key anywhere in git history. (3) MISCONCEPTION: "I'll just return None when extraction fails." The spec requires a named ExtractionError so callers can branch on it — None silently propagates and breaks downstream. (4) REFERENCES: - [Homework HW-0.2 specification](https://career-forge.org/agentic-ai-mastery/module/m0-2-python-sdks-structured-output) acceptance criteria - [Course repo — submissions](https://github.com/drdgreed/career-foundry) production-track fork + CI grader - [instructor — max_retries](https://python.useinstructor.com)
_notes: (1) KEY CONCEPTS: Two to three hours is realistic for a learner who has the prerequisites. The binary setup gate is lab1_env.py printing READY — if it does, the environment is good and the rest is schema work. The in-page AI tutor is live and handles real-time questions; the cohort chat is in development and tracked on ROADMAP.md. GitHub Issues, auto-labeled module:m0-2, is the interim peer-help venue. The catch-up path lets a stuck learner get the artifact working with instructor directly before studying the manual retry loop. (2) EMPHASIZE: If you spend more than 30 minutes fighting setup, the issue is almost always a missing package or a misplaced .env — run lab1 to localize it, do not debug in the homework. (3) MISCONCEPTION: None — logistics slide. Note that model calls take a few seconds each; the failure-path test deliberately triggers retries, so budget a little extra wall-clock time. (4) REFERENCES: - [instructor quickstart](https://python.useinstructor.com) - [uv documentation](https://docs.astral.sh/uv/) - [ROADMAP.md](https://github.com/drdgreed/career-foundry/blob/main/ROADMAP.md) cohort chat and support widget status
ACT 8: REFLECTION & BRIDGE — Slides 21–25
_notes: (1) KEY CONCEPTS: These three reflection prompts are not a summary — they force prediction and transfer before the lab. Prompt 1 makes the reliability argument personal: the learner observes their own Rung 1 failure rate and reasons about what it means for prose-based guarantees. Prompt 2 is the professional transfer question: which real systems consume their LLM output, and how schema enforcement reshapes that contract. Prompt 3 captures the learner's remaining confusion about the instructor retry loop before the lab distracts them, so they can check afterward whether doing resolved it. (2) EMPHASIZE: Write the answers down. Thinking them is not the same as writing them; writing surfaces the gap. This is documented in adult-learning research on reflective practice. (3) MISCONCEPTION: "Reflection is filler before the real work." Reframe: a misconception caught in 5 minutes of reflection is far cheaper than one caught in 2 hours of debugging. (4) REFERENCES: - [Kolb 1984 — Experiential Learning](https://citt.it.ufl.edu/resources/course-development/the-learning-process/types-of-learners/kolbs-four-stages-of-learning/) reflective observation - [Knowles 1980 — The Adult Learner](https://elearningindustry.com/the-adult-learning-theory-andragogy-of-malcolm-knowles) self-directed reflection
_notes: (1) KEY CONCEPTS: Five criteria, each scored 0–3, for a 15-point total; the advance threshold is 8/15. Score 3 on each criterion is the "production-ready" bar: pinned deps plus the llm.py seam for environment; choosing the correct rung per task; validate-plus-retry-plus-named-error for Pydantic; an actionable message and retry policy for failure handling; and justifying the client-vs-framework choice per scenario. Most learners should land at Proficient (2) on the first pass and revisit the criteria where they scored 0 or 1. (2) EMPHASIZE: The extract_contact artifact is reused in M1.1's prompt-evaluation harness. Writing it cleanly now pays forward — it becomes the unit under test when you start measuring prompt quality. (3) MISCONCEPTION: "8/15 is a low bar." It is the advance gate, not the goal. Aim for 3s on the criteria tied to req-026 (the ladder and Pydantic) because those are the directly hireable signals. (4) REFERENCES: - [M0.2 self-assessment rubric](https://career-forge.org/agentic-ai-mastery/module/m0-2-python-sdks-structured-output) self-scoring - [Anderson & Krathwohl 2001 — Bloom's Apply indicators](https://quincycollege.edu/wp-content/uploads/Anderson-and-Krathwohl_Revised-Blooms-Taxonomy.pdf)
_notes: (1) KEY CONCEPTS: The closing slide does three things. It names the three artifacts produced and maps each to a learning objective. It bridges to M1.1 as a Kolb concrete-experience seed: you have the structured-output tool, now you will measure and improve the inputs that drive it, and your extract_contact function becomes the unit under test in the eval harness. And it states the portfolio signal honestly: unlike M0.1, M0.2 produces a Score-3 artifact for req-026 — extract_contact is directly hireable evidence. (2) EMPHASIZE: The artifact is the point. A hiring manager can read a typed, validated, retrying extraction function in 60 seconds and infer that you understand production LLM reliability. That is the signal req-026 is testing for. (3) MISCONCEPTION: "M0.2 is just setup." No — it is the first module that produces direct portfolio evidence, and the extract_contact function recurs as infrastructure in M1.1 and beyond. (4) REFERENCES: - [M1.1 — Prompting for Reliability](https://career-forge.org/agentic-ai-mastery/module/m1-1-prompting-reliability) next module - [Coverage Matrix — req-026 Score 3](https://github.com/drdgreed/career-foundry/blob/main/backend/content/agentic-mastery/curriculum.json) - [Kolb 1984 — Experiential Learning](https://citt.it.ufl.edu/resources/course-development/the-learning-process/types-of-learners/kolbs-four-stages-of-learning/) CE seed for next module