abb-opencode-local-rag 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/LICENSE +21 -0
  2. package/README.de.md +416 -0
  3. package/README.es.md +416 -0
  4. package/README.fr.md +416 -0
  5. package/README.md +491 -0
  6. package/README.pt-BR.md +416 -0
  7. package/README.zh-CN.md +416 -0
  8. package/dist/bin/install-skills.d.ts +20 -0
  9. package/dist/bin/install-skills.d.ts.map +1 -0
  10. package/dist/bin/install-skills.js +195 -0
  11. package/dist/bin/install-skills.js.map +1 -0
  12. package/dist/chunker/index.d.ts +24 -0
  13. package/dist/chunker/index.d.ts.map +1 -0
  14. package/dist/chunker/index.js +2 -0
  15. package/dist/chunker/index.js.map +1 -0
  16. package/dist/chunker/semantic-chunker.d.ts +97 -0
  17. package/dist/chunker/semantic-chunker.d.ts.map +1 -0
  18. package/dist/chunker/semantic-chunker.js +294 -0
  19. package/dist/chunker/semantic-chunker.js.map +1 -0
  20. package/dist/chunker/sentence-splitter.d.ts +28 -0
  21. package/dist/chunker/sentence-splitter.d.ts.map +1 -0
  22. package/dist/chunker/sentence-splitter.js +219 -0
  23. package/dist/chunker/sentence-splitter.js.map +1 -0
  24. package/dist/cli/common.d.ts +65 -0
  25. package/dist/cli/common.d.ts.map +1 -0
  26. package/dist/cli/common.js +138 -0
  27. package/dist/cli/common.js.map +1 -0
  28. package/dist/cli/delete.d.ts +8 -0
  29. package/dist/cli/delete.d.ts.map +1 -0
  30. package/dist/cli/delete.js +173 -0
  31. package/dist/cli/delete.js.map +1 -0
  32. package/dist/cli/file-collection.d.ts +2 -0
  33. package/dist/cli/file-collection.d.ts.map +1 -0
  34. package/dist/cli/file-collection.js +53 -0
  35. package/dist/cli/file-collection.js.map +1 -0
  36. package/dist/cli/ingest.d.ts +100 -0
  37. package/dist/cli/ingest.d.ts.map +1 -0
  38. package/dist/cli/ingest.js +363 -0
  39. package/dist/cli/ingest.js.map +1 -0
  40. package/dist/cli/list.d.ts +35 -0
  41. package/dist/cli/list.d.ts.map +1 -0
  42. package/dist/cli/list.js +210 -0
  43. package/dist/cli/list.js.map +1 -0
  44. package/dist/cli/options.d.ts +100 -0
  45. package/dist/cli/options.d.ts.map +1 -0
  46. package/dist/cli/options.js +241 -0
  47. package/dist/cli/options.js.map +1 -0
  48. package/dist/cli/query.d.ts +24 -0
  49. package/dist/cli/query.d.ts.map +1 -0
  50. package/dist/cli/query.js +191 -0
  51. package/dist/cli/query.js.map +1 -0
  52. package/dist/cli/read-neighbors.d.ts +11 -0
  53. package/dist/cli/read-neighbors.d.ts.map +1 -0
  54. package/dist/cli/read-neighbors.js +224 -0
  55. package/dist/cli/read-neighbors.js.map +1 -0
  56. package/dist/cli/status.d.ts +8 -0
  57. package/dist/cli/status.d.ts.map +1 -0
  58. package/dist/cli/status.js +80 -0
  59. package/dist/cli/status.js.map +1 -0
  60. package/dist/cli/sync.d.ts +8 -0
  61. package/dist/cli/sync.d.ts.map +1 -0
  62. package/dist/cli/sync.js +244 -0
  63. package/dist/cli/sync.js.map +1 -0
  64. package/dist/cli-main.d.ts +12 -0
  65. package/dist/cli-main.d.ts.map +1 -0
  66. package/dist/cli-main.js +63 -0
  67. package/dist/cli-main.js.map +1 -0
  68. package/dist/embedder/index.d.ts +85 -0
  69. package/dist/embedder/index.d.ts.map +1 -0
  70. package/dist/embedder/index.js +284 -0
  71. package/dist/embedder/index.js.map +1 -0
  72. package/dist/features/list.d.ts +37 -0
  73. package/dist/features/list.d.ts.map +1 -0
  74. package/dist/features/list.js +40 -0
  75. package/dist/features/list.js.map +1 -0
  76. package/dist/features/sync.d.ts +207 -0
  77. package/dist/features/sync.d.ts.map +1 -0
  78. package/dist/features/sync.js +380 -0
  79. package/dist/features/sync.js.map +1 -0
  80. package/dist/index.d.ts +3 -0
  81. package/dist/index.d.ts.map +1 -0
  82. package/dist/index.js +53 -0
  83. package/dist/index.js.map +1 -0
  84. package/dist/ingest/compute.d.ts +86 -0
  85. package/dist/ingest/compute.d.ts.map +1 -0
  86. package/dist/ingest/compute.js +177 -0
  87. package/dist/ingest/compute.js.map +1 -0
  88. package/dist/ingest/file.d.ts +27 -0
  89. package/dist/ingest/file.d.ts.map +1 -0
  90. package/dist/ingest/file.js +67 -0
  91. package/dist/ingest/file.js.map +1 -0
  92. package/dist/ingest/visual.d.ts +45 -0
  93. package/dist/ingest/visual.d.ts.map +1 -0
  94. package/dist/ingest/visual.js +234 -0
  95. package/dist/ingest/visual.js.map +1 -0
  96. package/dist/parser/docx-parser.d.ts +12 -0
  97. package/dist/parser/docx-parser.d.ts.map +1 -0
  98. package/dist/parser/docx-parser.js +328 -0
  99. package/dist/parser/docx-parser.js.map +1 -0
  100. package/dist/parser/html-parser.d.ts +18 -0
  101. package/dist/parser/html-parser.d.ts.map +1 -0
  102. package/dist/parser/html-parser.js +102 -0
  103. package/dist/parser/html-parser.js.map +1 -0
  104. package/dist/parser/index.d.ts +214 -0
  105. package/dist/parser/index.d.ts.map +1 -0
  106. package/dist/parser/index.js +454 -0
  107. package/dist/parser/index.js.map +1 -0
  108. package/dist/parser/pdf-extract.d.ts +81 -0
  109. package/dist/parser/pdf-extract.d.ts.map +1 -0
  110. package/dist/parser/pdf-extract.js +112 -0
  111. package/dist/parser/pdf-extract.js.map +1 -0
  112. package/dist/parser/pdf-filter.d.ts +117 -0
  113. package/dist/parser/pdf-filter.d.ts.map +1 -0
  114. package/dist/parser/pdf-filter.js +528 -0
  115. package/dist/parser/pdf-filter.js.map +1 -0
  116. package/dist/parser/title-extractor.d.ts +69 -0
  117. package/dist/parser/title-extractor.d.ts.map +1 -0
  118. package/dist/parser/title-extractor.js +145 -0
  119. package/dist/parser/title-extractor.js.map +1 -0
  120. package/dist/pdf-visual/captioner.d.ts +16 -0
  121. package/dist/pdf-visual/captioner.d.ts.map +1 -0
  122. package/dist/pdf-visual/captioner.js +63 -0
  123. package/dist/pdf-visual/captioner.js.map +1 -0
  124. package/dist/pdf-visual/captioners/fast.d.ts +7 -0
  125. package/dist/pdf-visual/captioners/fast.d.ts.map +1 -0
  126. package/dist/pdf-visual/captioners/fast.js +103 -0
  127. package/dist/pdf-visual/captioners/fast.js.map +1 -0
  128. package/dist/pdf-visual/captioners/quality.d.ts +7 -0
  129. package/dist/pdf-visual/captioners/quality.d.ts.map +1 -0
  130. package/dist/pdf-visual/captioners/quality.js +127 -0
  131. package/dist/pdf-visual/captioners/quality.js.map +1 -0
  132. package/dist/pdf-visual/captioners/shared.d.ts +44 -0
  133. package/dist/pdf-visual/captioners/shared.d.ts.map +1 -0
  134. package/dist/pdf-visual/captioners/shared.js +104 -0
  135. package/dist/pdf-visual/captioners/shared.js.map +1 -0
  136. package/dist/pdf-visual/detector.d.ts +9 -0
  137. package/dist/pdf-visual/detector.d.ts.map +1 -0
  138. package/dist/pdf-visual/detector.js +234 -0
  139. package/dist/pdf-visual/detector.js.map +1 -0
  140. package/dist/pdf-visual/index.d.ts +13 -0
  141. package/dist/pdf-visual/index.d.ts.map +1 -0
  142. package/dist/pdf-visual/index.js +45 -0
  143. package/dist/pdf-visual/index.js.map +1 -0
  144. package/dist/pdf-visual/renderer.d.ts +9 -0
  145. package/dist/pdf-visual/renderer.d.ts.map +1 -0
  146. package/dist/pdf-visual/renderer.js +177 -0
  147. package/dist/pdf-visual/renderer.js.map +1 -0
  148. package/dist/pdf-visual/types.d.ts +62 -0
  149. package/dist/pdf-visual/types.d.ts.map +1 -0
  150. package/dist/pdf-visual/types.js +32 -0
  151. package/dist/pdf-visual/types.js.map +1 -0
  152. package/dist/server/error-utils.d.ts +79 -0
  153. package/dist/server/error-utils.d.ts.map +1 -0
  154. package/dist/server/error-utils.js +148 -0
  155. package/dist/server/error-utils.js.map +1 -0
  156. package/dist/server/index.d.ts +258 -0
  157. package/dist/server/index.d.ts.map +1 -0
  158. package/dist/server/index.js +1104 -0
  159. package/dist/server/index.js.map +1 -0
  160. package/dist/server/list-scanner.d.ts +52 -0
  161. package/dist/server/list-scanner.d.ts.map +1 -0
  162. package/dist/server/list-scanner.js +72 -0
  163. package/dist/server/list-scanner.js.map +1 -0
  164. package/dist/server/tool-definitions.d.ts +8 -0
  165. package/dist/server/tool-definitions.d.ts.map +1 -0
  166. package/dist/server/tool-definitions.js +181 -0
  167. package/dist/server/tool-definitions.js.map +1 -0
  168. package/dist/server/tool-input.d.ts +37 -0
  169. package/dist/server/tool-input.d.ts.map +1 -0
  170. package/dist/server/tool-input.js +216 -0
  171. package/dist/server/tool-input.js.map +1 -0
  172. package/dist/server/types.d.ts +331 -0
  173. package/dist/server/types.d.ts.map +1 -0
  174. package/dist/server/types.js +3 -0
  175. package/dist/server/types.js.map +1 -0
  176. package/dist/server-main.d.ts +46 -0
  177. package/dist/server-main.d.ts.map +1 -0
  178. package/dist/server-main.js +242 -0
  179. package/dist/server-main.js.map +1 -0
  180. package/dist/utils/base-dirs.d.ts +212 -0
  181. package/dist/utils/base-dirs.d.ts.map +1 -0
  182. package/dist/utils/base-dirs.js +422 -0
  183. package/dist/utils/base-dirs.js.map +1 -0
  184. package/dist/utils/errors.d.ts +24 -0
  185. package/dist/utils/errors.d.ts.map +1 -0
  186. package/dist/utils/errors.js +53 -0
  187. package/dist/utils/errors.js.map +1 -0
  188. package/dist/utils/limits.d.ts +26 -0
  189. package/dist/utils/limits.d.ts.map +1 -0
  190. package/dist/utils/limits.js +28 -0
  191. package/dist/utils/limits.js.map +1 -0
  192. package/dist/utils/list-sources.d.ts +47 -0
  193. package/dist/utils/list-sources.d.ts.map +1 -0
  194. package/dist/utils/list-sources.js +50 -0
  195. package/dist/utils/list-sources.js.map +1 -0
  196. package/dist/utils/raw-data-utils.d.ts +131 -0
  197. package/dist/utils/raw-data-utils.d.ts.map +1 -0
  198. package/dist/utils/raw-data-utils.js +255 -0
  199. package/dist/utils/raw-data-utils.js.map +1 -0
  200. package/dist/utils/scan.d.ts +126 -0
  201. package/dist/utils/scan.d.ts.map +1 -0
  202. package/dist/utils/scan.js +221 -0
  203. package/dist/utils/scan.js.map +1 -0
  204. package/dist/utils/scope-match.d.ts +43 -0
  205. package/dist/utils/scope-match.d.ts.map +1 -0
  206. package/dist/utils/scope-match.js +87 -0
  207. package/dist/utils/scope-match.js.map +1 -0
  208. package/dist/utils/sensitive-path.d.ts +23 -0
  209. package/dist/utils/sensitive-path.d.ts.map +1 -0
  210. package/dist/utils/sensitive-path.js +91 -0
  211. package/dist/utils/sensitive-path.js.map +1 -0
  212. package/dist/utils/sync-path-key.d.ts +20 -0
  213. package/dist/utils/sync-path-key.d.ts.map +1 -0
  214. package/dist/utils/sync-path-key.js +33 -0
  215. package/dist/utils/sync-path-key.js.map +1 -0
  216. package/dist/vectordb/index.d.ts +168 -0
  217. package/dist/vectordb/index.d.ts.map +1 -0
  218. package/dist/vectordb/index.js +619 -0
  219. package/dist/vectordb/index.js.map +1 -0
  220. package/dist/vectordb/search-filters.d.ts +39 -0
  221. package/dist/vectordb/search-filters.d.ts.map +1 -0
  222. package/dist/vectordb/search-filters.js +136 -0
  223. package/dist/vectordb/search-filters.js.map +1 -0
  224. package/dist/vectordb/types.d.ts +196 -0
  225. package/dist/vectordb/types.d.ts.map +1 -0
  226. package/dist/vectordb/types.js +224 -0
  227. package/dist/vectordb/types.js.map +1 -0
  228. package/package.json +105 -0
  229. package/skills/mcp-local-rag/SKILL.md +308 -0
  230. package/skills/mcp-local-rag/references/cli-reference.md +175 -0
  231. package/skills/mcp-local-rag/references/html-ingestion.md +78 -0
  232. package/skills/mcp-local-rag/references/query-optimization.md +57 -0
  233. package/skills/mcp-local-rag/references/result-refinement.md +56 -0
