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:
@ -205,46 +205,56 @@ func partOfDay(t time.Time) string {
|
||||
}
|
||||
}
|
||||
|
||||
// resolveTurnScene 解析本轮所属的**涌现场景**,一轮只解析一次。
|
||||
// resolveTurnScenes 解析本轮的场景集合,**同时走主动与被动两条路**:
|
||||
//
|
||||
// 与「声明场景」(sceneKeysFor:注入点 > 通道 > 工具)的关系:两者并存且都被
|
||||
// 用于召回。声明是"我知道这是哪个场面",涌现是"这轮看起来像哪个场面"——
|
||||
// 后者不需要任何人知道场景这回事。
|
||||
// 主动(声明):注入点/通道/工具声明了"这是哪个场面" → 场景存在化并喂入
|
||||
// 本轮指纹(声明场景因此慢慢学会自己认自己)
|
||||
// 被动(涌现):场面指纹聚类 → 同类指纹重复出现时自己长出场景
|
||||
//
|
||||
// 返回结果的 Primary 用于**写**(优先细粒度的涌现场景,首次交互退到声明场景
|
||||
// 兜底),Keys 用于**读**(两条路的并集,去重)。
|
||||
//
|
||||
// 解析会**写库**(场景强化/长出),所以必须一轮一次:多调一次就多给场景记
|
||||
// 一次强度,"工具调得多"会被误读成"这个场面更常出现"。
|
||||
func (a *Agent) resolveTurnScene(f *TaskFrame, tool string) string {
|
||||
func (a *Agent) resolveTurnScenes(f *TaskFrame, tool string) memory.TurnScene {
|
||||
var out memory.TurnScene
|
||||
if a == nil || a.memory == nil {
|
||||
return ""
|
||||
return out
|
||||
}
|
||||
if f == nil {
|
||||
return a.emergentSceneFor(nil, "", tool)
|
||||
if f != nil && f.sceneDone {
|
||||
return f.turnScene
|
||||
}
|
||||
if f.sceneDone {
|
||||
return f.Scene
|
||||
}
|
||||
f.Scene = a.emergentSceneFor(f.Evt, f.CleanInput, tool)
|
||||
f.sceneDone = true
|
||||
return f.Scene
|
||||
}
|
||||
|
||||
// emergentSceneFor 采集指纹并交给图库做「归属或长出」。
|
||||
func (a *Agent) emergentSceneFor(evt *agentIO.InputEvent, cleanInput, tool string) string {
|
||||
feats := situationFeaturesFor(evt, cleanInput, tool)
|
||||
if len(feats) == 0 {
|
||||
return ""
|
||||
}
|
||||
declared := sceneKeysFor(evtOf(f), tool)
|
||||
feats := situationFeaturesFor(evtOf(f), cleanInputOf(f), tool)
|
||||
sig := memory.NewSituation(feats...)
|
||||
if sig.Empty() {
|
||||
return ""
|
||||
}
|
||||
key, created, err := a.memory.EnterScene(sig)
|
||||
|
||||
turn, err := a.memory.EnterSceneWithHint(sig, declared)
|
||||
if err != nil {
|
||||
log.Printf("[agent] scene enter failed: %v", err)
|
||||
// 出错时至少把声明场景交给召回,不让整条召回链一起失效
|
||||
turn = memory.TurnScene{Keys: declared}
|
||||
if len(declared) > 0 {
|
||||
turn.Primary = declared[0]
|
||||
}
|
||||
}
|
||||
if turn.Emergent {
|
||||
log.Printf("[agent] 场景涌现/命中: %q(指纹 %v)", turn.Primary, sig.Keys())
|
||||
} else if len(turn.DeclaredCreated) > 0 {
|
||||
log.Printf("[agent] 声明场景成立: %v(指纹 %v)", turn.DeclaredCreated, sig.Keys())
|
||||
}
|
||||
if f != nil {
|
||||
f.turnScene = turn
|
||||
f.Scene = turn.Primary
|
||||
f.sceneDone = true
|
||||
}
|
||||
return turn
|
||||
}
|
||||
|
||||
// cleanInputOf 安全取出清洗后输入(f 为 nil 时为空)。
|
||||
func cleanInputOf(f *TaskFrame) string {
|
||||
if f == nil {
|
||||
return ""
|
||||
}
|
||||
if created {
|
||||
log.Printf("[agent] 场景涌现: %q(由场面指纹 %v 长出)", key, sig.Keys())
|
||||
}
|
||||
return key
|
||||
return f.CleanInput
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user