fix(memory,loop): 记忆作用域读写成对 + source_conversation 结构化(裁决 1.9/04.4.2/04.4.3)
- 04.4.2/1.9 读侧:Retrieve 候选集并集 = 本人 persona 域 ∪ user 共享域 ∪ project 共享域(他角色 persona 域仍排除),群聊/陌生人取受限收窄
- 写侧:群聊/陌生人整段跳过记忆提取(可查不可写),persona 域仅属主可写
- 04.4.3:source_conversation 不再由 LLM 自由生成(prompt 不索要、候选结构不含该键),落库时无条件盖真实 {session_id,message_id},编解码集中在 memory/source.go
- 新增跨角色种子用例(原用例即使关掉并集也会通过)+ 成对用例「同群聊上下文:读得到共享域、写不进 store」
- 同步 store.go/retrieve.go 文档注释,与 design/03 §3/§4 口径一致
This commit is contained in:
1 parent
af8591b78a
commit
0ce498754a
11 files changed
+428
-60
No files matched your search
@@ -0,0 +1,67 @@
|
||||
package loop
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||||
)
|
||||
|
||||
// TestPersistMemories_StampsRealSource 证明裁决 04.4.3 的落库值:
|
||||
// 提取调用点(persistMemories)从运行上下文盖真实 session/message id,
|
||||
// extractor 产出的自由文本来源被无条件覆盖。
|
||||
func TestPersistMemories_StampsRealSource(t *testing.T) {
|
||||
t.Parallel()
|
||||
store := newTestMemoryStore(t)
|
||||
l := New(Options{Memory: store, Extractor: memory.NewManualExtractor("persona-a", "owner"), PersonaID: "persona-a"})
|
||||
|
||||
// extractor 刻意夹带一条伪造来源,证明持久化层会盖掉它。
|
||||
bogus := []memory.MemoryItem{{
|
||||
PersonaID: "persona-a", Scope: memory.ScopeUser, Kind: memory.KindRelationship,
|
||||
Content: "用户习惯:爱喝冰美式", SourceConversation: "模型瞎编的来源",
|
||||
}}
|
||||
ctx := withLoopSession(WithTurnID(WithSessionEnv(context.Background(), OwnerEnv()), "turn-1"), "web:abc")
|
||||
l.persistMemories(ctx, bogus, 1)
|
||||
|
||||
items, err := store.ListByPersona(context.Background(), "persona-a")
|
||||
if err != nil {
|
||||
t.Fatalf("ListByPersona: %v", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("stored %d items, want 1", len(items))
|
||||
}
|
||||
ref, ok := memory.ParseSourceConversation(items[0].SourceConversation)
|
||||
if !ok {
|
||||
t.Fatalf("SourceConversation %q is not structured, want real ids", items[0].SourceConversation)
|
||||
}
|
||||
if ref.SessionID != "web:abc" || ref.MessageID != "turn-1" {
|
||||
t.Fatalf("source ref = %+v, want session web:abc / message turn-1", ref)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPersistMemories_SourceSessionOnly 证明未挂 turn id 的调用方
|
||||
// (渠道路径现状)溯源降级为仅会话粒度 —— 真实、可解析,不伪造。
|
||||
func TestPersistMemories_SourceSessionOnly(t *testing.T) {
|
||||
t.Parallel()
|
||||
store := newTestMemoryStore(t)
|
||||
l := New(Options{Memory: store, Extractor: memory.NewManualExtractor("persona-a", "owner"), PersonaID: "persona-a"})
|
||||
|
||||
ctx := withLoopSession(WithSessionEnv(context.Background(), OwnerEnv()), "qq-obv11:group:42")
|
||||
l.extractMemory(ctx, []model.Message{model.TextMessage(model.RoleUser, "记住 我爱喝冰美式")}, 1)
|
||||
|
||||
items, err := store.ListByPersona(context.Background(), "persona-a")
|
||||
if err != nil {
|
||||
t.Fatalf("ListByPersona: %v", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("stored %d items, want 1", len(items))
|
||||
}
|
||||
ref, ok := memory.ParseSourceConversation(items[0].SourceConversation)
|
||||
if !ok {
|
||||
t.Fatalf("SourceConversation %q is not structured", items[0].SourceConversation)
|
||||
}
|
||||
if ref.SessionID != "qq-obv11:group:42" || ref.MessageID != "" {
|
||||
t.Fatalf("source ref = %+v, want session-only provenance", ref)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user