of 14 · Oct 11, 2026 · 10 min read
How an AI Assistant Answers From Company Documents
Companies want assistants that answer questions from their own documents. Build the simplest version, retrieval-augmented generation, step by step, see what each part does, and find the first question it gets wrong.
A customer calls an e-bike shop. Their battery stopped holding a charge, and they want to know if the warranty covers it. The person at the desk knows there is a warranty policy, but there are several versions, and the answer depends on when the bike was bought. They search a shared drive, open two PDFs, and ask a colleague which one applies.
In the workshop, a mechanic sees the display blinking twice and wonders what it means. The answer is in a training deck from last year, in the speaker notes. Nobody remembers that.
The information exists in both cases. Getting from a question to a reliable answer is the slow part. That is why so many companies want an assistant that can answer in plain language from their own documents, and show where the answer came from.
This series builds one, step by step, for a fictional company called Atlas Bikes. It makes the C-3 e-bike and the S-2 scooter. Its documents are the kind most companies have: warranty policies in Word, specifications as PDFs, an old assembly guide that was scanned, training slides, and a web page listing display error codes. Every document and number in the series is invented for teaching.
The companion code
Each article comes with working code: a small Python project, published as the companion repository, that builds the Atlas Bikes assistant one article at a time. Every article has its own version of the project, marked with a git tag (part-01 for this one). Moving from one tag to the next shows exactly what that article changed, and every output shown in the series comes from running that version.
You don't need to run anything to follow the articles. If you do, it takes Python and nothing else: the parts that would normally call an AI model use simple stand-ins, so the examples run offline, without an API key, and give the same result every time. Each article points out where a real model would replace the stand-in.
The model needs something to read
A language model is the part of the assistant that writes the answer. It learned from public text, so it knows how warranties work in general. It has never seen Atlas Bikes' warranty policy.
You can see the fix in any chat assistant: paste the policy, then ask your question. The model reads the dates and terms you gave it and explains how they apply. With one short document, that is enough.
A company has thousands of documents. Pasting all of them into every request would be slow and expensive, and often impossible, because a model can only read a limited amount of text at once. So the application first searches for the few passages that look relevant, then gives only those to the model together with the question.
That pattern is called retrieval-augmented generation, or RAG: retrieve information, add it to the model's input, then generate an answer. Google Cloud's RAG overview describes the same idea.
The system has two phases. Documents are prepared once, before anyone asks anything. Then each question is answered using what was prepared.
flowchart LR
subgraph prepare["Prepare documents (once)"]
D[Documents] --> T[Text] --> C[Chunks] --> E[Embeddings] --> I[(Index)]
end
subgraph answer["Answer a question (every time)"]
Q[Question] --> QE[Embedding] --> S[Search the index] --> P[Prompt: question + passages] --> M[Language model] --> A[Answer]
end
I -.-> SPreparing the documents
Get the text out. Each file has to become text the application can work with. For now we take every piece of text a document parser found, in order. Part 3 shows how much this shortcut loses on real documents.
Cut it into chunks. A chunk is a small passage, here at most 200 characters. Searching small passages lets us hand the model the paragraph about warranty terms instead of a whole handbook. Here is one chunk from the Atlas Bikes collection, exactly as the code stores it:
Chunk( id="WARR-R2-2-chunk-0", text="C-3 batteries purchased on or after 1 January 2026 carry a warranty of " "3 years from the purchase date. Purchases made before that date keep " "the term in effect when they were bought.", source_id="WARR-R2", section="2", meta={"title": "C-3 battery warranty, revision 2", "product": "C-3", "access": "employees", "applies_from": "2026-01-01"},)The text is what the model will read. The source and section let us show where an answer came from. The rest is metadata: facts about the passage, such as which product it covers and who may read it. We won't use most of it until Part 6.
Turn each chunk into an embedding. People don't always use the document's words. A customer says "how long is the battery covered", the policy says "warranty of 3 years". To find passages that mean the same thing, an embedding model turns each text into a list of numbers, a vector, so that texts about similar things get similar vectors.
Real embedding models produce hundreds of numbers that no person can read. The companion code uses a small stand-in that maps words to concepts, so you can see what it does. Here is the vector for the chunk above:
{"purchase": 4.0, "duration": 2.0, "date": 2.0, "warranty": 1.0, "battery": 1.0, "bike": 1.0, "january": 1.0, ...}"Purchased" and "bought" both count as the concept purchase; "years" counts as duration. A trained model learns these relationships from huge amounts of text instead of from a list.
Store everything in an index. The vectors, the text, and the metadata go into a searchable index, often a vector database. Our collection becomes 30 chunks.
Answering a question
A staff member asks:
What battery warranty applies to a C-3 bought on 15 March 2026?
The question is turned into a vector with the same embedding model. The index compares it with every stored vector and returns the closest ones. This is semantic search: matching by meaning rather than exact words. We keep the five closest, a setting usually called top-k:
0.476 WARR-R2-2-chunk-00.453 WARR-S2-R1-2-chunk-00.440 WARR-R1-2-chunk-00.336 TEST-BAT-R1-3-chunk-10.320 SPEC-C3-R5-6-chunk-5The application then builds the prompt, the text sent to the model: a few instructions, the question, and the five passages with their sources. Here is how it starts:
Answer the question using the supplied passages.Identify the source of your answer.If the passages don't establish an answer, say so.
Question:What battery warranty applies to a C-3 bought on 15 March 2026?
Source: WARR-R2, section 2C-3 batteries purchased on or after 1 January 2026 carry a warranty of 3 yearsfrom the purchase date. Purchases made before that date keep the term in effectwhen they were bought....A model reading this would answer that the battery is covered for 3 years from purchase, because 15 March 2026 is after 1 January 2026, and cite WARR-R2. The companion code's stand-in model doesn't write answers; it lists what it was given, so you can see exactly what a real model would read.
The whole answering path is four lines of the code:
hits = self.index.search(self.embedder.embed(question), user.groups, self.k)trace.context = [chunk for _, chunk in hits]trace.prompt = build_prompt(INSTRUCTIONS, question, trace.context)trace.answer = self.generator.generate(trace.prompt)Search runs only over documents this user may read (user.groups), so nothing restricted can reach the prompt. The model has no connection to the documents: everything it knows about Atlas Bikes is the text in that prompt.
One detail in the results deserves a note. The second and third passages are also warranty policies: the S-2 scooter's, and the older C-3 policy for 2025 purchases. A capable model reads the dates and picks the right one. Keep that in mind; Part 6 deals with it properly.
Different words, same meaning
Now a dealer asks:
How do I get back into the display after forgetting the PIN?
The service manual never says "get back into" or "forgetting". Its section is called "Recovering display access" and says "when the owner's PIN is unavailable". Semantic search still finds it, because "forgetting" and "unavailable" land on the same concept, as do "get back into" and "access":
0.500 SERVICE-C3-R4-8-chunk-40.208 SERVICE-C3-R4-7-chunk-10.204 SERVICE-C3-R4-7-chunk-2Section 7 holds the recovery procedure; section 8, about changing the PIN, points to it. People can ask the way they talk and still reach the passage written in the document's words.
A few settings that matter
Chunk size. Small chunks are easy to match but can cut a sentence away from the condition that changes its meaning. Large chunks keep context but mix unrelated topics. Part 4 replaces the fixed size with the document's own structure.
How many passages (k). More passages give the model more chances to receive the right evidence, but also more irrelevant text, more cost, and slower answers.
The model's limit. Models read text in tokens, pieces of words. Their context window caps how many tokens fit in one request: instructions, question, passages, and the answer itself.
Now ask for everything
The same assistant gets one more question, this time from the delivery team:
List all battery safety checks for the C-3 under revision 5.
The specification has eleven checks, R01 to R11. The model receives these passages:
Source: SPEC-C3-R5, section 6Scope: C-3, 36 V battery pack, frame-mounted, standard charger.ID CheckR01 Check that the battery serial number matches the delivery note.R02 Inspect the battery casing for cracks or dents.
Source: SPEC-C3-R5, section 6R11 Complete the battery safety sign-off before handing over the bike.
Source: SPEC-C3-R5, section 6Atlas Bikes | C-3 Specification rev. 5 | InternalR06 Check that the battery lock engages and the key turns freely.R07 Replace the battery seal wherever the casing has been opened.
Source: SPEC-C3-R5, section 6R08 Keep the battery vent holes clear of tape and stickers.R09 Record the battery batch number in the bike's service record.R10 Report any battery fault found during the final check.
Source: TEST-BAT-R1, section 3Figure 2 compares the two battery packs after repeated charging. ...Eight of the eleven checks arrive. R03, R04, and R05 never do: check the charging port cover, measure the resting voltage, charge to at least 50 percent. Nothing in the prompt tells the model that anything is missing, so it will list eight checks as if that were all of them. A bike could be delivered with the charging port open.
No error appears anywhere. The search worked as designed: it returned the five closest passages. The question asked for something different: every item, not the closest few.
Stop here if
Stop here if your questions are single facts, your documents are clean digital text, and a person reads each answer together with its source.
The next article starts from this failure and a few others like it. Before changing anything in the system, we'll work out how to tell what each question needs, and how to see which questions fail.