Harness engineering

Sign in to save progress
0:00
0:00

On this lesson: Why models need tools to be useful

The discussion is for premium members.Go premium
  • Be the first to say something about this lesson.

Prefer reading? Every lesson in this course as text

Read this lesson as text: Why models need tools to be useful

Why models need tools to be useful

What exactly is a tool for an AI model? Let's try to understand. In a simple way. On its own, a model does just one thing.

It predicts text, one word at a time. No eyes, no hands, no memory of right now. And its knowledge is frozen. It learned from old text, so it cannot see today's news or the price of something right now.

It does not even calculate. Ask for exact arithmetic, and it guesses the answer from patterns. Confident, and sometimes just wrong. And it cannot touch the world.

It cannot send an email, book a table, or change a file. It can only talk about them. A tool is the fix. Think of it as a door out of the box, a small piece of real code the model is allowed to call.

A search tool, a calculator, a database. With one of these, the model stops guessing and reaches out for the real thing. Now the same model can look up the live fact, run an exact calculation, or take a real action, instead of only describing it. The model plus its tools is called a harness.

The words still come from the model. The facts and the actions come from the tools. So, on its own a model only talks. Give it tools, and a talker becomes a doer that is actually useful.

Quick check now. One question is coming up. Let's see if it clicked.

Read this lesson as text: How a model decides which tool to use

How a model decides which tool to use

How does a model decide which tool to use? Let's try to understand. In a simple way. Every tool you give the model comes with a written description.

A name, one line about what it does, and the inputs it needs. The harness pastes that whole list into the context, right before your question. So the model reads the menu and the request together. There is no separate chooser inside the model.

Nothing votes. It is still just predicting the next words, as always. It compares the wording of your request with the wording of each description. Whichever one lines up best makes a tool call the likely continuation.

So instead of an answer, it writes out a tool name and the arguments to run it with. The harness spots that, and stops the model there. This means the description is the steering wheel. Write it vaguely and the model reaches for the wrong tool, or none at all.

Two tools that sound alike are the classic trap. If both descriptions say, look up a record, the model has no way to tell them apart. And picking nothing is a real choice too. If no description matches, the likely continuation is a plain answer, and it just replies.

So the pick is not magic. Names and descriptions in the context, matched against your words, and the most likely continuation wins. Quick check now. One question is coming up.

Let's see if it clicked.

Read this lesson as text: Giving your model a simple calculator

Giving your model a simple calculator

How does giving your model a calculator work? Let's try to understand. In a simple way. Start with the easy part.

The calculator is just a plain function you write. It takes an expression, does the math, returns a number. No AI inside. The model cannot see your code.

So you also write a short description. A name, calculate. One line of what it does. And the input it needs.

That description rides along inside the request, above your question. The model never sees the code itself. Only the label on the door. Now ask for a big multiplication.

Instead of guessing the digits, the model stops and writes a request. Use calculate, with this expression. That request is called a tool call. It is still just text the model predicted.

Nothing has run yet, and nothing has been computed yet. Your program is watching for that. It reads the name and the expression, then calls your real function. Your machine does the arithmetic, exactly.

Then you paste the result back into the conversation, as a message tagged as the tool's answer. The model now reads a number it did not invent. The model continues from there and writes the sentence around it. The wording is the model's.

The number is your code's. One thing to notice. The model will repeat whatever your function returned. Your calculator is now the source of truth, so keep it small and boring.

So, a calculator tool is a function, a description, and a round trip. Ask, request, run, return, speak. Quick check now. One question is coming up.

Let's see if it clicked.

Read this lesson as text: Letting your model search the internet

Letting your model search the internet

How does letting your model search the internet work? Let's try to understand. In a simple way. A search tool is a function, like any other tool.

But what comes back is not a number your code computed. It is text somebody else wrote. First, the model turns your question into search words. Not the whole sentence.

A short query, the way you would type it into a search box. The tool runs that query and returns a list. A few results, each with a title, a link, and a snippet. Notice, no answer yet.

The harness pastes those snippets back into the conversation, tagged as the tool's answer. They are now just more text in the model's context. So the model answers from words it is reading right now, not from what it memorized. That is the whole trick.

Fresh text in the window. Often it takes two tools. Search gives you links. A fetch tool opens one link and pulls the full page text back.

A page can run to tens of thousands of words. So the harness keeps only the top few results, and a few hundred words of each. Otherwise the context fills up. And be careful.

The model repeats what the page said. Whatever you paste in becomes its source of truth, so keep the link and show where the words came from. So, searching is a query, a list of snippets, and a paste back into the context. The web supplies the words.

