( chunks: SimilaritySearchResult[], nTextChunksInclude: number, )
| 181 | } |
| 182 | |
| 183 | export function deduplicateChunks( |
| 184 | chunks: SimilaritySearchResult[], |
| 185 | nTextChunksInclude: number, |
| 186 | ): SimilaritySearchResult[] { |
| 187 | /** Deduplicate chunks by combining chunks with the same page_url, page_title and |
| 188 | * section_title. |
| 189 | * |
| 190 | * Each doc_chunk in the DB has multiple text chunks. If the doc_chunks with the |
| 191 | * most similar embeddings are from the same document, we don't want to add the |
| 192 | * same text twice. |
| 193 | * |
| 194 | * E.g. If one doc_chunk contains [a,b,c], the other [b,c,d], then we have a chunk |
| 195 | * with [a,b,c,d] (no duplication of b and c). **/ |
| 196 | // Deep copy to avoid modifying the original array |
| 197 | chunks = JSON.parse(JSON.stringify(chunks)); |
| 198 | const getChunkUniquenessString = (chunk: SimilaritySearchResult) => |
| 199 | `${chunk.page_url} ${chunk.page_title} ${chunk.section_title}`; |
| 200 | |
| 201 | const chunkOrdering = chunks.map((chunk) => getChunkUniquenessString(chunk)); |
| 202 | chunks.sort((a, b) => |
| 203 | `${chunkOrdering.findIndex((str) => str === getChunkUniquenessString(b))} ${ |
| 204 | b.chunk_idx |
| 205 | }` > |
| 206 | `${chunkOrdering.findIndex((str) => str === getChunkUniquenessString(a))} ${ |
| 207 | a.chunk_idx |
| 208 | }` |
| 209 | ? -1 |
| 210 | : 1, |
| 211 | ); |
| 212 | const deduped: SimilaritySearchResult[] = [chunks[0]]; |
| 213 | |
| 214 | // Don't need to dedupe the first chunk, hence start with i=1 |
| 215 | for (let i = 1; i < chunks.length; i++) { |
| 216 | const chunk = chunks[i]; |
| 217 | // Check if there's another chunk with the same page_url, page_title and section_title |
| 218 | const matchedChunk = deduped.find( |
| 219 | (seenCh) => |
| 220 | seenCh.page_url === chunk.page_url && |
| 221 | seenCh.page_title === chunk.page_title && |
| 222 | seenCh.section_title === chunk.section_title, |
| 223 | ); |
| 224 | if (matchedChunk) { |
| 225 | if ( |
| 226 | chunk.chunk_idx > matchedChunk.chunk_idx && |
| 227 | // Below check stops gaps in the text chunks |
| 228 | chunk.chunk_idx - matchedChunk.chunk_idx <= |
| 229 | matchedChunk.text_chunks.length |
| 230 | ) { |
| 231 | chunk.text_chunks.forEach((textChunk) => { |
| 232 | if (!matchedChunk.text_chunks.includes(textChunk)) { |
| 233 | // push() adds to the end of the array |
| 234 | matchedChunk.text_chunks.push(textChunk); |
| 235 | } |
| 236 | }); |
| 237 | } else if ( |
| 238 | chunk.chunk_idx < matchedChunk.chunk_idx && |
| 239 | matchedChunk.chunk_idx - chunk.chunk_idx <= chunk.text_chunks.length |
| 240 | ) { |
no test coverage detected