mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-15 03:52:25 +00:00
feat(memory): 场景从「声明」改为「涌现」——场面指纹自己长成场景
上一版场景是声明/派生的:调用方写 scene="chan:qq",或由通道机械派生。 那不是涌现,是贴标签——标签谁定、怎么定全靠人。按「像人一样:干了什么事, 后续类似场面自动唤起对应记忆」的要求重做。 机制(全部取自运行时可观察量,无需模型配合、无需人工标注): - **场面指纹 Situation**:每轮采集 `chan:xx / peer:xx / peer_group:xx / tool:xx / topic:xx / part:xx`。权重按种类:通道与对象最强(1.0), 工具次之(0.8),话题是软信号(0.4),时段最弱(0.2)。 - **归属判定用加权 Jaccard**(不是字符串相等):共享特征权重和 / 并集权重和。 加权是必须的——`chan:qq` 与 `topic:排班` 的证据力差 2.5 倍,不加权会让 一次偶然的话题重合把两个不同场面并成一个。 - **涌现**:同类指纹重复到 minSceneEvidence=2 次才长出场景 (首次只登记 situation_evidence 足迹)。一次性的交互不是「场面」, 给它建场景会让库被一次性事件撑满、之后每次路过都召回一堆只发生过一次的事。 - **强化**:场景每次重现 strength+1、并入新特征。 - **唤起**:RecallBySituation 按**相似度**取回(阈值 0.35,比归属阈值 0.5 低 ——想不起来是损失,多想起一条只是多几行上下文),与措辞无关。 - **遗忘**:DecaySceneRefs 按半衰期让久未重现的关联淡出,低于 floor 直接删; 已接进 archive 心跳(半衰期 30 天,比「这个月没做过这类事」更久)。 三个必须讲清的边界: 1. 一轮只解析一次场景(TaskFrame 缓存)——多解析一次就多记一次强度, 「工具调得多」会被误读成「这个场面更常出现」。 2. 声明与涌现**并存**:声明是「我知道这是哪个场面」(插件注入点最清楚), 涌现是「这轮看起来像哪个场面」。两者都进召回。 3. 记忆挂载全自动:memory_commit 没写 scene 时落到本轮涌现场景, 模型不需要知道场景这回事。 验证:go build/vet 干净,go test -count=1 ./... 全绿。 新增用例(核心证据): - TestSceneEmergesFromRepetition:首次不建场景 → 第 2 次同类场面长出场景 → 同场面**不同话题**仍并入同一场景 → 换通道的场面自己长出独立场景(共 2 个)→ 强度随重现增长、特征多条。全程没有任何人声明过场景键。 - TestSceneRecallsBySituationNotWording:场面里写下的规则,换措辞后仍被 自动唤起(含原句),无关场面不唤起。 - TestSceneRefDecay:一个半衰期权重减半、第二个半衰期低于 floor 被清掉, 仍在重现的场景不受影响。 - TestSituationFeaturesFor:指纹维度齐全、归一化、数值 group_id 转换、nil 安全。
This commit is contained in:
@ -13,7 +13,7 @@ import (
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/text"
|
||||
)
|
||||
|
||||
func (a *Agent) executeToolCall(tc agentAPI.ToolCall, channel string) (ret string) {
|
||||
func (a *Agent) executeToolCall(tc agentAPI.ToolCall, channel string, turnScene ...string) (ret string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
stack := debug.Stack()
|
||||
@ -31,7 +31,7 @@ func (a *Agent) executeToolCall(tc agentAPI.ToolCall, channel string) (ret strin
|
||||
|
||||
done := make(chan string, 1)
|
||||
go func() {
|
||||
done <- a.executeToolCallInner(tc, channel)
|
||||
done <- a.executeToolCallInner(tc, channel, firstOr(turnScene))
|
||||
}()
|
||||
|
||||
select {
|
||||
@ -43,12 +43,21 @@ func (a *Agent) executeToolCall(tc agentAPI.ToolCall, channel string) (ret strin
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) executeToolCallInner(tc agentAPI.ToolCall, channel string) string {
|
||||
// firstOr 取可选参数的首个值(工具执行路径只有调用方知道本轮场景,
|
||||
// 用变参是为了不让「不关心场景」的调用点(spawn/测试)被迫传空串)。
|
||||
func firstOr(v []string) string {
|
||||
if len(v) == 0 {
|
||||
return ""
|
||||
}
|
||||
return v[0]
|
||||
}
|
||||
|
||||
func (a *Agent) executeToolCallInner(tc agentAPI.ToolCall, channel string, scene string) string {
|
||||
switch {
|
||||
case tc.Name == "persona_set":
|
||||
return a.executePersonaTool(tc)
|
||||
case strings.HasPrefix(tc.Name, "memory_"):
|
||||
return a.executeMemoryTool(tc)
|
||||
return a.executeMemoryTool(tc, scene)
|
||||
case strings.HasPrefix(tc.Name, "social_"):
|
||||
return a.executeSocialTool(tc)
|
||||
case strings.HasPrefix(tc.Name, "knowledge_"):
|
||||
@ -123,7 +132,7 @@ func (a *Agent) executeToolCallInner(tc agentAPI.ToolCall, channel string) strin
|
||||
return fmt.Sprintf("%v", result)
|
||||
}
|
||||
|
||||
func (a *Agent) executeMemoryTool(tc agentAPI.ToolCall) string {
|
||||
func (a *Agent) executeMemoryTool(tc agentAPI.ToolCall, turnScene string) string {
|
||||
g := a.graphMem()
|
||||
if g == nil {
|
||||
if tc.Name == "memory_document_query" {
|
||||
@ -214,6 +223,11 @@ func (a *Agent) executeMemoryTool(tc agentAPI.ToolCall) string {
|
||||
// 两条路都为空则这条记忆不参与场景召回——不做猜测:猜错的场景会把
|
||||
// 无关记忆钉死,之后每次进入该场面都会被注入,比漏标更难发现。
|
||||
batchScene := getString(tc.Arguments, "scene")
|
||||
// 没有显式声明时,落到本轮**涌现**出来的场景上:模型不需要知道场景
|
||||
// 这回事,记忆也会因为「是在什么场面里写下的」而自动获得唤起入口。
|
||||
if batchScene == "" {
|
||||
batchScene = turnScene
|
||||
}
|
||||
var triples []memory.Triple
|
||||
for _, td := range triplesData {
|
||||
if m, ok := td.(map[string]interface{}); ok {
|
||||
|
||||
Reference in New Issue
Block a user