Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

M0.2 — Python SDKs & Structured Output

From "it works 90% of the time" to a typed function your code can trust — schema enforcement is the line between a prototype and a production component.

🟪 Hiring signal: Structured output / Pydantic / JSON schema (req-026, Score 3) shows up at High frequency across harvested 2026 JDs. This module installs the first reliability tool of the curriculum.

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

The 2 AM Parser Crash

You shipped an AI feature. It calls the model, expects a clean object, and hands it downstream:

{"name": "Alice", "email": "alice@co.com"}

Instead, one call in ten returns a paragraph of prose — or JSON wrapped in markdown fences. Your downstream parser throws JSONDecodeError, and the pager goes off at 2 AM.

  • You shipped a feature. It works 90% of the time.
  • The 10% failures are not model errors — they are format errors your code cannot parse.
  • Structured output is the difference between a prototype and a production component.

🟪 Career anchor: req-026 (structured output / Pydantic / JSON schema) appears at High frequency in harvested 2026 JDs — schema fluency is a baseline expectation, not a specialty.

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Why This Module → Career Anchor

Requirement Score JD frequency M0.2 coverage
Structured output / Pydantic / JSON schema (req-026) 3 High Direct — the homework artifact
LLM API integration / production deployment 2 28/30 JDs Practiced in every lab
Python proficiency 1 30/30 JDs Reproducible env + typed code

🟩 Production tip: Treat every LLM call that feeds downstream code as a typed function — a schema, a validator, and an explicit failure mode. Free-form output is only acceptable at the user-facing surface.

🟪 Archetype fit: AI Engineer — Generalist · Agentic Systems Engineer. M0.2 is the first module that produces a portfolio artifact: a validated extract_contact function.

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Before We Begin — What M0.1 Gave You

This module assumes you finished M0.1 and can do all four:

  • Tokens & context window — you can explain what a token is and why context length drives cost.
  • Temperature & sampling — you can set temperature=0 and explain the reliability trade-off.
  • Statelessness — you know every API call is independent; the messages array IS the memory.
  • finish_reason / stop_reason — you can inspect why generation stopped and read usage.

🟨 Gotcha — Missing one of these? Do not proceed. Re-run the M0.1 worked example first; the labs here build directly on that wrapper. Planned remedial decks: python/env-vars-secrets, python/http-json-basics (status: planned, tracked in the backlog).

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

New Terms in This Module

Term Definition
Structured output An LLM response constrained to a specific schema (JSON, typed object) instead of free prose.
Tool schema A JSON Schema definition passed to the model that constrains its response to the schema's shape.
Pydantic A Python library for runtime type validation; defines typed models and raises on violations.
instructor A library wrapping the client (OpenAI / Anthropic / litellm) to add Pydantic validation and auto-retry.
Client SDK vs Agent SDK Client SDK = you own the request/response loop; Agent SDK = the framework owns the loop and calls your functions.

🟦 Pre-training principle: These five terms recur on every slide that follows. Meeting them now keeps them from becoming cognitive load mid-explanation later.

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Bridge — Where M0.2 Fits

The curriculum arc: Foundation → Retrieval → Orchestration → Production

M0.1 Mental model  ──►  M0.2 Structured output  ──►  M1.1 Prompt reliability
   (probabilistic)        (controlled output)          (measured inputs)
        │                       │
   "why variance"        "first tool to contain it"

M0.1 explained why LLM output varies. M0.2 gives you the first reliability tool: forcing the model's output into a shape your code can trust.

🟦 Connecting concept: Schema enforcement means variance no longer flows into invalid JSON — it is caught at the validation boundary and either repaired or raised, never silently passed downstream.

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Concept 1 — Reproducible Env & Secret Hygiene

The #1 reason learners stall is environment friction. Solve it once, correctly:

  • Isolated environmentuv (fastest) or venv. Never install AI deps globally; litellm / langchain transitive conflicts are a constant source of pain.
  • Secrets in .env, never in code.env is gitignored (real keys); .env.example is committed (placeholders).
  • Pin dependenciesrequirements.txt or pyproject.toml. litellm==1.30.2 today may break on 1.31.0 tomorrow.
  • The llm.py seam — one file routes every model call, so you swap providers by changing one env var.
# .env.example — copy to .env and fill in real values. NEVER commit .env.
ANTHROPIC_API_KEY=sk-ant-REPLACE_ME
LLM_MODEL=claude-sonnet-4-6

🟥 Anti-pattern — hardcoding API keys in source: Committed keys are found by automated scanners within minutes and your billing reflects it. Use .env from day one.

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Concept 1b — The First Authenticated Call

Lab 1's whole job: stand up the scaffold and make one authenticated call that fails loudly if the key is missing.

import os
from dotenv import load_dotenv
load_dotenv()  # reads .env in the current directory