The model reads them out. Quick check now. One question is coming up. Let's see if it clicked.

Read this lesson as text: What happens when a tool call goes wrong

What happens when a tool call goes wrong

What exactly is a failed tool call? Let's try to understand. In a simple way. Remember what a tool call really is.

Text the model predicted. Nothing has checked it. So it can be wrong before your code runs. Sometimes the call is the broken part.

It names a tool you never gave it, drops an argument, or sends a word where a number belongs. Other times the call is fine and the function still fails. The site is down. The file is missing.

Something divided by zero. So, rule one. Wrap the call so the error cannot escape. If it escapes, your whole agent stops mid conversation.

But catching it is not enough. The error happened inside your program. The model was not watching. It sees only the conversation.

And every tool call opens a slot, waiting for a result. Leave that slot empty, and the model has nothing to carry on from. So you fill it anyway. A message goes back where the answer would sit, saying the call failed.

To the model, that is ordinary text. The worst case is the quiet one. The function hides the error and returns an empty string. It looks like a real answer, and the model repeats it.

One more. A tool that never answers is a failure too. Put a clock on every call. When it runs out, stop waiting.

So, a call breaks before it runs, or while it runs. Catch it, never crash, always send something back. A failure it can read beats a silence. Quick check now.

One question is coming up. Let's see if it clicked.

Read this lesson as text: Telling the model how it made a mistake

Telling the model how it made a mistake

What exactly is a good error message for a model? Let's try to understand. In a simple way. Your program saw the error.

The model did not. All it ever sees is the text you send back. So that text is a prompt, not a log line. Now compare two messages.

The first one says, error, four hundred, bad request. True, but useless. It never says which part was wrong. With nothing to go on, the model guesses.

Usually it sends almost the same call again. You burn tokens and end up in the same place. The better message points at one thing. The tool, the argument, and the value that came in.

Say, start date was the word yesterday. Then show what would have worked. The expected shape, or the list of allowed values. Now fixing it is a lookup, not a guess.

And end with one instruction. Call it again with the date written as year, month, day. The model follows instructions, so give it one. Resist dumping fifty lines of stack trace.

Most of it is your own file paths. It fills the context and buries the one line that matters. Also say whether trying again could work. A wrong argument is fixable.

A server that is down is not. Otherwise the model retries forever. So, write the error like a note to a reader who gets one more try. What broke, what is allowed, what to do next.

Quick check now. One question is coming up. Let's see if it clicked.

Read this lesson as text: How to stop a model from getting stuck

How to stop a model from getting stuck

What exactly is a stuck agent? Let's try to understand. In a simple way. Remember the shape of an agent.

The model writes a call, your code runs it, the result goes back, and it goes again. Nothing in that circle says stop. So it can spin. The common shape is the repeat.

Same tool, same arguments, same error coming back. Three times, thirty times, it does not notice. The other shape is the ping pong. Search, then fetch, then search again.

Every step looks reasonable, and the pair goes nowhere. Why does it not notice? The model has no counter. It reads a transcript and predicts one more line.

Repeating is a likely continuation. So the counter lives in your code. Pick a number of steps, maybe ten. Count every trip around the loop.

When it runs out, the loop ends. Counting alone is blunt. So also watch for sameness. Store the tool name and arguments of every call.

If a new one matches an old one, that is a loop, not progress. When you catch it, say so. Send a line back. You already called this and got the same result.

Try something different, or answer with what you have. If it is still stuck at the limit, stop asking. Take the tools away for the last turn. Then the only thing left to write is an answer.

So, the loop will not end itself. Count the steps, spot the repeat, tell the model what you saw, and force a final answer. Quick check now. One question is coming up.

Let's see if it clicked.

Read this lesson as text: When a model invents an answer instead of searching

When a model invents an answer instead of searching

What exactly is an invented answer? Let's try to understand. In a simple way. You wired up a search tool.

Then you ask about this week, and the model answers instantly. No call, no result, just a smooth paragraph. Why does it skip? Because the model is still predicting the next words.

A finished sentence is always available. Calling a tool is only one option. And it has something to say, because training left a blurry memory in the weights. That memory is frozen.

It cannot tell which parts have gone stale. Here is the danger. The invented answer and the real one look the same. Same confidence, same tone.

The only difference sits in the transcript, where no tool was called. So the harness has to push. First, name the triggers. Prices, dates, people, anything after training.

For those, searching first is the rule, not a suggestion. Second, ask for the receipt. Every fact in the answer must be quoted from what the tool returned. No passage, no claim.

