# From chatbot to component

Express course on structured output and tool use — turning a language model from a chatbot that emits prose into a dependable component your code can build on. 20 concepts — Structured Output, Constrained Generation, Boundary Validation, Function Calling, the Tool-Use Loop, Tool Poisoning, Least Privilege, MCP and more — each with its English name, a diagram, a card and an example. A one-page cheat sheet of every concept at the end.

- **Author:** Fedor Chishchin
- **Level:** intermediate
- **Reading time:** ~35 minute read
- **Published:** 2026-06-13
- **Source:** https://fedorthinks.com/learn/structured-output-express

---

## 1. Free text is not an API

The first step to building on a model is recognising the mismatch: it speaks prose, but your program needs data and actions. Bridging that gap is what this whole course is about.

### The map: 20 concepts in seven groups

> A metro map: first you see all the lines at once, then you ride station by station.

The course moves through seven groups. First, **the problem** (why prose doesn't work for code). Then the two halves of the fix: **structured output** with **validation** (data you can trust) and **tool use** with good **tool design** (actions under control). Then **MCP**, the standard layer for plugging tools in, and finally how to **use it all well**.

_Diagram: Seven groups of concepts — the chapters of the course follow them._

### How to read each concept

> A passport: the same fields for everyone, so they're easy to compare.

Every concept below has the same parts: its **English name** (remember it — it's what the docs call it), a picture from everyday life, a **diagram**, and a **card** with fields: _problem_, _idea_, _example_, _cost_ or _catch_, _when to use_, and how it's _related_ to other concepts. Threats get slightly different fields: _what it is_, _how it works_, _defence_.

### Free Text — prose for humans

> A handwritten paragraph describing an order versus a filled-in form with labelled boxes — a person reads the paragraph easily, but only the form can be filed by a machine.

By default a language model outputs **free text** — a paragraph, an explanation, prose. That's perfect for a human reader and useless for your code, which needs predictable values it can act on: a number here, a category there, a yes-or-no. A program can't reliably pull "the customer wants a refund of $40" out of a sentence the model phrased however it liked. Prose and data are different things, and code runs on data.

_Diagram: Left: prose — clear to a human, nothing for code to hold on to. Right: typed fields — code can act._

#### Free Text (prose)

- **Problem:** By default the model answers in prose, but code needs data.
- **Idea:** Tell two kinds of output apart: **for a human** (prose) and **for a program** (data).
- **Example:** "So the customer seems to want around $40 back, the order was late…" — a human gets it, code doesn't.
- **When it fits:** Chat, explanations, text that only a human reads.
- **Related:** Trying to parse it with code is **Prose Parsing**; the real fix is **Structured Output**.

### Prose Parsing — an anti-pattern

> Trying to extract the total from a letter that might say "$40", "forty dollars", or "a refund of 40 bucks" — your rules work until the day the wording shifts, and then everything falls over.

The naive fix — have the model write text and then parse it with your own code (regexes, string search) — is brittle. The model is **non-deterministic**: it will phrase the same thing ten different ways, add a friendly preamble, or wrap the answer in markdown. Your parser handles the cases you saw and shatters on the one you didn't. Building on parsing the model's prose is building on sand: it works in the demo and fails in production.

_Diagram: Same meaning, different wording: the parser only catches what it was prepared for._

#### Prose Parsing (string-parsing model output)

- **Problem:** You need data from the model's answer, and it returned free text.
- **Idea:** Pull the values out with your own rules — regexes, string search.
- **Example:** A regex catches `$40` but misses "forty dollars" and trips over `**$40**`.
- **Cost:** Breaks silently and suddenly: the model changes its wording, your code falls over.
- **When to use:** Almost never; one-off scripts at most, not production.
- **Related:** Cured by **Structured Output** and **Constrained Generation**.

### Model as a Component — a reliable part

> An engine becomes useful to a car only once it has standard mountings and connectors — predictable interfaces the rest of the machine can bolt onto with confidence.

To build software on a model, you have to turn it from a chatty oracle into a **component** — a part with predictable interfaces: output your code can trust, and actions it can take safely. That's exactly the two halves of this course: **structured output** (data, not prose) and **tool use** (acting, not just talking). Get those, and the model stops being a demo and becomes a dependable part you can engineer around like any other.

_Diagram: A chatbot becomes a component with two interfaces: data (structured output) and actions (tool use)._

#### Model as a Component (the model as a dependable part)

- **Problem:** An "oracle" model answers however it likes — you can't build a system on that.
- **Idea:** Give the model two predictable interfaces: **data** out and **actions** through tools.
- **Example:** The model reads an email and returns `{intent: "refund"}`, not a paragraph about a refund.
- **Cost:** You have to design schemas, tools and checks — that's real work.
- **When to use:** Whenever code does something with the model's answer.
- **Related:** The two halves are **Structured Output** and **Function Calling**.

> **The framing question:** "Does my code need to act on this output — and if so, am I relying on parsing free prose, or getting structured data the program can actually trust?"

> A model speaks prose; your code needs data and actions. Parsing free text is fragile — the goal is to make the model a reliable component, with output you trust and actions it can take.

## 2. Structured output

The first half of making the model a component is getting it to return data in a shape your code defines, every time — not prose you have to guess at, but a predictable structure.

### Structured Output — a schema, not a sentence

> Handing someone a form with labelled boxes instead of a blank page — they fill in exactly the fields you need, in the order you need, with nothing extra.

**Structured output** means asking the model to return data in a defined format — typically **JSON** matching a **schema** you specify: these fields, these types, this shape. Instead of "tell me about this order," you say "return `{customer, amount, reason}`," and you get back data your code can use directly. You define the shape; the model fills it in. This is the single move that turns model output from prose into something a program can rely on.

_Diagram: Prompt + schema → model → JSON of the right shape → your code._

#### Structured Output (JSON to a schema)

- **Problem:** Code can't reliably build on the model's prose.
- **Idea:** Define a **schema** (fields and types) and ask the model for **JSON** of exactly that shape.
- **Example:** A customer email → `{"customer": "Ana", "amount": 40, "reason": "late"}`.
- **Cost:** You have to design the shape up front; the schema is a contract you'll evolve.
- **When to use:** Every time the model's answer is read by code, not a person.
- **Related:** Hardened by **Constrained Generation**; checked by **Boundary Validation**; applied to actions it becomes **Function Calling**.

### Constrained Generation — only valid output

> A railway switch that physically can only send the train down one of the defined tracks — there's no way for it to end up somewhere off the map.

Modern models support **constrained generation**: they can be _forced_ to produce output that actually matches your schema — valid JSON, the right fields, the correct types — rather than just being politely asked to. This closes the gap where the model "mostly" returns the right shape but occasionally drifts. With the output constrained to the schema, your code can count on the structure being there, which is what makes the model usable as a real building block.

_Diagram: Asking in the prompt gives you “almost always”. Constraining to the schema gives you “always a valid shape”._

#### Constrained Generation (constrained decoding)

- **Problem:** Even when asked for JSON, the model sometimes drifts: extra text, a missing field.
- **Idea:** While generating, the model can only pick what matches the schema.
- **Example:** `amount` is declared a number — the string "forty" physically can't appear there.
- **Catch:** Guarantees the **shape**, not the **correctness** of the values.
- **When to use:** Whenever your provider supports it and code reads the output.
- **Related:** Makes **Structured Output** dependable; does not replace **Boundary Validation**.

### Model-to-Code Hand-off — the bridge to your code

> A translator who converts a guest's spoken wishes into a precise, standardised order ticket the kitchen can act on — the same meaning, now in a form the system can use.

Structured output is the bridge between the model's language ability and your software's need for data. It lets the model do what it's brilliant at — understanding messy human input — and hand the result to your code as clean, typed values. **Classification**, **extraction**, **routing**, form-filling: all become reliable when the output is structured. The model reads the chaos; the schema delivers the order. That hand-off is where most real LLM features actually live.

_Diagram: A messy email → model → JSON with the intent → code routes it to the right queue._

#### Model-to-Code Hand-off (classification, extraction, routing)

- **Problem:** Incoming emails, chats and documents are chaos your code can't read.
- **Idea:** The model understands the chaos and hands back fields to a schema.
- **Example:** "Package late, want my money back!!" → `{intent: "refund"}` → the refunds queue.
- **Cost:** A misclassification now travels on into your system — it needs checking.
- **When to use:** Classification, extraction, routing, form-filling.
- **Related:** Built on **Structured Output**; the result goes through **Boundary Validation**.

> **The structure question:** "Have I defined the exact shape I want back — fields and types — and constrained the model to it, so my code gets data it can rely on instead of prose to parse?"

> Structured output means the model returns data matching a schema you define, constrained to valid shape — the bridge that turns its language ability into typed values your code can rely on.

## 3. Validate at the boundary

Structured output gets you a predictable shape, but shape is not correctness. Before your code trusts what the model returned, it has to check it — the same discipline as any untrusted input.

### Shape ≠ Content — well-formed is not correct

> A form filled in neatly, every box completed — but the date is impossible and the total doesn't add up. Tidy is not the same as correct.

A schema guarantees the _shape_ — the right fields and types — but not that the values make sense. The model can return perfectly-formed JSON with a negative quantity, a category that doesn't exist, or an amount it **hallucinated**. Structured output solves "can my code read it"; it doesn't solve "is it right." Treat a well-formed response as a starting point to check, not a guarantee of a correct one.

_Diagram: The schema check passes, but the values are nonsense: −3 items, category “banana”, amount 9999._

#### Shape vs Content (well-formed vs correct)

- **Problem:** Valid JSON is easy to mistake for a correct answer.
- **Idea:** Keep two questions apart: **"can it be read?"** (schema) and **"is it right?"** (the values' meaning).
- **Example:** `{"quantity": -3, "category": "banana"}` passes the schema, but it's junk.
- **Catch:** Content errors are quieter than shape errors — neither a parser nor constrained generation catches them.
- **Related:** Solved by **Boundary Validation**.

### Boundary Validation — check before anything acts

> A customs check between countries — nothing crosses into the next system until it's been inspected against the rules and declared safe.

So you **validate** the model's structured output before your code uses it: enforce the schema strictly, check values are in allowed ranges and sets, confirm anything referenced actually exists. This check runs in your code, at the **boundary** between the model and the rest of your system, because the model is a non-deterministic component you can't fully trust. The same lesson as security: model output is **untrusted input** to the next stage until you've verified it.

_Diagram: A validator in your code: schema, ranges, existence. Pass — into the system; fail — reject or retry._

#### Boundary Validation (validation at the boundary)

- **Problem:** The model can return a well-formed but wrong answer.
- **Idea:** A check sits between the model and the system: **strict schema**, ranges, allowed sets, references that exist.
- **Example:** `amount` between 0 and the order total; `category` from the list; `order_id` exists in the database — otherwise reject or re-ask.
- **Cost:** Extra code, and sometimes a second model call.
- **When to use:** Always, whenever anything acts on model output.
- **Related:** Catches what **Constrained Generation** lets through; the base of **Boundary Discipline**.

### Insecure Output Handling — raw output into something dangerous

> You don't pour an unfiltered stream straight into the drinking supply — whatever was upstream is now in every tap, with no chance to catch it.

The rule gets strict when output flows somewhere consequential. Never feed raw model output directly into a database write, a shell command, a query, or another system without validating and **escaping** it first — that's how a hallucination or an injected instruction becomes a real bug or breach. This mistake has a name: **insecure output handling**. Structured output plus strict validation is what lets you safely act on what the model produced. The structure makes it readable; the validation makes it trustworthy.

_Diagram: Top: raw output straight into SQL or a shell — a hole. Bottom: validate and escape first._

#### Insecure Output Handling (trusting raw model output)

- **What it is:** Raw model output goes straight into a database, shell, query or API.
- **How it works:** A hallucination or an injected instruction becomes a real command or record.
- **Example:** The model "suggested" a file name, and code pasted it into a shell command.
- **Defence:** Validate and escape; use parameterised queries; never let output run as code.
- **Related:** Closed by **Boundary Validation**; a cousin of **Tool Poisoning**.

> **The validation question:** "Between the model's output and the code that acts on it, what checks the values are actually valid — or am I trusting well-formed JSON as if well-formed meant correct?"

> A valid schema guarantees shape, not correctness. Validate the model's output at the boundary — ranges, sets, existence — before any code acts on it, because model output is untrusted until checked.

## 4. Tool use

Structured output lets the model return data. The other half of making it a component is letting it do things — reach past text to search, calculate, fetch, and act in the real world. That's tool use.

### Tool Use / Function Calling — functions the model can request

> A smart assistant who can't open the filing cabinet themselves — but can tell you precisely which drawer and file to pull, and use what you bring back.

With **tool use** (also called **function calling**), you describe functions the model is allowed to request — `search_orders`, `send_email`, `get_weather`. When the model decides it needs one, it returns a structured **tool call**: the function's name and its arguments. It's structured output applied to actions: instead of data, the model emits a request to _do_ something. This is how an LLM reaches beyond its own text into your systems and the world.

_Diagram: You describe the tools; the model picks one and returns a call: name + arguments._

#### Function Calling (tool use)

- **Problem:** The model only knows what's in its context and can't do anything itself.
- **Idea:** Describe functions; the model returns a **tool call** — a name and arguments to a schema.
- **Example:** "Where's my order 1042?" → `search_orders({id: 1042})`.
- **Cost:** Every tool is a new capability that can be misused.
- **When to use:** When the task needs fresh data or needs something done.
- **Related:** It's **Structured Output** for actions; run under **Model Decides, Code Executes**.

### Model Decides, Code Executes — the division of labour

> A foreman who points and says exactly what needs doing — but the crew, not the foreman, actually operates the machines and stays in control of the site.

The division of labour is the key: the model decides _what_ to do and with what arguments; **your code decides whether and how to actually do it.** When the model returns a tool call, your code runs the function (or doesn't), gets the result, and feeds it back into the context for the model to continue. The model never touches your systems directly — it requests, your code executes. That boundary is where you keep control and safety.

_Diagram: The model only asks. Deciding whether to run it — and running it — happens in your code, across the boundary._

#### Model Decides, Code Executes (request vs execute)

- **Problem:** If the model presses the buttons itself, one mistake is already in your system.
- **Idea:** The model returns a **request**; code checks it and decides whether to run it.
- **Example:** The model asks for `refund_order(amount: 900)` — code sees the $100 limit and refuses.
- **Cost:** Code has to be able to check every call: permissions, limits, arguments.
- **When to use:** Always, whenever the model has tools.
- **Related:** It's **Boundary Validation** for actions; the base of the **Tool-Use Loop**.

### Tool-Use Loop — the result feeds back

> A researcher asks for a document, reads what's brought back, and uses it to decide the next question — a back-and-forth, not a single shot.

Tool use is a **loop**: the model requests a tool, your code runs it and returns the result into the context, and the model reasons on with that new information — possibly calling another tool. This loop is exactly what turns a model into an **agent** (the agents course goes deep). Tools are the hands; the loop is the persistence. For now, the key idea is that tool use lets the model gather what it needs and act, step by step, instead of answering blindly from memory.

_Diagram: Model → call → code runs it → result into context → model again. Repeat until the job is done._

#### Tool-Use Loop (agent loop)

- **Problem:** One call often isn't enough: find out, think, then take the next step.
- **Idea:** Repeat: model → tool call → code runs it → result into context → model.
- **Example:** Find the order → check delivery status → issue the refund → reply to the customer.
- **Cost:** More steps mean more calls, more money and more ways to go off track.
- **When to use:** Multi-step tasks where the next step depends on the result of the last.
- **Related:** The top rung of the **Capability Ladder**; rests on **Model Decides, Code Executes**.

> **The tool question:** "Does this task need the model to actually do or fetch something — and have I exposed that as a tool it can request, with my code staying in control of whether to run it?"

> Tool use lets the model request functions — structured output for actions. The model decides what to do; your code decides whether and how to do it, and feeds the result back.

## 5. Designing good tools

The model picks and uses tools based on how you describe them, so tool design is part of getting reliable behaviour. A few principles separate tools that work from tools that confuse the model.

### Tool Description — names and descriptions guide the choice

> A toolbox where every tool is clearly labelled with what it's for — the worker reaches for the right one instantly, instead of guessing from a row of unmarked handles.

The model chooses which tool to call almost entirely from its **name** and **description**. So those aren't documentation for humans — they're the instructions the model reasons from. A vague or misleading description leads to the wrong tool, the wrong arguments, or a tool ignored when it was needed. Write tool names and descriptions as carefully as you'd write a prompt, because to the model, that's exactly what they are.

_Diagram: “do_stuff / handles data” — the model guesses. A clear name and description — the model makes the right call._

#### Tool Description (tool name and description)

- **Problem:** The model picks the wrong tool or the wrong arguments.
- **Idea:** The name and description are a **prompt** for the choice: what it does, when to call it, what it returns.
- **Example:** Not `do_stuff: "handles data"` but `search_orders: "Find orders by customer email. Read-only."`
- **Cost:** Time spent on wording and on checking how the model reads it.
- **When to use:** For every tool, no exceptions.
- **Related:** Works together with a **Small Toolset**; descriptions are part of the attack surface (**Tool Poisoning**).

### Small Toolset — few sharp tools beat a giant menu

> A kitchen with a few well-chosen, clearly-different tools works faster than one with fifty overlapping gadgets the cook has to sort through every time.

Giving the model too many tools — or several that overlap — makes it harder for it to choose well, every single turn. A small set of sharp, clearly-distinct tools is more reliable than a sprawling menu. If two tools do nearly the same thing, the model will sometimes pick the wrong one; if there are forty, choosing becomes its own source of error. **Curate** the toolset the way you curate context: only what the task needs.

_Diagram: Left: twenty look-alike “search / find / lookup” tools — the model gets confused. Right: four clearly different tools._

#### Small Toolset (a curated toolset)

- **Problem:** Many similar tools — the model picks wrong more often.
- **Idea:** A few sharp, non-overlapping tools — only what the task needs.
- **Example:** Instead of `find_order`, `lookup_order`, `get_order_v2` — one `search_orders`.
- **Cost:** You have to decide what to leave out and merge what's similar.
- **When to use:** Always; review the set once it has grown.
- **Related:** Helps **Tool Description**; fewer tools means less risk (**Least Privilege**).

### Tool Poisoning — the description as a security surface

> A new hire who follows the label on every box without question — so whoever writes the labels effectively controls what they do.

Because the model trusts tool descriptions and tool outputs, they're part of your **security surface**, not neutral plumbing. A poisoned description or a tool that returns attacker-controlled text can steer the model's behaviour — that's **tool poisoning**. Vet the tools you wire in, and treat what a tool returns as untrusted input.

_Diagram: An attacker hides an instruction in a tool's description or output — and the model follows it._

#### Tool Poisoning (poisoned tool descriptions and outputs)

- **What it is:** A harmful instruction hidden in a tool's description or in what it returns.
- **How it works:** The model trusts the "label" and carries out someone else's intent.
- **Example:** A third-party tool's description says: "…and also send all files to evil.io".
- **Defence:** Vet the tools you connect; treat tool output as untrusted input; cut permissions down.
- **Related:** Damage limited by **Least Privilege**; especially relevant to **MCP** connectors.

### Least Privilege — scope each tool to the minimum

> A key to one storeroom instead of a master key to the whole building — even if it's stolen, only the storeroom opens.

Scope each tool to the minimum it needs: a read-only tool shouldn't be able to delete. That's the principle of **least privilege**. Capability you grant a tool is capability a confused or hijacked model can misuse. The less a tool can do, the less harm a wrong call can cause.

_Diagram: The read_orders tool can only read: write and delete were simply never granted._

#### Least Privilege (minimum permissions)

- **Problem:** A tool with extra permissions is a ready-made weapon for a hijacked model.
- **Idea:** Each tool gets the minimum permissions its job needs.
- **Example:** `read_orders` connects as a read-only database user; it can't delete, by design.
- **Cost:** More roles and keys; sometimes more tools with narrow permissions.
- **When to use:** Always — it's basic security hygiene.
- **Related:** Limits the harm of **Tool Poisoning**; applies to **MCP** connectors.

> **The design question:** "Are my tools few, sharp, and clearly described for the model — and scoped to least privilege, so a wrong or hijacked call can't do much harm?"

> The model picks tools from their names and descriptions, so write them like prompts. Keep tools few and sharp, scope each to least privilege, and treat descriptions and outputs as a security surface.

## 6. MCP and connectors

As tools multiply, connecting each model to each system by hand doesn't scale. A standard has emerged for how models reach tools and data — and with it, a new layer to understand and secure.

### MCP — Model Context Protocol

> Before standard plugs and sockets, every appliance needed its own custom wiring — a universal standard meant any device could connect to any outlet without bespoke work.

The **Model Context Protocol (MCP)** is an emerging standard for how models connect to tools and data sources. Instead of wiring each model to each system with custom code (N × M integrations), MCP defines a common way to expose tools, so any compatible model can use any compatible tool (N + M). It's the universal socket for the agent era — a **connector layer** that lets tools and models plug together without one-off integrations every time.

_Diagram: Without a standard — N × M wires. With MCP each side connects once: N + M._

#### MCP (Model Context Protocol)

- **Problem:** Every model has to be wired to every system with custom code.
- **Idea:** One standard: an **MCP server** exposes tools and data, an **MCP client** (an agent, an app) uses them.
- **Example:** One MCP server for GitHub — used by an agent, an IDE assistant and a chat app alike.
- **Cost:** A new layer you have to secure (see **Connector Security**).
- **When to use:** When there are many tools and models, or you want to plug into an existing ecosystem.
- **Related:** The standard way to expose **Function Calling**; makes tools **composable**.

### Composability — the plumbing of agents, standardised

> A power grid: standardised so a new appliance just plugs in and works, and a new power source just feeds the same grid — the standard is what makes the whole network compose.

MCP matters because it makes tools and data **composable**: a tool built once can be used by many models and agents, and a new agent can immediately use the whole ecosystem of existing tools. This is a big part of why agents got powerful so fast — the plumbing got standardised, so capabilities snap together. When you give an agent access to your systems today, MCP is increasingly the layer doing it.

_Diagram: Clients on top, servers below, MCP in the middle: a tool built once is usable by all._

### Connector Security — the connector is a door

> New plumbing run through the whole house fast — and a survey finds a large share of the valves were never fitted with a lock.

This new layer is also a new place to get breached. MCP connectors expose real actions and data, and the rush to adopt them has left many deployed with weak or missing security — a large share of remote MCP servers have shipped with **no authentication** at all. So treat a connector like the door it is: **authenticate** it, **scope** what it exposes, keep it **off the public internet** unless it must be there, and **inventory** what you've connected. Powerful plumbing demands the basic locks.

_Diagram: Four locks on an MCP server: authentication, narrow scope, a private network, an inventory._

#### Connector Security (securing MCP connectors)

- **Problem:** MCP connectors expose real actions and data, and many are deployed with no authentication.
- **Idea:** Four locks: **authenticate**, **scope**, keep it **private**, keep an **inventory**.
- **Example:** A database MCP server: token only, read only, internal network only, listed in the registry.
- **Cost:** Access setup and the discipline to keep the list current.
- **When to use:** For every connector you deploy or plug in.
- **Related:** Applies **Least Privilege** to **MCP**; guards against **Tool Poisoning**.

> **The connector question:** "If I'm exposing tools and data through MCP, is each connector authenticated, scoped, and accounted for — or have I opened a powerful door and left it unlocked?"

> MCP standardises how models reach tools and data, making the ecosystem composable. That same connector layer is a real attack surface — authenticate it, scope it, and inventory it.

## 7. Using it well

Structured output and tool use are the tools that make a model a component, but reaching for them well still means climbing the ladder — using the least powerful piece that solves your problem.

### Capability Ladder — structured output first, tools when it must act

> You don't hand someone the car keys when all you needed was an answer — you give just the capability the task requires, no more.

There's a **capability ladder**. A plain prompt answers; **structured output** makes that answer usable by code; **tools** let the model fetch and act; a full **agent loop** lets it pursue a multi-step goal. Each rung adds power and new ways to fail. Reach only as high as the task needs: if you just need clean data, structured output is enough — don't give the model tools, and certainly not a loop, when a constrained response solves it. Most features never need the top rung.

_Diagram: Prompt → structured output → tools → agent loop. Higher means more powerful and more risky._

#### Capability Ladder (least powerful rung that works)

- **Problem:** It's tempting to reach for an agent even when JSON would do.
- **Idea:** Use the least powerful rung that solves the task.
- **Example:** Classifying emails — structured output; finding an order — one tool; a multi-step refund — a loop.
- **Cost:** Every rung up means more calls, more spend and more ways to fail.
- **When to use:** When choosing the approach for every new feature.
- **Related:** The rungs: **Structured Output** → **Function Calling** → **Tool-Use Loop**.

### Boundary Discipline — validate everything that crosses back

> A returns desk that inspects every item coming back before it goes on the shelf — nothing re-enters the system unchecked, no matter how trustworthy the customer seemed.

The unifying habit across both halves is **distrust at the boundary**: **validate the model's structured output** before your code uses it, and **keep your code in control** of whether to run a tool the model requested. The model is a brilliant, non-deterministic component, and you treat everything crossing from it into your system as input to verify. That boundary discipline is what lets you build reliably on something that's fundamentally fallible.

#### Boundary Discipline (distrust at the boundary)

- **Problem:** The model makes mistakes, and they leak into your system.
- **Idea:** Everything coming out of the model is **untrusted input**: data gets validated, actions get approved by code.
- **Example:** The JSON passed the validator; the tool call passed the permission and limit checks — only then does anything happen.
- **Cost:** Checks at every crossing — a bit of code and time.
- **When to use:** Always — it's the common thread of the whole course.
- **Related:** Unites **Boundary Validation** (data) and **Model Decides, Code Executes** (actions).

### How it all connects

> The whole metro map at once: you can see where the lines cross and where you change trains.

**Structured output** replaces free text; **constrained generation** keeps it in shape, and **boundary validation** checks what it means. The same structured output applied to actions is **function calling**; **MCP** gives a standard way to expose tools; your code executes the calls, and out of that grows the **tool-use loop**. One habit holds both halves together — **boundary discipline**.

_Diagram: The main links between the course's concepts: data on top, actions below, the boundary in the middle._

#### Before you build on a model

- **Does my code need data** — am I getting structured output to a schema, not parsing prose?
- **Is the output validated** — values checked, not just well-formed, before anything acts on it?
- **Does it need to act** — and have I exposed that as a tool, with my code in control of running it?
- **Are the tools few, sharp, and well-described**, and scoped to least privilege?
- **If using MCP**, is each connector authenticated, scoped, and inventoried?
- **Am I on the lowest rung** that works — output, tools, or a full loop only if needed?

#### Signs you build with it well

- You get **structured output to a schema** instead of parsing the model's prose.
- You **validate** the output at the boundary before any code acts on it.
- The model **requests** tools and **your code stays in control** of running them.
- Your tools are **few, sharp, well-described**, and **least-privilege**.
- You climb only to the **rung the task needs**, and treat MCP connectors as a **locked** door.

## Cheat sheet: all 20 concepts

### The problem

- **Free Text** (prose): Prose for a human; code can't build on it.
- **Prose Parsing** (anti-pattern): Parsing the model's text with regexes. Breaks on the next new wording.
- **Model as a Component** (a dependable part): A model with two predictable interfaces: data and actions.

### Structured output

- **Structured Output** (JSON to a schema): You define the shape (fields and types); the model fills it in.
- **Constrained Generation** (constrained decoding): The model physically can only emit what matches the schema.
- **Model-to-Code Hand-off** (classification, extraction): The model understands the chaos; code gets clean fields.

### Validation

- **Shape vs Content** (well-formed vs correct): Valid JSON ≠ correct values.
- **Boundary Validation** (check at the boundary): Schema, ranges, sets, existence — before code acts.
- **Insecure Output Handling** (raw output into danger): Threat: model output straight into SQL, a shell or a database.

### Tool use

- **Function Calling** (tool use): The model returns a call: function name + arguments.
- **Model Decides, Code Executes** (request vs execute): The model only asks; code decides whether to run it.
- **Tool-Use Loop** (agent loop): Call → result into context → next step. That's how a model becomes an agent.

### Tool design

- **Tool Description** (name and description): It's the prompt the model picks tools from — write it carefully.
- **Small Toolset** (few sharp tools): A few clearly different tools beat a giant menu.
- **Tool Poisoning** (poisoned labels): Threat: a harmful instruction in a tool's description or output.
- **Least Privilege** (minimum permissions): Each tool gets the minimum permissions it needs.

### MCP

- **MCP** (Model Context Protocol): The standard for plugging tools into models: N + M instead of N × M, tools become composable.
- **Connector Security** (securing connectors): Authentication, narrow scope, a private network, an inventory.

### Using it well

- **Capability Ladder** (lowest rung that works): Prompt → output → tools → loop. Take the lowest rung that works.
- **Boundary Discipline** (distrust at the boundary): Everything from the model is untrusted: validate data, let code approve actions.

> To build on a model, make it a component: structured output your code can trust, validated at the boundary, and tools it requests but your code controls — the least powerful rung that solves the job.

End of express course · 7 chapters · 20 concepts

Next comes practice: take a feature where you're parsing the model's prose, and switch it to structured output against a schema — then validate the result before you use it. If the task needs the model to fetch or do something, expose one small, well-described tool and keep your code in charge of running it. The shift clicks the moment the model stops being a chatbot you wrangle and becomes a part you can rely on. But hold one idea above the rest: a model becomes useful to software when it returns data you can trust and takes actions you control. Structured output and tool use are how a chatbot becomes a component.