@@ -0,0 +1,97 @@
1
+ import type { AtomicTextRange, TextChunk } from './index.js';
2
+ /**
3
+ * Semantic Chunker configuration
4
+ * Based on paper recommendations: hardThreshold=0.6, initConst=1.5, c=0.9
5
+ */
6
+ export interface SemanticChunkerConfig {
7
+ /** Hard threshold for minimum similarity (default: 0.6) */
8
+ hardThreshold: number;
9
+ /** Initial constant for first sentence pair (default: 1.5) */
10
+ initConst: number;
11
+ /** Scaling constant for threshold calculation (default: 0.9) */
12
+ c: number;
13
+ /** Minimum chunk length in characters (default: 50) */
14
+ minChunkLength: number;
15
+ }
16
+ /**
17
+ * Embedder interface for generating embeddings
18
+ */
19
+ export interface EmbedderInterface {
20
+ embedBatch(texts: string[]): Promise<number[][]>;
21
+ }
22
+ /**
23
+ * Check if a chunk is garbage (should be filtered out)
24
+ *
25
+ * Criteria (language-agnostic):
26
+ * 1. Empty after trimming
27
+ * 2. Contains alphanumeric -> valid content (keep)
28
+ * 3. Only decoration characters (----, ====, etc.) -> garbage
29
+ * 4. Single character repeated >80% of text -> garbage
30
+ *
31
+ * Note: Applied after minChunkLength filter
32
+ *
33
+ * @param text - Chunk text to check
34
+ * @returns true if chunk is garbage and should be removed
35
+ */
36
+ export declare function isGarbageChunk(text: string): boolean;
37
+ /** Default minimum chunk length in characters */
38
+ export declare const DEFAULT_MIN_CHUNK_LENGTH = 50;
39
+ /**
40
+ * Semantic chunker using Max-Min algorithm
41
+ *
42
+ * The algorithm groups consecutive sentences based on semantic similarity:
43
+ * 1. Split text into sentences
44
+ * 2. Generate embeddings for all sentences
45
+ * 3. For each sentence, decide whether to add to current chunk or start new chunk
46
+ * 4. Decision is based on comparing max similarity with new sentence vs min similarity within chunk
47
+ *
48
+ * Key insight: A sentence belongs to a chunk if its maximum similarity to any chunk member
49
+ * is greater than the minimum similarity between existing chunk members (with threshold adjustment)
50
+ */
51
+ export declare class SemanticChunker {
52
+ private readonly config;
53
+ constructor(config?: Partial<SemanticChunkerConfig>);
54
+ /**
55
+ * Split text into semantically coherent chunks
56
+ *
57
+ * @param text - The text to chunk
58
+ * @param embedder - Embedder to generate sentence embeddings
59
+ * @returns Array of text chunks
60
+ */
61
+ chunkText(text: string, embedder: EmbedderInterface, atomicRanges?: readonly AtomicTextRange[]): Promise<TextChunk[]>;
62
+ /**
63
+ * Group sentences into chunks using Max-Min algorithm
64
+ */
65
+ private groupSentences;
66
+ /**
67
+ * Decide if a sentence should be added to the current chunk
68
+ * Based on Max-Min algorithm from the paper
69
+ */
70
+ private shouldAddToChunk;
71
+ /**
72
+ * Get minimum pairwise similarity within a chunk.
73
+ * Only compares the last WINDOW_SIZE sentences for O(1) complexity.
74
+ * This approximation is valid because recent sentences are most relevant
75
+ * for determining chunk coherence (per Max-Min paper's experimental setup).
76
+ */
77
+ private getMinSimilarity;
78
+ /**
79
+ * Get maximum similarity between a sentence and any sentence in the chunk
80
+ */
81
+ private getMaxSimilarity;
82
+ /**
83
+ * Calculate dynamic threshold based on chunk size
84
+ * threshold = max(c * minSim * sigmoid(|C|), hardThreshold)
85
+ */
86
+ private calculateThreshold;
87
+ /**
88
+ * Sigmoid function
89
+ */
90
+ private sigmoid;
91
+ /**
92
+ * Calculate cosine similarity between two vectors
93
+ * Public for testing
94
+ */
95
+ cosineSimilarity(vec1: number[], vec2: number[]): number;
96
+ }
97
+ //# sourceMappingURL=semantic-chunker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic-chunker.d.ts","sourceRoot":"","sources":["../../src/chunker/semantic-chunker.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAO5D;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAA;IACrB,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAA;IACjB,gEAAgE;IAChE,CAAC,EAAE,MAAM,CAAA;IACT,uDAAuD;IACvD,cAAc,EAAE,MAAM,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;CACjD;AAoBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAmBpD;AAMD,iDAAiD;AACjD,eAAO,MAAM,wBAAwB,KAAK,CAAA;AAa1C;;;;;;;;;;;GAWG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAE9C,YAAY,MAAM,GAAE,OAAO,CAAC,qBAAqB,CAAM,EAEtD;IAED;;;;;;OAMG;IACG,SAAS,CACb,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,iBAAiB,EAC3B,YAAY,GAAE,SAAS,eAAe,EAAO,GAC5C,OAAO,CAAC,SAAS,EAAE,CAAC,CAgDtB;IAED;;OAEG;IACH,OAAO,CAAC,cAAc;IAsEtB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAaxB;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAuBxB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAWxB;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;OAEG;IACH,OAAO,CAAC,OAAO;IAIf;;;OAGG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAqBvD;CACF"}
@@ -0,0 +1,294 @@
1
+ // Semantic Chunker implementation using Max-Min algorithm
2
+ // Based on: "Max–Min semantic chunking of documents for RAG application" (Springer, 2025)
3
+ import { splitIntoSentenceUnits } from './sentence-splitter.js';
4
+ // ============================================
5
+ // Performance Optimization Constants
6
+ // ============================================
7
+ /**
8
+ * Number of recent sentences to compare in getMinSimilarity.
9
+ * Based on Max-Min paper's experimental conditions (median 5 sentences per chunk).
10
+ * Reduces complexity from O(k²) to O(WINDOW_SIZE²) = O(25) = O(1).
11
+ */
12
+ const WINDOW_SIZE = 5;
13
+ /**
14
+ * Maximum number of sentences per chunk before forced split.
15
+ * Safety limit to prevent computational explosion on homogeneous documents.
16
+ * Set to 3x the paper's median chunk size for reasonable margin.
17
+ */
18
+ const MAX_SENTENCES = 15;
19
+ /**
20
+ * Check if a chunk is garbage (should be filtered out)
21
+ *
22
+ * Criteria (language-agnostic):
23
+ * 1. Empty after trimming
24
+ * 2. Contains alphanumeric -> valid content (keep)
25
+ * 3. Only decoration characters (----, ====, etc.) -> garbage
26
+ * 4. Single character repeated >80% of text -> garbage
27
+ *
28
+ * Note: Applied after minChunkLength filter
29
+ *
30
+ * @param text - Chunk text to check
31
+ * @returns true if chunk is garbage and should be removed
32
+ */
33
+ export function isGarbageChunk(text) {
34
+ const trimmed = text.trim();
35
+ if (trimmed.length === 0)
36
+ return true;
37
+ // If contains any alphanumeric, consider valid content
38
+ if (/[a-zA-Z0-9]/.test(trimmed))
39
+ return false;
40
+ // Decoration line patterns only (----, ====, ****, etc.)
41
+ if (/^[-=_.*#|~`@!%^&*()[\]{}\\/<>:+\s]+$/.test(trimmed))
42
+ return true;
43
+ // Excessive repetition of single character (>80%)
44
+ const charCounts = new Map();
45
+ for (const char of trimmed) {
46
+ charCounts.set(char, (charCounts.get(char) ?? 0) + 1);
47
+ }
48
+ const maxCount = Math.max(...charCounts.values());
49
+ if (maxCount / trimmed.length > 0.8)
50
+ return true;
51
+ return false;
52
+ }
53
+ // ============================================
54
+ // Default Configuration
55
+ // ============================================
56
+ /** Default minimum chunk length in characters */
57
+ export const DEFAULT_MIN_CHUNK_LENGTH = 50;
58
+ const DEFAULT_SEMANTIC_CHUNKER_CONFIG = {
59
+ hardThreshold: 0.6,
60
+ initConst: 1.5,
61
+ c: 0.9,
62
+ minChunkLength: DEFAULT_MIN_CHUNK_LENGTH,
63
+ };
64
+ // ============================================
65
+ // SemanticChunker Class
66
+ // ============================================
67
+ /**
68
+ * Semantic chunker using Max-Min algorithm
69
+ *
70
+ * The algorithm groups consecutive sentences based on semantic similarity:
71
+ * 1. Split text into sentences
72
+ * 2. Generate embeddings for all sentences
73
+ * 3. For each sentence, decide whether to add to current chunk or start new chunk
74
+ * 4. Decision is based on comparing max similarity with new sentence vs min similarity within chunk
75
+ *
76
+ * Key insight: A sentence belongs to a chunk if its maximum similarity to any chunk member
77
+ * is greater than the minimum similarity between existing chunk members (with threshold adjustment)
78
+ */
79
+ export class SemanticChunker {
80
+ config;
81
+ constructor(config = {}) {
82
+ this.config = { ...DEFAULT_SEMANTIC_CHUNKER_CONFIG, ...config };
83
+ }
84
+ /**
85
+ * Split text into semantically coherent chunks
86
+ *
87
+ * @param text - The text to chunk
88
+ * @param embedder - Embedder to generate sentence embeddings
89
+ * @returns Array of text chunks
90
+ */
91
+ async chunkText(text, embedder, atomicRanges = []) {
92
+ // Handle empty input
93
+ if (!text || text.trim().length === 0) {
94
+ // Supplied ranges are programmer contracts and must fail fast even when
95
+ // ordinary empty text would otherwise return before sentence splitting.
96
+ if (atomicRanges.length > 0)
97
+ splitIntoSentenceUnits(text, atomicRanges);
98
+ return [];
99
+ }
100
+ // Split into sentences
101
+ const sentenceUnits = splitIntoSentenceUnits(text, atomicRanges);
102
+ if (sentenceUnits.length === 0) {
103
+ return [];
104
+ }
105
+ // Generate embeddings for all sentences
106
+ const embeddings = await embedder.embedBatch(sentenceUnits.map((unit) => unit.text));
107
+ // Apply Max-Min algorithm to group sentences into chunks
108
+ const sentenceGroups = this.groupSentences(sentenceUnits, embeddings);
109
+ // Convert groups to TextChunks
110
+ const chunks = [];
111
+ let chunkIndex = 0;
112
+ for (const group of sentenceGroups) {
113
+ const chunkText = group.map((unit) => unit.text).join(' ');
114
+ const containsAtomicUnit = group.some((unit) => unit.atomic);
115
+ // Filter out chunks that are too short or garbage
116
+ if ((containsAtomicUnit || chunkText.length >= this.config.minChunkLength) &&
117
+ !isGarbageChunk(chunkText)) {
118
+ const firstUnit = group[0];
119
+ const lastUnit = group[group.length - 1];
120
+ if (!firstUnit || !lastUnit)
121
+ continue;
122
+ chunks.push({
123
+ text: chunkText,
124
+ index: chunkIndex,
125
+ sourceStart: firstUnit.sourceStart,
126
+ sourceEnd: lastUnit.sourceEnd,
127
+ });
128
+ chunkIndex++;
129
+ }
130
+ }
131
+ return chunks;
132
+ }
133
+ /**
134
+ * Group sentences into chunks using Max-Min algorithm
135
+ */
136
+ groupSentences(sentences, embeddings) {
137
+ if (sentences.length === 0)
138
+ return [];
139
+ if (sentences.length === 1) {
140
+ const sentence = sentences[0];
141
+ return sentence ? [[sentence]] : [];
142
+ }
143
+ const groups = [];
144
+ let currentGroup = [];
145
+ let currentGroupEmbeddings = [];
146
+ for (let i = 0; i < sentences.length; i++) {
147
+ const sentence = sentences[i];
148
+ const embedding = embeddings[i];
149
+ if (!sentence || !embedding)
150
+ continue;
151
+ if (currentGroup.length === 0) {
152
+ // Start new group with first sentence
153
+ currentGroup.push(sentence);
154
+ currentGroupEmbeddings.push(embedding);
155
+ }
156
+ else if (currentGroup.length === 1) {
157
+ // Special case for second sentence (init phase)
158
+ const firstEmbedding = currentGroupEmbeddings[0];
159
+ if (!firstEmbedding)
160
+ continue;
161
+ const similarity = this.cosineSimilarity(firstEmbedding, embedding);
162
+ if (this.config.initConst * similarity > this.config.hardThreshold) {
163
+ // Add to current group
164
+ currentGroup.push(sentence);
165
+ currentGroupEmbeddings.push(embedding);
166
+ }
167
+ else {
168
+ // Start new group
169
+ groups.push([...currentGroup]);
170
+ currentGroup = [sentence];
171
+ currentGroupEmbeddings = [embedding];
172
+ }
173
+ }
174
+ else {
175
+ // Force split if chunk reaches MAX_SENTENCES (safety limit for performance)
176
+ if (currentGroup.length >= MAX_SENTENCES) {
177
+ groups.push([...currentGroup]);
178
+ currentGroup = [sentence];
179
+ currentGroupEmbeddings = [embedding];
180
+ continue;
181
+ }
182
+ // Normal case: check if sentence should join current group
183
+ const shouldAdd = this.shouldAddToChunk(embedding, currentGroupEmbeddings);
184
+ if (shouldAdd) {
185
+ currentGroup.push(sentence);
186
+ currentGroupEmbeddings.push(embedding);
187
+ }
188
+ else {
189
+ // Start new group
190
+ groups.push([...currentGroup]);
191
+ currentGroup = [sentence];
192
+ currentGroupEmbeddings = [embedding];
193
+ }
194
+ }
195
+ }
196
+ // Don't forget the last group
197
+ if (currentGroup.length > 0) {
198
+ groups.push(currentGroup);
199
+ }
200
+ return groups;
201
+ }
202
+ /**
203
+ * Decide if a sentence should be added to the current chunk
204
+ * Based on Max-Min algorithm from the paper
205
+ */
206
+ shouldAddToChunk(newEmbedding, chunkEmbeddings) {
207
+ // Calculate min similarity within current chunk
208
+ const minSim = this.getMinSimilarity(chunkEmbeddings);
209
+ // Calculate max similarity between new sentence and chunk
210
+ const maxSim = this.getMaxSimilarity(newEmbedding, chunkEmbeddings);
211
+ // Calculate dynamic threshold
212
+ const threshold = this.calculateThreshold(minSim, chunkEmbeddings.length);
213
+ return maxSim > threshold;
214
+ }
215
+ /**
216
+ * Get minimum pairwise similarity within a chunk.
217
+ * Only compares the last WINDOW_SIZE sentences for O(1) complexity.
218
+ * This approximation is valid because recent sentences are most relevant
219
+ * for determining chunk coherence (per Max-Min paper's experimental setup).
220
+ */
221
+ getMinSimilarity(embeddings) {
222
+ if (embeddings.length < 2)
223
+ return 1.0;
224
+ // Only compare the last WINDOW_SIZE embeddings to reduce O(k²) to O(1)
225
+ const startIdx = Math.max(0, embeddings.length - WINDOW_SIZE);
226
+ const windowEmbeddings = embeddings.slice(startIdx);
227
+ let minSim = 1.0;
228
+ for (let i = 0; i < windowEmbeddings.length; i++) {
229
+ for (let j = i + 1; j < windowEmbeddings.length; j++) {
230
+ const embI = windowEmbeddings[i];
231
+ const embJ = windowEmbeddings[j];
232
+ if (!embI || !embJ)
233
+ continue;
234
+ const sim = this.cosineSimilarity(embI, embJ);
235
+ if (sim < minSim) {
236
+ minSim = sim;
237
+ }
238
+ }
239
+ }
240
+ return minSim;
241
+ }
242
+ /**
243
+ * Get maximum similarity between a sentence and any sentence in the chunk
244
+ */
245
+ getMaxSimilarity(embedding, chunkEmbeddings) {
246
+ let maxSim = -1.0;
247
+ for (const chunkEmb of chunkEmbeddings) {
248
+ const sim = this.cosineSimilarity(embedding, chunkEmb);
249
+ if (sim > maxSim) {
250
+ maxSim = sim;
251
+ }
252
+ }
253
+ return maxSim;
254
+ }
255
+ /**
256
+ * Calculate dynamic threshold based on chunk size
257
+ * threshold = max(c * minSim * sigmoid(|C|), hardThreshold)
258
+ */
259
+ calculateThreshold(minSim, chunkSize) {
260
+ const sigmoidValue = this.sigmoid(chunkSize);
261
+ const dynamicThreshold = this.config.c * minSim * sigmoidValue;
262
+ return Math.max(dynamicThreshold, this.config.hardThreshold);
263
+ }
264
+ /**
265
+ * Sigmoid function
266
+ */
267
+ sigmoid(x) {
268
+ return 1 / (1 + Math.exp(-x));
269
+ }
270
+ /**
271
+ * Calculate cosine similarity between two vectors
272
+ * Public for testing
273
+ */
274
+ cosineSimilarity(vec1, vec2) {
275
+ if (vec1.length !== vec2.length || vec1.length === 0) {
276
+ return 0;
277
+ }
278
+ let dotProduct = 0;
279
+ let norm1 = 0;
280
+ let norm2 = 0;
281
+ for (let i = 0; i < vec1.length; i++) {
282
+ const v1 = vec1[i] ?? 0;
283
+ const v2 = vec2[i] ?? 0;
284
+ dotProduct += v1 * v2;
285
+ norm1 += v1 * v1;
286
+ norm2 += v2 * v2;
287
+ }
288
+ const denominator = Math.sqrt(norm1) * Math.sqrt(norm2);
289
+ if (denominator === 0)
290
+ return 0;
291
+ return dotProduct / denominator;
292
+ }
293
+ }
294
+ //# sourceMappingURL=semantic-chunker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic-chunker.js","sourceRoot":"","sources":["../../src/chunker/semantic-chunker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { AtomicTextRange } from './index.js';
2
+ export interface SentenceUnit {
3
+ text: string;
4
+ atomic: boolean;
5
+ sourceStart: number;
6
+ sourceEnd: number;
7
+ }
8
+ /**
9
+ * Split text into sentences using Intl.Segmenter
10
+ *
11
+ * Uses the Unicode Text Segmentation standard (UAX #29) via Intl.Segmenter.
12
+ * This provides multilingual support for sentence boundary detection.
13
+ *
14
+ * Note: Intl.Segmenter may split on abbreviations like "Mr." or "e.g."
15
+ * These edge cases are acceptable for semantic chunking as:
16
+ * 1. Short fragments will be grouped with adjacent sentences by similarity
17
+ * 2. Fragments below minChunkLength are filtered out
18
+ *
19
+ * @param text - The text to split into sentences
20
+ * @returns Array of sentences
21
+ */
22
+ export declare function splitIntoSentences(text: string): string[];
23
+ /**
24
+ * Split ordinary text while preserving the supplied positional ranges as
25
+ * indivisible sentence units.
26
+ */
27
+ export declare function splitIntoSentenceUnits(text: string, atomicRanges?: readonly AtomicTextRange[]): SentenceUnit[];
28
+ //# sourceMappingURL=sentence-splitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentence-splitter.d.ts","sourceRoot":"","sources":["../../src/chunker/sentence-splitter.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAyBjD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,OAAO,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;CAClB;AAoKD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAKzD;AAuBD;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,YAAY,GAAE,SAAS,eAAe,EAAO,GAC5C,YAAY,EAAE,CA0BhB"}
@@ -0,0 +1,219 @@
1
+ // Sentence Splitter for Semantic Chunking
2
+ // Created: 2025-12-27
3
+ // Purpose: Split text into sentences using Intl.Segmenter (Unicode standard)
4
+ // ============================================
5
+ // Constants
6
+ // ============================================
7
+ /**
8
+ * Placeholder for code blocks during processing
9
+ */
10
+ const CODE_BLOCK_PLACEHOLDER = '\u0000CODE_BLOCK\u0000';
11
+ /**
12
+ * Placeholder for inline code during processing
13
+ */
14
+ const INLINE_CODE_PLACEHOLDER = '\u0000INLINE_CODE\u0000';
15
+ // ============================================
16
+ // Helper Functions
17
+ // ============================================
18
+ function maskCode(text, sourceStart) {
19
+ const blocks = [];
20
+ const fenced = [...text.matchAll(/```[\s\S]*?```/g)].flatMap((match) => match.index === undefined ? [] : [{ start: match.index, end: match.index + match[0].length }]);
21
+ const matchedRanges = [...fenced];
22
+ let gapStart = 0;
23
+ for (const fencedRange of [...fenced, { start: text.length, end: text.length }]) {
24
+ const gap = text.slice(gapStart, fencedRange.start);
25
+ for (const match of gap.matchAll(/`[^`]+`/g)) {
26
+ if (match.index === undefined)
27
+ continue;
28
+ matchedRanges.push({
29
+ start: gapStart + match.index,
30
+ end: gapStart + match.index + match[0].length,
31
+ });
32
+ }
33
+ gapStart = fencedRange.end;
34
+ }
35
+ matchedRanges.sort((left, right) => left.start - right.start);
36
+ let mappedText = '';
37
+ let cursor = 0;
38
+ const segments = [];
39
+ const append = (value, start, end, linear) => {
40
+ const mappedStart = mappedText.length;
41
+ mappedText += value;
42
+ segments.push({
43
+ mappedStart,
44
+ mappedEnd: mappedText.length,
45
+ sourceStart: sourceStart + start,
46
+ sourceEnd: sourceStart + end,
47
+ linear,
48
+ });
49
+ };
50
+ for (const [index, range] of matchedRanges.entries()) {
51
+ if (range.start > cursor)
52
+ append(text.slice(cursor, range.start), cursor, range.start, true);
53
+ const content = text.slice(range.start, range.end);
54
+ const placeholderPrefix = content.startsWith('```')
55
+ ? CODE_BLOCK_PLACEHOLDER
56
+ : INLINE_CODE_PLACEHOLDER;
57
+ const placeholder = `${placeholderPrefix}${index}${placeholderPrefix}`;
58
+ blocks.push({ placeholder, content });
59
+ append(placeholder, range.start, range.end, false);
60
+ cursor = range.end;
61
+ }
62
+ if (cursor < text.length)
63
+ append(text.slice(cursor), cursor, text.length, true);
64
+ return { mapped: { text: mappedText, segments }, blocks };
65
+ }
66
+ function sourceOffsetAt(mapped, offset, fallback) {
67
+ let low = 0;
68
+ let high = mapped.segments.length - 1;
69
+ while (low <= high) {
70
+ const middle = Math.floor((low + high) / 2);
71
+ const segment = mapped.segments[middle];
72
+ if (offset < segment.mappedStart) {
73
+ high = middle - 1;
74
+ }
75
+ else if (offset > segment.mappedEnd) {
76
+ low = middle + 1;
77
+ }
78
+ else if (offset === segment.mappedEnd) {
79
+ return segment.sourceEnd;
80
+ }
81
+ else {
82
+ return segment.linear
83
+ ? segment.sourceStart + (offset - segment.mappedStart)
84
+ : segment.sourceStart;
85
+ }
86
+ }
87
+ return fallback;
88
+ }
89
+ function restoreCode(text, blocks) {
90
+ let restored = text;
91
+ for (const block of blocks) {
92
+ restored = restored.replace(block.placeholder, () => block.content);
93
+ }
94
+ return restored;
95
+ }
96
+ function trimmedRange(text, start, end) {
97
+ const value = text.slice(start, end);
98
+ const trimmedStart = start + (value.length - value.trimStart().length);
99
+ const trimmedEnd = start + value.trimEnd().length;
100
+ return trimmedStart < trimmedEnd ? [trimmedStart, trimmedEnd] : null;
101
+ }
102
+ function splitOrdinaryRange(text, sourceStart, sourceEnd) {
103
+ if (sourceStart >= sourceEnd)
104
+ return [];
105
+ const { mapped, blocks } = maskCode(text.slice(sourceStart, sourceEnd), sourceStart);
106
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: NUL delimiters identify masked code placeholders.
107
+ const paragraphSeparator = /\n{2,}|\n(?=\S)|(?<=\u0000)\n/g;
108
+ const paragraphRanges = [];
109
+ let cursor = 0;
110
+ for (const match of mapped.text.matchAll(paragraphSeparator)) {
111
+ const separatorStart = match.index;
112
+ if (separatorStart === undefined)
113
+ continue;
114
+ paragraphRanges.push([cursor, separatorStart]);
115
+ cursor = separatorStart + match[0].length;
116
+ }
117
+ paragraphRanges.push([cursor, mapped.text.length]);
118
+ const units = [];
119
+ const appendUnit = (start, end) => {
120
+ const range = trimmedRange(mapped.text, start, end);
121
+ if (!range)
122
+ return;
123
+ const [trimmedStart, trimmedEnd] = range;
124
+ const restored = restoreCode(mapped.text.slice(trimmedStart, trimmedEnd), blocks).trim();
125
+ if (!restored)
126
+ return;
127
+ units.push({
128
+ text: restored,
129
+ atomic: false,
130
+ sourceStart: sourceOffsetAt(mapped, trimmedStart, sourceStart),
131
+ sourceEnd: sourceOffsetAt(mapped, trimmedEnd, sourceEnd),
132
+ });
133
+ };
134
+ for (const [paragraphStart, paragraphEnd] of paragraphRanges) {
135
+ const range = trimmedRange(mapped.text, paragraphStart, paragraphEnd);
136
+ if (!range)
137
+ continue;
138
+ const [trimmedStart, trimmedEnd] = range;
139
+ const paragraph = mapped.text.slice(trimmedStart, trimmedEnd);
140
+ if (/^#{1,6}\s/.test(paragraph)) {
141
+ appendUnit(trimmedStart, trimmedEnd);
142
+ continue;
143
+ }
144
+ for (const segment of segmenter.segment(paragraph)) {
145
+ appendUnit(trimmedStart + segment.index, trimmedStart + segment.index + segment.segment.length);
146
+ }
147
+ }
148
+ return units;
149
+ }
150
+ // ============================================
151
+ // Intl.Segmenter-based splitting
152
+ // ============================================
153
+ // Create segmenters for supported languages
154
+ // Using 'und' (undetermined) as fallback for general Unicode support
155
+ const segmenter = new Intl.Segmenter('und', { granularity: 'sentence' });
156
+ /**
157
+ * Split text into sentences using Intl.Segmenter
158
+ *
159
+ * Uses the Unicode Text Segmentation standard (UAX #29) via Intl.Segmenter.
160
+ * This provides multilingual support for sentence boundary detection.
161
+ *
162
+ * Note: Intl.Segmenter may split on abbreviations like "Mr." or "e.g."
163
+ * These edge cases are acceptable for semantic chunking as:
164
+ * 1. Short fragments will be grouped with adjacent sentences by similarity
165
+ * 2. Fragments below minChunkLength are filtered out
166
+ *
167
+ * @param text - The text to split into sentences
168
+ * @returns Array of sentences
169
+ */
170
+ export function splitIntoSentences(text) {
171
+ if (!text || text.trim().length === 0) {
172
+ return [];
173
+ }
174
+ return splitOrdinaryRange(text, 0, text.length).map((unit) => unit.text);
175
+ }
176
+ function validateAtomicRanges(text, atomicRanges) {
177
+ let previousEnd = 0;
178
+ for (const range of atomicRanges) {
179
+ const validOffsets = Number.isInteger(range.start) &&
180
+ Number.isInteger(range.end) &&
181
+ range.start >= 0 &&
182
+ range.start < range.end &&
183
+ range.end <= text.length;
184
+ const orderedAndNonOverlapping = range.start >= previousEnd;
185
+ if (!validOffsets || !orderedAndNonOverlapping) {
186
+ throw new Error(`Invalid atomic range [${range.start}, ${range.end}) for text length ${text.length}`);
187
+ }
188
+ previousEnd = range.end;
189
+ }
190
+ }
191
+ /**
192
+ * Split ordinary text while preserving the supplied positional ranges as
193
+ * indivisible sentence units.
194
+ */
195
+ export function splitIntoSentenceUnits(text, atomicRanges = []) {
196
+ validateAtomicRanges(text, atomicRanges);
197
+ if (atomicRanges.length === 0) {
198
+ return splitOrdinaryRange(text, 0, text.length);
199
+ }
200
+ const units = [];
201
+ let cursor = 0;
202
+ for (const range of atomicRanges) {
203
+ units.push(...splitOrdinaryRange(text, cursor, range.start));
204
+ const atomicText = text.slice(range.start, range.end).trim();
205
+ if (!atomicText) {
206
+ throw new Error(`Invalid atomic range [${range.start}, ${range.end}): empty text`);
207
+ }
208
+ units.push({
209
+ text: atomicText,
210
+ atomic: true,
211
+ sourceStart: range.start,
212
+ sourceEnd: range.end,
213
+ });
214
+ cursor = range.end;
215
+ }
216
+ units.push(...splitOrdinaryRange(text, cursor, text.length));
217
+ return units;
218
+ }
219
+ //# sourceMappingURL=sentence-splitter.js.map