QMD ่ฎพ่ฎกๆ€ๆƒณๆ€ป็ป“

ๆ•ดไฝ“ๆžถๆž„่ฎพ่ฎกๅ“ฒๅญฆ

1. ๅˆ†ๅฑ‚ๆžถๆž„

QMD ้‡‡็”จๆธ…ๆ™ฐ็š„ๅˆ†ๅฑ‚ๆžถๆž„๏ผŒๆฏไธ€ๅฑ‚้ƒฝๆœ‰ๆ˜Ž็กฎ็š„่Œ่ดฃ๏ผš

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ๆŽฅๅฃๅฑ‚ (CLI / MCP / HTTP)                                   โ”‚
โ”‚  - ๅ‘ฝไปค่กŒ็•Œ้ข                                                โ”‚
โ”‚  - Model Context Protocol ๆœๅŠกๅ™จ                             โ”‚
โ”‚  - HTTP API                                                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                       โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ๅบ”็”จๅฑ‚ (qmd.ts)                                             โ”‚
โ”‚  - ๅ‘ฝไปค่ทฏ็”ฑๅ’Œๅ‚ๆ•ฐ่งฃๆž                                         โ”‚
โ”‚  - ็”จๆˆทไบคไบ’ๅ’Œ่พ“ๅ‡บๆ ผๅผๅŒ–                                       โ”‚
โ”‚  - ็”Ÿๅ‘ฝๅ‘จๆœŸ็ฎก็†                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                       โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ไธšๅŠก้€ป่พ‘ๅฑ‚ (store.ts)                                       โ”‚
โ”‚  - ๆœ็ดข็ผ–ๆŽ’ (BM25 + Vector + Rerank)                         โ”‚
โ”‚  - ๆ–‡ๆกฃๅˆ†ๅ—ๅ’Œ็ดขๅผ•                                            โ”‚
โ”‚  - Context ็ฎก็†ๅ’Œ่™šๆ‹Ÿ่ทฏๅพ„                                     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                       โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ๆ•ฐๆฎ่ฎฟ้—ฎๅฑ‚ (db.ts / collections.ts)                         โ”‚
โ”‚  - SQLite ๆ•ฐๆฎๅบ“ๆ“ไฝœ                                         โ”‚
โ”‚  - YAML ้…็ฝฎ็ฎก็†                                             โ”‚
โ”‚  - ๆ–‡ไปถ็ณป็ปŸๆŠฝ่ฑก                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                       โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ๅŸบ็ก€่ฎพๆ–ฝๅฑ‚ (llm.ts)                                         โ”‚
โ”‚  - node-llama-cpp ๅฐ่ฃ…                                       โ”‚
โ”‚  - ๆจกๅž‹ๅŠ ่ฝฝๅ’Œ่ต„ๆบ็ฎก็†                                         โ”‚
โ”‚  - GPU/CPU ๅนถ่กŒไผ˜ๅŒ–                                          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

่ฎพ่ฎกๅŽŸๅˆ™๏ผš

  • ไพ่ต–ๅ€’็ฝฎ๏ผšไธŠๅฑ‚ไพ่ต–ไธ‹ๅฑ‚ๆŽฅๅฃ๏ผŒไธไพ่ต–ๅ…ทไฝ“ๅฎž็Žฐ
  • ๅ•ไธ€่Œ่ดฃ๏ผšๆฏไธชๆจกๅ—ๅชๅšไธ€ไปถไบ‹๏ผŒๅšๅฅฝไธ€ไปถไบ‹
  • ๅฏๆต‹่ฏ•ๆ€ง๏ผšๆฏๅฑ‚้ƒฝๅฏไปฅ็‹ฌ็ซ‹ๆต‹่ฏ•๏ผŒ้€š่ฟ‡ๆŽฅๅฃๆณจๅ…ฅ mock

2. ๆ•ฐๆฎๆต่ฎพ่ฎก

QMD ้‡‡็”จๅ‡ฝๆ•ฐๅผๆ•ฐๆฎๆต๏ผŒ้ฟๅ…ๅ‰ฏไฝœ็”จ๏ผš

