|
2 | 2 |
|
3 | 3 | PageIndex turns long documents into a navigable tree of sections, each with a |
4 | 4 | summary, so agents can reason over structure instead of flat chunks. This |
5 | | -sample doc is used by the incremental update demo. |
| 5 | +sample document is used by the incremental update demo, and its sections are |
| 6 | +deliberately long enough that section summaries are generated by a model rather |
| 7 | +than passed through as raw text, which is what makes the incremental behaviour |
| 8 | +observable when only part of the document is edited. |
6 | 9 |
|
7 | 10 | ## 1. What PageIndex Does |
8 | 11 |
|
9 | 12 | PageIndex parses a PDF or Markdown file into a hierarchical structure of nodes. |
10 | | -Each node holds a title, its text, and a generated summary. The tree lets a |
11 | | -retrieval agent walk from the document root down to the exact section that |
12 | | -answers a question, without embedding every chunk into a vector store. |
| 13 | +Each node holds a title, its own text span, and a generated summary. The tree |
| 14 | +lets a retrieval agent walk from the document root down to the exact section |
| 15 | +that answers a question, without embedding every chunk into a vector store and |
| 16 | +without relying on nearest-neighbour similarity to decide relevance. |
| 17 | + |
| 18 | +The practical consequence is that retrieval becomes an act of navigation rather |
| 19 | +than an act of matching. An agent reads the root description, decides which |
| 20 | +branch is plausible, reads that branch's summary, and descends. At every step |
| 21 | +the decision is legible: there is a title and a summary that explain why the |
| 22 | +branch was taken. When the agent lands on a leaf it has the full text of that |
| 23 | +section, not a windowed fragment that may have been cut mid-argument. |
| 24 | + |
| 25 | +This matters most for documents where meaning depends on position. A clause in |
| 26 | +a contract, a subsection of a policy manual, or a numbered requirement in a |
| 27 | +regulatory filing all derive part of their meaning from where they sit in the |
| 28 | +document. Flat chunking discards that placement. A tree preserves it, and the |
| 29 | +path from root to leaf is itself a piece of evidence the agent can cite. |
13 | 30 |
|
14 | 31 | ## 2. Indexing |
15 | 32 |
|
16 | | -Indexing builds the tree once. For Markdown, headings define the hierarchy; for |
17 | | -PDFs, the table of contents and page layout are used. Every section is |
18 | | -summarized, and the whole document gets a short description. The result is |
19 | | -persisted in a workspace as JSON keyed by a document id. |
| 33 | +Indexing builds the tree once. For Markdown, headings define the hierarchy |
| 34 | +directly: each heading opens a node, and the heading level determines where |
| 35 | +that node attaches to its parent. For PDFs, the table of contents and the page |
| 36 | +layout are used instead, with a series of checks that verify the extracted |
| 37 | +table of contents actually corresponds to the physical pages of the document. |
| 38 | + |
| 39 | +Every section is then summarized, and the whole document is given a short |
| 40 | +description derived from the structure. Summarization is conditional: a section |
| 41 | +whose text falls below a token threshold is stored verbatim, on the grounds |
| 42 | +that a summary of a short passage costs a model call and returns something no |
| 43 | +more useful than the passage itself. Longer sections are sent to the model. |
| 44 | + |
| 45 | +The result is persisted in a workspace directory as JSON, keyed by a document |
| 46 | +identifier. Alongside the tree, the record stores a hash of the whole file and |
| 47 | +a map of per-section hashes. Those hashes are what make the next run cheap: |
| 48 | +they are the record of what the tree was built from, so a later run can compare |
| 49 | +against them instead of re-deriving the tree from scratch to find out whether |
| 50 | +anything moved. |
20 | 51 |
|
21 | 52 | ## 3. Incremental Update |
22 | 53 |
|
23 | | -When a document changes, PageIndex avoids rebuilding everything. It hashes the |
24 | | -file and each section: if the file hash is unchanged the update is skipped |
25 | | -entirely, and if only some sections changed, only those (plus their ancestors) |
26 | | -are re-summarized. Unchanged sections reuse their cached summary. |
| 54 | +When a document changes, PageIndex avoids rebuilding everything. The update |
| 55 | +path applies two gates in sequence, and each gate that passes eliminates a |
| 56 | +larger amount of work than the one before it. |
| 57 | + |
| 58 | +The first gate is the file hash. If the hash of the file's current contents |
| 59 | +matches the hash recorded at index time, nothing in the document has changed, |
| 60 | +the update returns immediately with a status of unchanged, and no model call is |
| 61 | +made at all. This is the common case for a scheduled re-ingest over a corpus |
| 62 | +where most documents are static between runs. |
| 63 | + |
| 64 | +The second gate is the section diff. The file is re-parsed into sections, each |
| 65 | +section is hashed, and the new hash map is compared against the stored one. |
| 66 | +That comparison yields three sets: sections that are new, sections that were |
| 67 | +removed, and sections whose text changed in place. The changed and added |
| 68 | +sections are marked dirty, and the ancestors of each dirty section are added to |
| 69 | +the set as well, on the assumption that a parent's roll-up may be affected by |
| 70 | +what happened underneath it. |
| 71 | + |
| 72 | +Everything in that set is re-summarized. Everything outside it reuses the |
| 73 | +summary already stored on the previous tree. A two-page revision to a five |
| 74 | +hundred page manual therefore costs a handful of model calls rather than a |
| 75 | +full rebuild, and the cost scales with the size of the edit rather than with |
| 76 | +the size of the document. |
27 | 77 |
|
28 | 78 | ## 4. Vectorless Retrieval |
29 | 79 |
|
30 | 80 | Because the tree carries summaries at every level, an agent can retrieve by |
31 | | -traversing the structure instead of doing nearest-neighbor search over |
32 | | -embeddings. This keeps retrieval explainable and cheap to maintain. |
| 81 | +traversing the structure instead of doing nearest-neighbour search over |
| 82 | +embeddings. There is no index to build beyond the tree itself, no embedding |
| 83 | +model to keep consistent between ingest and query time, and no drift when the |
| 84 | +embedding model is upgraded underneath a corpus that was embedded with an |
| 85 | +older version. |
| 86 | + |
| 87 | +It also keeps retrieval explainable. A vector search returns a ranked list with |
| 88 | +a similarity score, and the score is not an explanation: it does not say why |
| 89 | +one passage outranked another, and it cannot be audited after the fact. A |
| 90 | +traversal returns a path, and the path is an explanation: this section, inside |
| 91 | +this chapter, inside this document, chosen because its summary matched what was |
| 92 | +asked. For compliance and audit workflows that difference is the point. |
33 | 93 |
|
34 | 94 | ## Appendix: Key Methods |
35 | 95 |
|
36 | | -`client.index(path)` builds the tree. `client.update(doc_id)` refreshes it |
37 | | -incrementally. `client.get_doc_id_by_path(path)` resolves an existing document |
38 | | -so the same file is never indexed twice. |
| 96 | +`client.index(path)` builds the tree for a document that has not been seen |
| 97 | +before and returns its document identifier. `client.update(doc_id)` refreshes |
| 98 | +an existing tree incrementally, applying the two gates described above and |
| 99 | +returning a dictionary describing which sections were updated, added, or |
| 100 | +deleted. `client.get_doc_id_by_path(path)` resolves an existing document by its |
| 101 | +source path, so that re-ingesting the same file finds the tree that already |
| 102 | +exists instead of minting a second identifier and orphaning the first. |
0 commit comments