# Fail loudly if the key isn't set — better than a cryptic 401
api_key = os.getenv("ANTHROPIC_API_KEY") or os.getenv("OPENAI_API_KEY")
if not api_key:
    raise EnvironmentError("No API key found. Copy .env.example to .env.")

import litellm
model = os.getenv("LLM_MODEL", "claude-sonnet-4-6")
resp = litellm.completion(model=model, max_tokens=10, temperature=0,
    messages=[{"role": "user", "content": "Reply with exactly: READY"}])
print(resp.choices[0].message.content.strip())

🟩 Production tip: Failing loudly at startup on a missing key beats a buried HTTP 401 three frames deep in a stack trace.

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Concept 2 — The Reliability Ladder

Three approaches to structured output, in increasing order of guarantee. Climb only as high as your task needs.

Reliability ladder: prompt-only JSON, tool schema at the API level, then Pydantic plus instructor for validation and retry

🟩 Production tip: Use Rung 3 (Pydantic + instructor) for any field your code reads programmatically.

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Concept 2a — Rung 1: Prompt-Only JSON (Fragile)

Ask the model to "reply with only a JSON object." It usually works — and that usually is the problem.

r1 = complete([{"role": "user", "content":
    f"Extract name, email, order_id, issue. Reply with ONLY JSON.\n\n{CUSTOMER_TEXT}"}],
    temperature=0, max_tokens=256)
raw = r1.choices[0].message.content.strip()
if raw.startswith("```"):          # strip markdown fences the model added anyway
    raw = raw.split("```")[1]
    if raw.startswith("json"):
        raw = raw[4:]
data = json.loads(raw)             # may still raise JSONDecodeError
  • No enforcement: the model can wrap JSON in prose or markdown, or emit invalid JSON.
  • You write defensive parsing code — and still get surprised in production.

🟨 Gotcha: Every line of fence-stripping and try/except here is a symptom that the format guarantee lives in the wrong place — the prompt, not the API.

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Concept 2b — Rung 2: Tool Schema (Reliable)

Define a typed function schema and force the model to "call" it. The response matches the schema's shape — you get a dict.

EXTRACT_TOOL = {"type": "function", "function": {
    "name": "extract_customer_request",
    "parameters": {"type": "object",
        "properties": {
            "customer_name":   {"type": "string"},
            "customer_email":  {"type": "string"},
            "refund_requested":{"type": "boolean"}},
        "required": ["customer_name", "customer_email", "refund_requested"]}}}
r2 = complete([{"role": "user", "content": CUSTOMER_TEXT}], tools=[EXTRACT_TOOL],
    tool_choice={"type": "function", "function": {"name": "extract_customer_request"}})
data = json.loads(r2.choices[0].message.tool_calls[0].function.arguments)

🟦 Concept: Tool schema guarantees JSON syntax and field presence — but not semantic validity. customer_email: "not provided" is syntactically valid and semantically wrong.

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Concept 2c — Rung 3: Pydantic + instructor

Add the semantic layer: typed fields, validators, and automatic retry on violation.

from pydantic import BaseModel, field_validator
import instructor, litellm

class CustomerRequest(BaseModel):
    customer_name: str
    customer_email: str
    refund_requested: bool

    @field_validator("customer_email")
    @classmethod
    def looks_like_email(cls, v: str) -> str:
        if "@" not in v:
            raise ValueError(f"'{v}' is not an email")
        return v.lower().strip()

client = instructor.from_litellm(litellm.completion)   # returns Pydantic objects

🟩 Production tip: A validator that rejects "not provided" turns a silent bad value into a retried — or loudly raised — failure.

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Concept 3 — Client SDK vs Agent SDK

The distinction the rest of the course is built on — who owns the loop.

Client SDK (litellm, openai, anthropic) Agent SDK / framework (LangGraph, smolagents)
Who owns the loop You write the while loop and tool dispatch Framework runs perceive→reason→act→observe
Control vs code Full control, maximum transparency, more code Less code for common patterns, harder to debug
Good for Single-turn calls, custom loops, learning Multi-step loops, multi-agent, stateful workflows

Rule of thumb: loop logic simple and fully specified → Client SDK. Loop complex or needs persistence → framework.

🟨 Gotcha: Choosing a framework early locks you into its abstractions. Understand the client SDK first — every Milestone 0 and 1 lab uses litellm directly so you see every line.

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Concept 3b — Same Task, Two Loops

# Version A — Client SDK: you own every line of the loop
def answer_with_retry(task, max_retries=2):
    messages = [{"role": "user", "content": task}]
    for _ in range(max_retries):
        r = litellm.completion(model=MODEL, messages=messages,
                               temperature=0, max_tokens=512)
        content = r.choices[0].message.content.strip()
        if content:
            return content
        messages.append({"role": "user", "content": "Empty — answer again."})
    return "No answer after retries."

