feat: add natural elder story companion flow

This commit is contained in:
Codex Local
2026-09-06 20:16:01 +08:00
parent 029a6097a2
commit 8c834c0aa7
20 changed files with 582 additions and 149 deletions
+25 -6
View File
@@ -78,28 +78,47 @@ func TestBuildContextSummaryIncludesContinuityAndBoundariesWithinSnapshotLimit(t
Background: "退休教师", Interests: []string{"戏曲"}, ConversationGoal: "自然陪伴", Pace: "slow",
PreferredTopics: []string{"家乡"}, AvoidedTopics: []string{"收入", "家庭矛盾"},
}
result := buildContextSummary("王阿姨", profile,
[]string{"上次聊到刚刚注册了个体工商户,准备开始新的工作。"},
result := buildCompanionContextSummary("王阿姨", profile,
[]recentConversationContext{{Summary: "上次聊到刚刚注册了个体工商户,准备开始新的工作。", OpenTopic: "如果她愿意,可以聊聊开始新工作后最期待的事。"}},
[]string{"喜欢在周末听越剧。"})
for _, expected := range []string{"交流边界", "收入", "王阿姨", "本次交流目标:自然陪伴", "交流节奏:放慢语速", "优先话题:家乡", "背景资料:退休教师", "兴趣:戏曲", "最近第1次通话摘要", "注册了个体工商户", "已人工确认的长期记忆", "越剧"} {
for _, expected := range []string{"交流边界", "收入", "王阿姨", "历史资料仅作背景", "先回应长者当前说的话", "可选续聊线索", "最期待的事", "本次交流目标:自然陪伴", "交流节奏:放慢语速", "优先话题:家乡", "背景资料:退休教师", "兴趣:戏曲", "最近第1次通话摘要", "注册了个体工商户", "已确认的长期记忆", "越剧"} {
if !strings.Contains(result, expected) {
t.Fatalf("context %q does not contain %q", result, expected)
}
}
oversized := buildContextSummary(strings.Repeat("长", 120), authorizedSubjectContext{
oversized := buildCompanionContextSummary(strings.Repeat("长", 120), authorizedSubjectContext{
Background: strings.Repeat("背", 500), AvoidedTopics: []string{strings.Repeat("禁", 50), "家庭矛盾"},
PreferredTopics: []string{strings.Repeat("优", 50)}, Interests: []string{strings.Repeat("兴", 50)},
}, []string{strings.Repeat("摘", 500), "第二次摘要", "第三次摘要"}, []string{strings.Repeat("忆", 500), "第二条记忆", "第三条记忆", "第四条记忆", "第五条记忆"})
}, []recentConversationContext{
{Summary: strings.Repeat("摘", 500), OpenTopic: strings.Repeat("续", 500)},
{Summary: "第二次摘要", OpenTopic: "较旧的续聊线索"},
{Summary: "第三次摘要"},
}, []string{strings.Repeat("忆", 500), "第二条记忆", "第三条记忆"})
if len([]rune(oversized)) > contextSummaryLimit {
t.Fatalf("context length=%d exceeds snapshot budget", len([]rune(oversized)))
}
for _, expected := range []string{"交流边界", "家庭矛盾", "最近第3次通话摘要", "第三次摘要", "已人工确认的长期记忆", "第五条记忆"} {
for _, expected := range []string{"交流边界", "家庭矛盾", "可选续聊线索", strings.Repeat("续", 20), "最近第3次通话摘要", "第三次摘要", "已确认的长期记忆"} {
if !strings.Contains(oversized, expected) {
t.Fatalf("oversized context %q lost priority field %q", oversized, expected)
}
}
}
func TestCompanionContextUsesOnlyNewestAvailableOpenTopic(t *testing.T) {
recent := []recentConversationContext{
{Summary: "最近一次只聊了天气。"},
{Summary: "上一次谈到老同事。", OpenTopic: "有机会可以聊聊最难忘的一位老同事。"},
{Summary: "更早谈到家乡。", OpenTopic: "可以聊聊家乡。"},
}
if got := suggestedOpenTopic(recent); got != "有机会可以聊聊最难忘的一位老同事。" {
t.Fatalf("selected open topic=%q", got)
}
result := buildCompanionContextSummary("李叔叔", authorizedSubjectContext{}, recent, nil)
if strings.Count(result, "可选续聊线索") != 1 || strings.Contains(result, "可以聊聊家乡") {
t.Fatalf("context exposed multiple continuation cues: %q", result)
}
}
func TestConversationContinuityRequiresBothExplicitConsents(t *testing.T) {
for _, test := range []struct {
value json.RawMessage