- P0-3 持久向量索引**不再空壳**:Store.Add 落库后真做 Embed+Add(失败只记日志、绝不让记忆写入失败); NewRetriever 拿到 WithVectorIndex 时**自动接上写入路径**(无需改组合根);索引无命中 → WARN + **回退内存余弦**; 新增每进程一次的**回填**(索引确实为空时把已落库条目向量化,分批 64、上限 512) - P0-4 工作记忆**按会话接线**:回合落定时写入(owner 会话 + 带会话 id 的窗口,LRU 上限 64), 组装上下文时按会话读取 ⇒ 前导块真的出现;原单窗口 API 语义不变 - P0-17 L4 学习回环**真回写**:新增 capacity.Judge(只跑判定、不施人格覆盖/不发事件/不改状态); loop 在回合落定"只写 owner 会话 + 跳过子代理分支",写 ScopeUser(L4 的读域)+ 结构化溯源 - 顺带红线加固:意图记录是主人**原话**的机器可读形态 ⇒ **绝不注入群聊/陌生人块**(design/03 §5) - 复核:三条各自的包 build/vet/test 绿;「真生效」断言含生产构造路径(vector.New + 真实 SQLite 后端 + n-gram embedder)、跑完一轮窗口非空、L4 回写后能唤醒一个自身无强信号的表达; **5 条变异反证**(其中两条专门打「受限会话零写入」与「群聊不泄露主人原话」)均已还原 - 未做(登记):Index.Search 无 ID 过滤(客户端按候选集过滤,极端情况走回退);每条记忆一次 Embed
45 lines
1.5 KiB
Go
45 lines
1.5 KiB
Go
package memory
|
|
|
|
import (
|
|
"math"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/embedding"
|
|
)
|
|
|
|
// 本文件收拢语义打分的辅助件:内存余弦相似度与「索引没答上、改走
|
|
// 内存回退」的一次性告警。它们的用量小但分散,独立成文件是为了让
|
|
// retrieve.go 保持可读(文件 ≤500 行,AGENTS §10)。
|
|
|
|
// cosineSim 计算两个向量的余弦相似度,由调用方夹到 [0,1]。
|
|
// 长度为零或不一致的向量得 0。
|
|
func cosineSim(a, b embedding.Vector) float64 {
|
|
if len(a) == 0 || len(b) == 0 || len(a) != len(b) {
|
|
return 0
|
|
}
|
|
var dot, na, nb float64
|
|
for i := range a {
|
|
dot += float64(a[i]) * float64(b[i])
|
|
na += float64(a[i]) * float64(a[i])
|
|
nb += float64(b[i]) * float64(b[i])
|
|
}
|
|
if na == 0 || nb == 0 {
|
|
return 0
|
|
}
|
|
return dot / (math.Sqrt(na) * math.Sqrt(nb))
|
|
}
|
|
|
|
// warnSemanticFallback 报告一次「接入了向量索引但在候选集内无命中,
|
|
// 已改走内存余弦」。它每个检索器只报一次:该状态在一次运行里
|
|
// 基本不变(索引尚未写入或 embedder 维度不匹配),逐次 Retrieve
|
|
// 重复告警只会淹没有用日志。索引报错本身由 vectorIndexScores 以
|
|
// Warn 记录,这里用 Debug 区分「已知会回退」与「真的出错了」。
|
|
func (r *Retriever) warnSemanticFallback() {
|
|
r.seedMu.Lock()
|
|
defer r.seedMu.Unlock()
|
|
if r.fallbackWarned {
|
|
return
|
|
}
|
|
r.fallbackWarned = true
|
|
r.log.Debug("memory: vector index had no candidate hit, using in-memory cosine fallback")
|
|
}
|