Jul 07, 2026

The Text Existed. The Chunk Was Wrong.

Chunking Philippine law for RAG

I’m building a RAG system over Philippine law. Early on, it failed a question it should have aced: “What law punishes online libel?” The Cybercrime Act provision was already in the index, but retrieval still missed it because it was attached to a much longer spam clause. The text existed. The chunk was wrong.

That became the rule for the rest of the work: shape lies; sequence matters. A marker like (i) or Section 4 only makes sense when you understand what came before it and what level of the document it belongs to.


Understanding the Corpus

A corpus is the collection of documents your system retrieves from. It is the source material the model is grounded in. In this project, it is the body of Philippine legal texts the assistant is allowed to answer from.

The corpus is the main data your model depends on. By understanding it, you can better reason about what answer the model should generate and what context has been handed to it. Since I am using Philippine legal documents, I can look for structural patterns and markers that help me chunk the text. So before fixing the chunker, I had to understand the document that beat it.


Anatomy of a Philippine Statute

A Philippine statute can be broken down into three broad frames: Head, Body, and Tail. I will use Republic Act No. 10175, the Cybercrime Prevention Act of 2012, the law that contains the online libel provision retrieval kept missing as the running example. You can check it out here.

The Head Frame

The Head is everything before Section 1: the Congress/session line, Republic Act number, long title, and enacting clause. It identifies the law, but usually does not contain the operative rule a user is asking about.

The Body Frame

The Body contains the actual rules. This is where chunking gets difficult, because legal rules are nested: Chapters contain Sections, Sections contain lettered items, lettered items contain numbered items, and numbered items can contain roman numerals or double-letter sub-items.

The Tail Frame

The Tail contains final provisions: appropriations, implementing rules, separability, repealing, and effectivity clauses. These are still numbered Sections, but they usually handle housekeeping.

For retrieval, the Body is where the hard problems live. The Head and Tail matter for parsing, but the failures in this post happen inside Section 4: a single numbered Section containing many smaller legal rules.

Here is an overview of RA 10175. Keep an eye on where libel sits, dead last inside Section 4, the biggest Section in the law:

Republic Act No. 10175 · Cybercrime Prevention Act of 2012

  ├─ HEAD FRAME  (formal parts, not Sections)
  │   ├─ Legislative caption .... "Fifteenth Congress…"
  │   ├─ Number / designation ...... "REPUBLIC ACT NO. 10175"
  │   ├─ Long title ................. "AN ACT DEFINING CYBERCRIME…"
  │   └─ Enacting clause ........... "Be it enacted by the Senate and House…"
  │                                   (RAs usually skip the preamble)

  ├─ BODY  (numbered Sections / provisions)
  │   │
  │   ├─ CHAPTER I — Preliminary Provisions
  │   │   ├─ Section 1. Title ............. short title lives here
  │   │   ├─ Section 2. Declaration of Policy
  │   │   └─ Section 3. Definition of Terms ... (a)…(p)
  │   │
  │   ├─ CHAPTER II — Punishable Acts
  │   │   ├─ Section 4. Cybercrime Offenses
  │   │   │   ← first structure-aware chunker kept this as one unit
  │   │   │   ├─ (a) Confidentiality, Integrity & Availability offenses
  │   │   │   │     (1) Illegal Access
  │   │   │   │     (2) Illegal Interception
  │   │   │   │     (3) Data Interference
  │   │   │   │     (4) System Interference
  │   │   │   │     (5) Misuse of Devices
  │   │   │   │         ├─ (i) use/produce/sell…
  │   │   │   │         │   ├─ (aa) a device…
  │   │   │   │         │   └─ (bb) a password…
  │   │   │   │         └─ (ii) possession of 5(i)(aa)/(bb) ← 4-deep
  │   │   │   │     (6) Cyber-squatting
  │   │   │   ├─ (b) Computer-related Offenses
  │   │   │   │     (1) Forgery
  │   │   │   │     (2) Fraud
  │   │   │   │     (3) Identity Theft
  │   │   │   └─ (c) Content-related Offenses
  │   │   │         (1) Cybersex
  │   │   │         (2) Child Pornography
  │   │   │         (3) Unsolicited Commercial Comms
  │   │   │             └─ (i)(ii)(iii) → (aa)(bb)(cc)
  │   │   │                ← long one; struck down later*
  │   │   │         (4) Libel ← needle, dead last
  │   │   ├─ Section 5. Other Offenses
  │   │   │   ├─ (a) Aiding/Abetting
  │   │   │   └─ (b) Attempt
  │   │   ├─ Section 6. RPC/special-law crimes via ICT
  │   │   │   ← penalty one degree higher
  │   │   └─ Section 7. Liability under Other Laws
  │   │
  │   ├─ CHAPTER III — Penalties
  │   │   ├─ Section 8. Penalties
  │   │   │   ← maps each 4(a)/(b)/(c) crime to prison/fine
  │   │   └─ Section 9. Corporate Liability   ← juridical persons
  │   │
  │   ├─ CHAPTER IV — Enforcement & Implementation
  │   │   ├─ Section 10. Law Enforcement Authorities (NBI, PNP)
  │   │   ├─ Section 11. Duties of Law Enforcement Authorities
  │   │   ├─ Section 12. Real-Time Traffic Data ← struck down later*
  │   │   ├─ Section 13. Preservation of Computer Data (6 months)
  │   │   ├─ Section 14. Disclosure   ·  Section 15. Search/Seizure
  │   │   ├─ Section 16. Custody   ·  Section 17. Destruction
  │   │   ├─ Section 18. Exclusionary Rule
  │   │   ├─ Section 19. Restricting/Blocking Access   ← struck down later*
  │   │   └─ Section 20. Noncompliance
  │   │
  │   ├─ CHAPTER V — Jurisdiction
  │   │     └─ Section 21. Jurisdiction ← RTC
  │   │
  │   ├─ CHAPTER VI — International Cooperation
  │   │     └─ Section 22. International Cooperation
  │   │
  │   └─ CHAPTER VII — Competent Authorities
  │         ├─ Section 23. DOJ — Office of Cybercrime
  │         ├─ Section 24–25. CICC
  │         └─ Section 26. Powers and Functions

  └─ TAIL FRAME  (CHAPTER VIII — Final Provisions)
      ├─ Section 27. Appropriations ....... ₱50M/year
      ├─ Section 28. Implementing Rules ... IRR clause
      ├─ Section 29. Separability Clause .. one bad provision won't kill the rest
      ├─ Section 30. Repealing Clause ..... supersedes clashing laws
      └─ Section 31. Effectivity .......... 15 days after publication

* Sections 4(c)(3), 12, and 19 were struck down as unconstitutional in Disini v. Secretary of Justice (2014), including 4(c)(3), the very spam clause that dominates the chunking examples below, but all three still sit in the official text on the elibrary. A law’s text and a law’s current force are two different things, which becomes its own problem later in this series.


Chunking Strategies

Chunking is the process of breaking down long texts into shorter pieces to save in your vector store. You then retrieve these chunks to give the model context. The rest of this post is the hunt for the chunk shape that stops burying libel.

Fixed Size Chunking

In the early stages of this project, I used the default chunking strategy: split by a fixed number of tokens, with some token overlap between chunks. Overlap helps the retriever avoid losing context at chunk boundaries. This works fine for generic prose and is fast to build.