Third, do not just ask nicely. Read the transcript in your own code. If the answer makes a claim and no tool result is there, send it back. Fourth, leave it an exit.

Say plainly that, I could not find it, is a complete answer. If admitting is not allowed, inventing is the only way to finish. So, an invented answer is what fills the gap when nothing forces a lookup. Name the triggers, demand the source, check the transcript, and allow a no.

Quick check now. One question is coming up. Let's see if it clicked.

Read this lesson as text: What are guardrails for AI systems

What are guardrails for AI systems

What exactly is a guardrail? Let's try to understand. In a simple way. You wrote the rule in the prompt.

Never share the internal notes. And most of the time, the model follows it. But a prompt is a request, not a wall. A guardrail is the other thing.

A check that runs in your own code, around the model. Code cannot be argued with. It decides the same way every time. And it sits in one of two places.

On the way in, before text reaches the model. Or on the way out, before the answer leaves. The gate on the way in reads what arrived. Too long, the wrong shape, or a hidden instruction inside a pasted page.

Stop it here, and you never pay. The gate on the way out reads what came back. A card number, an internal link, a broken shape. The answer waits until that check passes.

Every guardrail is the same three parts. A rule you can write down. A test that answers yes or no. And a decision that follows.

And there are only three decisions. Let it through. Block it. Or repair it, cover the number, drop the bad line, and carry on.

Now the part people forget. What if the checker itself breaks? If it times out and everything sails through, the guardrail was never there. Fail closed.

So, a guardrail is a check in code, not a wish in a prompt. One gate in, one gate out, and a decision you can test. Quick check now. One question is coming up.

Let's see if it clicked.

Read this lesson as text: Keeping your model from going off topic

Keeping your model from going off topic

How does staying on topic work? Let's try to understand. In a simple way. You built a support bot for a shoe shop.

Someone asks it for a poem, and it writes a lovely one. Underneath your instructions, it is still a general model. Nothing in the weights knows where your product ends. It learned everything, so everything feels answerable.

The only fence is the one you write. So write it down as two lists. What you handle, orders, returns, sizing. And what you do not, everything else.

Stay professional is not a boundary. Then hand it the exit line, word for word. I can only help with orders and returns. Leave the refusal open, and the model improvises its way back in.

Stronger still, check before the big model runs. A small cheap classifier reads the message and labels it, in scope or out. Out of scope never reaches the model. Long chats drift.

One turn about a late delivery, then shipping in general, then trade policy. Nobody asked an off topic question. The conversation just walked there. So the scope goes back in on every turn, and it should sit near the end of what the model reads.

The last instruction wins. And read the answer too, not just the question. An in scope question can still produce a wandering reply. Same list, checked at the exit.

So, staying on topic is a written scope, one fixed refusal, a check going in, and a check coming out. The prompt asks. Your code decides. Quick check now.

One question is coming up. Let's see if it clicked.

Read this lesson as text: Preventing a model from taking risky actions

Preventing a model from taking risky actions

How does stopping a risky action work? Let's try to understand. In a simple way. Give a model tools, and it stops just talking.

A sentence becomes a deed. Bad text you can edit. A deleted account is simply gone. So sort your tools by what they can break.

Reading a record changes nothing. A refund moves real money. Dropping a table ends the day. The sharpest line is undo.

A saved draft can be taken back. An email sent, a payment made, a file deleted, cannot. And the model cannot feel any of this. It sounds equally calm reading one row and wiping the table.

Risk lives in your code, not its words. So put a pause between deciding and doing. The model picks the tool and the arguments. Your code holds that call and waits for a person.

And show the person what will really happen. Not, run the delete tool. Delete four hundred and twelve customer rows. What runs without asking is a short list you wrote.

Reading, searching, drafting. Anything not on that list stops and waits. Unknown means no. Then add ceilings the model cannot talk past.

Refund up to fifty dollars. Ten rows per call. A person gets tired. A number never does.

One more. The approver cannot be the model. Ask that same model whether its plan is safe, and you get one opinion, twice. So, rank tools by damage.

Pause before anything you cannot undo. Show the exact arguments. And let a person, or a hard limit, say yes. Quick check now.

One question is coming up. Let's see if it clicked.

Read this lesson as text: Forcing your model to speak structured data

Forcing your model to speak structured data

How does structured output work? Let's try to understand. In a simple way. Your code needs a number it can add up.

The model hands back a sentence. Sure, the order comes to forty two dollars. Lovely to read, impossible to parse. So you ask for JSON in the prompt.

