mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-22 18:08:04 +00:00
refactor(memory): 移除 media_refs/引用计数,媒体成为一等记忆块
媒体此前是"文本块 + digest 引用 + owner 账本 + 独立 GC":ContextEvent.Media
记 digest,media_refs 表用 owner_kind/owner_id 保活,ref_count 决定 GC 能否清。
这与文本记忆块的管理方式不一致,也是本次一并纠正的核心偏差。
改为与文本块完全一致的生命周期:
1. 一等记忆块直接由所在层持有
- ContextEvent.Blocks / Doc.Blocks / GraphDB memory_blocks
- 块带 modality/digest/MIME/size/vector/fingerprint,文本、图片、视频同构
- Context→Document→Graph 迁移的是块本身(ID 不变),迁移后清空源容器,
同一块不同时存在于两层
2. 删除平行生命周期账本
- media.Store 去掉 media_refs 表、OwnerKind 常量、RefCount 字段、
AddRef/DropRef/DropOwner/Refs、ref_count 列与索引
- 删除 mediaGCLoop、GC(keep,minAge)、容量上限与 media.gc_* / media.max_mb 配置
- 媒体内容在块被永久删除时一并删除(media.Store.Delete + forgetPayloads),
与"删除文本块即删除内容"同一语义
3. L3 原生结构
- memory_blocks / memory_block_edges(contains/depicts/derived_from)
- 边端点必须是真实图节点,不再用 owner 字符串伪装关系
- BlocksForNode 支持 sentence --contains--> block 反查
4. SDK 与检索同步
- 插件附件/标记直接变成块,不再 AddRef
- 跨模态检索改用 QueryMediaScored(CAS 内不再有孤儿缓存需要过滤)
测试全部改写为块语义:删除 refcount/media_refs/GC 断言,新增块迁移、
单层不变量、Delete 语义与并发删除回归。
注:cmd/homed/main.go 同时携带工作区中既有的 CLIP→Qwen 模型目录接线改动。
This commit is contained in:
@ -24,10 +24,10 @@ import (
|
||||
logpkg "gitcode.com/JianFeeeee/HomeAgent/internal/log"
|
||||
luapkg "gitcode.com/JianFeeeee/HomeAgent/internal/lua"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/clip"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/document"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/media"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/pipeline"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/qwen"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/social"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/text"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/vector"
|
||||
@ -327,15 +327,13 @@ func main() {
|
||||
log.Printf("[homed] warning: document store: %v", err)
|
||||
}
|
||||
|
||||
// 媒体存储(内容寻址):对话里出现的图片/音频按 sha256 落盘去重,
|
||||
// L0/L2/L3 只记 digest。开关默认开;关闭后全部媒体接线静默跳过,
|
||||
// 对话行为与本特性上线前完全一致。
|
||||
// 媒体存储(内容寻址):记忆块的内容后端。
|
||||
// 开关默认开;关闭后全部媒体接线静默跳过,对话行为与本特性上线前一致。
|
||||
var mediaStore *media.Store
|
||||
if cfgReg.GetBool("core.memory.media.enabled", true) {
|
||||
mediaDir := cfgReg.GetString("core.memory.media.dir",
|
||||
filepath.Join(cfg.Daemon.DataDir, "memory", "media"))
|
||||
maxMB := cfgReg.GetInt("core.memory.media.max_mb", 2048)
|
||||
ms, err := media.New(mediaDir, int64(maxMB)*1024*1024)
|
||||
ms, err := media.New(mediaDir)
|
||||
if err != nil {
|
||||
// 媒体存储开不起来不该阻止启动——它是记忆增强,不是对话必需品
|
||||
log.Printf("[homed] warning: media store: %v(媒体记忆已禁用)", err)
|
||||
@ -343,32 +341,32 @@ func main() {
|
||||
mediaStore = ms
|
||||
defer mediaStore.Close()
|
||||
st := mediaStore.Stats()
|
||||
log.Printf("[homed] media store active: %v 条 / %v 字节(上限 %d MB)",
|
||||
st["count"], st["total_bytes"], maxMB)
|
||||
log.Printf("[homed] media store active: %v 条 / %v 字节",
|
||||
st["count"], st["total_bytes"])
|
||||
}
|
||||
}
|
||||
|
||||
// 统一多模态向量空间(可选)。
|
||||
//
|
||||
// 两条路径共享同一套基础设施(L0/L2/L3 向量缓存、media.Store 坐标、
|
||||
// QueryMemoryMediaScored 检索),只是「算向量的源头」不同:
|
||||
// - onnx:内嵌 ONNX 模型(如 CLIP)
|
||||
// QueryMediaScored 检索),只是「算向量的源头」不同:
|
||||
// - onnx:内嵌 Qwen3-VL 完整图文共享空间
|
||||
// - http:外部向量 API 服务(Jina / OpenAI / 自建)
|
||||
// type 为空时禁用多模态向量检索,退回纯 fastText 文本路径。
|
||||
var multimodalSpace vector.MultimodalEmbedder
|
||||
switch mmType := cfgReg.GetString("core.memory.multimodal_space.type", ""); mmType {
|
||||
case "onnx":
|
||||
if clipDir := cfgReg.GetString("core.memory.media.clip_model_dir", ""); clipDir != "" {
|
||||
e, err := clip.New(clipDir)
|
||||
if modelDir := cfgReg.GetString("core.memory.multimodal_space.onnx.model_dir", ""); modelDir != "" {
|
||||
e, err := qwen.New(modelDir)
|
||||
if err != nil {
|
||||
log.Printf("[homed] warning: onnx embedder load failed: %v(多模态向量检索已禁用)", err)
|
||||
log.Printf("[homed] warning: qwen multimodal embedder load failed: %v(多模态向量检索已禁用)", err)
|
||||
} else {
|
||||
multimodalSpace = e
|
||||
defer e.Close()
|
||||
log.Printf("[homed] multimodal space (onnx) active: dim=%d fp=%s", e.Dim(), e.Fingerprint()[:min(12, len(e.Fingerprint()))])
|
||||
log.Printf("[homed] multimodal space (qwen onnx) active: dim=%d fp=%s", e.Dim(), e.Fingerprint()[:min(12, len(e.Fingerprint()))])
|
||||
}
|
||||
} else {
|
||||
log.Println("[homed] multimodal_space.type=onnx 但未配置 clip_model_dir,多模态向量检索已禁用")
|
||||
log.Println("[homed] multimodal_space.type=onnx 但未配置 onnx.model_dir,多模态向量检索已禁用")
|
||||
}
|
||||
case "http":
|
||||
dim := cfgReg.GetInt("core.memory.multimodal_space.http.dimension", 0)
|
||||
@ -376,7 +374,7 @@ func main() {
|
||||
if dim > 0 && ep != "" {
|
||||
e, err := vector.NewHTTPEmbedder(vector.HTTPEmbedderConfig{
|
||||
Endpoint: ep,
|
||||
APIKey: cfgReg.GetString("core.memory.multimodal_space.http.api_key", ""),
|
||||
APIKey: cfgReg.GetString("core.memory.multimodal_space.http.api_key", ""),
|
||||
Model: cfgReg.GetString("core.memory.multimodal_space.http.model", ""),
|
||||
Dimension: dim,
|
||||
Timeout: cfgReg.GetDuration("core.memory.multimodal_space.http.timeout", 30*time.Second),
|
||||
@ -398,7 +396,6 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ks := knowledge.NewStore(filepath.Join(cfg.Daemon.DataDir, "knowledge"))
|
||||
if err := ks.Start(); err != nil {
|
||||
log.Printf("[homed] warning: knowledge store: %v", err)
|
||||
@ -508,8 +505,6 @@ func main() {
|
||||
SocialStore: socialStore,
|
||||
TextMemory: textMem,
|
||||
MediaStore: mediaStore,
|
||||
MediaGCInterval: cfgReg.GetDuration("core.memory.media.gc_interval", 6*time.Hour),
|
||||
MediaGCMinAge: cfgReg.GetDuration("core.memory.media.gc_min_age", time.Hour),
|
||||
MediaDescribe: cfgReg.GetBool("core.memory.media.describe_on_ingest", false),
|
||||
Personality: personality,
|
||||
PluginReg: pluginReg,
|
||||
@ -521,7 +516,7 @@ func main() {
|
||||
ContextSavePath: filepath.Join(cfg.Daemon.DataDir, "memory", "context.json"),
|
||||
EmbeddingModelPath: cfgReg.GetString("core.agent.embedding_model_path", ""),
|
||||
Embedder: embedder,
|
||||
MultimodalSpace: multimodalSpace,
|
||||
MultimodalSpace: multimodalSpace,
|
||||
StageHost: stageHost,
|
||||
EventBus: evBus,
|
||||
ThinkingEnabled: cfg.LLM.ThinkingEnabled,
|
||||
|
||||
Reference in New Issue
Block a user