Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

From Source File to Cited Answer

Hello again. Last lesson separated RAG into indexing, which prepares files for search, and question answering, which uses that index. Now we will follow actual pieces of a Markdown file and a Python file across that boundary. The aim is to make every part of an answer traceable: Which source text was loaded? Which excerpt was retrieved? Which claim does it support?

The files below are invented examples, not files from your project. They let us inspect the journey without depending on a particular database, embedding model, or LLM provider.


Start with the source, not the vector

Consider these two repository files. The numbers at left are original source line numbers; they are not part of the file text.

docs/ingestion.md
1  # Ingestion
2
3  ## Supported files
4  The index includes Markdown (.md) and Python (.py) files.
5  After editing a source file, rerun the indexing command.
src/loader.py
1  from pathlib import Path
2
3  ALLOWED_SUFFIXES = {".md", ".py"}
4
5  def accepts(path: Path) -> bool:
6      return path.suffix.lower() in ALLOWED_SUFFIXES

A loader reads each file and associates its text with its path. A chunker then selects passages small enough to retrieve usefully. For this example, suppose it makes one Markdown chunk from lines 3–5 and one Python chunk from lines 3–6. Keeping the Markdown heading helps explain what its sentences concern; keeping the Python declaration with the function helps explain what accepts checks.

Watch pixegami’s RAG + Langchain Python Project: Easy AI/Chat For Your Docs for a concrete view of a file becoming a document and then a chunk. The demonstration uses Markdown and a library-specific splitter; our Python file can travel through the same conceptual stages without requiring you to adopt that splitter.

RAG + Langchain Python Project: Easy AI/Chat For Your Docs

Pixegami demonstrates loading Markdown files, splitting their contents, and inspecting a chunk’s text and metadata.

Watch loading and splitting. Pay particular attention to the displayed chunk’s content and its source metadata near the end. The demo records a source path and a starting character position; for our project, we also want original line ranges so citations can identify the relevant code or documentation lines.

At this point, each illustrative chunk needs two things that serve different purposes:

Chunk C1
text:       ## Supported files
            The index includes Markdown (.md) and Python (.py) files.
            After editing a source file, rerun the indexing command.
source:     docs/ingestion.md
lines:      3–5

Chunk C2
text:       ALLOWED_SUFFIXES = {".md", ".py"}

            def accepts(path: Path) -> bool:
                return path.suffix.lower() in ALLOWED_SUFFIXES
source:     src/loader.py
lines:      3–6

The text is potential evidence. The source path and line range tell us where to check that evidence. C1 and C2 are illustrative stored chunk identifiers, not citations to show the user.

During indexing, an embedding model converts each chunk’s text into a numeric vector. The index stores that vector together with the chunk text—or a reliable way to recover it—and its source information. The vector makes the chunk searchable by similarity; it cannot substitute for the original words or their location.

That last distinction is worth holding onto: a system that stores vectors but loses the association with docs/ingestion.md:3–5 may still find a relevant match, yet struggle to show the user why the eventual answer is credible.


A question retrieves excerpts, not files wholesale

Now a user asks:

Which files does the loader accept, and what should I do after editing one?

The answering process represents the question for search using an embedding configuration compatible with the indexed vectors. Suppose the search returns C2 and C1. This is an illustrative retrieval result, not a guaranteed ranking: actual results depend on the indexed material and retrieval method.

Retrieval gives the application candidate evidence. It has not answered the question. The application still needs to put the selected text, rather than just vectors or similarity scores, where the generator can read it. One possible assembled prompt would contain:

Instruction: Answer using the supplied source excerpts. If they do not
support a requested detail, say so. Cite the supporting excerpt.

Question: Which files does the loader accept, and what should I do after editing one?

[S1] src/loader.py, lines 3–6
ALLOWED_SUFFIXES = {".md", ".py"}

def accepts(path: Path) -> bool:
    return path.suffix.lower() in ALLOWED_SUFFIXES

[S2] docs/ingestion.md, lines 3–5
## Supported files
The index includes Markdown (.md) and Python (.py) files.
After editing a source file, rerun the indexing command.

Here, [S1] and [S2] are labels assigned to the excerpts in this prompt. The application keeps a corresponding lookup: [S1] means src/loader.py, lines 3–6; [S2] means docs/ingestion.md, lines 3–5. These prompt labels need not be the same as the stored chunk identifiers C2 and C1.