Nine replies out of ten come back perfect. The tenth arrives wrapped in a friendly note, and your parser throws. So you stop asking and you declare. A schema lists the fields, their types, and which ones are required.

You hand it to the API next to the prompt. Here is what changes. At every step the model scores every token in its vocabulary. Constrained decoding reads the schema and sets the score of every illegal token to impossible.

So after an opening brace, only a quote can follow. After the word total, only a digit. The wrong token is not discouraged. It cannot be picked at all.

Now the trap. A guaranteed shape is not a guaranteed answer. Total will always hold a number. Nothing promises it is the right number.

So narrow what a field may hold. Status is one of three words, not free text. Quantity is a whole number above zero. And leave room for nothing.

If the schema demands a total, and the invoice has none, the model must invent one. Allow an empty field instead. So, structured output is a schema, enforced while the model decodes. The shape comes free.

The meaning is still yours to check. Quick check now. One question is coming up. Let's see if it clicked.

Read this lesson as text: How we know if an AI system works

How we know if an AI system works

How does testing an AI system work? Let's try to understand. In a simple way. You tweak the prompt, type three questions by hand, and the replies look fine.

So you ship it. That is a feeling, not a measurement. So you write the cases down instead. One file.

Each case holds an input, and what a correct answer has to contain. Fifty of them. That file is frozen. The same inputs run every time.

Change the cases and the model together, and the score tells you nothing. A grader in your code marks each case. Does the reply carry the order number. Is the JSON valid.

Was the search tool called. Sum the passes and you get one number. Forty three out of fifty. That single number is what you watch, run after run.

Where do cases come from? Real failures. A user breaks it, and that exact input becomes case fifty one. The same bug cannot slip back in quietly.

Careful with the average. Ninety percent passing can hide ten failures that are all refund questions. Group the cases by type. And an easy set flatters you.

Put the awkward ones in. Empty input, an angry user, a question your documents simply cannot answer. Then run the set automatically. A new prompt, a new model, a new tool.

The whole file runs before the change reaches a single user. So, knowing your system works is a frozen set of cases, a grader in code, and one number that you can watch move. Quick check now. One question is coming up.

Let's see if it clicked.

Read this lesson as text: Using an LLM to judge another LLM

Using an LLM to judge another LLM

What exactly is an LLM judge? Let's try to understand. In a simple way. Your grader in code checks exact things.

Does the reply contain the order number. But is this summary faithful to the document? String matching cannot decide that. So you hand that decision to a second model.

The question, the answer your system gave, and one instruction. Grade this. That second model is the judge. The judge is only as good as its instructions.

Spell the rule out. Mark this correct only if every fact appears in the document. And make it answer in a fixed shape. Pass or fail, plus one line of reason.

Now your script can count the passes, run after run. Do not ask for a score out of ten. Run the same answer twice and you get a seven, then a five. Pass or fail is steadier.

Grading blind is hard. So paste in the reference answer when you have one. Comparing two texts is much easier than judging from nothing. The judge has quirks.

Shown two answers, it leans towards the first one. So run it again with the order swapped. And a model rates its own writing highly. Remember the judge is a model too.

So label fifty answers by hand, and check how often it agrees with you. If it disagrees a lot, fix the rubric. So, an LLM judge is a second model with a written rubric, a fixed verdict, and an agreement score of its own. Use it only where code cannot decide.

Quick check now. One question is coming up. Let's see if it clicked.

Read this lesson as text: How evaluations make your prompts better

How evaluations make your prompts better

How does improving a prompt with evaluations work? Let's try to understand. In a simple way. You rewrite a prompt, read one reply, and it feels better.

So you keep it. But better than what? Without a score, every edit is a guess. So run your evaluation set first, before you change a word.

Thirty eight out of fifty. That is your baseline, the number every edit has to beat. Now do not stare at the score. Open the twelve failures.

Ten of them forgot the order number. The failures tell you what to write. So you add one line. Always repeat the order number back to the user.

One change, not three. Change three and you never learn which one helped. Run the same frozen fifty again. Forty five pass now.

Your edit just earned a number instead of an opinion. That gives you a rule. Score up, keep the line. Score down or flat, delete it and try something else.

But watch the cases, not just the total. Two refund cases that used to pass are failing now. Your new line stepped on them. So keep a small log.

Prompt version, score, one line on what changed. Then you can always walk back to the best one. One warning. Tune against the same fifty long enough and you are writing a prompt for those fifty.

Hold back a second set you never tune on. So evaluations turn prompt writing into a loop. Read the failures, make one change, run the set, keep whatever scores higher. Quick check now.

One question is coming up. Let's see if it clicked.