mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-03 15:53:56 +00:00
feat(vector): pluggable multimodal vector space
核心暴露 MultimodalEmbedder 接口,两条路径共享同一套 L0/L2/L3 向量缓存、media.Store 坐标、QueryMemoryMediaScored 检索: - onnx:内嵌 ONNX 模型(CLIP 等),通过 build tag 编译 - http:外部向量 API 服务(Jina v5 / OpenAI / 自建) 跨模态融合权重改为 CrossModalFusionConfig 可配置结构体, 移除所有模型特定硬编码(CLIP/Jina),版本切换只需改配置。 模型切换自动迁移: - StaleVecDigestsAll 支持全模态(image+audio+video) - 启动时并发重算(ONNX 4 workers / API 8 workers) - 修复 SQL 运算符优先级导致 kind 过滤失效的 bug 实测对比(492 篇生产文档 + 3 张真实图片): - TF-IDF:MRR 0.457(精确匹配快,语义差) - fastText:MRR 0.530(语义中等,延迟 8ms) - Jina v5-omni:MRR 0.900(全面领先,延迟 40ms) - 中文文本→图片:Jina MRR 0.833 vs CLIP 0.611 See docs/embedding-comparison.md for full benchmark.
This commit is contained in:
@ -30,6 +30,7 @@ import (
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/pipeline"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/social"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/text"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/vector"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/meta"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/nlp"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/plugin"
|
||||
@ -347,21 +348,57 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// 多模态嵌入(CLIP ONNX,可选):配置 clip_model_dir 时启用,
|
||||
// 图片入库/描述时计算视觉向量,供跨模态检索;未配置则退回纯 fastText 文本路径。
|
||||
var clipEmbedder *clip.Embedder
|
||||
if clipDir := cfgReg.GetString("core.memory.media.clip_model_dir", ""); clipDir != "" {
|
||||
e, err := clip.New(clipDir)
|
||||
if err != nil {
|
||||
// 配置了但加载失败:记日志降级,不阻塞启动(媒体记忆是增强项)
|
||||
log.Printf("[homed] warning: clip embedder load failed: %v(多模态向量检索已禁用)", err)
|
||||
// 统一多模态向量空间(可选)。
|
||||
//
|
||||
// 两条路径共享同一套基础设施(L0/L2/L3 向量缓存、media.Store 坐标、
|
||||
// QueryMemoryMediaScored 检索),只是「算向量的源头」不同:
|
||||
// - onnx:内嵌 ONNX 模型(如 CLIP)
|
||||
// - 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 err != nil {
|
||||
log.Printf("[homed] warning: onnx 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()))])
|
||||
}
|
||||
} else {
|
||||
clipEmbedder = e
|
||||
defer e.Close()
|
||||
log.Printf("[homed] clip embedder active: dim=%d fp=%s", e.Dim(), e.Fingerprint()[:min(12, len(e.Fingerprint()))])
|
||||
log.Println("[homed] multimodal_space.type=onnx 但未配置 clip_model_dir,多模态向量检索已禁用")
|
||||
}
|
||||
case "http":
|
||||
dim := cfgReg.GetInt("core.memory.multimodal_space.http.dimension", 0)
|
||||
ep := cfgReg.GetString("core.memory.multimodal_space.http.endpoint", "")
|
||||
if dim > 0 && ep != "" {
|
||||
e, err := vector.NewHTTPEmbedder(vector.HTTPEmbedderConfig{
|
||||
Endpoint: ep,
|
||||
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),
|
||||
Fingerprint: cfgReg.GetString("core.memory.multimodal_space.http.fingerprint", ""),
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("[homed] warning: http embedder init failed: %v(多模态向量检索已禁用)", err)
|
||||
} else {
|
||||
multimodalSpace = e
|
||||
defer e.Close()
|
||||
log.Printf("[homed] multimodal space (http) active: endpoint=%s dim=%d", ep, dim)
|
||||
}
|
||||
} else {
|
||||
log.Println("[homed] multimodal_space.type=http 但 endpoint/dimension 配置不完整,多模态向量检索已禁用")
|
||||
}
|
||||
default:
|
||||
if mmType != "" {
|
||||
log.Printf("[homed] warning: 未知 multimodal_space.type=%q,多模态向量检索已禁用", mmType)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ks := knowledge.NewStore(filepath.Join(cfg.Daemon.DataDir, "knowledge"))
|
||||
if err := ks.Start(); err != nil {
|
||||
log.Printf("[homed] warning: knowledge store: %v", err)
|
||||
@ -484,7 +521,7 @@ func main() {
|
||||
ContextSavePath: filepath.Join(cfg.Daemon.DataDir, "memory", "context.json"),
|
||||
EmbeddingModelPath: cfgReg.GetString("core.agent.embedding_model_path", ""),
|
||||
Embedder: embedder,
|
||||
ClipEmbedder: clipEmbedder,
|
||||
MultimodalSpace: multimodalSpace,
|
||||
StageHost: stageHost,
|
||||
EventBus: evBus,
|
||||
ThinkingEnabled: cfg.LLM.ThinkingEnabled,
|
||||
|
||||
Reference in New Issue
Block a user