分块与向量索引
这条链路把「笔记 / 文件 / 网址的正文」变成「可分块检索的向量」:先分块,再逐块算 embedding,最后同时写正规表与 sqlite-vec 虚表。它的每一步都有可降级路径,目标是索引可以慢,但不能把数据写坏。
分块策略
| 项 | 取值 |
|---|---|
| 首选方式 | 配了「分块模型」且 provider 可用时走 LLM 分块(ChunkMethod = llm) |
| 回退方式 | 没有模型、调用失败或解析为空 → 结构化分块(ChunkMethod = structure),按标题与段落切分 |
| 结构参数 | MaxChunkSize = 1500、MinChunkSize = 200、OverlapSize = 100 字符 |
| LLM 分块约束 | 提示词要求每块 300–1500 字;原文 < 500 字时允许返回单块 |
写入顺序
- 按「知识项 + chunk 序号」写
NoteChunks(内容、分块方式、摘要)。 - 逐块计算 embedding,写
NoteChunkEmbeddings(模型名、维度、时间戳)与向量本体。 - 同步写入
sqlite-vec虚表(vec_chunk_embeddings);虚表不支持 REPLACE,一律先 DELETE 再 INSERT。 - 单块内容时额外写一条笔记级向量(
vec_note_embeddings),让整篇笔记也能被直接召回。
复用策略:内容与摘要都未变的旧分块会被保留,不重算向量——避免每次编辑笔记都把全文重新 embedding。笔记级检查还会顺手把「正规表里有、vec 表里缺」的向量补写回去,修复上次中断留下的缺口。
维度处理
| 顺序 | 来源 |
|---|---|
| 1 | 数据库中最近一条 embedding 的 Dimensions(最可信) |
| 2 | 用途为 embedding 的模型配置 |
| 3 | 配置项 Embedding:Dimensions(默认 1536) |
连接打开时若发现维度与虚表不一致,会 drop 并重建四张 vec 表(笔记 / 分块 / 记忆 / 代码)——这是「换 embedding 模型后必须重新索引」的技术原因。
失败与降级
| 情况 | 行为 |
|---|---|
| 没有 embedding provider | 整条链路直接跳过,不写半成品数据 |
| LLM 分块失败 | 回退结构化分块(不会因为分块模型抖动而丢索引) |
sqlite-vec 加载失败 / 虚表缺失 / 维度不符 | 检索侧回退内存余弦相似度;正规表数据始终完整 |
| 单块向量写入失败 | 记日志;批量写入连续失败 3 次则中止本批,避免雪崩 |
| 任务被取消 | OperationCanceledException 透传,不算失败 |
观测
- 日志前缀
[Chunk]与[KI Embed];包含分块方式、块数、维度与失败原因。 - 数据侧可见
NoteChunkDto:ChunkIndex / ChunkMethod / HasEmbedding / Summary,前端据此渲染分块列表与索引状态。 - 后台任务类型:
GenerateEmbedding(笔记)与GenerateKnowledgeItemEmbedding(文件 / 网址),在「后台任务」页可看耗时与失败原因。
实作要点
- 分块是「索引质量」的第一决定因素:结构化分块以标题为边界,长笔记建议保留小标题。
- 换 embedding 模型后必须重新索引,并同步更新
Embedding:Dimensions。 - 向量本体只用于检索;
NoteChunkEmbeddings是元数据与降级路径的依托,两者必须同时写。 - 代码分块走独立的
vec_work_code_chunks,与笔记向量互不干扰。