Someone at your company has already asked for it. An assistant that answers “what was the completion design on the Sooner 3-14H?” or “which wells in this section have had a casing failure?” by reading your own well files, not the open internet. The technology to build that is mature. Retrieval-augmented generation is the standard pattern, and ThoughtWorks moved it to Adopt on their Technology Radar.[1] The models are good enough. The libraries are good enough.
The data usually is not.
That is the whole post, but it is worth unpacking, because the failure mode here is quiet. A RAG system built on messy upstream data does not throw an error. It returns a confident, fluent, wrong answer, and it does that consistently. The retrieval quality problem people blame on the model is almost always a data problem. We have watched teams swap embedding models and tune prompts for weeks when the actual issue was that half their well reports were scanned images with no text layer and the other half referenced wells by three different naming conventions.
What RAG actually does
Strip away the acronyms and RAG is a lookup step bolted onto a language model.
You take a question. You convert it into a vector, a list of a few hundred to a few thousand numbers that represents its meaning, using an embedding model. You search a store of pre-computed vectors for the chunks of text whose vectors are closest to your question’s vector. You take the top handful of those chunks and paste them into the prompt, ahead of the question, as context. The model answers using that context.
That is it. Retrieval finds relevant text. Generation writes the answer. The “augmented” part is just the paste step. The model never learned your data and was never fine-tuned on it. Every answer is only as good as the chunks retrieval handed it.
Which means the entire system rises and falls on one question: when someone asks about the Sooner 3-14H, does the search actually surface the right pages from the right well file? If it surfaces a page from a different well, or a garbled OCR page, or nothing at all, the model still answers. It just answers wrong, and it sounds exactly as sure of itself as when it is right.
What upstream data looks like as a source
The documents you would want to feed a RAG system are the ones nobody has structured yet. That is not a coincidence. They are unstructured precisely because they were hard to structure, and they are the same reason retrieval is hard.
Well files: drilling reports, mud logs, completion summaries, daily reports, wireline logs. Regulatory filings, Form 1000s, completion reports, the OCC and Railroad Commission paperwork. Historical production notes, the free-text remarks a pumper or engineer wrote in a system field years ago. Workover and intervention histories. AFEs and their supporting narratives.
A few things about this corpus make it different from the tidy documentation a RAG demo runs on.
The formats are mixed and often bad. A meaningful fraction of your older well files are scanned paper, sometimes faxed paper, with no machine-readable text underneath. A PDF that looks fine to your eye may be an image. If you embed it without running OCR first, you are embedding nothing, and that well silently drops out of every search.
Well identity is inconsistent. The same well appears as a lease name, an API number (sometimes 10-digit, sometimes 14), an internal well ID, and a colloquial pad name, across different documents from different eras and vendors. This is the same well-identity problem we have written about for years in the context of reconciling land and production data and OCC ingestion. RAG does not make it go away. It makes it worse, because a human reconciling a spreadsheet can spot that “Sooner 3-14” and “Sooner 3-14H” are the same well. A cosine similarity search will not, unless you told it so through metadata.
The domain vocabulary is dense and abbreviated. “RIH w/ 4.5in liner, set at 8,240’ MD, POOH, RU wireline.” An embedding model trained on general text has some sense of this, but not the sense your reservoir engineer has. This is where naive retrieval quietly loses recall.
Chunking is a real decision, not a default
Every RAG tutorial tells you to split documents into chunks of some fixed token count with some overlap, embed each chunk, and move on. For clean prose that is fine. For upstream data it is where a lot of quality leaks out.
The problem with fixed-size chunking on a well file is that it cuts across meaning. A completion summary has sections: formation tops, casing program, perforation intervals, treatment stages. A 512-token window dropped blindly through that document will routinely split a perforation table down the middle, so half the depths land in one chunk and half in another, and neither chunk retrieves cleanly against “what intervals were perfed.”
Chunk on the document’s own structure where you can. Split on section headers, on table boundaries, on the natural units the document already has. A completion report chunked by section retrieves far better than the same report chunked every 500 tokens, because each chunk is about one thing.
Structured records are a different problem entirely. If the source is a PPDM table or a production record, do not treat it as text to be chunked at all. Serialize each logical row into a compact, consistent string (“Well: Sooner 3-14H. API: 35-017-… . First production: 2019-03. Formation: Woodford. …”) and embed that. One record, one chunk, with the identifiers intact. Mixing free-text chunking logic with structured records is a common early mistake, and it produces a store where the structured data is nearly unretrievable.
The point is that chunking strategy is downstream of knowing what your document actually is. Which brings us back to the data.
Embeddings and the metadata that saves you
You need an embedding model to turn text into vectors. There are good general-purpose ones, both hosted and open-weight, and for most upstream use cases a strong general model is fine to start. The embedding model is rarely the bottleneck. Do not spend your first month A/B testing embedding models. Spend it on the data.
What actually moves retrieval quality is metadata filtering. Store, alongside each vector, the structured fields you already know: the resolved well ID, the API number, the document type, the date, the operator, the section-township-range. Then a query for “casing failures on the Sooner 3-14H” does not rely on the embedding model to figure out which well you mean. You filter to that well’s documents first, then do the vector search within them.
This is the single highest-value thing you can do, and it depends entirely on having resolved well identity before you embedded anything. If your documents are not tagged to a canonical well, you cannot filter by well, and you are back to hoping cosine similarity guesses right. The master well table is not a nice-to-have for RAG. It is the filter key.
Where to put the vectors
Two options cover most mid-size operators, and the choice is mostly about what you already run.
If you are already on PostgreSQL, use pgvector. It is a Postgres extension that adds a vector column type and nearest-neighbor search. As of version 0.8.4 it supports HNSW and IVFFlat indexes, cosine distance and five other operators, and half-precision (halfvec) storage to cut memory.[2] The reason it is the right default for most teams is not benchmarks. It is that your metadata is already in Postgres. You filter by well ID with a plain SQL WHERE clause and run the vector search in the same query, in the same database, under the same backups and access controls you already have. No second system to operate. pgvector 0.8.0 also improved filtered search specifically, so the “filter to this well, then search” pattern that matters most for upstream data got faster.[3]
Reach for a dedicated vector database like Qdrant when scale or filtering demands it. Qdrant does payload filtering and quantization (binary and scalar, which trade a little accuracy for large memory savings) as first-class features, and it scales horizontally.[4] The rough line people draw is tens of millions of vectors, or sub-10ms latency requirements, or a query pattern that is almost always “vector plus several filters” at high volume.[5] Most operators are not there on day one. A few hundred wells of documents is not tens of millions of vectors. Start on pgvector, and move to Qdrant when you can point at the specific constraint pushing you off Postgres, not before.
The honest version: the vector store is the least important decision in this whole post. Teams obsess over it because it is the part with a clean comparison table. The chunking and the metadata and the OCR are what determine whether the thing works.
The quality floor, and how to know you cleared it
Here is the part that gets skipped. RAG on dirty data does not fail loudly. It returns a confident wrong answer, and in this domain a confident wrong answer about a casing depth or a perforation interval is worse than no answer, because someone acts on it.
So the question is not “is our data perfect.” It never will be. The question is “is it clean enough that retrieval is meaningful,” and that has a testable answer. What clean-enough looks like, concretely:
Every document that should be searchable has actual text. Run OCR on the scans first and verify the text layer exists, or those wells are invisible. A quick check: pull a random sample of your PDFs and confirm you can select text in them. If a quarter come back as images, you have an OCR project before you have a RAG project.
Well identity is resolved and attached as metadata. Every chunk knows which canonical well it belongs to. This is the reconciliation work, done once, and it is the same work that pays off everywhere else in the stack.
You have a way to measure retrieval, separate from generation. Build a small evaluation set: thirty or forty real questions with the documents that should answer them, written down by someone who knows the wells. Then measure whether retrieval surfaces the right documents, before you ever look at what the model wrote. If retrieval is only pulling the correct source half the time, no prompt engineering will save you, and you now know the problem is the data, not the model. This is the same discipline we argue for in knowing whether an AI pipeline is still working: measure the machine part with ground truth, not vibes.
That evaluation set is the cheapest insurance in the project. It turns “the AI seems off lately” into “retrieval recall dropped from 0.8 to 0.5 when we ingested the batch of scanned files that never got OCR’d,” which is a fixable statement.
Do the boring part first
The pattern here is the same one that shows up in every honest AI-for-operations conversation, including the dirty data problem the whole AI series is built around and the field ticket digitization gap. The impressive-looking part, the model answering questions in plain English, is the part that mostly works out of the box. The unglamorous part, getting your documents into a state where retrieval is meaningful, is the part that decides whether the whole thing is useful or actively dangerous.
If you want a RAG assistant over your well files, the first work is not choosing a model or a vector store. It is OCR on the scans, well-identity resolution attached as metadata, structure-aware chunking, and a small evaluation set to prove retrieval works before you trust generation. Do that, and a general embedding model and pgvector will take you a long way. Skip it, and the best model in the world will confidently tell your engineer the wrong casing depth.
Thoughtworks, “Retrieval-augmented generation (RAG),” Technology Radar. https://www.thoughtworks.com/en-us/radar/techniques/retrieval-augmented-generation-rag ↩︎
pgvector, project README (v0.8.4): supported index types, distance operators, and vector storage formats. https://github.com/pgvector/pgvector ↩︎
Amazon Web Services, “Supercharging vector search performance and relevance with pgvector 0.8.0 on Amazon Aurora PostgreSQL” (2024). https://aws.amazon.com/blogs/database/supercharging-vector-search-performance-and-relevance-with-pgvector-0-8-0-on-amazon-aurora-postgresql/ ↩︎
Qdrant, “Quantization” documentation. https://qdrant.tech/documentation/guides/quantization/ ↩︎
Tiger Data (Timescale), “Pgvector vs. Qdrant: Open-Source Vector Database Comparison.” https://www.tigerdata.com/blog/pgvector-vs-qdrant ↩︎