For example:

  ┌─ CHUNK 1 ───────────────────────────────────────────────┐
  │ …(6) Cyber-squatting. The acquisition of a domain name  │
  │ over the internet in bad faith… (b) Computer-related    │
  │ Offenses: (1) Computer-related Forgery. (i) The input,  │
  │ alteration, or deletion of any computer data without    │
  │ right resulting in inauthentic data with the intent     │  ← cuts here,
  │                                                         │   mid provision
  └─────────────────────────────────────────────────────────┘
  ┌─ CHUNK 2 ───────────────────────────────────────────────┐
  │ that it be considered or acted upon for legal purposes  │  ← tail of Forgery,
  │                                                         │    orphaned
  │ …(c) Content-related Offenses: (1) Cybersex… (2) Child  │
  │ Pornography… (3) Unsolicited Commercial Communications. │
  │ The transmission of commercial electronic communication │
  │ …unless: (i) There is prior affirmative consent…        │
  └─────────────────────────────────────────────────────────┘

The Problem

Fixed-size chunking is blind to the law’s structure. It cuts on a raw token count, so it slices straight through a single provision: in the example above, Computer-related Forgery begins in Chunk 1 and finishes, orphaned, in Chunk 2. A retriever that pulls Chunk 2 gets the tail of a definition with no idea which offense it belongs to and a provision split down the middle is hard for any retriever to match cleanly. The chunker doesn’t know that a legal provision is a unit that shouldn’t be broken.

The Solution

The solution was structure-aware chunking, which splits on the document’s own Article and Section markers instead of raw length. Instead of treating every document as generic text, the chunker started looking for legal markers like “Article I”, “Article II”, “Section 1”, “Sec. 2”, and “Rule 3 > Sec. 1”.


Structure-Aware Chunking

After switching from fixed-size chunks to Article/Section-aware chunks, the system got better at respecting legal boundaries.

For example:

  ┌─ CHUNK 1 ──(Section 4 > (c) Content-related, part 3/3) ──┐
  │ (3) Unsolicited Commercial Communications. The           │
  │ transmission of commercial electronic communication      │
  │ with the use of computer system… unless: (i) There is    │
  │ prior affirmative consent from the recipient; or (ii)…   │
  │ (iii)… (aa)… opt-out… (bb)… does not purposely disguise  │
  │ the source… (cc)… misleading information…                │
  │                                                          │
  │ (4) Libel. The unlawful or prohibited acts of libel as   │ ← libel is a 
  │ defined in Article 355 of the Revised Penal Code, as     │ PASSENGER on the
  │ amended, committed through a computer system…            │ tail of a spam-
  └──────────────────────────────────────────────────────────┘ dominated chunk

Section boundaries are respected now, no provision gets split mid-sentence. But Section 4 is enormous: it holds thirteen separate offenses across three lettered groups. It doesn’t fit in one chunk, so the chunker falls back to slicing it by length and libel (Section 4(c)(4)), the last and shortest item, lands as a passenger on the tail of the long spam clause (Section 4(c)(3)), exactly as the diagram above shows.

The Problem

The chunker respected Sections, but some Sections contained several independent legal rules. The system stopped cutting randomly through Articles and Sections, but it still treated a long Section as one chunk or a length-sliced block. That buried small important rules inside unrelated surrounding text.

This is worse than it sounds. When a user asks “What law punishes online libel?”, dense search, BM25, and the reranker all score that chunk low for the query and all three are right to, because most of the chunk’s text really is about spam. Nothing in the retrieval stack is broken. The chunk is simply mis-cut: the libel rule is drowned out by the clause it was glued to.

The Solution

The solution was enumeration-aware chunking, which does not stop at Articles or Sections. It also understands that a long legal provision may contain smaller numbered or lettered units, and that each unit may be a separate rule.


Enumeration-Aware Chunking

When a Section is too large, inspect its internal list structure. If the list items are meaningful legal units, split them into separate chunks. The chunk now has a clearer identity.

For example:

┌─ CHUNK 1 (Section 4(c)(3)) ─────────────────────────────────┐
│ Unsolicited Commercial Communications.                      │
│ The transmission of commercial electronic communication…    │
│ unless:                                                     │
│   (i) prior affirmative consent…                            │
│   (ii) the communication complies with conditions…          │
│   (iii) the recipient may opt out, and the message:         │
│       (aa) includes a functional opt-out mechanism…         │
│       (bb) does not disguise the source…                    │
│       (cc) does not contain misleading information…         │
│ structure_path: Section 4 > (c) > (3)                       │
└─────────────────────────────────────────────────────────────┘