// ๅฅฝ็š„่ฎพ่ฎก๏ผš็บฏๅ‡ฝๆ•ฐ๏ผŒ่พ“ๅ…ฅ -> ่พ“ๅ‡บ
function reciprocalRankFusion(resultLists: RankedResult[][]): RankedResult[] {
  // ไธไฟฎๆ”น่พ“ๅ…ฅ๏ผŒ่ฟ”ๅ›žๆ–ฐ็ป“ๆžœ
  return sortedResults;
}
 
// ้ฟๅ…๏ผšๅ‰ฏไฝœ็”จ๏ผŒไฟฎๆ”นๅ…จๅฑ€็Šถๆ€
let globalResults: RankedResult[] = [];
function badFusion(lists: RankedResult[][]) {
  globalResults = ...;  // ๅ‰ฏไฝœ็”จ๏ผ
}

ไผ˜ๅŠฟ๏ผš

  • ๅฏ้ข„ๆต‹ๆ€ง๏ผš็›ธๅŒ่พ“ๅ…ฅๆ€ปๆ˜ฏไบง็”Ÿ็›ธๅŒ่พ“ๅ‡บ
  • ๅฏๆต‹่ฏ•ๆ€ง๏ผšๅฎนๆ˜“็ผ–ๅ†™ๅ•ๅ…ƒๆต‹่ฏ•
  • ๅฏๅนถ่กŒๆ€ง๏ผšๆ— ๅ‰ฏไฝœ็”จ็š„ๅ‡ฝๆ•ฐๅฏไปฅๅฎ‰ๅ…จๅนถ่กŒๆ‰ง่กŒ

3. ้…็ฝฎไธŽๆ•ฐๆฎๅˆ†็ฆป

QMD ๅฐ†้…็ฝฎ๏ผˆYAML๏ผ‰ๅ’Œๆ•ฐๆฎ๏ผˆSQLite๏ผ‰ๅˆ†็ฆป๏ผš

# ~/.config/qmd/index.yml (้…็ฝฎ)
collections:
  notes:
    path: ~/notes
    pattern: "**/*.md"
    context:
      "/": "Personal notes and ideas"
-- ~/.cache/qmd/index.sqlite (ๆ•ฐๆฎ)
-- documents, content, vectors ็ญ‰่กจ

ไธบไป€ไนˆ่ฟ™ๆ ท่ฎพ่ฎก๏ผŸ

  • ้…็ฝฎๅฏ็‰ˆๆœฌๆŽงๅˆถ๏ผšYAML ๅฏไปฅๆ”พๅ…ฅ git๏ผŒSQLite ไธๅบ”่ฏฅ
  • ๆ•ฐๆฎๅฏ้‡ๅปบ๏ผšๅˆ ้™ค SQLite ๅฏไปฅ้‡ๆ–ฐ็ดขๅผ•๏ผŒ้…็ฝฎไธไผšไธขๅคฑ
  • ๅคš่ฎพๅค‡ๅŒๆญฅ๏ผš้…็ฝฎๅฎนๆ˜“ๅŒๆญฅ๏ผŒๆ•ฐๆฎ้€šๅธธไธ้œ€่ฆๅŒๆญฅ

ๅ…ณ้”ฎ่ฎพ่ฎกๅ†ณ็ญ–ๅˆ†ๆž

1. ไธบไป€ไนˆไฝฟ็”จ SQLite + FTS5 + sqlite-vec๏ผŸ

้€‰ๆ‹ฉ็†็”ฑ๏ผš

็‰นๆ€งSQLiteๅ…ถไป–้€‰ๆ‹ฉ (Elasticsearch, Pinecone)
ๆœฌๅœฐ่ฟ่กŒโœ… ๅฎŒๅ…จๆœฌๅœฐโŒ ้œ€่ฆๆœๅŠกๅ™จ
ๅ•ๆ–‡ไปถโœ… ไธ€ไธช .sqlite ๆ–‡ไปถโŒ ๅคšๆ–‡ไปถ/ๆœๅŠก
้›ถ้…็ฝฎโœ… ๅผ€็ฎฑๅณ็”จโŒ ้œ€่ฆ้…็ฝฎ
FTS5โœ… ๅ†…็ฝฎๅ…จๆ–‡ๆœ็ดขโš ๏ธ ้œ€่ฆๆ’ไปถ
ๅ‘้‡ๆœ็ดขโœ… sqlite-vec ๆ‰ฉๅฑ•โœ… ๅŽŸ็”Ÿๆ”ฏๆŒ
ACIDโœ… ไบ‹ๅŠกๆ”ฏๆŒโœ… ๆ”ฏๆŒ
่ต„ๆบๅ ็”จโœ… ไฝŽโŒ ้ซ˜