# Version B — Agent framework: the loop is managed for you
agent = CodeAgent(tools=[], model=LiteLLMModel(model_id=MODEL))
result = agent.run(task)   # the while-loop, message accumulation, dispatch — all hidden

🟦 Concept: Both produce the same answer. Version A is ~12 lines you can debug; Version B is 2 lines that hide the loop. When B misbehaves, you need A to debug it.

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Check Your Mental Model — Before We Code

Formative Check 1 of 2 | Bloom level: Understand | ~8 minutes

This is a mirror, not a grade. Answer it before the worked example.

Q: A teammate says "I just added 'please respond in JSON' to the system prompt — that should be enough for production." What is wrong with this approach, and what would you recommend instead?

Answer direction: Prompt-only JSON (Rung 1) has no enforcement — the model can still return prose or malformed JSON. For production, climb to a tool schema (Rung 2) or Pydantic + instructor (Rung 3), which enforce the schema at the API / validation layer and retry on violation.

🟨 Misconception named — Prose-Prompt-JSON: believing that instructing the model to output JSON is equivalent to enforcing a schema.

▶ Open Check 1 (Understand) — scenario correction · ~8 min

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Worked Example — extract_contact

Goal: Turn a raw customer email into a typed Contact object — the same task as the homework, walked through end to end. Raw text in, validated object out, loud failure on bad data.

extract_contact flow: raw email to instructor client to LLM filling the Contact schema, Pydantic validation, then return the typed object or retry and finally raise ExtractionError

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Worked Example — Step 1: The Schema

Define the contract first. The schema is the specification of what "done" means.

from pydantic import BaseModel
from typing import Optional

class Contact(BaseModel):
    name: str
    email: str
    phone: Optional[str] = None
    company: Optional[str] = None
  • Required fields (name, email) must be present or validation fails.
  • Optional fields default to Nonephone not in the email is fine, not an error.

🟦 Concept: The Pydantic model is a machine-checkable contract. Anything that does not match it is rejected before it reaches your code.

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Worked Example — Step 2: The instructor Call

Wrap the client once, then call it like a typed function.

import instructor, litellm, os

client = instructor.from_litellm(litellm.completion)

def extract_contact(text: str) -> Contact:
    return client.chat.completions.create(
        model=os.getenv("LLM_MODEL", "claude-sonnet-4-6"),
        response_model=Contact,
        messages=[{"role": "user",
                   "content": f"Extract contact info:\n\n{text}"}],
        max_retries=2,        # retry on ValidationError
        temperature=0,
    )
  • response_model=Contact tells instructor what shape to enforce and return.
  • max_retries=2 runs the validate-and-repair loop automatically.

🟩 Production tip: Returning Contact (not dict) means every caller gets type-checking and autocomplete for free.

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Worked Example — Step 3: Handle the Failure Mode

Wrap exhausted retries in a named exception, so callers can catch the right thing.

class ExtractionError(Exception):
    """Raised when a valid Contact cannot be produced after retries."""

def extract_contact(text: str) -> Contact:
    try:
        return client.chat.completions.create(
            model=MODEL, response_model=Contact, max_retries=1,
            messages=[{"role": "user", "content": f"Extract contact:\n\n{text}"}],
            temperature=0)
    except Exception as e:           # instructor exhausted its retries
        raise ExtractionError(f"Could not extract a valid Contact: {e}") from e

🟥 Anti-pattern — swallowing the error: A bare except: pass returns a half-built or None contact downstream. Raise ExtractionError so the caller decides what to do.

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Worked Example — Step 4: Side-by-Side

Dimension Rung 1: Prompt JSON Rung 2: Tool schema Rung 3: Pydantic + instructor
Lines of glue code High (fence-strip + try/except) Low Low
Enforcement None Syntax + presence Syntax + presence + semantics
On bad output Silent / JSONDecodeError KeyError downstream Retry, then ExtractionError
Cost of failure Debugged at 2 AM Debugged at parse site Caught at call time
Use when Never for production Display-only fields Any field code reads

🟩 Production tip: The three rungs cost roughly the same to write. The difference is where failure surfaces — and Rung 3 surfaces it at the call, where it is cheapest to handle.

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Apply Your Mental Model — Design the Schema

Formative Check 2 of 2 | Bloom level: Apply | ~10 minutes

You need to extract {product_name, quantity, unit_price} from order-confirmation emails.

Q: Write the Pydantic model and the instructor-wrapped client call that enforces this schema. What happens if the model returns quantity as a string instead of an int?

Answer direction: Define OrderLine(BaseModel) with typed fields (quantity: int, unit_price: float); wrap the client with instructor.from_litellm. If the model returns quantity as a string, Pydantic v2 raises by default — instructor catches it and retries; if retries are exhausted, a ValidationError propagates for the caller to handle.

