feat: enhance local knowledge retrieval

This commit is contained in:
lofyer
2026-08-12 21:45:47 +08:00
parent e0e5a8c1b3
commit 111f487e20
65 changed files with 13517 additions and 1155 deletions
+176 -3
View File
@@ -1,6 +1,10 @@
import { strToU8, zipSync } from 'fflate'
import { describe, expect, it } from 'vitest'
import { chunkDocument, parseDocument } from './document-parser'
import {
buildChunkContextPrefix,
chunkDocumentAdvanced,
parseDocument
} from './document-parser'
function createPdfFixture(text: string): Buffer {
const stream = `BT /F1 18 Tf 50 100 Td (${text}) Tj ET`
@@ -35,7 +39,15 @@ describe('document parser', () => {
'notes.md',
Buffer.from(`# GoodBuddy\n\n${'知识内容。'.repeat(500)}`)
)
const chunks = chunkDocument(parsed, 500, 50)
const chunks = chunkDocumentAdvanced(parsed, {
version: 1,
mode: 'fixed',
targetCharacters: 500,
overlapCharacters: 50,
parentCharacters: 4_800,
childCharacters: 900,
contextualIndexingEnabled: false
})
expect(parsed.title).toBe('notes')
expect(chunks.length).toBeGreaterThan(1)
@@ -103,9 +115,12 @@ describe('document parser', () => {
expect(parsed.sections).toEqual([
{
locator: '第 1 页',
content: 'PDF body text'
content: 'PDF body text',
pageNumber: 1,
blockKind: 'text'
}
])
expect(parsed.pageCount).toBe(1)
})
it('rejects unsupported or oversized content', async () => {
@@ -122,4 +137,162 @@ describe('document parser', () => {
parseDocument('expanded.docx', Buffer.from(expandedArchive))
).rejects.toThrow('损坏')
})
it('preserves headings and creates recall-only children with parent context', () => {
const chunks = chunkDocumentAdvanced(
{
title: 'Guide',
sourceFormat: '.md',
content: '# 安装\n' + '安装步骤和配置说明。'.repeat(250),
sections: [
{
locator: '全文',
content: '# 安装\n' + '安装步骤和配置说明。'.repeat(250)
}
],
warnings: []
},
{
version: 1,
mode: 'parent-child',
targetCharacters: 1_600,
overlapCharacters: 100,
parentCharacters: 1_600,
childCharacters: 400,
contextualIndexingEnabled: false
}
)
const parents = chunks.filter((chunk) => chunk.role === 'parent')
const children = chunks.filter((chunk) => chunk.role === 'child')
expect(parents.length).toBeGreaterThan(0)
expect(children.length).toBeGreaterThan(parents.length)
expect(children.every((chunk) => chunk.heading === '安装')).toBe(true)
expect(
children.every((chunk) =>
parents.some((parent) => parent.position === chunk.parentPosition)
)
).toBe(true)
})
it('tracks nested Markdown heading paths and resets deeper levels', () => {
const chunks = chunkDocumentAdvanced(
{
title: 'Guide',
sourceFormat: '.md',
content: '# A\none\n## B\ntwo\n### C\nthree\n## D\nfour\n# E\nfive',
sections: [
{
locator: '全文',
content:
'# A\none\n## B\ntwo\n### C\nthree\n## D\nfour\n# E\nfive'
}
],
warnings: []
},
{
version: 1,
mode: 'structure',
targetCharacters: 500,
overlapCharacters: 0,
parentCharacters: 1_000,
childCharacters: 300,
contextualIndexingEnabled: false
}
)
expect(chunks.map((chunk) => chunk.headingPath)).toEqual([
['A'],
['A', 'B'],
['A', 'B', 'C'],
['A', 'D'],
['E']
])
expect(chunks.map((chunk) => chunk.heading)).toEqual([
'A',
'B',
'C',
'D',
'E'
])
expect(chunks[1]?.content).toBe('## B\ntwo')
})
it('propagates page and table metadata without crossing section boundaries', () => {
const chunks = chunkDocumentAdvanced(
{
title: 'Workbook',
sourceFormat: '.xlsx',
content: '第一页\n\n表格行',
sections: [
{
locator: '第 1 页',
content: '第一页',
pageNumber: 1,
blockKind: 'text'
},
{
locator: '工作表 1',
content: '表格行'.repeat(200),
blockKind: 'table'
}
],
warnings: []
},
{
version: 1,
mode: 'parent-child',
targetCharacters: 300,
overlapCharacters: 20,
parentCharacters: 300,
childCharacters: 100,
contextualIndexingEnabled: false
}
)
expect(
chunks
.filter((chunk) => chunk.locator === '第 1 页')
.every(
(chunk) =>
chunk.pageNumber === 1 && chunk.blockKind === 'text'
)
).toBe(true)
expect(
chunks
.filter((chunk) => chunk.locator === '工作表 1')
.every(
(chunk) =>
chunk.pageNumber === undefined && chunk.blockKind === 'table'
)
).toBe(true)
expect(
chunks.every((chunk) =>
chunk.locator === '第 1 页'
? chunk.content.includes('第一页')
: !chunk.content.includes('第一页')
)
).toBe(true)
})
it('builds deterministic bounded context without changing citation content', () => {
const chunk = {
position: 0,
locator: ' 第 2 页 \n 附录 ',
content: '## API\n原始引用内容',
headingPath: [' 指南 ', 'API'],
pageNumber: 2,
blockKind: 'table' as const
}
const originalContent = chunk.content
const first = buildChunkContextPrefix(' GoodBuddy \n 手册 ', chunk)
const second = buildChunkContextPrefix(' GoodBuddy \n 手册 ', chunk)
expect(first).toBe(second)
expect(first).toBe(
'[context title="GoodBuddy 手册" heading="指南 > API" page="2" locator="第 2 页 附录" block="table"]\n'
)
expect(first.length).toBeLessThanOrEqual(512)
expect(chunk.content).toBe(originalContent)
expect(first).not.toContain(originalContent)
})
})