Notice that the original question appears alongside the excerpts. The retrieved text supplies possible facts, while the question tells the LLM which facts to address. The line ranges travel with the text even though the embedding vectors themselves are no longer useful to the generator.

Continue the pixegami demonstration to see retrieval results turned into a prompt and response. Its implementation choices are incidental here; inspect what information crosses each boundary.

RAG + Langchain Python Project: Easy AI/Chat For Your Docs

Pixegami demonstrates querying the prepared index, placing retrieved excerpts in a prompt, and displaying an answer with source references.

In retrieving chunks, watch the search return candidate passages. Then watch prompt and sources for the assembled context, generated answer, and displayed file references. Treat the displayed source list as a demonstration of preserved metadata—not proof that every sentence in an answer is supported by every listed file.


An answer needs claim-level evidence

Given our illustrative prompt, an appropriately limited answer could be:

The loader’s accepts function accepts paths whose suffix is .md or .py, ignoring suffix case. [S1] After editing a source file, the ingestion guide says to rerun the indexing command. [S2]

The citation layer can present those labels as checkable locations:

  • [S1] — src/loader.py, lines 3–6
  • [S2] — docs/ingestion.md, lines 3–5

The first claim follows from the suffix set and the use of .lower() in the function. The second follows from the Markdown instruction. The answer does not claim that the user should run a particular command such as python ingest.py, because our excerpts do not name one. Nor does the function alone establish which directories the whole indexing system excludes: it only shows a suffix check.

This is the difference between listing retrieved sources and citing support for claims. A list of both filenames beneath any answer would tell you what was retrieved, but not whether a particular sentence came from either file. Conversely, a model could emit [S7] even if no [S7] was supplied. A dependable citation step checks that a label exists, resolves it to a source location, and checks that the cited passage supports the nearby claim. Producing a citation-shaped marker is not itself verification.

The source location must be preserved early. Tensorlake’s Citation-Aware RAG discusses this principle in the context of documents that may need page or spatial locations. Read the short passage for the preservation principle; Markdown and Python files need paths and original line ranges, not PDF bounding boxes.

Citation-Aware RAG: How to add Fine Grained ...

Read Tensorlake’s discussion of why citation information has to survive chunk creation and indexing.

Start in the introductory paragraphs at “At its core, generating citations in RAG is about,” immediately before “How to Build Citation-Ready Document Chunks.” Continue through that section’s explanation of why merging text without anchors loses precision, ending where it says you may “lose the ability to cite precise lines.” Read the citation rationale. Translate its document-location examples into source paths and line spans for our .md and .py files; you can leave its PDF-specific spatial metadata aside.

For line citations to mean anything, the recorded numbers must still refer to the original file. If preprocessing removes lines or rearranges text, the application must retain an accurate mapping back to those originals. We will deal with preserving that mapping during ingestion and chunking later in the course.


Follow the evidence backward

When inspecting a questionable answer, start with its claim and work backward. Suppose the response says, “The loader also accepts .txt files [S1].” Reading src/loader.py:3–6 immediately exposes the problem: the cited excerpt contains .md and .py, not .txt. The label may resolve to a real chunk, but the claim is unsupported.

A different failure could occur earlier. If docs/ingestion.md was edited but its indexed chunk was not refreshed, retrieval might return an old instruction. Even a careful generator would then be answering from stale evidence. If the correct Markdown chunk was indexed but never retrieved, the prompt would lack the editing instruction; the generator should not invent it.

For this lesson, the complete trace is the important artifact:

  1. Original location: src/loader.py:3–6 and docs/ingestion.md:3–5.
  2. Indexed records: chunk text remains associated with its source location and searchable embedding.
  3. Retrieved context: selected chunk text is copied into the question’s prompt with labels [S1] and [S2].
  4. Answer: each factual claim uses the excerpt that supports it.
  5. Presented citations: each label resolves back to the original path and lines for inspection.

That is a more informative account than “we put files in a vector database and the LLM answers.” It identifies both where the answer obtained its evidence and where a reader can challenge it.


Key takeaways

A Markdown or Python file becomes useful to RAG when its source text, searchable representation, and original location stay connected. Retrieval selects excerpts; context assembly gives those excerpts and the question to the LLM; citations carry supported claims back to checkable file locations. Retrieved sources are candidates for evidence, not automatic proof of an answer.

Next, we will assign clear responsibilities and input/output boundaries to the components involved—loader, chunker, embedder, vector store, retriever, generator, and citation layer—so this trace can become a practical project design.

Can't find a good explanation? Sign up and we'll make it for you

Sign up