fix(memory): 堵住 knowledge 工具绕过群聊/陌生人过滤的 P0 / owner 召回 hits 如实上报 / 静默吞点补来源日志
This commit is contained in:
1 parent
e792baa316
commit
eeb5f26668
5 files changed
+250
-52
No files matched your search
@@ -0,0 +1,79 @@
|
||||
package loop
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/trace"
|
||||
)
|
||||
|
||||
// TestMemoryRecallSpan_OwnerReportsRealHits 钉住审计 2026-10-05 §7 的
|
||||
// 「owner 召回 hits 恒 0」:真实跑一轮后,memory 召回节点上报的命中条数
|
||||
// 必须等于实际注入的记忆条数(3 条种子),而不是硬编码的 0。
|
||||
func TestMemoryRecallSpan_OwnerReportsRealHits(t *testing.T) {
|
||||
t.Parallel()
|
||||
rec := newFlowRecorder()
|
||||
ad := &mockAdapter{stream: []mgResp{{resp: model.Response{Model: "mock-1", Content: "好的"}}}}
|
||||
td := dispatcherWith(t)
|
||||
a := newTestAgent(t, ad, td)
|
||||
store := newTestMemoryStore(t)
|
||||
seedMemories(t, store)
|
||||
|
||||
l := New(Options{
|
||||
Agent: a, Tools: td, MaxRounds: 8,
|
||||
Tracer: trace.New(nil),
|
||||
Flow: rec.publisher(),
|
||||
Retriever: memory.NewRetriever(store),
|
||||
PersonaID: "persona-a",
|
||||
OnStream: func(model.StreamEvent) {},
|
||||
})
|
||||
|
||||
if _, err := l.Run(context.Background(), "web", []model.Message{model.TextMessage(model.RoleUser, "go")}); err != nil {
|
||||
t.Fatalf("Run: %v", err)
|
||||
}
|
||||
|
||||
recall := nodesOfKind(rec.last(t, "web"), "memory")[0]
|
||||
if got := recall.Props["scope"]; got != "owner" {
|
||||
t.Fatalf("memory 召回 scope = %v,want owner(props:%+v)", got, recall.Props)
|
||||
}
|
||||
hits, ok := recall.Props["hits"].(int)
|
||||
if !ok {
|
||||
t.Fatalf("hits prop 类型 = %T,want int(props:%+v)", recall.Props["hits"], recall.Props)
|
||||
}
|
||||
if hits != 3 {
|
||||
t.Fatalf("owner 召回 hits = %d,want 3(3 条种子全部命中)", hits)
|
||||
}
|
||||
}
|
||||
|
||||
// TestExtractMemoryAsync_NoStoreLogsSource 钉住审计 2026-10-05 §7 的静默吞点:
|
||||
// 配置了 extractor 却没有 store 时,提取会被跳过 —— 但必须带来源记一条告警
|
||||
// (source=runtime/loop),绝不静默吞掉。
|
||||
func TestExtractMemoryAsync_NoStoreLogsSource(t *testing.T) {
|
||||
t.Parallel()
|
||||
log, buf := captureLoopLog()
|
||||
l := New(Options{
|
||||
Extractor: memory.NewManualExtractor("persona-a", "owner"),
|
||||
PersonaID: "persona-a",
|
||||
Log: log,
|
||||
// Memory 故意不配置:提取结果无处可落。
|
||||
})
|
||||
|
||||
ctx := WithSessionEnv(context.Background(), OwnerEnv())
|
||||
l.extractMemoryAsync(ctx, []model.Message{model.TextMessage(model.RoleUser, "记住 我爱喝冰美式")}, 1)
|
||||
|
||||
found := false
|
||||
for _, rec := range loopJSONRecords(t, buf) {
|
||||
if rec["source"] != "runtime/loop" {
|
||||
continue
|
||||
}
|
||||
if msg, _ := rec["msg"].(string); strings.Contains(msg, "no memory store configured") {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("缺带来源(source=runtime/loop)的「无 store 跳过提取」告警:%q", buf.String())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user