mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-15 12:15:16 +00:00
块自带负载(MemoryBlock.Data),删 PayloadDigest;检索纯向量,媒体随块 同生灭。生产代码全绿;CAS 专属测试将在下一提交随判据重写。 - memory: Data BLOB + migration9;blockColumns 不含 data(召回只比向量); MediaBlockID 内容派生 ID;BlockByIDPrefix/BlockData 按需加载 - sdk: memory_impl 去 media.Store;MemoryBlockView PayloadDigest→HasData - core: mediaref/graphmedia/crossmodal/medialoop 全部改块语义; captureBlockMedia 直接产块;resolveMediaRefs 按块 ID 解析 - knowledge: 媒体字节随条目目录自带(File+Data),删 mediaGet/mediaPut - webui: graphBlockView/pulse payload_digest→has_data - bootstrap/registry: 去 MediaStore 接线(SetMediaStore 空实现,待⑥删包)
135 lines
3.6 KiB
Go
135 lines
3.6 KiB
Go
package knowledge
|
||
|
||
import (
|
||
"os"
|
||
"path/filepath"
|
||
"testing"
|
||
)
|
||
|
||
// 稠密向量缓存:第二次启动不得重算。
|
||
func TestDenseCacheAvoidsRecompute(t *testing.T) {
|
||
dir := t.TempDir()
|
||
mm := &fakeMM{dim: 3, fp: "fp-x", loaded: true,
|
||
text: map[string][]float64{"__default": {1, 1, 0}},
|
||
img: map[string][]float64{"__default": {0, 1, 1}},
|
||
}
|
||
|
||
s := NewStore(dir)
|
||
_ = s.Start()
|
||
s.SetDenseSpace(mm)
|
||
for _, n := range []string{"a", "b", "c"} {
|
||
_ = s.Add(n, "正文"+n)
|
||
}
|
||
if built, _ := s.ReindexDense(); built != 0 {
|
||
t.Fatalf("Add 已算过,首次 Reindex 应为 0,实为 %d", built)
|
||
}
|
||
fi, err := os.Stat(filepath.Join(dir, ".dense.json"))
|
||
if err != nil {
|
||
t.Fatalf("稠密缓存未落盘: %v", err)
|
||
}
|
||
t.Logf(".dense.json = %d 字节", fi.Size())
|
||
s.Stop()
|
||
|
||
// 重启:条目 + 缓存都在,Reindex 不该新建任何向量
|
||
s2 := NewStore(dir)
|
||
_ = s2.Start()
|
||
s2.SetDenseSpace(mm)
|
||
for _, id := range s2.List() {
|
||
if len(s2.items[id].Dense) == 0 {
|
||
t.Fatalf("重启后 %s 未从缓存恢复稠密向量", id)
|
||
}
|
||
}
|
||
if built, _ := s2.ReindexDense(); built != 0 {
|
||
t.Errorf("重启后应命中缓存(built 应为 0),实为 %d", built)
|
||
}
|
||
s2.Stop()
|
||
}
|
||
|
||
// 换模型/维度后缓存必须整体作废并重算。
|
||
func TestDenseCacheInvalidatedOnModelChange(t *testing.T) {
|
||
dir := t.TempDir()
|
||
old := &fakeMM{dim: 3, fp: "fp-old", loaded: true,
|
||
text: map[string][]float64{"__default": {1, 1, 0}},
|
||
img: map[string][]float64{"__default": {0, 1, 1}},
|
||
}
|
||
s := NewStore(dir)
|
||
_ = s.Start()
|
||
s.SetDenseSpace(old)
|
||
_ = s.Add("a", "A")
|
||
s.Stop()
|
||
|
||
// 新空间:不同 fp 与维度
|
||
newMM := &fakeMM{dim: 5, fp: "fp-new", loaded: true,
|
||
text: map[string][]float64{"__default": {1, 1, 1, 0, 0}},
|
||
img: map[string][]float64{"__default": {0, 1, 1, 0, 0}},
|
||
}
|
||
s2 := NewStore(dir)
|
||
_ = s2.Start()
|
||
s2.SetDenseSpace(newMM)
|
||
defer s2.Stop()
|
||
if len(s2.items["a"].Dense) != 0 {
|
||
t.Error("旧空间的缓存不得被新空间采用")
|
||
}
|
||
if built, _ := s2.ReindexDense(); built != 1 {
|
||
t.Errorf("换空间后应重算 1 条,实为 %d", built)
|
||
}
|
||
if len(s2.items["a"].Dense) != 5 {
|
||
t.Errorf("重算后应为 5 维,实为 %d", len(s2.items["a"].Dense))
|
||
}
|
||
}
|
||
|
||
// 媒体引用必须跨重启存活(此前只在内存,重启即静默丢失)。
|
||
func TestMediaRefsSurviveRestart(t *testing.T) {
|
||
dir := t.TempDir()
|
||
s := NewStore(dir)
|
||
_ = s.Start()
|
||
if err := s.AddWithMedia("cat", "猫的图", []KnowledgeMediaRef{
|
||
{Digest: "d1", MIME: "image/png", Kind: "image"},
|
||
}); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
s.Stop()
|
||
|
||
s2 := NewStore(dir)
|
||
_ = s2.Start()
|
||
defer s2.Stop()
|
||
k := s2.items["cat"]
|
||
if k == nil {
|
||
t.Fatalf("条目未载入,实为 %v", s2.List())
|
||
}
|
||
if len(k.Media) != 1 || k.Media[0].Digest != "d1" {
|
||
t.Fatalf("媒体引用未跨重启存活:%+v", k.Media)
|
||
}
|
||
}
|
||
|
||
// AttachMedia 新挂的媒体也必须落盘。
|
||
func TestAttachMediaPersists(t *testing.T) {
|
||
dir := t.TempDir()
|
||
s := NewStore(dir)
|
||
_ = s.Start()
|
||
_ = s.Add("k", "正文")
|
||
if err := s.AttachMedia("k", KnowledgeMediaRef{Digest: "d9", MIME: "image/jpeg"}); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
s.Stop()
|
||
|
||
s2 := NewStore(dir)
|
||
_ = s2.Start()
|
||
defer s2.Stop()
|
||
if got := s2.items["k"].Media; len(got) != 1 || got[0].Digest != "d9" {
|
||
t.Fatalf("AttachMedia 未落盘:%+v", got)
|
||
}
|
||
}
|
||
|
||
// 无媒体的条目不应产生 .media.json 残留。
|
||
func TestNoMediaSidecarForPlainEntry(t *testing.T) {
|
||
dir := t.TempDir()
|
||
s := NewStore(dir)
|
||
_ = s.Start()
|
||
defer s.Stop()
|
||
_ = s.Add("plain", "正文")
|
||
if _, err := os.Stat(filepath.Join(dir, "plain", mediaSidecarName)); err == nil {
|
||
t.Errorf("纯文本条目不该有 %s", mediaSidecarName)
|
||
}
|
||
}
|