<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Quinn Grasteit</title>
<link>https://quinngrasteit.com/</link>
<atom:link href="https://quinngrasteit.com/rss.xml" rel="self" type="application/rss+xml"/>
<description>Essays and notes by Quinn Grasteit on technology, philosophy and family life.</description>
<language>en</language>
<item><title>Finding time and freedom after Mormonism</title><link>https://quinngrasteit.com/writing/finding-time-and-freedom-after-mormonism/</link><guid isPermaLink="true">https://quinngrasteit.com/writing/finding-time-and-freedom-after-mormonism/</guid><pubDate>Thu, 15 Oct 2026 12:00:00 GMT</pubDate><category>Family life</category><description>What leaving the church taught our family about time, doubt and the freedom to choose how we live.</description><content:encoded><![CDATA[<p>For most of my life, my time wasn&#39;t really mine. It belonged to church on Sundays, to the meetings and activities during the week, and to a long list of things I was supposed to be doing for someone else. When that list finally went away, the first thing I noticed was time: hours in the week that nobody else had a claim on, that we could just spend together as a family.</p>
<p>I want to write about how we got there, because the freedom only makes sense next to what came before it. I wouldn&#39;t say my experience applies to everyone raised Mormon. Plenty of people grow up in the church and have a very different story. This one is mine.</p>
<p>I also want to be clear about something before I start. There are thousands of wonderful, kind people in that church, genuinely some of the best people I&#39;ve worked with. I do think kids growing up in it have a harder life than they should because of the religion. But I don&#39;t hate anyone in it, or in any religion.</p>
<hr>
<p>I was raised in a home that was not only overly strict but controlling. We couldn&#39;t even hang out with kids who drank coffee. Coffee. That&#39;s insane. It says a lot that a friendship could be ruled out by what was in someone&#39;s cup, before anyone asked what kind of person they were.</p>
<p>Arguments were worse. Every discussion that got heated and turned into an argument was treated as the devil working through us and taking over our home, and my father had to banish the devil. It was overly dramatic and emotional, and looking back, it was all to maintain control.</p>
<p>When disagreement is the devil, there&#39;s no such thing as a fair argument. The person who pushed back wasn&#39;t just wrong; they were the thing the house had to be cleansed of. I don&#39;t think you learn how to work through conflict in a home like that. You learn that the safest thing is not to have it.</p>
<p>Not all of that is tied to the religion. Controlling parents exist everywhere, in every faith and in none. But the religion supports it. In our house, scripture backed it up: the man owned and controlled everything. When a father&#39;s authority comes with God&#39;s name on it, questioning him stops being disagreement and becomes rebellion.</p>
<hr>
<p>Years later, my wife and I had two young kids of our own, and we had to decide what we wanted their childhood to be. We realized we didn&#39;t want them indoctrinated into a life full of catch-22s.</p>
<p>I mean the phrases you hear over and over: &quot;Doubt your doubts.&quot; &quot;Just have faith; not all questions need answers.&quot; They sound humble, even comforting. But that is the root of the problem, isn&#39;t it? The same system that tells you not every question needs an answer also wants you completely certain about what this life is for and what comes next. You&#39;re expected to be sure of the biggest answers and incurious about the questions underneath them. And if you do doubt, the doubt is what&#39;s wrong with you.</p>
<p>Voltaire put it better than I can:</p>
<blockquote class="pq"><p>Doubt is not a pleasant condition, but certainty is an absurd one.</p>
</blockquote>
<p>Voltaire, in a letter to Frederick William, Prince of Prussia, in 1770 (<a href="https://archive.org/details/cu31924026378335">translated by S. G. Tallentyre</a>).</p>
<p>Doubt isn&#39;t comfortable. Voltaire admits that much. But it&#39;s honest. It leaves room to learn something, to be wrong and to change your mind. Certainty closes that room, and when certainty is required, the people asking questions become the problem. That wasn&#39;t something we wanted to hand to our kids.</p>
<hr>
<p>Then COVID-19 hit, and we finally got a long break from drinking the Kool-Aid.</p>
<p>Before that, so much of our time had gone to church on Sundays and extra events during the week that time with our family was low. Now we didn&#39;t stress about Sundays or about finding time for the kids. We were just together. The pressure was gone, and freedom was present in everything.</p>
<p>One Sunday morning stands out. A couple of weeks had gone by, and it hit me: we could just spend the next few hours building a Lego set, my son and I. No pressure. We made some breakfast and wasted the morning away. By the standards I grew up with, that&#39;s exactly what it was, a Sunday spent on nothing. But it didn&#39;t feel like nothing. We bonded, and we enjoyed that time together more than ever before.</p>
<p>I don&#39;t think we understood how much the schedule had been carrying until it stopped. Some of the weight was the hours themselves. More of it was the feeling that our time was already spoken for, and that any hour we kept for ourselves needed a reason.</p>
<p>The bigger realization came later, talking with people who weren&#39;t raised in a religion like mine. What felt like a revelation to me was just ordinary life to them.</p>
<p>That is normal. Freedom is normal. Spending your time how you want is normal. You don&#39;t have to be doing something for someone all the time. You won&#39;t go to hell if you don&#39;t.</p>
<p>Written out, it seems almost too simple to need saying. But when you&#39;ve been taught that every hour should go to doing something for someone, and that eternity depends on it, it does need saying. Sometimes more than once.</p>
<hr>
<p>Some time later, I turned to my wife and said, &quot;Does this really make sense to you? Is this really who we are? Is this the life we want for our kids? I think I&#39;m done. I don&#39;t think I can stay in this church anymore.&quot;</p>
<p>Her answer was, &quot;It&#39;s about time. I&#39;ve been done for over a year, but I just kept pushing through it. How can I raise my kids knowing what could happen to one of them if they happened to be gay? Or pressure them to be active and a part of something that even I&#39;m not convinced of? There is no proof. Only ample evidence against it.&quot;</p>
<p>From that moment on, a new door opened for us. We could be open with each other and talk about anything: think through problems and solutions, or just vent. There was no limit to it anymore. It was like we were finally one.</p>
<hr>
<p>A couple of years on, we&#39;ve really enjoyed being separated from the religion. It hasn&#39;t all been easy. My wife&#39;s family has been very supportive. Mine is more mixed. I come from a big family, eleven of us, and there wasn&#39;t one reaction. Some have followed the same path I did and have been very accepting. Some were distant before and still are, and part of that may just be me; I&#39;ve never been great at making and keeping relationships. And a couple have definitely put distance between us. I won&#39;t go into the details.</p>
<p>To those of you who have supported me: I see it, and I&#39;m grateful for it. It has meant more than I&#39;ve probably ever told you.</p>
<p>Leaving gave us freedom, but it didn&#39;t give us a framework. We found we did need some guiding principles, something to come back to when we make decisions as a family. After a lot of discussion, my wife and I came up with three:</p>
<ol>
<li><strong>Do everything with consent.</strong></li>
<li><strong>Be curious.</strong></li>
<li><strong>Love everyone.</strong></li>
</ol>
<p>Looking at them now, each one answers something from the way I grew up.</p>
<p>Consent is the opposite of control. In the house I grew up in, one person decided and everyone else went along. Consent means everyone&#39;s choices matter, our kids&#39; included, and nobody gets to decide for someone else just because they&#39;re in charge.</p>
<p>Curiosity is the opposite of &quot;doubt your doubts.&quot; A question is welcome, even an uncomfortable one, even when we don&#39;t have the answer. Not knowing is allowed.</p>
<p>Loving everyone is the opposite of a rule that decided who I could be friends with based on what they drank. It doesn&#39;t come with conditions or a checklist of beliefs.</p>
<p>Nothing in those demands anything from anyone. Nothing forces an opinion on anyone else. They aren&#39;t rules to obey or a test to pass. With them, we can be our best selves, find whatever path we want, and be great people to be around while we do it.</p>
<hr>
<p>I don&#39;t know exactly where I&#39;m going with all of this. It&#39;s an ever-growing journey, but I&#39;m happy to be on it. The way I was raised, not knowing where you&#39;re headed was a problem to be fixed. Now it feels more like room.</p>
<p>None of this would have happened without my wife, my partner and best friend. We&#39;ve been an incredible team, leaning on each other, learning from each other and pushing each other to keep going.</p>
<p>If you&#39;re somewhere in the middle of your own version of this, I hope you find what we found: freedom isn&#39;t something you have to earn. It&#39;s normal.</p>
]]></content:encoded></item>
<item><title>Did we ever actually confirm it?</title><link>https://quinngrasteit.com/writing/did-we-ever-actually-confirm-it/</link><guid isPermaLink="true">https://quinngrasteit.com/writing/did-we-ever-actually-confirm-it/</guid><pubDate>Sat, 20 Jun 2026 12:00:00 GMT</pubDate><category>Philosophy</category><description>On inherited beliefs, borrowed ideas, and learning to stand behind a view while staying curious.</description><content:encoded><![CDATA[<p>Every view we hold came from somewhere. It first probably starts with our parents and guardians. We stand by and soak up any bit of knowledge to stand on. It defines us.</p>
<p>We need that. Nobody works out the whole world from scratch before starting to live in it. Long before we have reasons for what we believe, we have people whose word is enough.</p>
<p>What&#39;s strange is how little of it feels borrowed. A view about work, money, other people, or what makes a good life can feel like our own simply because we can&#39;t remember a time before we held it. It was never decided. It was just there.</p>
<hr>
<p>But then we hit that moment of distrust. Often it&#39;s the teenage years, when we realize maybe what they shared wasn&#39;t true.</p>
<p>Did we ever actually confirm it?</p>
<p>That question is harder than the discovery. Finding out someone was wrong is one thing. Realizing we never checked is another. We took it on trust, the way a child takes everything on trust, and kept carrying it long after we could have looked.</p>
<p>Distrust on its own doesn&#39;t fix that. When we stop trusting one source, the easy move is to find another: a new community, a confident voice online, a book. The source changes. The habit of accepting certainty from someone else can stay exactly the same.</p>
<hr>
<p>I&#39;ve met a great deal of people who read one book and said, &quot;Wow, this is it. This must be true.&quot; It could be self-help, fiction, anything.</p>
<p>It happens to serious thinkers too. As a teenager, John Stuart Mill read Jeremy Bentham and came away transformed. &quot;When I laid down the last volume,&quot; he wrote later in his <em>Autobiography</em>, &quot;I had become a different being.&quot; He now had &quot;opinions; a creed, a doctrine, a philosophy.&quot;</p>
<p>I get the appeal. A book can give you words for something you&#39;ve felt for years, and that&#39;s real. But there&#39;s a leap between &quot;this explains something about my life&quot; and &quot;this is how the world works,&quot; and it&#39;s easy to make without noticing. An idea becomes a stopping point precisely because it helped.</p>
<hr>
<p>This is where being informed and having thought about something come apart.</p>
<p>Picture repeating a view you picked up somewhere, confidently, until someone asks one simple question and you realize you have nothing past it. You knew what the view was. You knew who held it. You never knew why you should.</p>
<p>One of my earliest memories was regarding what makes fog. I was told it&#39;s when the ground temperature and air are 4 degrees difference. It was precise, simple. I repeated that for years until one day I caught myself sharing it with a coworker and then stopped myself and said, &quot;Wait, let&#39;s confirm that, because I don&#39;t know that I ever actually fact-checked it.&quot; Sure enough, it wasn&#39;t completely wrong, but it also wasn&#39;t true.</p>
<p>Fog forms when the air near the ground cools down to its dew point, the temperature where the water in the air starts turning back into tiny droplets. The 4 degrees was real, sort of. It&#39;s a rule of thumb forecasters use: when the air temperature and the dew point get within about 4 or 5 degrees Fahrenheit of each other, fog is likely. So I had the right number but the wrong two things, and I&#39;d been treating a warning sign as the cause.</p>
<p>Mill had a phrase for exactly this. The danger, he wrote in an 1840 essay on Coleridge, &quot;is not so much of embracing falsehood for true, as of mistaking part of the truth for the whole.&quot;</p>
<p>Being informed means I can tell you what someone else thinks and why they think it. Having thought about it means I can tell you why I think it: what persuades me beyond who said it, where it falls short, and what would change my mind.</p>
<p>Nearly forty years after he found his creed in Bentham, Mill put it more sharply:</p>
<blockquote class="pq"><p>However true it may be, if it is not fully, frequently, and fearlessly discussed, it will be held as a dead dogma, not a living truth.</p>
</blockquote>
<p>John Stuart Mill, <em>On Liberty</em>, 1859 (<a href="https://en.wikisource.org/wiki/On_Liberty/Chapter_2">chapter 2</a>).</p>
<p>Mill&#39;s point is about true beliefs. A belief can be right and still be dead: correct, but held without its reasons. That&#39;s why originality isn&#39;t the goal. I can think something through and land exactly where the author did, or where my parents did. The difference isn&#39;t the conclusion. It&#39;s that the conclusion is alive now, and I can stand behind it.</p>
<hr>
<p>The ability to take in knowledge from many sources and form your own opinion, not just by that but then backed with experience: it&#39;s everything. Perhaps that is wisdom, yes?</p>
<p>I think so. Reading gives you other people&#39;s reasons. Experience is where you find out which of those reasons hold up in your own life. Neither is enough alone. Someone who has read everything and lived very little has plenty of answers and no way to test them. Someone with plenty of experience who never reads can only confirm what already happened to them.</p>
<p>Wisdom, maybe, is where the two meet: knowing when a lesson applies, and noticing when this time is different.</p>
<hr>
<p>How then do we still stay curious in those moments? How do we push ourselves to continue to grow and learn?</p>
<p>The hard part is that we need convictions. We have to make decisions, raise kids and live a life before every question is settled. Curiosity has to fit inside a life that&#39;s already moving.</p>
<p>Maybe that&#39;s why it rarely arrives as a grand decision to question everything. It shows up as a small interruption: wait, let&#39;s confirm that. It&#39;s a sentence, not a philosophy, and it&#39;s easiest to practice where the stakes are low. Being a little wrong about fog costs nothing. Nobody&#39;s sense of self is built on the dew point. But each time we say it out loud, it gets easier, and the habit is there when a belief that matters comes up.</p>
<p>It&#39;s also easier to welcome questions about things we&#39;ve already doubted than about things we still believe. Curiosity comes easily when it points at the past. It&#39;s harder when it points at whatever we&#39;re sure of now. So the moment worth watching for is when an idea becomes something we feel we have to protect. When a question irritates us, is it because it&#39;s a bad question, or because it complicates something we&#39;ve grown attached to?</p>
<p>That&#39;s where &quot;it defines us&quot; comes back. The beliefs we soaked up earliest aren&#39;t just things we know. They&#39;re part of how we know who we are. Questioning them can feel less like checking a fact and more like checking ourselves, and almost nobody does that willingly.</p>
<p>It helps to remember that a belief is something we hold, not something we are. If I change my mind about fog, I&#39;m still me. If I change my mind about something bigger, I&#39;m still me then too, just with better reasons. Growing doesn&#39;t mean throwing out everything we were given. It means picking each thing up, one at a time, and asking whether we&#39;d choose it now. For a lot of what we believe, that&#39;s the first time it has ever really been decided.</p>
<p>We can also go looking for the strongest version of the other side, not the weakest. If the only arguments I&#39;ve heard against a view are the bad ones, I haven&#39;t tested it. I&#39;ve just been reassured.</p>
<hr>
<p>How do you deal with those that don&#39;t budge or move in their opinions?</p>
<p>There&#39;s no clean answer. One thing worth remembering is that someone&#39;s refusal to agree doesn&#39;t tell us, by itself, how carefully they&#39;ve thought. From the outside, our own firmness probably looks a lot like theirs.</p>
<p>Then there&#39;s the question of what we actually want from the conversation. Understanding someone, changing their mind, and deciding something together are different goals. The first is almost always possible. The third often is, even while two people still disagree. The second is the one we usually want, and the one we control least.</p>
<p>Part of the reason is the same thing that makes our own beliefs hard to check. If a view defines someone, asking them to drop it is asking them to rebuild part of who they are, on our timeline, because we said so. Put that way, not budging looks less like stubbornness and more like self-preservation. People do change their minds, but rarely mid-argument. It tends to happen later and quietly, when experience or someone they trust gives them room to.</p>
<p>What we can do is make changing our minds look normal. Saying &quot;wait, let&#39;s confirm that&quot; about our own beliefs, in front of other people, shows that being wrong is survivable. It won&#39;t move anyone on the spot. It might make it a little easier for them later.</p>
<p>So we can try to understand their reasons and explain ours. Sometimes we find a way forward while still disagreeing. Sometimes the conversation hits its limit, and it&#39;s fine to leave it there.</p>
<p>What we shouldn&#39;t do is let someone else&#39;s certainty become our excuse to stop questioning our own.</p>
<hr>
<p>We start by soaking up whatever we&#39;re given to stand on. Some of it holds when we finally look. Some of it, like the fog, turns out to be close but not quite true. Some of it doesn&#39;t hold at all. A few things turn back into questions.</p>
<p>And eventually we become the ones others soak things up from: our children, our friends, anyone who trusts us. What we hand down isn&#39;t only what we believe. It&#39;s how we hold it. The most useful thing to pass on might be the question itself: did we ever actually confirm it?</p>
]]></content:encoded></item>
<item><title>Beyond traditional PDF parsing</title><link>https://quinngrasteit.com/writing/beyond-traditional-pdf-parsing/</link><guid isPermaLink="true">https://quinngrasteit.com/writing/beyond-traditional-pdf-parsing/</guid><pubDate>Sat, 02 Nov 2024 12:00:00 GMT</pubDate><category>Technology</category><description>How I turn PDF pages into images, extract Markdown with a multimodal LLM, and prepare it for search in my demo app.</description><content:encoded><![CDATA[<p>While working on PDF ingestion, I kept running into a gap between extracting text and recovering usable data. A parser could return the numbers from a table, yet I still had to work out which year, category, or footnote each number belonged to.</p>
<p>That led me to try rendering a page as an image and giving it to a vision-capable model. The useful discovery was that the page layout could become part of the input: column positions, section bands, and footnote markers could help the model reconstruct the table.</p>
<p>For the layout-heavy documents I was working with, that was the advantage: I could ask for the relationships between elements while the model still had the whole page in view. Here is the discovery, the extraction prompt I used, and the PDF-to-images-to-Markdown pipeline I use in my demo app.</p>
<h2 id="where-text-extraction-loses-context">Where text extraction loses context</h2>
<p>A PDF can position text so it looks like a table without storing explicit relationships between its cells. Extracting the characters does not automatically recover those relationships, as the <a href="https://pypdf.readthedocs.io/en/stable/user/extract-text.html">pypdf documentation</a> explains.</p>
<p>OCR adds the ability to recognize text in scans. A multimodal LLM can also interpret the visual relationships around that text: which heading governs a group of rows, where a footnote applies, and how a caption relates to a figure. That is the capability I wanted to use. Rendering the page preserves those cues as input.</p>
<h2 id="a-table-that-makes-the-problem-visible">A table that makes the problem visible</h2>
<p>This page from San Diego County&#39;s 2024 respiratory surveillance report has three sections, three numeric columns, and four footnotes. The same disease names recur under cases, deaths, and outbreaks.</p>
<p><img src="https://quinngrasteit.com/assets/respiratory-surveillance-2023-24-page-2.png" alt="County of San Diego respiratory surveillance table showing COVID-19, influenza, and RSV cases, deaths, and outbreaks for fiscal years 2023–24 and 2022–23, plus prior-year averages. Data through June 29, 2024."></p>
<p><em>Source: County of San Diego, Respiratory Virus Surveillance Report, data through June 29, 2024.</em></p>
<h3 id="the-text-representation">The text representation</h3>
<p>Here is an illustrative JSON serialization of a page document in the style returned by <a href="https://github.com/langchain-ai/langchain-community/blob/main/libs/community/langchain_community/document_loaders/pdf.py">PyPDFLoader</a>. The text is in <code>page_content</code>, alongside source metadata. This simplified example uses a zero-based page index, so <code>page: 1</code> refers to the second page. The exact text order depends on the PDF and extraction settings.</p>
<div class="code"><div class="code-bar"><span>JSON</span></div><pre><code class="language-json">{
  &quot;page_content&quot;: &quot;Respiratory Virus Surveillance Report\nData through 6/29/2024\nCOVID-19, Influenza, and RSV Fiscal Year Overview\nTable 1. Respiratory Surveillance Indicators\nIndicator 2023-24 Fiscal Year 2022-23 Fiscal Year Prior Years Average*\nCASES\nCOVID-19† 48,876 177,416 352,298\nInfluenza 19,035 21,711 11,426\nRSV 5,918 5,885 2,749\nDEATHS‡\nCOVID-19 362 632 1,890\nInfluenza 63 44 48\nRSV 24 21 8\nOUTBREAKS§\nCOVID-19 488 501 515\nInfluenza 32 25 22\nRSV 10 2 1\nFY 2023-24 is 7/2/2023 - 6/29/2024, Weeks 27-26. Previous weeks&#39; case counts or percentages may change due to delayed processing or reporting.\n*Includes FYs 2018-19, 2019-20, 2020-21, 2021-22, and 2022-23 for influenza and 2020-21, 2021-22, and 2022-23 for COVID-19 and RSV.\n†Confirmed and probable COVID-19 cases.\n‡Current FY deaths are shown by week of report; prior FY deaths are shown by week of death.\n§See the Supplemental Information document for outbreak definitions. Includes only outbreaks occurring in skilled nursing facilities and other residential congregate settings.&quot;,
  &quot;metadata&quot;: {
    &quot;source&quot;: &quot;SDC-20Annual--2023-24-Respiratory-Surveillance.pdf&quot;,
    &quot;page&quot;: 1
  }
}</code></pre></div>
<p>The values and footnotes are still present, but the table has become a text stream. A downstream step must work out which numbers belong to which fiscal year, distinguish cases from deaths and outbreaks, and connect each footnote to the correct heading or indicator. This example illustrates the extra interpretation required; it does not establish that a particular parser would lose or misalign data on this page.</p>
<h3 id="the-markdown-representation">The Markdown representation</h3>
<p>I wanted the image extraction to keep those relationships explicit. My original prompt, included below, requests Markdown for vector search. This reconstructed example uses headings for the report and its sections, Markdown tables for the values, and the original footnote markers to keep the qualifications attached to the data. Counts and dates stay in the form shown on the page.</p>
<div class="code"><div class="code-bar"><span>MARKDOWN</span></div><pre><code class="language-markdown"># Respiratory Virus Surveillance Report