┌─ CHUNK 2 (Section 4(c)(4)) ─────────────────────────────────┐
│ Libel.                                                      │
│ The unlawful or prohibited acts of libel as defined in      │
│ Article 355 of the Revised Penal Code, as amended, committed│
│ through a computer system or any other similar means…       │
│ structure_path: Section 4 > (c) > (4)                       │
└─────────────────────────────────────────────────────────────┘

For the question “What law penalizes online libel?” the chunk about “Section 4(c)(4). Libel” can rank highly because most of its text is about libel. It is no longer buried inside a larger chunk dominated by spam, cybersex, or child pornography.

The hard part was deciding which markers should create a split. At first, it sounds easy:

  • Split on (a), (b), (c)
  • Split on (1), (2), (3)
  • Do not split on (i), (ii), (iii)

But this rule failed because legal numbering is messy. For example, (i) can mean two different things. In one document, it may be the ninth item in an alphabetic list:

(g)
(h)
(i)
(j)

Note: (i) here is a sibling item. It may deserve its own chunk.

But in another document, it may be a roman numeral child item:

(a) Misuse of Devices
	(i) first condition
	(ii) second condition

Note: (i) here should probably stay inside the parent item.

This is where the opening rule matters. Enumeration-aware chunking cannot judge by marker shape alone. It has to judge by sequence context because a legal section may be a container, not the actual retrieval unit.

The Problem

Making the chunks finer improved one specific retrieval case, but it hurt some questions that needed a wider legal passage. Some legal answers depend on multiple neighboring list items or a whole section. When enumeration-aware chunking split everything too finely, those answers became fragmented.

Before:
	Chunk contains Section 21 with several related requirements together.

After:
	Chunk A has one requirement.
	Chunk B has another requirement.
	Chunk C has another requirement.

For a question that asks about the whole procedure, retrieval may only bring back one or two fragments instead of the complete rule.

This is the first big tradeoff in the chunking evolution: coarse chunks give better coverage but worse precision, while finer chunks give better precision but worse completeness.

The Solution

The solution was parent expansion. Instead of changing the chunker again, retrieval got a post-processing step.

  1. Retrieve small precise child chunks.
  2. Rerank them.
  3. If a child chunk belongs to a larger parent provision, optionally bring the parent text along.

For example:

Before parent expansion:

┌─ RETRIEVED CHUNK ──────────────────────────────────────────┐
│ Section 21(a)(1)                                           │
│ One witness or inventory requirement appears here.         │
│                                                            │
│ The model sees one fragment of the procedure, but not the  │
│ whole custody-and-inventory rule around it.                │
└────────────────────────────────────────────────────────────┘

After parent expansion:

┌─ EXPANDED CONTEXT ─────────────────────────────────────────┐
│ Section 21. Custody and Disposition of Confiscated, Seized │
│ and/or Surrendered Dangerous Drugs                         │
│                                                            │
│ (a) Inventory and photographing requirement                │
│   (1) presence of the accused or representative            │
│   (2) required witnesses                                   │
│   (3) chain-of-custody handling                            │
│   ...                                                      │
│                                                            │
│ The retriever still uses the precise child chunk, but the  │
│ model receives enough surrounding law to answer the whole  │
│ procedural question.                                       │
└────────────────────────────────────────────────────────────┘

At first I thought chunking was a token-size setting. It turned out to be a claim about the document’s legal structure. Fixed-size chunks ignored that structure. Section-aware chunks respected it, but missed smaller rules inside long sections. Enumeration-aware chunks fixed precision, but caused fragmentation. Parent expansion finally separated the two jobs: retrieve small, answer with enough surrounding law.

That solved chunk shape. It did not solve legal time. The next problem was harder: a chunk can be perfectly shaped and still be obsolete, amended, superseded, or partly repealed.