- 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 口径一致
68 lines
2.6 KiB
Go
68 lines
2.6 KiB
Go
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)
|
||
}
|
||
}
|