ealeng

Episode 27 of 56 · Multimodal & voice

RAG over scans and screenshots

How to build search and RAG over pages that have no text inside: scanned paper and screenshots. Three ways to index them (the file's own text, CLIP picture matching, and a vision model's transcript), three ways to answer from what search found, and why a misread name at index time is the error to check for.

Lab 08 · Multimodal & Voice10 min read
Video transcript6:16

0:00Cold open

0:01

Our assistant answers questions about our archive, scans and screenshots too. I asked it: what budget was approved for Project Tern?

0:10

It found the Tern memo. First place.

0:13

Then it answered: not found.

0:17

But the budget is right there on that page: ninety-one thousand dollars. I ran it three times. Not found, every time.

0:25

It found the right page. So why couldn’t it answer?

0:29Intro

0:30

Hands-on AI engineering: multimodal AI, part two.

0:35

Today: searching scans and screenshots, and a mistake made before anyone asks.

0:41Meet Fatima

0:42

I’m Fatima, an AI engineer. Our archive has twenty-four pages, of three kinds.

0:48

Eight invoices: P D F files with real text inside.

0:53

Eight scanned memos: just pictures of paper.

0:56

And eight dashboard screenshots, where the words are only pixels.

1:00

The pages are made up, by code, so every answer is known. The replies are real, from OpenAI’s gpt-5.4-nano.

1:11Two steps

1:12

Picture a filing clerk. When a page arrives, the clerk writes an index card for it: what the page says.

1:20

When a question comes, step one: find. The clerk compares the question with every card, and pulls the three closest pages.

1:29

Step two: read. The model answers from what it’s handed.

1:33

Search first, then answer. That’s called RAG.

1:37

So two things can go wrong: what’s written on the cards, and what the model gets to read.

1:4301 · Cards from the file

1:45

The usual way to write the cards: copy the text that’s stored inside each file.

1:50

An invoice P D F has text inside. Its card is full.

1:54

A scanned memo is just a picture. Its card is blank. So is every screenshot’s.

2:00

Sixteen of the twenty-four cards were blank.

2:04

So only the eight invoice questions got right answers, in every run.

2:1002 · Match by picture

2:11

Second idea: skip the cards. A model called CLIP compares the question with the picture of each page, without reading it.

2:19

I asked for Project Heron’s budget. Heron is a scanned memo.

2:24

CLIP’s top three: three invoices. Not one memo.

2:28

Over all twenty-four questions, it put the right page first only three times.

2:34

It doesn’t seem to read the names. For every memo question, its first pick was an invoice.

2:4003 · Write out every page

2:41

Third idea: when a page arrives, a vision model, one that can read images, writes out all of its text. That transcript becomes the card.

2:51

Here’s Heron’s card. The name is right, and so is the budget: eighty-six thousand dollars.

2:57

Now every page has a card. Did search put the right page in its top three?

3:04

All twenty-four questions. In all three runs.

3:08

Step one, finding the page, is solved.

3:1104 · Tern became Term

3:13

Now step two: the model answers from the three cards it’s handed. Remember Tern? Here’s its card.

3:20

Project Term. With an M. In all three runs.

3:24

The budget is on the card: ninety-one thousand dollars.

3:29

Search still pulled this card first, every time. Term is close enough to Tern.

3:34

The question says Tern. The card says Term. And the model answered: not found.

3:41

The slip happened once, when the page arrived. Any question that names Tern runs into it.

3:4704 · Tern became Term

3:49

Here’s every answer: one square per question, in three runs.

3:53

Reading the written-out cards: twenty-two or twenty-three right, out of twenty-four, in each run.

4:0005 · Hand over the pages

4:02

So instead of the cards, I handed the model the three page images. It reads the pages itself.

4:09

Did Tern come back?

4:12

Yes: ninety-one thousand, every run.

4:16

But a new miss appeared. Asked about Plover, it said eighty-three thousand, in two of three runs.

4:22

Plover’s real budget is sixty-six thousand. The eighty-three thousand belongs to Osprey, another memo it was handed.

4:31

Here’s Osprey’s memo, second of the three. The memos look almost alike.

4:37

With page images, the score was about the same as with cards, just with different misses.

4:4306 · Pages and cards

4:44

Last way: hand over both, the pages and their cards, with a note that cards can have reading errors.

4:51

How many right now?

4:55

Twenty-four of twenty-four. In all three runs.

4:59

This test doesn’t show why. My guess: the page covers the card’s slips, and the card covers the page’s.

5:0707 · The price

5:08

There’s a catch: the price. A model bills by tokens, small chunks of text. A picture costs a lot of tokens.

5:16

Pages and cards together: fifty-eight cents per thousand questions. Cards alone: six cents.

5:23

