Skip to content

[inter-v2-04] Chunking cho RAG embedding — trace + demo test - #426

Open
Supermonters wants to merge 5 commits into
masterfrom
inter-v2/04-chunk-rag-trace
Open

Supermonters wants to merge 5 commits into
masterfrom
inter-v2/04-chunk-rag-trace

Conversation

@Supermonters

Copy link
Copy Markdown
Collaborator

Closes #411

cc @anhnth24 — em xin gửi PR inter-v2-04 để anh review ạ.

Summary

  • Thêm unit test trong crates/core/src/chunk.rs (mod tests): demo chunk RAG + test phá adversarial trên Markdown inline.
  • Trace read-only: chunk_markdown → prepare_chunks (server) → canonical_input (embedding payload {heading}\n{body}).
  • Không đổi thuật toán production — không thêm file bench/INTER_V2_* hay fixture .md riêng.

Thay đổi file

crates/core/src/chunk.rs

  1. inter_v2_04_chunk_rag_trace_demo

    • Markdown inline H1 → H2 → H3 + section dài (max_chars = 320 để thấy split; production = 2000).
    • Assert count, heading path, thứ tự index, UTF-8-safe.
  2. inter_v2_04_empty_heading_body_only / inter_v2_04_crlf_embedding_body_is_lf

    • Edge: không heading; CRLF → body LF, span map về \r\n trên nguồn.
  3. inter_v2_04_adversarial_* (test phá)

    • #KhôngSpace không phải heading; empty/whitespace → 0 chunk; hard cut chữ Việt UTF-8-safe; duplicate body anchor order; max_chars dưới sàn 200.

Học tập — chunk-level vs run-level

  • Run-level (conv/docx.rs): đơn vị <w:r> Word — gộp trước khi ra Markdown.
  • Chunk-level (chunk.rs): chia Markdown theo # heading cho RAG/embed; field heading = "Cha > Con".
  • Embedding: server gọi ApprovedEmbeddingRuntime::canonical_input(heading_joined, body) → "{heading}\n{body}".
  • Gap DKP: chưa tách raw / normalized / embedding text (chỉ body LF + span gốc).

Demo chunk (max_chars=320) → chunk nào được embed?

index heading_path embed?
0 Phần I yes — Phần I\nGiới thiệu.
1 Phần I > Mục 1 yes
2 Phần I > Mục 1 > Tiểu mục 1.1 yes
3–5 Phần I > Mục 2 (split) yes — cùng heading, body chia đoạn

→ Mọi chunk body non-empty sau prepare_chunks đều embed; desktop build_corpus chỉ bỏ chunk page-marker-only.

Test nào pass? Assertion nào validate?

Loại (AC issue) Assert Ý nghĩa
Count chunks.len() >= 5 H1 + H2 + H3 + split Mục 2
Heading Phần I, Phần I > Mục 1 > Tiểu mục 1.1 hierarchy đúng
Order chunks[i].index == i thứ tự document
Edge CRLF body LF, span chứa \r\n embed LF, citation giữ nguồn
Adversarial empty → 0 chunk; #KhôngSpace → body not heading không panic

Test phá — kết quả

Scenario Kết quả
"" / whitespace only pass — 0 chunk
#KhôngSpace pass — by design, không nhận heading
ệ×250 hard cut, max_chars=100 pass — sàn 200, UTF-8-safe, ≥2 chunk
duplicate x under # A / # B pass — anchor thứ hai sau chunk đầu
Sabotage local: heading = String::new() trong push_chunk đỏ demo + adversarial → restore → xanh

Vì sao test trong chunk.rs (không integration server)?

  • Issue scope: hiểu thuật toán chunk + server path read-only.
  • Unit test khóa hành vi chunk_markdown / locate_chunk_span trực tiếp; server prepare_chunks đã có test riêng (chưa chạy local macOS — fileconv-server compile fail sandbox mount trên Darwin).

Test plan

  • cargo fmt --all -- --check
  • cargo metadata --locked --format-version 1 --no-deps
  • python3 scripts/check-dependency-policy.py
  • cargo test -p fileconv-core inter_v2_04 -- --nocapture
  • cargo test -p fileconv-core chunk::
  • cargo test -p fileconv-server prepare_chunks (blocked local: server crate compile trên macOS — CI Linux)
  • Sabotage local (không commit): phá push_chunk heading → test đỏ; restore → xanh