ๆƒ่กก๏ผš

  • ๆ€ง่ƒฝ๏ผšSQLite ๅ•ๆœบๆ€ง่ƒฝไธๅฆ‚ไธ“็”จๆœ็ดขๅผ•ๆ“Ž
  • ๆ‰ฉๅฑ•ๆ€ง๏ผšไธ้€‚ๅˆๅˆ†ๅธƒๅผ้ƒจ็ฝฒ
  • ้€‚็”จๅœบๆ™ฏ๏ผšไธชไบบ็Ÿฅ่ฏ†็ฎก็†๏ผˆ< 100K ๆ–‡ๆกฃ๏ผ‰ๅฎŒๅ…จ่ถณๅคŸ

2. ไธบไป€ไนˆไฝฟ็”จ node-llama-cpp๏ผŸ

ๆœฌๅœฐๆŽจ็†็š„ไผ˜ๅŠฟ๏ผš

ไบ‘็ซฏ API (OpenAI, Claude)
    โ”‚
    โ”œโ”€โ†’ ้œ€่ฆ็ฝ‘็ปœ่ฟžๆŽฅ
    โ”œโ”€โ†’ ๆ•ฐๆฎ็ฆปๅผ€ๆœฌๅœฐ๏ผˆ้š็ง้—ฎ้ข˜๏ผ‰
    โ”œโ”€โ†’ ๆŒ‰ token ่ฎก่ดน๏ผˆๆˆๆœฌ้—ฎ้ข˜๏ผ‰
    โ””โ”€โ†’ ๅปถ่ฟŸ่พƒ้ซ˜๏ผˆRTT๏ผ‰

ๆœฌๅœฐๆŽจ็† (node-llama-cpp)
    โ”‚
    โ”œโ”€โ†’ ๅฎŒๅ…จ็ฆป็บฟ
    โ”œโ”€โ†’ ๆ•ฐๆฎไธๅ‡บๅขƒ
    โ”œโ”€โ†’ ไธ€ๆฌกๆ€งๆˆๆœฌ๏ผˆ็กฌไปถ๏ผ‰
    โ””โ”€โ†’ ๅปถ่ฟŸไฝŽ๏ผˆๆœฌๅœฐ่ฎก็ฎ—๏ผ‰

ๆŠ€ๆœฏ้€‰ๆ‹ฉ๏ผš

  • GGUF ๆ ผๅผ๏ผš้‡ๅŒ–ๆจกๅž‹๏ผŒๅ‡ๅฐ‘ๅ†…ๅญ˜ๅ ็”จ
  • Metal/CUDA/Vulkan๏ผšGPU ๅŠ ้€Ÿ๏ผŒๆๅ‡ๆŽจ็†้€Ÿๅบฆ
  • ๅคšไธŠไธ‹ๆ–‡ๅนถ่กŒ๏ผšๅ……ๅˆ†ๅˆฉ็”จ GPU ่ฎก็ฎ—่ต„ๆบ

3. ไธบไป€ไนˆ่ฎพ่ฎก Context ็ณป็ปŸ๏ผŸ

้—ฎ้ข˜๏ผš็บฏๅŸบไบŽๅ†…ๅฎน็š„ๆœ็ดขๆ— ๆณ•็†่งฃๆ–‡ๆกฃ็š„ไธŠไธ‹ๆ–‡

็คบไพ‹๏ผš

# Meeting Notes
 
## Action Items
- Fix bug #123
- Update documentation

ๆฒกๆœ‰ Context๏ผš

  • ๆœ็ดข โ€œbugโ€ ่ฟ”ๅ›ž่ฟ™ไธชๆ–‡ๆกฃ
  • ไธ็Ÿฅ้“่ฟ™ๆ˜ฏไป€ไนˆ็ฑปๅž‹็š„ bug

ๆœ‰ Context๏ผš

qmd context add qmd://meetings "Meeting notes and action items"

