feat(rag): 大文件 RAG 做深 —— 并发入库/图谱窗口化 + Bleve落盘 + file_id治理

几十万字文件从"广而浅"到准生产级:
- 向量化串行→并发分批保序(embedAll);几十万字上千块快数倍
- 图谱整篇喂LLM(爆上下文只抽开头)→窗口化并发抽(extractGraphWindowed),
  全覆盖;窗口/封顶/并发 env 可配;图谱可单配便宜模型(GRAPH_CHAT_*,未配回退主chat)
- Bleve 内存索引(重启即丢、三路退两路)→落盘 scorch(env BLEVE_PATH,失败退内存兜底)
- 下游键改稳定 file_id:Neo4j 关系打 file_id(实体仍 kb+name 共享);
  新增 kb_delete 工具 + Engine.DeleteDoc 级联删 Milvus/Bleve/Neo4j
- 单测:窗口化/去重/env可配/落盘持久/图谱模型回退

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-06-30 13:37:45 +08:00
parent 6f16062dfc
commit 79e834e8e9
8 changed files with 462 additions and 46 deletions
+43 -3
View File
@@ -4,6 +4,8 @@ import (
"fmt"
"hash/fnv"
"log"
"os"
"path/filepath"
"github.com/blevesearch/bleve/v2"
"github.com/blevesearch/bleve/v2/analysis/analyzer/keyword"
@@ -12,8 +14,9 @@ import (
"github.com/blevesearch/bleve/v2/search/query"
)
// bleveStore 是全文(BM25)检索路。内存索引:随 ingest 写入,进程重启重建。
// 真实生产应落盘(bleve.New(path,...));此处内存优先求简
// bleveStore 是全文(BM25)检索路。落盘索引(scorch:随 ingest 写入并持久,进程重启不丢,
// 与 Milvus(向量)/Neo4j(图谱) 同为持久存储——保证"三路混合"重启后仍是三路
// 落盘不可用时退回内存索引(功能在、重启会丢),好过全文路全哑。
type bleveStore struct {
idx bleve.Index
}
@@ -34,17 +37,54 @@ func bleveMapping() mapping.IndexMapping {
return im
}
// blevePath 返回全文索引落盘目录(env BLEVE_PATH,默认 ./.data/bleve)。
// 生产部署应指向持久卷;开发期落在工作目录下的 .data。
func blevePath() string {
if p := os.Getenv("BLEVE_PATH"); p != "" {
return p
}
return ".data/bleve"
}
// openBleve 打开(或新建)落盘全文索引;落盘失败退回内存索引兜底。
func openBleve() *bleveStore {
path := blevePath()
idx, err := bleve.Open(path)
if err == bleve.ErrorIndexPathDoesNotExist {
if mkerr := os.MkdirAll(filepath.Dir(path), 0o755); mkerr != nil {
log.Printf("[rag] bleve 目录创建失败,全文路退内存(重启会丢): %v", mkerr)
return memBleve()
}
idx, err = bleve.New(path, bleveMapping())
}
if err != nil {
log.Printf("[rag] bleve 落盘打开失败,全文路退内存(重启会丢): %v", err)
return memBleve()
}
log.Printf("[rag] bleve 全文索引落盘就绪: %s", path)
return &bleveStore{idx: idx}
}
// memBleve 内存兜底:落盘不可用时退回内存索引(功能在、重启会丢),好过全文路全哑。
func memBleve() *bleveStore {
idx, err := bleve.NewMemOnly(bleveMapping())
if err != nil {
log.Printf("[rag] bleve 初始化失败,全文路降级: %v", err)
log.Printf("[rag] bleve 内存兜底也失败,全文路降级: %v", err)
return &bleveStore{}
}
log.Printf("[rag] bleve 退回内存索引(非持久)")
return &bleveStore{idx: idx}
}
func (b *bleveStore) ready() bool { return b != nil && b.idx != nil }
// close 关闭索引(落盘版释放锁并刷盘);优雅停机时调。
func (b *bleveStore) close() {
if b.ready() {
_ = b.idx.Close()
}
}
// index 把 (kb, doc, texts) 写入全文索引(id 含 kb+doc+文本哈希,幂等)。
func (b *bleveStore) index(kb, doc string, texts []string) error {
if !b.ready() {