🟨 Misconception named — Silent-Type-Coercion: assuming Pydantic will quietly coerce "3" into 3. In strict mode it raises; do not rely on silent coercion.

▶ Open Check 2 (Apply) — schema design + code completion · up to 3 attempts · ~10 min

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Lab Brief — Your Turn

Four labs build the muscle; the homework is the portfolio artifact.

  • Lab 1 — env scaffold: .env / .env.example, load with python-dotenv, confirm no secret in git diff.
  • Lab 2 — three approaches: prompt-only JSON vs tool schema; observe the failure surface of each.
  • Lab 3 — Pydantic + instructor: validation + retry; confirm schema enforcement (and the manual loop under the hood).
  • Lab 4 — SDK comparison: the same task with the client SDK vs a minimal agent framework; note what the framework hides.

Homework — extract_contact(text) -> Contact: Pydantic schema + validation + one retry. Required name, email; optional phone, company. email is lowercase-stripped. Raise ExtractionError (not a generic exception) after retry.

▶ Submit your homework — AI-graded in ~30s (primary). The grader scores against the rubric and returns line-level feedback.

Production-track: fork drdgreed/career-foundry, add your solution under submissions/<your-handle>/m0-2/, open a PR — the same grader runs in CI as a PR comment.

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Time, Support, and Resources

Expected time for HW-0.2: 2–3 hours (schema design + extraction + the failure-path test; longer if new to uv/.env setup).

Setup check: If lab1_env.py prints READY, your environment is ready for the homework.

Where to get help:

  • AI Tutor (live, in-page) — Claude-/OpenAI-backed inline assistant trained on this curriculum; it cites slides back to you. Chat bubble in the lower-right of every M0.2 page.
  • Report an issue / ask the cohort — the support widget files a pre-tagged GitHub Issue (auto-labels module:m0-2). 🚧 Cohort chat is tracked on ROADMAP.md.
  • Reflection promptsM0.2 reflection page (offline mirror).

Catch-up path: Use instructor directly (Lab 3 style) and wrap its ValidationError in ExtractionError — get the artifact working first, then study the manual loop.

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Reflection Prompt

Take 5 minutes before you start HW-0.2. Write your answers in a text file.

1. How often did Rung 1 fail?

When you ran prompt-only JSON, how often did it return prose or malformed output? What does that failure rate reveal about relying on prose instructions for structural guarantees?

2. Which systems receive your LLM output?

In your current or target role, what downstream systems consume model output? How would schema enforcement change the reliability contract with them?

3. What still feels like a black box?

Name one thing about the instructor retry loop you cannot yet explain. Write it down before the lab — then check whether the lab resolves it.

📖 Full reflection page: Open on career-forge.org — learner-friendly rendering with facilitator notes. (Offline mirror)

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

Self-Assessment Rubric

Score yourself 0–3 on each criterion. Threshold to advance: 8/15.

Criterion 0 1 2 3
Reproducible env & secrets Keys in code .env used .env.example committed, key gitignored Pinned deps + llm.py seam
Reliability ladder Cannot name rungs Names all three Explains enforcement per rung Chooses correct rung per task
Pydantic + instructor Cannot write a model Writes a model Adds a validator Validate + retry + named error
Failure handling Swallows errors Catches generically Raises a named exception Actionable message + retry policy
Client vs Agent SDK Cannot distinguish States the difference Names what a framework hides Justifies the choice per scenario

🟩 Production tip: Your extract_contact function carries forward into M1.1's prompt-evaluation harness — write it as production code, not throwaway.

📊 Self-assessment page: Open on career-forge.org — five-criterion self-scoring (threshold 8/15 to advance). (Offline mirror)

career-forge.org | v1.0.0 | 2026-06-14
Agentic AI Mastery · M0.2 — Python SDKs & Structured Output

What You Built + What Is Next

This module produces:

  • A reproducible project scaffold — .env secrets, pinned deps, the llm.py seam (→ mLO-0.2a).
  • extract_contact(text) -> Contact — a validated, retrying, loudly-failing typed function (→ mLO-0.2b).
  • A working command of the client SDK vs agent SDK distinction the rest of the course builds on (→ mLO-0.2c).

Next module: M1.1 — Prompting & Structured Output for Reliability.
M0.2 gave you the structured-output tool; M1.1 teaches you to systematically improve the inputs that produce those outputs — building a 30-example eval harness that measures prompt quality as an engineering metric. Your extract_contact function becomes the unit under test.

🟪 Portfolio signal: extract_contact is direct, demonstrable evidence for req-026 (structured output / Pydantic / JSON schema, Score 3) — the kind of artifact a hiring manager can read in 60 seconds and trust.

📎 Module links: Slides · Module hub · Course repo · Roadmap

career-forge.org | v1.0.0 | 2026-06-14

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