Files
Blizzard 79e834e8e9 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>
2026-06-30 13:37:45 +08:00

83 lines
2.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package rag
import "testing"
// TestBleve_ChineseSearch 钉死中文全文检索:CJK 分词器让"星云一号的总设计师"能命中含
// "星云一号""总设计师"的中文块。修复前默认标准分词器把整段中文当一个 token → 永远 0 命中。
func TestBleve_ChineseSearch(t *testing.T) {
t.Setenv("BLEVE_PATH", t.TempDir()+"/bleve") // 隔离落盘索引,测试互不污染
b := openBleve()
defer b.close()
if !b.ready() {
t.Skip("bleve 不可用")
}
const kb = "k1"
if err := b.index(kb, "d1", []string{
"星云一号卫星于2023年由长征七号发射,项目总设计师是李明华。",
"今天天气不错,适合出门散步。",
}); err != nil {
t.Fatal(err)
}
hits := b.search(kb, "星云一号的总设计师是谁", 5)
if len(hits) == 0 {
t.Fatal("中文全文检索应命中(CJK 分词),got 0 —— 分词器回归了?")
}
if !contains(hits[0].Text, "总设计师") {
t.Fatalf("最相关块应含'总设计师'got %q", hits[0].Text)
}
}
// TestQueryNgrams 钉死查询 n-gram:含"星云一号"等子串,能与更长实体名"星云一号卫星"双向匹配。
func TestQueryNgrams(t *testing.T) {
ng := queryNgrams("星云一号的总设计师是谁")
if !hasStr(ng, "星云一号") {
t.Fatalf("应含 2..8 长度子串'星云一号'got %v", ng)
}
// 长度边界:最短 2、最长 8。
for _, s := range ng {
if r := []rune(s); len(r) < 2 || len(r) > 8 {
t.Fatalf("n-gram 长度应在 [2,8]got %q(len=%d)", s, len(r))
}
}
}
// TestBleve_PersistsAcrossReopen 钉死落盘:写入→关闭→同路径重开,数据仍在。
// 修复"NewMemOnly 进程重启即丢、三路静默退化两路"的回归——这是 P0 的核心验收。
func TestBleve_PersistsAcrossReopen(t *testing.T) {
t.Setenv("BLEVE_PATH", t.TempDir()+"/bleve")
b1 := openBleve()
if !b1.ready() {
t.Skip("bleve 不可用")
}
if err := b1.index("kp", "d1", []string{"持久化测试:龙渊号探测器由总师陈思远负责。"}); err != nil {
t.Fatal(err)
}
b1.close() // 模拟进程退出(落盘刷盘)
b2 := openBleve() // 模拟重启:同路径重开
defer b2.close()
if hits := b2.search("kp", "龙渊号探测器", 5); len(hits) == 0 {
t.Fatal("重开后应仍能检索到(落盘持久),got 0 —— 退回内存了?")
}
}
func contains(s, sub string) bool {
return len(sub) == 0 || (len(s) >= len(sub) && indexOf(s, sub) >= 0)
}
func indexOf(s, sub string) int {
for i := 0; i+len(sub) <= len(s); i++ {
if s[i:i+len(sub)] == sub {
return i
}
}
return -1
}
func hasStr(ss []string, want string) bool {
for _, s := range ss {
if s == want {
return true
}
}
return false
}