Writing the cards is paid only once, when a page arrives: about twenty-eight cents per thousand pages.

5:3108 · What I do now

5:32

So here’s what I do now. Every scan and screenshot gets a written-out card, once, when it arrives.

5:39

I check the cards against names I already know. A check against our project list would catch Term: it isn’t a project.

5:48

When the exact number matters, the model gets the pages and their cards.

5:53

And my tests have a question for every kind of page, run more than once. Plover went wrong in two runs, not all three.

6:01

Next time: voice. An agent that has to answer within one second.Fatima: Check what the cards say.

6:07Summary

6:08

Search only sees what the cards say.

6:11

So check the cards, and hand over the page too.Fatima: Thanks!

Key takeaways

  • ✓RAG over scans has two steps that fail differently: find the page (search an index), then read it (the model answers from what it's handed). Measure each step on its own.
  • ✓Indexing the text stored in each file works only for files that have any: 16 of 24 pages (scans and screenshots) had none, so only the 8 invoice questions could be answered. CLIP, which matches the question against each page's picture without reading it, put the right page first for 3 of 24.
  • ✓Having a vision model write out every page once, when it arrives, made every page findable: the right page was in the top 3 for 24 of 24 questions in all 3 runs. Answers from those transcripts were right 23, 23 and 22 times of 24.
  • ✓One misread name was the costly error: "Project Tern" was written down as "Project Term" in every run. Search still ranked it first, the card had the $91,000, and the answer was NOT FOUND every time. The lab's own earlier run wrote Plover as "Mover", and that memo fell out of the top 3.
  • ✓Handing the model the page images fixed Tern but took Osprey's budget for Plover in 2 of 3 runs. Images and transcripts together got 24 of 24 in every run, for 58¢ per 1,000 questions against 6¢ for transcripts alone. Check the transcripts against names you already know.

Search over documents (Chapters 7 to 11) assumes every page has text to search. Scanned paper and screenshots don't: a scan stored as a PDF is a picture of paper, and a dashboard screenshot is pixels. RAG over them still has the same two steps, find the pages and read them to answer, but each step now has to deal with images, and each can go wrong on its own. This chapter covers three ways to index image-only pages, three ways to answer from what search found, and the error that matters most: a misread name, written into the index once and met by every question after.

The code is Lab 08's 04_multimodal_rag.py, with CLIP as Script 3 (03_image_search.py) introduces it: its archive, prompts, top-3 search and scoring. The archive has 24 pages made by code, so every answer is known: 8 digital invoices (PDFs with text inside), 8 scanned memos (one per project: Heron, Kestrel, Osprey…) and 8 dashboard screenshots. Each question's answer is on exactly one page. The transcripts and answers are real recorded replies from gpt-5.4-nano, in 3 runs; each run rebuilt the index from scratch. Search uses text-embedding-3-small and sends the top 3 pages.

Two steps, two places to go wrong

Think of an index as a filing clerk who writes an index card for every page when it arrives.

  1. Find: compare the question with every card and pull the 3 closest pages. That's retrieval, scored by recall@k: is the right page in the top k (Chapter 8)?
  2. Read: the model answers from what it's handed, with the lab's instruction: "Answer from these pages only. Reply with the number only, or NOT FOUND."

For scans, the question at step 1 is what goes on the card. At step 2 it's what the model gets to read: the cards, the page images, or both.

Step 1: what goes on the card

Index Right page first Right page in the top 3
The file's own text (the usual pipeline) 8 of 24 8 of 24
CLIP: match the question against each page's picture 3 of 24 5 of 24
A vision model's transcript of each page 23, 22, 24 (3 runs) 24 of 24, every run

The file's own text. The standard pipeline pulls the text stored in each file and embeds it (Chapter 26 covers what that loses on a single page). An invoice PDF has text, so its card is full. All 8 scans and all 8 screenshots came out as (no text): 16 blank cards. Only the 8 invoice questions found their page, and only those were answered right, in every run.

CLIP. CLIP was trained on hundreds of millions of image–caption pairs, so a picture and a sentence describing it land close together in one embedding space. That lets you skip text entirely: embed the question and each page image, and search by similarity. It costs nothing per page and runs on a laptop. Here it put the right page first 3 times. For every memo question, its first pick was an invoice. For 7 of 8 dashboard questions its first pick was a dashboard, but the right one only once. It doesn't seem to read the names, which is all that tells one memo from another. Models trained on document pages, like ColPali, read small print far better; they want a GPU, and the lab names them but doesn't run them.

A vision model's transcript. When a page arrives, a vision model writes out all its text once, with the lab's prompt: "Transcribe this page for a search index: write out all its text, keeping labels next to their values. For charts, say what they show." The transcript becomes the card, and search runs on text as usual. Every page now had a card, and the right page was in the top 3 for every question, in every run. Writing the 24 cards cost about 28¢ per 1,000 pages, paid once.

Pick a way, then click any square to see the question, what search pulled, the right page's card, and the recorded answer:

Try it · Five ways to answer from the archiveLab 08 · script 4 · 24 questions × 3 runs

Step 1 · find

A vision model writes out each page once; the transcript is the card.

Right page in the top 3: 24 · 24 · 24 of 24

First: 23 · 22 · 24 (runs 1 · 2 · 3)

Step 2 · read

The model reads the 3 cards.

Right answers: 23 · 23 · 22 of 24 (runs 1 · 2 · 3)

~263 input tokens per question · $0.06 per 1,000 questions

right NOT FOUND wrong numberOne square per question; rows are runs 1, 2, 3. Click one.

Invoices (PDF text)

Scanned memos

Dashboard screenshots

What budget was approved for Project Tern?

run 1 · right answer 91,000

Search pulled

  1. 1. memo · Tern ← the right page
  2. 2. memo · Kestrel
  3. 3. memo · Heron

This page's card: the vision model's transcript

MEMO

Re: Project Term

Project Term is approved with a budget of $91,000.

The kickoff is in room 2.19.

Please send questions to the project office.

Model answered: NOT FOUND · NOT FOUND

Step 2: what the model reads

All three readers below use the transcript index for step 1, so they get the same 3 pages.

The model reads Right of 24 (3 runs) Input tokens per question Per 1,000 questions
The file's own text (its own index) 8, 8, 8 ~275 $0.06
The 3 transcripts (cards) 23, 23, 22 ~263 $0.06
The 3 page images 23, 24, 22 ~2,627 $0.53
Page images + their transcripts 24, 24, 24 ~2,869 $0.58

From the cards. Most answers were right. The miss in every run was Tern. Its memo says "Project Tern is approved with a budget of $91,000." Its transcript said "Project Term", with an m, in all 3 runs. Three facts sit side by side:

  • Search still ranked Tern's card first, every run: Term was close enough to Tern.
  • The card has the budget: $91,000.
  • The question says Tern, the card says Term, and the model answered NOT FOUND, every run.

Run 3 had a second slip of the same kind: the Adatum Cleaning invoice was written out as "Datum Cleaning", search ranked it first, and the answer was NOT FOUND. In both, the near-miss name was close enough for search, and the answer was NOT FOUND.

From the page images. The model reads the pages itself, so the transcript's slip doesn't matter: Tern came back right, 91,000, every run. But a new miss appeared. Asked Plover's budget, it said 83,000 in 2 of 3 runs. Plover's budget is 66,000; 83,000 is Osprey's, and Osprey's memo was the second of the 3 pages it was handed, every run. The memos share one template, so they look almost alike. Plover's own page was first each time. Run 3 also said NOT FOUND for Merlin.

Pages and cards. The model got the 3 images and their transcripts, with a note that the transcripts may contain reading errors. 24 of 24, in all 3 runs. The test doesn't show why. A likely reading: the image covers the transcript's slips (Tern), and the transcript covers the image's (Plover).

The slip that happens before anyone asks

Tern shows the cheaper kind of index error: the page was still found, and a reader with the image recovered it. The lab's own earlier run of the same code shows the expensive kind. Its transcript wrote Plover as "Project Mover", and for the Plover question that memo ranked 4th, just outside the 3 pages the model reads. When a page isn't retrieved, no reader can recover it, text or image.

An index-time error is made once and stays. Every question about that page meets it, until someone re-indexes. So check the cards when they're written. A cheap check: compare every name on a card with the names you already know (vendors, projects, services). Here, that flags every name slip in all 4 runs, including Mover:

Try it · Check the cardsevery name on a card vs. our vendor, project and service lists

2 of 24 cards name something that isn't on our lists. Answers from the cards went wrong on 1; the check flagged 1 of 1.

memo · expected Ibis · card says IbIs

Answer from the cards: 52000 · right

memo · expected Tern · card says Term

Answer from the cards: NOT FOUND · NOT FOUND

The check reads "Project …" on memos, the first line on invoices, and "…-service" on dashboards. It's case-sensitive, so "IbIs" is flagged too, though that answer was right.

Across the 3 runs, answers from the cards went wrong 4 times (Tern 3 times, Adatum once), and the check flagged all 4. It also flagged "IbIs" for Ibis, which was harmless: that answer was right. A flagged card can be re-transcribed at a higher resolution or with a stronger model, or sent to a person. The lab also suggests fuzzy keyword search next to embeddings, so near-miss names still match.

Where the cost goes

There are two bills, paid at different times:

  • At index time, once per page: writing the transcripts, about 28¢ per 1,000 pages.
  • At answer time, on every question: about 6¢ per 1,000 questions from the cards, 53¢ from the page images, 58¢ from both. Three images per question are what cost the tokens (Chapter 26 shows how image size drives them).

So the usual plan: transcribe every scan and screenshot when it arrives, search the transcripts, and answer from the cards by default. Hand over the pages and their cards when the exact number matters.

Caveats

  • Made-up pages, one template per kind. All 8 memos share one layout and differ only in the project name and numbers, which is what made Osprey's budget easy to mistake for Plover's. Real archives vary more, in ways that help and hurt.
  • Small numbers. 24 questions, one per page, 3 runs. One question more or less changes a score by 4 points. Plover went wrong in 2 of 3 runs, so a single run could have missed it.
  • The lab's README reports a different run. Its single run had the Mover slip, so its numbers are lower: the right page in the top 3 for 23 of 24, transcripts 22 of 24, page images 22 of 24 (missing Wren), both 23 of 24. This chapter uses the 3 fresh runs recorded for the video, where Plover was written out right every time.
  • CLIP is a general model. It was trained on photos and captions, not documents; a document-trained retriever was not tested.
  • The causes aren't tested. That Term caused the NOT FOUND, and that the two readers cover each other's slips, are the likely readings, not measured results.
  • The scoring is loose. The lab's is_right counts an answer right if any number in it equals the right one. With "number only" answers that's fine, but a reply listing several numbers could pass.

The checklist

  1. Measure finding and reading separately: recall@k per kind of page, then answer accuracy.
  2. Don't index scans and screenshots by their file text. Transcribe each page once with a vision model, or use a document-trained retriever, and measure it.
  3. Check every transcript when it's written, against names you already know. Re-transcribe or review what's flagged.
  4. Hand over the page images, with their transcripts, when the exact number matters. Record the cost per question.
  5. Test every kind of page, over more than one run. One run would have missed Plover.

Practice: check the index

Count how often search found the right page, using the recorded ranks for all 24 questions; write the lab's answer check; flag the names on a transcript that aren't on your lists, using the real "Term", "Datum Cleaning", "IbIs" and "Mover" cards; and work out what each way of answering costs per 1,000 questions.

Try it · Practice in PythonRuns in your browser · ⌘/Ctrl + Enter to run
scan_practice.py53 lines

Quiz

Try it · Check your understanding0 of 6 answered
  1. 1.A scanned memo is a PDF. Why can't standard text-based RAG find it?

  2. 2.CLIP put the right page first for 3 of 24 questions. What did it do with the memo questions?

  3. 3.With a vision model's transcripts as the index, every right page was in the top 3. Why was Tern still answered NOT FOUND?

  4. 4.Why is a misread name at index time worse than a misread at answer time?

  5. 5.Given the 3 page images, the model answered Plover's budget as 83,000 in 2 of 3 runs. Where did that number come from?

  6. 6.Pages and their transcripts together got 24 of 24 in every run. Why not always do that?

Check your understanding

Try each question before opening its answer.

What are the two steps of RAG, and why measure them separately?

Find: search pulls the closest pages (scored with recall@k). Read: the model answers from what it's handed (scored by answer accuracy). They fail differently: Tern's page was found first every run and still answered NOT FOUND, so only measuring both tells you which step to fix.

Why did indexing the file's own text answer only 8 of 24?

Only the 8 invoice PDFs had text stored in the file. The 8 scans and 8 screenshots came out as "(no text)", so their cards were blank and search couldn't find them.

What does CLIP do, and why did it do badly here?

It puts images and text in one embedding space, so a question can be matched against page pictures without reading them. It's free and local, but it doesn't read small print: for every memo question its first pick was an invoice, and it put the right page first for 3 of 24.

Why was "Project Term" found but not answered?

Search only needs a close match, so the card ranked first every run, and it had the $91,000. But the question asked about Tern and the card said Term, and the model answered NOT FOUND every time. Given the page image instead, it answered 91,000.

How would you catch index-time slips like Term and Mover?

Check each transcript when it's written: compare the names on it with the names you know (projects, vendors, services). Here that flagged every name slip, including all 4 wrong answers from the cards and the lab's "Mover". Re-transcribe or review flagged pages, and consider fuzzy keyword search next to embeddings.

When is it worth handing the model the page images?

When the exact number matters and the page is a scan, screenshot or form. Pages plus transcripts got 24 of 24 in every run, but cost about 58¢ per 1,000 questions against 6¢ for transcripts alone, paid on every question.

Next up: Chapter 28 moves from pages to voice: a phone agent that has to start speaking within a second, timed step by step.

Learning something useful?

New labs, articles, and videos on evaluating and testing AI systems come out regularly. Follow along so you don't miss the next one.