mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-06 01:07:35 +00:00
fix(memory): 向量输入改用事件正文 —— 摘要是 jieba 词袋,把全库拉成同一方向
## 实测缺陷(真实 chineseclip,4 篇主题完全不同的短正文)
[1][3] 0.9478 [2][3] 0.9427
[1][2] 0.9316 [0][3] 0.8614
两两余弦 0.85~0.95,几乎同向。查询分数 0.70~0.78 挤在一起,区分度 0.08。
根因:denseFor 用 `doc.Summary + " " + doc.Content`,而 Summary 由
summarizeEntries → memory.ExtractKeywords → TokenizeContentWords → jieba.Tag()
生成,同一来源的摘要**逐字同质**:
来自 1 个来源的 1 条对话 (src0) 涉及: 用户, 提问
来自 1 个来源的 1 条对话 (src1) 涉及: 用户, 提问
这与设计直接矛盾:ARCHITECTURE L131/L174 写的向量层级是
「统一多模态空间(主)→ StaticEmbedder → TF-IDF(回退)」,
而 Summary 是**主路径的输入**,却由**最末一级的词法分词**产出。
## 修法
denseFor 改用 documentTextForVector(doc):按事件顺序拼原文(来自块里的正文)。
存量无 Events 的文档退回 Content(渲染视图,但比 Summary 更接近内容)。
★ 绝不含 Summary —— 它是词袋,会把全库拉向同一方向。
## 媒体为什么不单独生成摘要
媒体块与文本块**在同一事件内**(Doc.Events[i].Blocks),事件正文进入向量
即覆盖其语义。为媒体单独生成描述正是 CAS 时代「描述式索引」的错误
(描述是二手信息),v1.2.0 已整体拆除,不重走。
「按图搜图」是视觉路(RecallBlocks 读块自己的向量)的职责,已有独立通路。
## 判据
- TestRetrieval_SummaryNoiseMustNotPolluteVector:主题不同的短正文文档,
两两余弦必须 <0.80。修复前 0.82~0.86。
- TestRetrieval_VectorInputIsBodyNotSummary:正文里的唯一哨兵必须能检索到。
变异:denseFor 改回 Summary+Content ⇒ 前者立刻红(实测 0.8225~0.8573)。
This commit is contained in:
@ -224,19 +224,20 @@ func TestRetrieval_ScoreGapIsSignificant(t *testing.T) {
|
||||
|
||||
// ④ 向量输入必须是正文,而不是摘要。
|
||||
//
|
||||
// 做法:把哨兵串只放进 Response(正文),不放 Summary。
|
||||
// 若 denseFor 仍用 Summary,哨兵不会进向量 ⇒ 该文档检索不到唯一哨兵查询。
|
||||
// 做法:哨兵串只放进 Response(正文),不放 Summary。用 bigramSpace
|
||||
// (对任意文本都给可区分的向量)验证:哨兵查询的 top1 必须是含哨兵的那篇。
|
||||
//
|
||||
// 变异:denseFor 读 Summary ⇒ 红。
|
||||
// 变异:denseFor 读 Summary ⇒ 哨兵不在向量里 ⇒ top1 是别的文档 ⇒ 红。
|
||||
func TestRetrieval_VectorInputIsBodyNotSummary(t *testing.T) {
|
||||
s := NewStore(t.TempDir(), memory.TokenizeWords)
|
||||
if err := s.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(s.Stop)
|
||||
s.SetDenseSpace(&fakeDenseSpace{})
|
||||
ds := &bigramSpace{}
|
||||
s.SetDenseSpace(ds)
|
||||
|
||||
const sentinel = "紫蓝红三色带"
|
||||
const sentinel = "量子纠缠态退相干时间测量"
|
||||
_, err := s.ContextToDoc("context_archived", []ContextEntry{{
|
||||
Timestamp: time.Now(),
|
||||
Source: "test",
|
||||
@ -246,15 +247,16 @@ func TestRetrieval_VectorInputIsBodyNotSummary(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// 另一篇同长度干扰文档
|
||||
// 与哨兵完全无关的干扰文档(越长越能测出"摘要被当成内容")
|
||||
_, _ = s.ContextToDoc("context_archived", []ContextEntry{{
|
||||
Timestamp: time.Now().Add(time.Minute),
|
||||
Source: "test",
|
||||
Content: "另一条用户消息",
|
||||
Response: "这是完全不同的一段时间较长的正文内容,与配色毫无关系。",
|
||||
Response: "这是完全不同的一段很长的正文内容,只谈插件安装与打包部署流程," +
|
||||
"与物理测量毫无关系,纯粹用来干扰检索。" + strings.Repeat("补充无关叙述。", 20),
|
||||
}}, nil, nil, nil, nil)
|
||||
|
||||
s.BuildDenseIndex(&fakeDenseSpace{})
|
||||
s.BuildDenseIndex(ds)
|
||||
|
||||
hits := s.QueryScored(sentinel, 1)
|
||||
if len(hits) == 0 {
|
||||
|
||||
Reference in New Issue
Block a user