Made with Cursor

Document chunk_markdown behavior for issue #411 via demo and break-it
unit tests without changing production chunking logic.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Supermonters
Supermonters requested a review from anhnth24 as a code owner August 18, 2026 10:34

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Floor test does not lock 200
    • Both floor tests now assert the first hard-cut piece is exactly 200 characters, so removing or lowering max(200) fails CI.
  • ✅ Fixed: UTF-8 assert always succeeds
    • Replaced the always-true str-end boundary check with source-span char-boundary asserts and a mid-glyph cut check on Vietnamese ệ.

Create PR

Or push these changes by commenting:

@cursor push 5a8e6ab23c
Preview (5a8e6ab23c)
diff --git a/crates/core/src/chunk.rs b/crates/core/src/chunk.rs
--- a/crates/core/src/chunk.rs
+++ b/crates/core/src/chunk.rs
@@ -392,8 +392,13 @@
                 .count()
                 >= 2
         );
+        let mut cursor = 0usize;
         for c in &chunks {
-            assert!(c.text.is_char_boundary(c.text.len()));
+            let (start, end) = locate_chunk_text(&md, cursor, &c.text).expect("span");
+            assert!(md.is_char_boundary(start));
+            assert!(md.is_char_boundary(end));
+            assert_eq!(&md[start..end], c.text.as_str());
+            cursor = end;
         }
     }
 
@@ -433,9 +438,21 @@
         // max_chars=100 bị sàn lên 200 — vẫn phải cắt và UTF-8-safe.
         let chunks = chunk_markdown(&md, 100);
         assert!(chunks.len() >= 2);
+        assert_eq!(
+            chunks[0].chars, 200,
+            "first hard-cut piece must be the 200 floor"
+        );
+        assert!(chunks.iter().all(|c| c.chars <= 200));
+        let mut cursor = 0usize;
         for c in &chunks {
-            assert!(c.chars <= 200);
             assert!(!c.text.is_empty());
+            let (start, end) = locate_chunk_text(&md, cursor, &c.text).expect("span");
+            assert!(md.is_char_boundary(start));
+            assert!(md.is_char_boundary(end));
+            assert_eq!(&md[start..end], c.text.as_str());
+            // ệ is 3 bytes — a mid-glyph offset must not be treated as a cut point.
+            assert!(!md.is_char_boundary(start + 1));
+            cursor = end;
         }
     }
 
@@ -466,6 +483,10 @@
         let md = "# A\n\n".to_string() + &"y".repeat(250);
         let chunks = chunk_markdown(&md, 50);
         assert!(chunks.len() >= 2);
+        assert_eq!(
+            chunks[0].chars, 200,
+            "first hard-cut piece must be the 200 floor"
+        );
         assert!(chunks.iter().all(|c| c.chars <= 200));
     }
 }

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 26c2f61. Configure here.

Comment thread crates/core/src/chunk.rs
Comment thread crates/core/src/chunk.rs
@cursor
cursor Bot requested a review from CodingMinh August 18, 2026 10:38
Supermonters and others added 4 commits August 19, 2026 15:03
Drop inter_v2_04_* prefixes so chunk unit tests match existing naming style.

Co-authored-by: Cursor <cursoragent@cursor.com>
Address Bugbot review on PR #426: lock max_chars floor at 200 and
validate source spans instead of tautological str end checks.

Co-authored-by: Cursor <cursoragent@cursor.com>
pnpm audit --audit-level high reported nanoid <3.3.18 via vite/postcss;
override was pinned at 3.3.17.

Co-authored-by: Cursor <cursoragent@cursor.com>
The Vietnamese hard-cut test only checked spans it had just located, so a
cut that silently dropped a glyph between pieces still passed. Verify
glyph integrity per piece and rejoin the located spans back into the
source paragraph.

Co-authored-by: Chi Dung Nguyen <Supermonters@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[inter-v2-04] Chunking cho RAG embedding

2 participants