|
|
33e1a0bfaf
|
test(memory)!: 块 union 化判据重写(CAS 淘汰第二阶段:测试)
生产已在上一提交(86572d13)全绿;本提交把 CAS 专属测试改到块语义,
并修掉几处判据自身的缺陷(判据错 ≠ 生产错):
- sdk/memory_impl_test: media.Store→块;NewGraphMemoryWithPlugin
- core/{graphmedia,mediaref,medialive,medialoop,inputunify}_test:
mediaStore→memory;PayloadDigest→Data/BlockData;shortDigest→shortID
- graphmedia: 无引用句子用独立块 ID(shortID 20 字符会与 blk_src_ 撞长度)
- memory/sentence_block: BlockByIDPrefix 加 len<12 下限(短引用守卫)
- core/medialoop: reembedOneBlock 按 BlockData 兜底取字节(lean 列无 data)
- knowledge: broken.png 改目录造读失败(chmod 0000 对 root 无效);
dense/attach 测试补 Data 并先落盘后 AttachMedia
- webui: graph_pulse fixture payload_digest→has_data
注:internal/plugin/proc 的 shmVersion 不匹配(内核1/插件2)是
SDK 仓(777c3a0,v2 模板)与本分支内核(v1)的跨仓版本错位,
与块 union 无关(stash 全部改动后仍复现),属 proc-extra 特性线。
|
2026-10-12 14:26:13 +08:00 |
|
|
|
86572d134d
|
refactor(memory)!: 块 union 化,彻底淘汰 CAS 第一阶段(生产代码)
块自带负载(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 空实现,待⑥删包)
|
2026-10-12 10:13:36 +08:00 |
|
|
|
5187a62f47
|
feat(knowledge)!: 检索改为纯稠密向量路,去掉三路融合
按「完全改用向量方案」的要求,SearchIn 现在只走多模态稠密路,
不再与稀疏语义路 / 词法路融合。
## 为什么必须去掉融合(实测,不是判断)
生产知识库(192 条)上,10 条带标注查询的命中率:
改前(三路融合) top1= 1/10 top3= 2/10
改后(纯稠密路) top1= 6/10 top3= 7/10
改前连条目**自己的全名**都搜不到:
`plugin_dev_sdk` 搜不到 `plugin_dev_sdk`、`homeagent_identity` 搜不到
`homeagent_identity`,用户看到的是「查什么都返回同样的几个无关条目」。
而稠密路单独打分时**全库排名 1/192**:
the_jet_engine → the_jet_engine_rolls-royce 0.769
plugin_dev_sdk → plugin_dev_sdk 0.852
homeagent_identity → homeagent_identity 0.823
### 根因:逐路归一化把噪声抬到与信号同权
`addPath` 把每路都按**查询内最大值**归一化到 1.0 再加权:
scores[h.id] += weight * h.score / max
于是词法路原始分只有 **0.027** 的弱信号归一化后也变成 1.0,与稠密路的
1.0 等权相加。叠加两个因素:
- jieba 把英文标识符切碎:`the_jet_engine_rolls-royce` →
`[t h e _ j n g i r o l s - y c]`,两条**无关**文档 token 重叠 50%
- `SetMinScore(0)` 导致 186/192 条都进候选,几乎全库打分
余弦分数**本身就可比**(同空间同维度),根本不需要逐路归一化 ——
那一步当初是为「量级不同的三路」服务的,现在只剩一路,它反而成了
破坏排序的东西。
## 历史结论为何在此翻转
旧注释引的实测(仅稠密路 top-1 15% / MRR 0.271;融合后 21% / 0.376)
建立在这两个**已不成立的前提**上:
- 33 条的小规模 KB(现 192 条,含大量英文标识符)
- 稀疏 fastText 词向量(现 chineseclip 512 维多模态空间)
那段结论我没删,而是在 Search 的注释里写明「前提已变、结论翻转」,
免得后来者看到旧数字又把它改回去。
## 退化路径有意保留
未注入多模态空间时(provider 留空)仍走稀疏两路。不保留的话,
那些部署的知识库会直接变成**完全不可用** —— 比排序差更严重。
该路径行为与接入稠密路之前逐字一致(TestNoDenseSpaceKeepsLegacyBehavior)。
## 判据(都可杀变异,逐个实测)
新增 `dense_recall_quality_test.go`:
- `TestDenseRecallQuality`——带标注 10 条查询,门槛 top1≥4 且 top3≥7。
把 denseEnabled 改成恒假 → FAIL「top1 仅 1/10」(正是退化态)
- `TestDensePathIsSoleRecall`——钉形状:稠密路必须在稀疏路之前且
**结果真的参与组装**。第一版只查「有没有调 denseHits」,把结果赋给 `_`
仍能骗过它;补上 `s.items[h.id]` 断言后,变异(结果不参与组装)才被杀
- `TestSearchDocReflectsDenseOnly`——防文档漂移。第一版简单搜「三路」
会误伤(新文档解释「为何不再融合」时正当地提到它),改为检查只在
*声称当前如此* 时才会出现的句子
## 顺带修的
- `TestDenseCrossModalRecall` 的桩把**所有未知文本**映射到同一向量,
导致 `other` 与 `cat-photo` 正文同向、「猫」查询下 `other` 以余弦 1.0
压过 `cat-photo` 的 0.707。那是**桩的假象不是产品缺陷**。给 other
一个真正正交的方向(且 key 必须是 denseFor 实际传入的
`Name + " " + Content`,写成纯 Content 会静默回落到默认向量)。
- 清掉随融合退场的死代码 `denseSpaceWeight` / `hasAnyDense`,
并把 `vec`/`lex` 字段注释改准(它们现在只服务退化路径)。
- `rankdiag_test.go` 现在测的是**退化路径**(它不注入多模态空间),
注释与门槛说明据此改准:实测 top-1 24% / MRR 0.358,
别拿它当知识库的检索水平。
全仓 `go test ./...` → 48 包 ok / 0 FAIL。
|
2026-10-08 21:34:30 +08:00 |
|
|
|
6d38a164eb
|
chore(module): Go module 路径随托管地迁移 gitcode → github
## 背景
仓库早已迁移到 GitHub(`github.com/JianFeeeee/HomeAgent`,CI/CD 都在那边跑),
gitcode 保留为**国内只读镜像**(`ci.yml` 的 sync-gitcode-main 快进推送)。
但 go.mod 的 module 路径一直是 `gitcode.com/JianFeeeee/...` —— 既与权威源
不符,`meta.SourceURL`(AGPL §13 的源码提供入口)也指向镜像而非实际运行的代码。
## 关键区分(这是本次改动安全的前提)
两个 module 是**不同**的,影响面差一个数量级:
| module | 谁依赖 | 改它的后果 |
|---|---|---|
| `.../HomeAgent`(主仓) | **只有主仓自己** | 内部重构,零外部影响 |
| `.../homeagent-sdk`(SDK 仓) | 主仓 49 文件 + 所有外部插件 | 需重编插件 |
★ 实测确认:**插件不 import 主仓 module**(`strings qq/plugin.bin | grep HomeAgent` = 0),
只依赖 SDK 仓。而插件与内核的边界是 **RPC 方法名字符串**
(`plugin.init` / `tool.invoke`…,见 proc/protocol.go),**与 Go module 名无关**。
⇒ module 改名**不改变任何 wire 协议**,现网已装的插件照常运行。
SDK 的 GitHub 仓名是 `homeagentsdk`(无连字符),module 路径必须与之精确匹配,
否则 `go get` 会因路径不符失败,故取 `github.com/JianFeeeee/homeagentsdk`。
## 改动
- `go.mod` / `go.sum`:两个 module 路径 + require/replace
- 277 个 Go 文件 + Makefile + 打包脚本的 import 与 ldflags 路径
- SDK 仓 `meta.CoreModule`、`tools/hmapdev` 的 module 声明与工程模板
- 对外文档/链接的仓库地址(README、PLUGIN_DEV、OVERVIEW 等)
## 明确**保留** gitcode 的地方(镜像机制,不能动)
- `GITCODE_TOKEN`、`GC_URL="…@gitcode.com"`(CI 同步 job)
- `gitcode.com/api/v5/...`(release 附件上传)
- git remote `gitcode`(镜像拉取/推送)
- `docs/zh/ci-cd-runbook.md`(整篇讲镜像运维)
## 验证
- `go build ./...` / `go vet ./...` 绿
- `go test ./... -count=1` → 48 包 ok / 0 FAIL
- hmapdev 工具链编译通过(`third_party/homeagent-sdk/tools/hmapdev`)
- 全仓残留检查:module 形态的 `gitcode.com/JianFeeeee` = 0
- 镜像机制抽验:GC_URL / @gitcode / api/v5 / remote 均完好
## 顺带修正
`hmapdev/templates.go` 的注释此前写「gitcode 的模块不在 proxy.golang.org 上」——
module 已迁 GitHub,该论断不再成立。改为如实说明**为何仍默认 replace 到本机 SDK**:
离线可构建、避免版本漂移、内网可用。理由与托管地无关,故 replace 保留。
|
2026-10-08 10:40:17 +08:00 |
|
|
|
0dcb335cf3
|
fix(knowledge): 名称规范化/路径安全 + IDF 增量维护 + 多模态稠密路 + 派生数据落盘
一次知识库子系统的集中加固,四类缺陷各有实测复现:
1. 名称与路径(数据安全,最严重)
- sanitize 不过滤 .. ⇒ Remove("..") 直接 RemoveAll 掉整个数据目录
(实测把 <data> 整棵删掉,含 memory/documents/media),且返回 nil,
工具层回报"已删除";Add("../../x") 写到知识根外,重启扫不回来
⇒ 幽灵条目。
- Add 整串 sanitize 而建目录逐段 sanitize,内存键与盘上目录从**第一次
落盘起**就不一致;重启后 name 漂移,knowledge_delete 静默删不掉
(RemoveAll 删空目录返 nil)。同一个根因。
- 修法:新增 normalizeName 作为唯一入口(逐段 + 拒绝空段/点段/隐藏段);
Remove 改为取条目自记的 Path(不再用名字重拼)+ 返回 ErrNotFound。
- resolve 三层退让(原样 → 规范名 → 叶名大小写不敏感,唯一命中才接受):
scanDir 按盘上目录原样建键,遗留大写目录若只查规范名会变成
"List 看得到、Remove 报不存在"。删的路径仍取自 Path,退让无风险。
2. IDF 与索引不同步(功能缺陷,非优化)
- TFIDF Vectorize 跳过 df<=0 的特征,而 Add 只往只增不减的 summaries
追文本、从不更新 DF ⇒ 库满(≥3篇) + 新词时,新知识**当场搜不到**,
重启才恢复(实测 Search("量子纠缠") == [])。
- 修法:vector.Store 新增 AddDoc/RemoveDoc(文档级去重口径与 Train 一致,
totalDocs 下界守卫,零频 DF 删除防表膨胀);knowledge 删掉 summaries,
改 index/unindex/retrain 三件套,覆盖写先 RemoveDoc 旧文本。
3. 多模态稠密路(此前知识库端到端纯文本)
- 新增 SetDenseSpace/SetMediaGetter/ReindexDense/DenseStats 与
AddWithMedia/AttachMedia,媒体成为一等节点参与跨模态召回。
- 维度与指纹双守卫:维度不符的向量会被 FuseVectors 按最大维度拼成错维度
结果且被当成"已对齐"永久错下去(docStore 踩过);模态不支持(音频)时
静默跳过该媒体、退化为纯文本向量,绝不拿别的模型的向量顶替。
- 未注入多模态空间时行为与此前逐字一致(退化为 0.5/0.5 两路融合)。
4. 派生数据落盘 + 分层参与召回
- 媒体引用是**作者数据**(丢失即丢信息)→ 条目目录内 .media.json;
稠密向量是**派生数据**(可重算)→ 全局 .dense.json,tmp+rename 原子。
混存会让派生数据损坏连带作者数据一起丢。
- 新增 SearchIn(query, category, topK):分类前缀匹配子树,让分层真正
参与召回(此前检索全库平铺,分层只是存储布局)。归一化取作用域内
最大值,否则范围外的强命中会把域内分数压没。
- 删除 SearchTree/SearchCategories 死代码(零调用方,且停留在 Search
修复前的单路口径:无词法融合、0.05 阈值)。
顺带修掉 Add 的 O(N)/写:buildTreeLocked 逐条重算向量(s.vec 里已有)
改为一次建表复用;.index.json 改为标脏 + Flush/Stop 收口。实测单条 Add
2.1ms@50 → 13.7ms@400 压平到 ~400µs(34×)。
存量目录改名迁移落在 migrate_names.go,只报告不改名(os.Rename 不可逆),
冲突整批拒绝以免半迁移。
|
2026-09-26 14:20:18 +08:00 |
|