ๆœ็ดข โ€œbugโ€ ่ฟ”ๅ›ž๏ผš

meetings/2024-01-15.md #a1b2c3
Title: Meeting Notes
Context: Meeting notes and action items
Score: 85%

Action Items
- Fix bug #123
...

่ฎพ่ฎกๆ€ๆƒณ๏ผš

  • ๅ…ƒๆ•ฐๆฎๅขžๅผบ๏ผšไธบๆœบๅ™จๆไพ›ไบบ็ฑป็†่งฃ็š„ไธŠไธ‹ๆ–‡
  • ๅฑ‚ๆฌกๅŒ–็ปงๆ‰ฟ๏ผšๅ…จๅฑ€ โ†’ ้›†ๅˆ โ†’ ่ทฏๅพ„๏ผŒๅฑ‚ๅฑ‚็ป†ๅŒ–
  • Agent ๅ‹ๅฅฝ๏ผšLLM ๅฏไปฅๆ นๆฎ Context ๅšๅ‡บๆ›ดๅฅฝ็š„้€‰ๆ‹ฉ

4. ไธบไป€ไนˆไฝฟ็”จ่™šๆ‹Ÿ่ทฏๅพ„ (qmd://)๏ผŸ

็‰ฉ็†่ทฏๅพ„็š„้—ฎ้ข˜๏ผš

/Users/tobi/notes/work/project-a/README.md
/home/tobi/notes/work/project-a/README.md  # Linux
C:\Users\tobi\notes\work\project-a\README.md  # Windows
  • ๅนณๅฐๅทฎๅผ‚
  • ่ทฏๅพ„ๅ˜ๅŒ–ๅฏผ่‡ด้“พๆŽฅๅคฑๆ•ˆ
  • ๆšด้œฒๆ–‡ไปถ็ณป็ปŸ็ป“ๆž„

่™šๆ‹Ÿ่ทฏๅพ„็š„ไผ˜ๅŠฟ๏ผš

qmd://notes/work/project-a/README.md
  • ๅนณๅฐๆ— ๅ…ณ๏ผš็ปŸไธ€ๆ ผๅผ
  • ็จณๅฎš๏ผš็‰ฉ็†่ทฏๅพ„ๅ˜ๅŒ–ไธๅฝฑๅ“่™šๆ‹Ÿ่ทฏๅพ„
  • ๅฎ‰ๅ…จ๏ผšไธๆšด้œฒ็œŸๅฎžๆ–‡ไปถ็ณป็ปŸ
  • ็ฎ€ๆด๏ผšๆ˜“ไบŽ้˜…่ฏปๅ’Œ่ฎฐๅฟ†

5. ไธบไป€ไนˆไฝฟ็”จ RRF ่€Œไธๆ˜ฏๆœบๅ™จๅญฆไน ่žๅˆ๏ผŸ

RRF (Reciprocal Rank Fusion)๏ผš

ไผ˜็‚น๏ผš
- ๆ— ้œ€่ฎญ็ปƒๆ•ฐๆฎ
- ๆ— ้œ€่ฐƒๅ‚
- ่ฎก็ฎ—็ฎ€ๅ• O(n)
- ๅฏ่งฃ้‡Šๆ€งๅผบ

็ผบ็‚น๏ผš
- ไธๆ˜ฏๆœ€ไผ˜็š„๏ผˆ็›ธๆฏ”่ฎญ็ปƒๅฅฝ็š„ๆจกๅž‹๏ผ‰
- ไธ่ƒฝๅญฆไน ็‰นๅพไบคไบ’

ๆœบๅ™จๅญฆไน ่žๅˆ๏ผš

ไผ˜็‚น๏ผš
- ๅฏไปฅๅญฆไน ๆœ€ไผ˜ๆƒ้‡
- ๅฏไปฅๆ•ๆ‰็‰นๅพไบคไบ’

็ผบ็‚น๏ผš
- ้œ€่ฆๅคง้‡ๆ ‡ๆณจๆ•ฐๆฎ
- ้œ€่ฆ่ฎญ็ปƒๅ’Œ่ฐƒๅ‚
- ่ฟ‡ๆ‹Ÿๅˆ้ฃŽ้™ฉ
- ้šพไปฅ่งฃ้‡Š

QMD ็š„้€‰ๆ‹ฉ๏ผšRRF + ไฝ็ฝฎๆ„Ÿ็Ÿฅๆททๅˆ

  • ็ฎ€ๅ•ๆœ‰ๆ•ˆ๏ผšRRF ๅœจไฟกๆฏๆฃ€็ดข้ข†ๅŸŸ้ชŒ่ฏๅคšๅนด
  • ๆ— ้œ€ๆ•ฐๆฎ๏ผšไธ้œ€่ฆๆ”ถ้›†่ฎญ็ปƒๆ•ฐๆฎ
  • ๅฏ่ฐƒๆ•ด๏ผš้€š่ฟ‡ไฝ็ฝฎๆ„Ÿ็Ÿฅๆททๅˆๅพฎ่ฐƒ
  • ๅฏ่งฃ้‡Š๏ผš็”จๆˆทๅฏไปฅ็†่งฃ็š„่žๅˆ้€ป่พ‘

ไปฃ็ ็ป„็ป‡ๅ’ŒๆŠฝ่ฑกๅฑ‚ๆฌก

1. ็ฑปๅž‹้ฉฑๅŠจๅผ€ๅ‘

QMD ๅคง้‡ไฝฟ็”จ TypeScript ็ฑปๅž‹็ณป็ปŸ๏ผš

// ๆ˜Ž็กฎ็š„็ฑปๅž‹ๅฎšไน‰
export type SearchResult = DocumentResult & {
  score: number;
  source: "fts" | "vec";
  chunkPos?: number;
};
 
export type HybridQueryResult = {
  file: string;
  displayPath: string;
  title: string;
  body: string;
  bestChunk: string;
  bestChunkPos: number;
  score: number;
  context: string | null;
  docid: string;
};

ๅฅฝๅค„๏ผš

  • ็ผ–่ฏ‘ๆ—ถๆฃ€ๆŸฅ๏ผŒๅ‡ๅฐ‘่ฟ่กŒๆ—ถ้”™่ฏฏ
  • ่‡ชๆ–‡ๆกฃๅŒ–๏ผš็ฑปๅž‹ๅณๆ–‡ๆกฃ
  • IDE ๆ”ฏๆŒ๏ผš่‡ชๅŠจ่กฅๅ…จๅ’Œ้‡ๆž„

2. ๅ‡ฝๆ•ฐ็ป„ๅˆ

ๅคๆ‚ๅŠŸ่ƒฝ้€š่ฟ‡็ฎ€ๅ•ๅ‡ฝๆ•ฐ็ป„ๅˆๅฎž็Žฐ๏ผš

// ้ซ˜ๅฑ‚ๅ‡ฝๆ•ฐ๏ผšๆททๅˆๆœ็ดข
async function hybridQuery(query: string): Promise<HybridQueryResult[]> {
  const expanded = await expandQuery(query);
  const results = await Promise.all([
    searchFTS(query),
    searchVec(query),
    ...expanded.map(q => search(q))
  ]);
  const fused = reciprocalRankFusion(results);
  const reranked = await rerank(query, fused);
  return blendScores(fused, reranked);
}
 
// ๆฏไธชๅบ•ๅฑ‚ๅ‡ฝๆ•ฐ้ƒฝๆ˜ฏๅฏๆต‹่ฏ•็š„ๅ•ๅ…ƒ
function reciprocalRankFusion(lists: RankedResult[][]): RankedResult[] { ... }
function blendScores(fts: RankedResult[], reranked: RerankResult[]): HybridQueryResult[] { ... }

่ฎพ่ฎกๆ€ๆƒณ๏ผš

  • ๅ•ไธ€่Œ่ดฃ๏ผšๆฏไธชๅ‡ฝๆ•ฐๅชๅšไธ€ไปถไบ‹
  • ๅฏ็ป„ๅˆๆ€ง๏ผšๅ‡ฝๆ•ฐๅฏไปฅๅƒ็งฏๆœจไธ€ๆ ท็ป„ๅˆ
  • ๅฏๆต‹่ฏ•ๆ€ง๏ผšๆฏไธชๅ‡ฝๆ•ฐ็‹ฌ็ซ‹ๆต‹่ฏ•

3. ้”™่ฏฏๅค„็†็ญ–็•ฅ

QMD ้‡‡็”จไผ˜้›…้™็บง็ญ–็•ฅ๏ผš

async function searchVec(query: string): Promise<SearchResult[]> {
  try {
    const embedding = await getEmbedding(query);
    if (!embedding) return [];  // ้™็บง๏ผš่ฟ”ๅ›ž็ฉบ็ป“ๆžœ
    
    const results = await db.query(...);
    return results;
  } catch (error) {
    console.error("Vector search failed:", error);
    return [];  // ้™็บง๏ผš่ฟ”ๅ›ž็ฉบ็ป“ๆžœ๏ผŒไธไธญๆ–ญๆต็จ‹
  }
}

ๅŽŸๅˆ™๏ผš

  • ้ƒจๅˆ†ๅคฑ่ดฅไธๅฝฑๅ“ๆ•ดไฝ“ๅŠŸ่ƒฝ
  • ๅ‘้‡ๆœ็ดขๅคฑ่ดฅ๏ผŒFTS ไป็„ถๅฏไปฅๅทฅไฝœ
  • ่ฎฐๅฝ•้”™่ฏฏ๏ผŒไฝ†ไธๆŠ›ๅ‡บๅผ‚ๅธธไธญๆ–ญ็”จๆˆทๆ“ไฝœ

ๅฏๆ‰ฉๅฑ•ๆ€ง่ฎพ่ฎก

1. ๆ’ไปถๅŒ–ๆžถๆž„

่™ฝ็„ถ QMD ็›ฎๅ‰ๆฒกๆœ‰ๆญฃๅผๆ’ไปถ็ณป็ปŸ๏ผŒไฝ†ไปฃ็ ็ป“ๆž„ๆ”ฏๆŒๆ‰ฉๅฑ•๏ผš

// LLM ๆŽฅๅฃๅฏไปฅๆœ‰ไธๅŒ็š„ๅฎž็Žฐ
interface LLM {
  embed(text: string): Promise<EmbeddingResult>;
  rerank(query: string, docs: Document[]): Promise<RerankResult>;
}
 
// ๅฝ“ๅ‰ๅฎž็Žฐ
class LlamaCpp implements LLM { ... }
 
// ๆœชๆฅๅฏ่ƒฝ็š„ๅฎž็Žฐ
class OpenAILLM implements LLM { ... }
class OllamaLLM implements LLM { ... }

2. ้…็ฝฎ้ฉฑๅŠจ

ๆ–ฐๅŠŸ่ƒฝ้€š่ฟ‡้…็ฝฎๅฏ็”จ๏ผŒๆ— ้œ€ไฟฎๆ”นไปฃ็ ๏ผš

# ๆœชๆฅๅฏ่ƒฝ็š„ๆ‰ฉๅฑ•
collections:
  notes:
    path: ~/notes
    pattern: "**/*.md"
    # ๆ–ฐ็š„้…็ฝฎ้€‰้กน
    embed_model: "custom-model"  # ่‡ชๅฎšไน‰ๅตŒๅ…ฅๆจกๅž‹
    chunk_size: 1000             # ่‡ชๅฎšไน‰ๅˆ†ๅ—ๅคงๅฐ
    preprocessors:               # ่‡ชๅฎšไน‰้ข„ๅค„็†ๅ™จ
      - strip_frontmatter
      - normalize_links

3. ๆจกๅ—ๅŒ–่ฎพ่ฎก

ๆฏไธชๆจกๅ—ๅฏไปฅ็‹ฌ็ซ‹ๆผ”่ฟ›๏ผš

src/
โ”œโ”€โ”€ qmd.ts          # CLI ๅ…ฅๅฃ๏ผˆๅฏไปฅๆ›ฟๆขไธบ Web UI๏ผ‰
โ”œโ”€โ”€ store.ts        # ไธšๅŠก้€ป่พ‘๏ผˆๅฏไปฅ็‹ฌ็ซ‹ๅ‘ๅธƒไธบๅบ“๏ผ‰
โ”œโ”€โ”€ db.ts           # ๆ•ฐๆฎๅบ“ๅฑ‚๏ผˆๅฏไปฅๆ›ฟๆขไธบๅ…ถไป–ๆ•ฐๆฎๅบ“๏ผ‰
โ”œโ”€โ”€ llm.ts          # LLM ๅฑ‚๏ผˆๅฏไปฅๆ”ฏๆŒๅ…ถไป–ๆŽจ็†ๅผ•ๆ“Ž๏ผ‰
โ”œโ”€โ”€ collections.ts  # ้…็ฝฎ็ฎก็†
โ””โ”€โ”€ formatter.ts    # ่พ“ๅ‡บๆ ผๅผๅŒ–๏ผˆๅฏไปฅๆทปๅŠ ๆ–ฐๆ ผๅผ๏ผ‰

ๆ€ง่ƒฝไผ˜ๅŒ–็ญ–็•ฅ

1. ๅปถ่ฟŸๅŠ ่ฝฝ (Lazy Loading)

private async ensureEmbedModel(): Promise<LlamaModel> {
  if (this.embedModel) return this.embedModel;  // ๅทฒๅŠ ่ฝฝ๏ผŒ็›ดๆŽฅ่ฟ”ๅ›ž
  // ๅฆๅˆ™ๅŠ ่ฝฝๆจกๅž‹
}

ไผ˜ๅŠฟ๏ผš

  • ๅฏๅŠจ้€Ÿๅบฆๅฟซ๏ผˆไธๅŠ ่ฝฝๆœชไฝฟ็”จ็š„ๆจกๅž‹๏ผ‰
  • ๅ†…ๅญ˜ๅ ็”จไฝŽ๏ผˆๅชๅŠ ่ฝฝ้œ€่ฆ็š„ๆจกๅž‹๏ผ‰
  • ๆŒ‰้œ€ไป˜่ดน๏ผˆๅชๅœจ้œ€่ฆๆ—ถๆถˆ่€—่ต„ๆบ๏ผ‰

2. ๆ‰น้‡ๅค„็†

// ไฝŽๆ•ˆ๏ผš้กบๅบๅตŒๅ…ฅ
for (const text of texts) {
  await embed(text);  // N ๆฌก่ฐƒ็”จ
}
 
// ้ซ˜ๆ•ˆ๏ผšๆ‰น้‡ๅตŒๅ…ฅ
await embedBatch(texts);  // 1 ๆฌก่ฐƒ็”จ๏ผŒๅนถ่กŒๅค„็†

ๆ”ถ็›Š๏ผš2-3x ๆ€ง่ƒฝๆๅ‡

3. ๆ™บ่ƒฝ็ผ“ๅญ˜

// LLM ็ป“ๆžœ็ผ“ๅญ˜
const cacheKey = getCacheKey("expandQuery", { query, model });
const cached = getCachedResult(db, cacheKey);
if (cached) return JSON.parse(cached);
 
// ่ฎก็ฎ—ๅนถ็ผ“ๅญ˜
const result = await llm.expandQuery(query);
setCachedResult(db, cacheKey, JSON.stringify(result));

็ญ–็•ฅ๏ผš

  • ็ผ“ๅญ˜ๆŸฅ่ฏขๆ‰ฉๅฑ•็ป“ๆžœ๏ผˆ็›ธๅŒๆŸฅ่ฏขๆ€ปๆ˜ฏไบง็”Ÿ็›ธๅŒๆ‰ฉๅฑ•๏ผ‰
  • ็ผ“ๅญ˜้‡ๆŽ’ๅบ็ป“ๆžœ๏ผˆๆ–‡ๆกฃๅ’ŒๆŸฅ่ฏขไธๅ˜๏ผŒ็ป“ๆžœไธๅ˜๏ผ‰
  • LRU ๆธ…็†๏ผš้šๆœบๆฆ‚็އๆธ…็†๏ผŒ้ฟๅ…้›†ไธญๅผๆธ…็†็š„ๆ€ง่ƒฝๅฐ–ๅณฐ

4. ๅผบไฟกๅท็Ÿญ่ทฏ

const hasStrongSignal = topScore >= 0.85 && (topScore - secondScore) >= 0.15;
if (hasStrongSignal) {
  // ่ทณ่ฟ‡ๆ˜‚่ดต็š„ LLM ๆ‰ฉๅฑ•
  return initialResults;
}

ๆ”ถ็›Š๏ผšๅธธ่งๆƒ…ๅ†ตไธ‹่Š‚็œ 1-2 ็ง’

ๆต‹่ฏ•็ญ–็•ฅ

1. ๅˆ†ๅฑ‚ๆต‹่ฏ•

test/
โ”œโ”€โ”€ unit/           # ๅ•ๅ…ƒๆต‹่ฏ•๏ผˆ็บฏๅ‡ฝๆ•ฐ๏ผ‰
โ”‚   โ”œโ”€โ”€ chunk.test.ts
โ”‚   โ”œโ”€โ”€ rrf.test.ts
โ”‚   โ””โ”€โ”€ fts.test.ts
โ”œโ”€โ”€ integration/    # ้›†ๆˆๆต‹่ฏ•๏ผˆๆ•ฐๆฎๅบ“ + ้€ป่พ‘๏ผ‰
โ”‚   โ”œโ”€โ”€ search.test.ts
โ”‚   โ””โ”€โ”€ index.test.ts
โ””โ”€โ”€ e2e/            # ็ซฏๅˆฐ็ซฏๆต‹่ฏ•๏ผˆๅฎŒๆ•ดๆต็จ‹๏ผ‰
    โ””โ”€โ”€ cli.test.ts

2. ๆต‹่ฏ•ๆ•ฐๆฎ็ฎก็†

// ไฝฟ็”จๅ†…ๅญ˜ๆ•ฐๆฎๅบ“่ฟ›่กŒๆต‹่ฏ•
const db = openDatabase(":memory:");
 
// ๆต‹่ฏ•ๅŽๆธ…็†
afterEach(() => {
  db.close();
});

3. ๅ…ณ้”ฎ่ทฏๅพ„่ฆ†็›–

  • ๅˆ†ๅ—็ฎ—ๆณ•๏ผšๅ„็ง่พน็•Œๆƒ…ๅ†ต๏ผˆ็ฉบๆ–‡ๆกฃใ€ไปฃ็ ๅ—ใ€ๆ ‡้ข˜๏ผ‰
  • RRF ่žๅˆ๏ผšๅคšๅˆ—่กจใ€้‡ๅคๆ–‡ๆกฃใ€ๆƒ้‡่ฎก็ฎ—
  • ๆœ็ดขๆต็จ‹๏ผšFTSใ€ๅ‘้‡ใ€ๆททๅˆใ€้‡ๆŽ’ๅบ

ๆ€ป็ป“

QMD ็š„่ฎพ่ฎกไฝ“็Žฐไบ†ไปฅไธ‹ๆ ธๅฟƒๆ€ๆƒณ๏ผš

  1. ็ฎ€ๅ•ไผ˜ๅ…ˆ๏ผšไฝฟ็”จๆˆ็†ŸๆŠ€ๆœฏ๏ผˆSQLiteใ€RRF๏ผ‰๏ผŒ้ฟๅ…่ฟ‡ๅบฆๅทฅ็จ‹
  2. ๆœฌๅœฐไผ˜ๅ…ˆ๏ผšๆ•ฐๆฎไธๅ‡บๅขƒ๏ผŒไฟๆŠค้š็ง
  3. ๆ€ง่ƒฝๆ„่ฏ†๏ผšๅปถ่ฟŸๅŠ ่ฝฝใ€ๆ‰น้‡ๅค„็†ใ€ๆ™บ่ƒฝ็ผ“ๅญ˜
  4. ๅฏๆ‰ฉๅฑ•ๆ€ง๏ผšๆจกๅ—ๅŒ–่ฎพ่ฎก๏ผŒๆŽฅๅฃๆŠฝ่ฑก
  5. ็”จๆˆทไฝ“้ชŒ๏ผšๅฟซ้€Ÿใ€ๅ‡†็กฎใ€ๅฏ่งฃ้‡Š

่ฟ™ไบ›่ฎพ่ฎกๅ†ณ็ญ–ไฝฟๅพ— QMD ๆˆไธบไธ€ไธชๅฟซ้€Ÿใ€ๅฏ้ ใ€ๆ˜“็”จ็š„ไธชไบบ็Ÿฅ่ฏ†็ฎก็†ๆœ็ดขๅทฅๅ…ทใ€‚

็›ธๅ…ณๆ–‡ๆกฃ