mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-03 15:53:56 +00:00
feat(memory): 场景双通道——主动声明与被动涌现并存,且互不吞噬
按「声明式的也要支持,相当于主动被动两条路」落实。此前两者只是恰好并存,
没有边界,实测会互相吃掉(下面的坑就是)。
- Triple.Scenes []string(多值):一轮写下的记忆**两条路都挂**。
只挂一条会丢东西——只挂声明则细粒度唤起丢失,只挂涌现则首次交互
(场景还没长出来)没有兜底。单值 Scene 保留兼容。
- TurnScene:Primary 用于写(优先涌现场景,首次退到声明场景兜底),
Keys 是两条路的并集,用于召回(声明+涌动的场景一起进 RecallByScene)。
- EnterSceneWithHint:主动路 EnsureScene(声明即建场景,不等第二次),
被动路 EnterScene(指纹聚类)。写侧由 executeToolCall 把本轮场景集合
传给 memory_commit,模型不需要知道"场景"这回事。
踩到并修掉的坑(两条路互相吞噬):
最初让声明场景也吸收**整轮指纹**,于是 chan:qq 的相似度永远是 1.0,
把后续所有同类轮次全部吃掉 → 被动路再也长不出更细的场面,
实测 turn2.Emergent=true 但 Primary 仍是 chan:qq、没有 auto: 场景。
修法:给场景加 origin(declared/emergent):
- 被动聚类只认 origin='emergent' 的场景(声明场景不进相似度空间);
- 声明场景的特征**只从键自身解析**(chan:qq/peer:group_1 → {chan:qq, peer:group_1}),
白名单 kind(chan/peer/peer_group/tool/topic/part),不猜——
「老大2026-09-04_12:27_qq私聊图片」里的 12:27 也是 kind:value 形态,
放进特征空间就是往相似度里灌垃圾(有测试钉住)。
- 声明路的泛化靠**层级键前缀**(chan:qq 覆盖 chan:qq/peer:x),机制各归各。
- memgc -scene-stats 增加 [declared|emergent] 与 strength/features 两栏,
可直接观察两条路各自在长什么。
新增/改写用例:
- TestDeclaredAndEmergentBothLearn:首次交互兜底到声明场景 → 第 2 轮长出
细粒度涌现场景且**优先用于写入** → 声明场景不进相似度空间(防止压死被动路)
但仍走声明键取回 → 两条路都进召回集合 → 声明键特征解析与白名单。
- TestEffectiveScenes:多值+单值合并去重保序。
go build/vet 干净,go test -count=1 ./... 全绿。
This commit is contained in:
@ -123,8 +123,10 @@ type SceneStat struct {
|
||||
Entities int `json:"entities"`
|
||||
// Strength 是该场景被重现(强化)的次数;Features 是它长出的特征数。
|
||||
// 两者一起说明「这个场景是不是真的在涌现」,而不是被一次性写出来的。
|
||||
Strength int `json:"strength"`
|
||||
Features int `json:"features"`
|
||||
Strength int `json:"strength"`
|
||||
Features int `json:"features"`
|
||||
// Origin 是这条场景来自哪条路:declared(主动声明)或 emergent(被动涌现)。
|
||||
Origin string `json:"origin"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
@ -477,6 +479,7 @@ func (g *GraphDB) SceneStats() ([]SceneStat, error) {
|
||||
SUM(CASE WHEN sr.kind = 'entity' THEN 1 ELSE 0 END),
|
||||
COALESCE(s.strength, 1),
|
||||
(SELECT COUNT(*) FROM scene_features f WHERE f.scene_id = s.id),
|
||||
COALESCE(s.origin, 'emergent'),
|
||||
s.updated_at
|
||||
FROM scenes s LEFT JOIN scene_refs sr ON sr.scene_id = s.id
|
||||
GROUP BY s.id ORDER BY COUNT(sr.id) DESC, s.key`)
|
||||
@ -489,7 +492,7 @@ func (g *GraphDB) SceneStats() ([]SceneStat, error) {
|
||||
for rows.Next() {
|
||||
var st SceneStat
|
||||
var rels, ents sql.NullInt64
|
||||
if err := rows.Scan(&st.Key, &st.Refs, &rels, &ents, &st.Strength, &st.Features, &st.UpdatedAt); err != nil {
|
||||
if err := rows.Scan(&st.Key, &st.Refs, &rels, &ents, &st.Strength, &st.Features, &st.Origin, &st.UpdatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st.Relations = int(rels.Int64)
|
||||
|
||||
Reference in New Issue
Block a user