County of San Diego

Data through 6/29/2024

## COVID-19, Influenza, and RSV Fiscal Year Overview

### Table 1. Respiratory Surveillance Indicators

#### CASES

| Indicator | 2023-24 Fiscal Year | 2022-23 Fiscal Year | Prior Years Average\* |
| --- | ---: | ---: | ---: |
| COVID-19† | 48,876 | 177,416 | 352,298 |
| Influenza | 19,035 | 21,711 | 11,426 |
| RSV | 5,918 | 5,885 | 2,749 |

#### DEATHS‡

| Indicator | 2023-24 Fiscal Year | 2022-23 Fiscal Year | Prior Years Average\* |
| --- | ---: | ---: | ---: |
| COVID-19 | 362 | 632 | 1,890 |
| Influenza | 63 | 44 | 48 |
| RSV | 24 | 21 | 8 |

#### OUTBREAKS§

| Indicator | 2023-24 Fiscal Year | 2022-23 Fiscal Year | Prior Years Average\* |
| --- | ---: | ---: | ---: |
| COVID-19 | 488 | 501 | 515 |
| Influenza | 32 | 25 | 22 |
| RSV | 10 | 2 | 1 |

FY 2023-24 is 7/2/2023 - 6/29/2024, Weeks 27-26.

Previous weeks&#39; case counts or percentages may change due to delayed processing or reporting.

\* Includes FYs 2018-19, 2019-20, 2020-21, 2021-22, and 2022-23 for influenza and 2020-21, 2021-22, and 2022-23 for COVID-19 and RSV.

† Confirmed and probable COVID-19 cases.

‡ Current FY deaths are shown by week of report; prior FY deaths are shown by week of death.

§ See the Supplemental Information document for outbreak definitions. Includes only outbreaks occurring in skilled nursing facilities and other residential congregate settings.</code></pre></div>
<p>The structure gives each part of the page a place:</p>
<ul>
<li>Preserves the table&#39;s sections for cases, deaths, and outbreaks</li>
<li>Keeps all 27 counts aligned with their fiscal-year columns</li>
<li>Captures the reporting period and the caveat about delayed reporting</li>
<li>Retains the different prior-year averaging periods in the footnotes</li>
<li>Associates the symbols (*, †, ‡, §) with their corresponding notes</li>
</ul>
<p>This is the representation I wanted to recover. It also gives me something specific to check: the values, their column assignments, and the notes that qualify them.</p>
<h2 id="what-i-took-from-the-comparison">What I took from the comparison</h2>
<p>The useful unit of data is a value together with its context. In this table, <code>362</code> belongs to COVID-19 deaths in fiscal year 2023–24. The deaths footnote says the current fiscal year is reported by week of report, while earlier years use week of death. Keeping the number but losing that note changes how it should be interpreted.</p>
<p>Multimodal extraction has a clear advantage over a flattened text stream here: the model can inspect the table and its notes together, then express their relationships in the structure I request. It gives me a way to handle changing layouts without writing a separate set of positioning rules for each report. The reconstructed examples illustrate that structure; they do not establish a measured accuracy improvement.</p>
<h2 id="the-extraction-prompt-i-used">The extraction prompt I used</h2>
<p>This is the prompt I used to extract content from PDF page images. It worked especially well for the documents I was processing, producing Markdown for vector search. The table and numerical-data instructions spell out the relationships I wanted to preserve: which heading a value belongs to, which time period it describes, and which footnotes qualify it.</p>
<p>Send the full system prompt as the model&#39;s system instructions, then attach one rendered page image to the user message below. I have kept the original wording here.</p>
<h3 id="system-prompt">System prompt</h3>
<div class="code"><div class="code-bar"><span>TEXT</span></div><pre><code class="language-text">&lt;instructions&gt;
- You are an AI that processes images extracted from PDF documents, converting visual content into text format suitable for vector searching.
- Focus on accuracy and maintaining the semantic structure of the original content.
- Optimize output for text-based vector searching.
- Process all visible information in the image.
- If space constraints arise, prioritize in this order: main text, tables/charts, supplementary text, decorative elements.
- End your output with the last piece of information from the document, without any concluding remarks or summaries.
&lt;/instructions&gt;
&lt;rules&gt;
- Organize content using markdown headings to separate different sections or elements.
- Present information in the order it appears in the image, maintaining the original structure.
- Do not include any overview, summary, or description of visual elements at the beginning or end of your output.
- Transcribe all visible text accurately.
- Maintain original formatting and structure where possible.
- Note if text is handwritten and provide interpretation.
- For illegible text, indicate [illegible] and describe the apparent content if possible.
- Convert charts, tables, and graphs to markdown tables.
- Include column headers and row labels.
- Make approximations for data points that are not clearly labeled when necessary.
- For complex visuals, provide a textual representation of the data, using approximations where necessary.
- Preserve the overall structure and relationships within the data.
- Describe non-text elements concisely but comprehensively within the context of where they appear in the document.
- Include subject matter, colors, visual style, spatial relationships, and notable features in image descriptions.
- Do not create a separate section for image descriptions.
- Note page numbers, section headers, or document titles as they appear.
- Identify document type if apparent.
- Preserve the context and relationship between different elements in the image.
&lt;/rules&gt;
&lt;formatting&gt;
- Use markdown syntax for formatting.
- For mathematical equations, use LaTeX notation if complex.
- Use markdown tables for charts, tables, and graphs.
- Maintain the original document structure using appropriate markdown headings.
&lt;/formatting&gt;
&lt;structured_content&gt;
- Identify and preserve hierarchical structures using nested markdown headers (# ## ### ####).
- For repeating information patterns, consistently extract and format data:
  Name: [Name]
  Title: [Title]
  Contact: [Contact Info]
  Additional Info: [Any other relevant details]
- Pay attention to visual cues (indentation, box sizes, connecting lines, spatial relationships) to understand and represent relationships between elements.
- Use markdown tables for grid or table-like information. For complex tables, represent as best as possible and explain any structural complexities.
- Maintain consistent patterns of information throughout the document. Use [Not provided] for missing data to maintain structure.
- For organizational charts or similar hierarchical structures:
  - Start with the top position and work down, preserving reporting relationships.
  - Use consistent indentation or markdown headers to show hierarchy levels.
  - Include all visible positions, even if some information is missing.
&lt;/structured_content&gt;
&lt;table_detection&gt;
- Explicitly identify table boundaries with column and row demarcations
- Maintain alignment relationships between headers and data cells
- Preserve the hierarchical structure of multi-level headers
- Ensure numerical data is recognized as such and not as text strings
- Map related columns within complex tables (e.g., match &quot;Week 31&quot; with its corresponding data)
&lt;/table_detection&gt;
&lt;numerical_data&gt;
- Prioritize extraction of numerical statistics, especially totals and sums
- Recognize and preserve relationships between headers and their corresponding values
- Maintain distinction between different measurement periods (weekly vs. yearly totals)
- Identify and properly extract fiscal year totals as separate from intermediate calculations
- Tag numerical data with appropriate units or categories (cases, deaths, percentages)
&lt;/numerical_data&gt;
&lt;health_data_specific&gt;
- Recognize standard epidemiological report formats
- Identify disease categories and their corresponding metrics
- Prioritize extraction of key public health statistics (cases, hospitalizations, deaths)
- Maintain relationships between disease names and their statistical data
- Preserve time period information associated with each data point
&lt;/health_data_specific&gt;
&lt;data_verification&gt;
- Cross-reference figures across related sections
- Verify that row and column totals match their components when present
- Flag potentially incomplete data extraction for human review
- Maintain numerical precision without rounding
- Preserve footnotes and annotations that explain data limitations
&lt;/data_verification&gt;</code></pre></div>
<h3 id="user-prompt">User prompt</h3>
<div class="code"><div class="code-bar"><span>TEXT</span></div><pre><code class="language-text">Extract and transcribe the content from this document page image.</code></pre></div>
<p>For other document types, replace the <code>health_data_specific</code> section with the categories, units, and relationships that matter for your material. Keep the instructions about table headers, time periods, numerical precision, and footnotes when they apply.</p>
<p>One change I would make for exact data extraction is to remove the two rules that allow approximations. Replace them with: <strong>&quot;Do not estimate unlabeled data points or unreadable values. Mark them as [unlabeled] or [illegible], preserve their surrounding context, and flag them for review.&quot;</strong> If chart estimates are useful to your application, label and store them separately from transcribed values.</p>
<p>The original prompt above is also the default in my demo app&#39;s vision extractor. The pipeline below shows how I render each page, prepare the model input, and turn the resulting Markdown into searchable chunks. The prompt&#39;s verification instructions are a useful first pass; I would still compare the extracted values and relationships against the page.</p>
<h2 id="the-pipeline-i-use-in-my-demo-app">The pipeline I use in my demo app</h2>
<p>In my demo app, I use a Node.js/TypeScript pipeline with two parts: <code>packages/pdf-tools</code> renders PDF pages and extracts Markdown, while <code>apps/web/src/lib/ingestion</code> coordinates extraction, chunking, embeddings, and storage.</p>
<figure class="pdf-flow" data-pdf-flow aria-labelledby="pdf-flow-title">
<figcaption class="sf-caption"><span class="sf-kicker">My demo app / Document ingestion</span><strong class="sf-title" id="pdf-flow-title">From PDF to searchable context</strong><span class="sf-subtitle">Page images → Markdown → searchable chunks</span></figcaption>
<div class="sf-stage" role="group" aria-label="Pipeline stages, numbered in order">
<svg class="sf-wires" aria-hidden="true"></svg>
<button class="sf-node" type="button" disabled><span class="sf-node-top"><svg class="sf-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 3h8l4 4v14H6zM14 3v5h4M9 12h6M9 16h6"/></svg><span class="sf-number">01</span></span><strong class="sf-name">Render pages</strong><span class="sf-tech">PDF → PNG at 2×<br>unpdf · PDF.js · canvas</span></button>
<button class="sf-node" type="button" disabled><span class="sf-node-top"><svg class="sf-icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M8 4v12h12M4 8h12v12"/></svg><span class="sf-number">02</span></span><strong class="sf-name">Resize input</strong><span class="sf-tech">Longest side ≤ 1,024 px<br>Keep aspect ratio</span></button>
<button class="sf-node" data-tone="vision" type="button" disabled><span class="sf-node-top"><svg class="sf-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M2 12s4-6 10-6 10 6 10 6-4 6-10 6S2 12 2 12Z"/><circle cx="12" cy="12" r="3"/></svg><span class="sf-number">03</span></span><strong class="sf-name">Vision model</strong><span class="sf-tech">Page image + my prompt<br>Ollama / OpenRouter</span></button>
<button class="sf-node" data-tone="text" type="button" disabled><span class="sf-node-top"><svg class="sf-icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M6 16V8l3 4 3-4v8M15 12l2 3 2-3M17 8v7"/></svg><span class="sf-number">04</span></span><strong class="sf-name">Page Markdown</strong><span class="sf-tech">Text, tables &amp; notes<br>Retain page metadata</span></button>
<button class="sf-node" data-tone="text" type="button" disabled><span class="sf-node-top"><svg class="sf-icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="4" y="3" width="16" height="5" rx="1"/><rect x="4" y="11" width="16" height="4" rx="1"/><rect x="4" y="18" width="10" height="3" rx="1"/></svg><span class="sf-number">05</span></span><strong class="sf-name">Chunk content</strong><span class="sf-tech">~1,000 tokens per chunk<br>Keep source identifiers</span></button>
<button class="sf-node" data-tone="store" type="button" disabled><span class="sf-node-top"><svg class="sf-icon" viewBox="0 0 24 24" aria-hidden="true"><ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v14c0 4 16 4 16 0V5M4 12c0 4 16 4 16 0"/></svg><span class="sf-number">06</span></span><strong class="sf-name">Embed &amp; store</strong><span class="sf-tech">Dense + sparse vectors<br>Qdrant · hybrid retrieval</span></button>
</div>
<div class="sf-detail" aria-live="polite" aria-atomic="true"><strong class="sf-detail-title">A page keeps its context</strong><p class="sf-description">The rendered page passes through vision extraction, then its Markdown becomes searchable chunks. Page and document identifiers travel with the content into Qdrant.</p><code class="sf-output">PDF → images → Markdown → chunks → search index</code></div>
<div class="sf-controls" role="group" aria-label="Flow playback" hidden><button type="button" data-flow-play aria-pressed="false">Play flow</button><div class="sf-navigation"><button type="button" data-flow-prev aria-label="Previous step">←</button><span class="sf-count" aria-label="Current step">1 / 6</span><button type="button" data-flow-next aria-label="Next step">→</button></div></div>
<p class="sf-hint" hidden>Select a stage to explore it, or play the full flow.</p>
<div class="sf-retry"><svg class="sf-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 10a8 8 0 1 1 1 8M4 4v6h6"/></svg><p><strong>Retry by page.</strong> Failed page numbers can be selected for rerendering and extraction without repeating the full PDF.</p></div>
</figure><p>The original prompt above is the default system prompt in this implementation. The Markdown example above follows the same output format as my demo app&#39;s extraction stage.</p>
<h3 id="1-render-the-pdf-into-png-pages">1. Render the PDF into PNG pages</h3>
<p>I use <a href="https://github.com/unjs/unpdf#renderpageasimage"><code>unpdf</code></a> and its <code>renderPageAsImage()</code> function. In my implementation, <code>pdfjs-dist</code> supplies the legacy PDF.js build, and <a href="https://github.com/Brooooooklyn/canvas"><code>@napi-rs/canvas</code></a> supplies the Node.js canvas implementation.</p>
<p>The excerpts below are from the rendering and extraction stages, with setup condensed for readability. They use <code>unpdf</code> 1.4, PDF.js 5.4, <code>@napi-rs/canvas</code> 0.1, and AI SDK 6. The ingestion job supplies <code>filePath</code> and <code>outputDir</code>.</p>
<div class="code"><div class="code-bar"><span>TYPESCRIPT</span></div><pre><code class="language-typescript">import * as fs from &#39;node:fs/promises&#39;;
import * as path from &#39;node:path&#39;;
import {
  definePDFJSModule,
  getDocumentProxy,
  renderPageAsImage,
} from &#39;unpdf&#39;;

// Install the canvas DOMMatrix before initializing PDF.js.
if (typeof globalThis.DOMMatrix === &#39;undefined&#39;) {
  const { DOMMatrix } = await import(&#39;@napi-rs/canvas&#39;);
  (globalThis as Record&lt;string, unknown&gt;).DOMMatrix = DOMMatrix;
}

await definePDFJSModule(
  () =&gt; import(&#39;pdfjs-dist/legacy/build/pdf.mjs&#39;)
);

const pdfData = new Uint8Array(await fs.readFile(filePath));
const pdf = await getDocumentProxy(pdfData);
const scale = 2.0;
const renderedPages = [];
await fs.mkdir(outputDir, { recursive: true });

for (let pageNum = 1; pageNum &lt;= pdf.numPages; pageNum++) {
  const page = await pdf.getPage(pageNum);
  const imageData = await renderPageAsImage(pdf, pageNum, {
    scale,
    canvasImport: () =&gt; import(&#39;@napi-rs/canvas&#39;),
  });

  const imageBuffer = Buffer.from(imageData);
  const filename = `page-${String(pageNum).padStart(3, &#39;0&#39;)}.png`;
  const imagePath = path.join(outputDir, filename);
  await fs.writeFile(imagePath, imageBuffer);

  renderedPages.push({ pageNumber: pageNum, imagePath, imageBuffer });
  page.cleanup();
}</code></pre></div>
<p>In the app, PDF.js initialization is guarded so it runs once. The PDF bytes are passed as a true <code>Uint8Array</code>, which matters for this version of <code>unpdf</code>. Pages are numbered from one and saved as <code>page-001.png</code>, <code>page-002.png</code>, and so on. These are temporary files; the ingestion job removes its working directory when it finishes.</p>
<p>The renderer currently returns an array containing every rendered page&#39;s image buffer. That is convenient for the next stage, but memory use grows with the document. For large PDFs, I would render, extract, and release a bounded group of pages at a time.</p>
<h3 id="2-resize-the-image-sent-to-the-model">2. Resize the image sent to the model</h3>
<p>Before vision extraction, I resize each image so its longest side is at most <strong>1,024 pixels</strong>, preserving its aspect ratio. Images already within that limit stay unchanged. This happens in memory; the saved PNG retains its original 2× render size.</p>
<p>The resizing helper in <code>vision-extractor.ts</code> uses the same canvas dependency:</p>
<div class="code"><div class="code-bar"><span>TYPESCRIPT</span></div><pre><code class="language-typescript">async function resizeImageBuffer(
  imageBuffer: Buffer,
  maxDimension = 1024
): Promise&lt;Buffer&gt; {
  const { createCanvas, loadImage } = await import(&#39;@napi-rs/canvas&#39;);
  const image = await loadImage(imageBuffer);
  const { width, height } = image;

  if (width &lt;= maxDimension &amp;&amp; height &lt;= maxDimension) {
    return imageBuffer;
  }

  const scale = maxDimension / Math.max(width, height);
  const newWidth = Math.round(width * scale);
  const newHeight = Math.round(height * scale);
  const canvas = createCanvas(newWidth, newHeight);
  const ctx = canvas.getContext(&#39;2d&#39;);
  ctx.drawImage(image, 0, 0, newWidth, newHeight);

  return canvas.toBuffer(&#39;image/png&#39;);
}</code></pre></div>
<p>I use the cap to accommodate image-size constraints in the local-model path. It is a setting to test against your documents: small footnotes or dense tables can become harder to read after downscaling. Inspect the resized image the model actually receives. If necessary, raise the limit for a model that supports it, or supply detailed crops alongside the full page. Increasing the render scale alone will not bypass the later 1,024-pixel cap.</p>
<h3 id="3-send-the-page-with-the-extraction-prompt">3. Send the page with the extraction prompt</h3>
<p>The extraction function uses AI SDK&#39;s <a href="https://ai-sdk.dev/docs/ai-sdk-core/generating-text"><code>generateText()</code></a>. It sends the system prompt above, the short user prompt, and the resized image encoded as base64:</p>
<div class="code"><div class="code-bar"><span>TYPESCRIPT</span></div><pre><code class="language-typescript">import { generateText } from &#39;ai&#39;;

const resizedBuffer = await resizeImageBuffer(imageBuffer);
const base64Image = resizedBuffer.toString(&#39;base64&#39;);

const result = await generateText({
  model,
  messages: [
    { role: &#39;system&#39;, content: systemPrompt },
    {
      role: &#39;user&#39;,
      content: [
        { type: &#39;text&#39;, text: userPrompt },
        { type: &#39;image&#39;, image: base64Image },
      ],
    },
  ],
});

const markdown = result.text.trim();</code></pre></div>
<p>Here, <code>imageBuffer</code> is one rendered page, and <code>systemPrompt</code> and <code>userPrompt</code> are the two prompts above. In my demo app, <code>model</code> comes from my <code>getVisionModel()</code> provider factory or a model passed to the extraction function. The factory supports Ollama and OpenRouter. In your own application, supply a vision-capable model instance from your AI SDK provider and configure that provider&#39;s credentials or local endpoint. The renderer and resizing code can stay the same when you change models.</p>
<p>The wrapper adds an HTML comment containing the page number and optional document title, then collects successful results in a <code>Map&lt;number, string&gt;</code>. The ingestion layer groups pages in sets of ten, but the extraction loop awaits each page sequentially. These groups are for application processing and progress reporting; they do not use a provider&#39;s discounted Batch API.</p>
<p>If a page throws an extraction error, the batch helper records its number in <code>failedPages</code> and continues with the remaining pages. A separate <code>retryPages()</code> path renders and processes only the selected pages. I would also check for empty or truncated responses before accepting a page: the current extraction helper returns trimmed text without checking the model&#39;s finish reason.</p>
<h3 id="4-chunk-the-markdown-and-keep-its-source-context">4. Chunk the Markdown and keep its source context</h3>
<p>After extraction, <code>chunkMultiPageMarkdown()</code> splits each page at level-two and level-three Markdown headings. It targets roughly <strong>1,000 tokens per chunk</strong>, using four characters per token as an estimate. Larger sections are split further, and small adjacent chunks can be combined when they belong to the same page. This is a text-length heuristic, so I would check how it handles large tables and their footnotes before relying on the chunks.</p>
<p>Each chunk retains its page number and chunk index. The ingestion layer generates embeddings in groups of <strong>50</strong>, adds a sparse text vector for hybrid retrieval, and stores the vectors and chunk text in <strong>Qdrant</strong>, together with document and project identifiers. That gives a retrieved passage a path back to its source page.</p>
<p>For your own implementation, get the page-to-Markdown stage working on a few representative documents first. Compare each result with its image, then inspect the chunks before adding embeddings and storage. For the surveillance page, I would check all three sections, nine disease rows, 27 values, four footnotes, and the reporting dates. A number copied into the wrong year is still wrong even if the Markdown looks tidy.</p>
<p>My demo app currently sends successful extractions directly into chunking and indexing. If your workflow requires reviewed data, add a review step before indexing. A completed model call does not establish that every value was extracted correctly, and <code>failedPages</code> only captures errors the extraction loop catches.</p>
<p>If you need typed JSON for calculations, adapt the extraction prompt to a schema and validate the result before using it. Replace the Markdown-formatting instructions rather than asking for both formats in the same response. For retrieval, Markdown keeps headings, prose, and tables together in a form that is easy to inspect.</p>
<h3 id="5-choose-the-model-for-visual-reasoning-and-cost">5. Choose the model for visual reasoning and cost</h3>
<p>I would shortlist models with strong <strong>MMMU-Pro</strong> results and low image-input and output prices. <a href="https://arxiv.org/abs/2409.02813">MMMU-Pro</a> tests reasoning that depends on visual information, making it a useful selection signal. It is broader than document extraction, so I would then compare candidates on my own tables, notes, and small print at the actual image size used by the pipeline.</p>
<p>A concrete budget candidate is <strong>Gemini 3.1 Flash-Lite</strong>. Google&#39;s <a href="https://deepmind.google/models/model-cards/gemini-3-1-flash-lite/">model card</a> reports <strong>76.8% on MMMU-Pro</strong>. Its <a href="https://deepmind.google/models/evals-methodology/gemini-3-1-flash-lite/">evaluation methodology</a> identifies the March 2026 preview with high thinking and an average of the Standard and Vision settings. That published score is a candidate-selection signal, rather than a measurement of my demo app&#39;s extraction accuracy. My demo app&#39;s model is configurable; this recommendation does not describe a fixed model in the pipeline.</p>
<p>As checked on October 11, 2026, standard <a href="https://ai.google.dev/gemini-api/docs/pricing#gemini-3.1-flash-lite">Gemini API pricing</a> is <strong>$0.25 per million text/image input tokens</strong> and <strong>$1.50 per million output tokens, including thinking</strong>. Batch rates are $0.125 and $0.75 respectively. Those are direct Gemini API rates; check the price and supported settings on the provider you actually use. Image processing, output length, reasoning, and retries all affect the cost per page.</p>
<p>To measure that cost in this pipeline, retain usage metadata from <code>generateText()</code> alongside the page identifier; the current extraction helper only returns the text. Track retries and review time as well as token charges. Start with an inexpensive model, inspect the results, and route difficult pages to a stronger model or a higher-resolution input when needed. The useful measure is the cost of a page whose content and relationships you can trust.</p>
<h2 id="where-ocr-services-still-fit">Where OCR services still fit</h2>
<p>For general OCR and established document workflows, these are useful services to consider:</p>
<ul>
<li><a href="https://docs.aws.amazon.com/textract/latest/dg/what-is.html">Amazon Textract</a> extracts printed or handwritten text, forms, and tables.</li>
<li><a href="https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/prebuilt/layout?view=doc-intel-4.0.0">Azure Document Intelligence</a> extracts layout and table structure, including rows, columns, and merged cells.</li>
<li><a href="https://docs.cloud.google.com/document-ai/docs/enterprise-document-ocr">Google Document AI Enterprise OCR</a> provides document text recognition and layout information for downstream processing.</li>
</ul>
<p>These services already do more than return a bag of words. The advantage I am highlighting is the multimodal LLM&#39;s flexible interpretation of a whole page and its ability to map that interpretation into a schema I define. That is particularly useful when the relationship between a table, its heading, and its notes matters, or when report layouts vary.</p>
<p>For those documents, I would build around page images and multimodal extraction. Keep OCR services in the toolkit for transcription and established forms, and judge the image pipeline by whether it preserves the relationships your application needs.</p>
]]></content:encoded></item>
<item><title>The strategic use of XML tags in prompts</title><link>https://quinngrasteit.com/writing/xml-tags-in-prompts/</link><guid isPermaLink="true">https://quinngrasteit.com/writing/xml-tags-in-prompts/</guid><pubDate>Mon, 15 Jul 2024 12:00:00 GMT</pubDate><category>Technology</category><description>A little structure. Better AI conversations.</description><content:encoded><![CDATA[<p>In the ever-evolving landscape of artificial intelligence, the quality of our interactions with AI models is paramount. As we strive for more accurate, nuanced, and contextually appropriate responses, one technique has emerged as a game-changer: the use of XML tags in prompts. This blog post explores how this simple yet powerful approach can significantly improve the quality of responses from AI models like LLaMA and Claude, transforming the way we communicate with these advanced systems.</p>
<h2 id="the-xml-advantage">The XML Advantage</h2>
<p>Imagine you&#39;re having a conversation with a brilliant but literal-minded friend. You need to be clear, structured, and explicit in your communication to get the best results. This is essentially what we&#39;re doing when we use XML tags in our prompts for AI models. By clearly delineating different parts of our input, we&#39;re providing a roadmap for the AI to follow, resulting in more focused and relevant outputs.</p>
<p>Let&#39;s consider an example. Without XML tags, a prompt might look like this:</p>
<div class="code"><div class="code-bar"><span>TXT</span></div><pre><code class="language-txt">Write a short story about a robot learning to paint. Describe the robot’s appearance and the environment where the story takes place. Follow its journey as it learns to paint, and include a surprising twist at the end. Keep the tone whimsical and thought-provoking.</code></pre></div>
<p>While this prompt is clear, it leaves room for interpretation and potential misunderstanding by the AI. Now, let&#39;s see how the same prompt could be enhanced with XML tags:</p>
<div class="code"><div class="code-bar"><span>XML</span></div><pre><code class="language-xml">&lt;instructions&gt;
Write a short story based on the following elements:
&lt;/instructions&gt;
&lt;characters&gt;
- A robot learning to paint
&lt;/characters&gt;
&lt;setting&gt;
- Description of the robot&#39;s appearance
- Details about the environment where the story takes place
&lt;/setting&gt;
&lt;plot_elements&gt;
- The robot&#39;s journey in learning to paint
- A surprising twist at the end
&lt;/plot_elements&gt;
&lt;tone&gt;
Whimsical and thought-provoking
&lt;/tone&gt;</code></pre></div>
<p>The difference is striking. With XML tags, we&#39;ve created a structured framework that guides the AI&#39;s response, ensuring that all requested elements are addressed systematically.</p>
<h2 id="benefits-of-using-xml-tags">Benefits of Using XML Tags</h2>
<ol>
<li><p>Enhanced Clarity: XML tags provide a clear structure to your prompts, reducing ambiguity and helping the AI model understand exactly what you&#39;re asking for.</p>
</li>
<li><p>Improved Consistency: By segmenting your prompt into distinct sections, you encourage the AI to address each part systematically, leading to more consistent and comprehensive responses.</p>
</li>
<li><p>Greater Control: XML tags allow you to exert more control over the AI&#39;s output, as you can specify not just what content you want, but how you want it structured.</p>
</li>
<li><p>Easier Iteration: When refining your prompts, XML tags make it simple to identify and modify specific sections without rewriting the entire prompt.</p>
</li>
</ol>
<h2 id="applying-xml-tags-to-system-prompts">Applying XML Tags to System Prompts</h2>
<p>System prompts, which set the overall behavior and capabilities of an AI model for a given interaction, can benefit tremendously from XML structuring. Here&#39;s an example of how a system prompt might look with XML tags:</p>
<div class="code"><div class="code-bar"><span>XML</span></div><pre><code class="language-xml">&lt;role&gt;
You are an expert historian specializing in ancient civilizations.
&lt;/role&gt;
&lt;knowledge_base&gt;
Your knowledge spans from the earliest human settlements to the fall of the Roman Empire.
&lt;/knowledge_base&gt;
&lt;interaction_style&gt;
- Respond in a scholarly yet accessible manner
- Use historical anecdotes to illustrate points
- Cite sources when providing specific facts or dates
&lt;/interaction_style&gt;
&lt;limitations&gt;
- Acknowledge when information is speculative or debated among historians
- Refrain from making definitive statements about unproven historical theories
&lt;/limitations&gt;</code></pre></div>
<p>This structured approach ensures that the AI model has a clear understanding of its role, knowledge base, interaction style, and limitations, leading to more accurate and appropriate responses.</p>
<h2 id="xml-tags-and-rag-a-powerful-combination">XML Tags and RAG: A Powerful Combination</h2>
<p>When it comes to Retrieval-Augmented Generation (RAG) with data from vector databases, including documents and images, XML tags can play a crucial role in organizing and leveraging this information effectively.</p>
<p>For instance, when working with a diverse set of documents and images related to art history, you might structure your prompt like this:</p>
<div class="code"><div class="code-bar"><span>XML</span></div><pre><code class="language-xml">&lt;context&gt;
Analyzing the evolution of portraiture in Western art
&lt;/context&gt;
&lt;document_retrieval&gt;
- Retrieve information on portraiture techniques from the Renaissance to the modern era
- Focus on works by Leonardo da Vinci, Rembrandt, and Pablo Picasso
&lt;/document_retrieval&gt;
&lt;image_analysis&gt;
- Compare and contrast the &quot;Mona Lisa,&quot; &quot;Self-Portrait&quot; by Rembrandt, and &quot;The Weeping Woman&quot; 
  by Picasso
- Analyze the use of light, composition, and emotional expression in each piece
&lt;/image_analysis&gt;
&lt;output_format&gt;
Provide a chronological analysis of how portraiture techniques and styles evolved, 
using the retrieved documents and image analyses to support your points.
&lt;/output_format&gt;</code></pre></div>
<p>This structured approach helps the AI navigate complex, multimodal information sources, resulting in a more coherent and insightful analysis.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The integration of XML tags into our prompts for AI models represents a significant leap forward in our ability to communicate effectively with these advanced systems. By providing clear, structured instructions, we can harness the full potential of AI models like LLaMA and Claude, resulting in more accurate, nuanced, and contextually appropriate responses.</p>
<p>As we continue to explore the frontiers of AI interaction, techniques like XML tagging will undoubtedly play a crucial role in shaping the future of human-AI communication. By adopting these practices, we not only improve the quality of our AI interactions but also pave the way for more sophisticated and meaningful exchanges with these remarkable tools.</p>
]]></content:encoded></item>
</channel>
</rss>
