记忆

User Preference Learning

用户偏好学习 - 自动学习和应用用户偏好

用户偏好学习 (User Preference Learning)

概述

User Preference Learning 提供智能偏好学习系统,从用户交互中自动提取、管理和应用用户偏好,实现个性化的 Agent 体验。

核心功能

  • 自动提取 - 从用户消息中自动提取偏好
  • 分类管理 - 按类别组织偏好(UI、语言、格式等)
  • 强度跟踪 - 基于频率的偏好强度计算
  • 冲突解决 - 智能处理偏好冲突
  • 持久化存储 - 保存和加载用户偏好
  • 自动应用 - 根据学习到的偏好调整行为

快速开始

1. 创建偏好管理器

package main

import (
    "context"
    "github.com/astercloud/aster/pkg/memory"
)

func main() {
    // 创建偏好管理器
    config := memory.DefaultPreferenceManagerConfig()
    manager := memory.NewPreferenceManager(config)

    // 添加偏好
    pref := &memory.Preference{
        UserID:     "user-123",
        Category:   memory.CategoryUI,
        Key:        "theme",
        Value:      "dark",
        Strength:   0.8,    // 强度
        Confidence: 0.9,    // 置信度
    }

    err := manager.AddPreference(context.Background(), pref)
    if err != nil {
        panic(err)
    }
}

2. 自动提取偏好

// 创建偏好提取器
extractor := memory.NewPreferenceExtractor(manager)

// 从用户消息中提取偏好
message := agentext.Message{
    Role:    "user",
    Content: "I prefer dark mode and Chinese language",
}

prefs, err := extractor.ExtractFromMessage(ctx, "user-123", message)
if err != nil {
    panic(err)
}

fmt.Printf("提取到 %d 个偏好\n", len(prefs))
for _, pref := range prefs {
    fmt.Printf("- %s: %s = %s (强度: %.2f)\n",
        pref.Category, pref.Key, pref.Value, pref.Strength)
}

3. 使用持久化存储

// 创建文件存储
storage, err := memory.NewFilePreferenceStorage("/data/preferences")
if err != nil {
    panic(err)
}

// 创建持久化管理器
persistentManager := memory.NewPersistentPreferenceManager(
    config,
    storage,
)

// 添加偏好会自动保存
pref := &memory.Preference{
    UserID:     "user-123",
    Category:   memory.CategoryLanguage,
    Key:        "language",
    Value:      "zh",
    Strength:   0.9,
    Confidence: 1.0,
}
persistentManager.AddPreference(ctx, pref)

// 手动保存用户的所有偏好
persistentManager.SaveUser(ctx, "user-123")

// 从存储加载
persistentManager.LoadUser(ctx, "user-123")

偏好类别

CategoryUI - UI 偏好

pref := &memory.Preference{
    UserID:     "user-123",
    Category:   memory.CategoryUI,
    Key:        "theme",
    Value:      "dark",
    Strength:   0.8,
    Confidence: 0.9,
}

// 常见的 UI 偏好
// - theme: dark/light
// - font: monospace/sans-serif
// - layout: compact/spacious
// - colorScheme: default/high-contrast

CategoryLanguage - 语言偏好

pref := &memory.Preference{
    UserID:     "user-123",
    Category:   memory.CategoryLanguage,
    Key:        "language",
    Value:      "zh",
    Strength:   0.9,
    Confidence: 1.0,
}

// 常见的语言偏好
// - language: zh/en/ja/...
// - responseLanguage: zh/en

CategoryFormat - 格式偏好

pref := &memory.Preference{
    UserID:     "user-123",
    Category:   memory.CategoryFormat,
    Key:        "codeIndent",
    Value:      "spaces",
    Strength:   0.7,
    Confidence: 0.8,
}

// 常见的格式偏好
// - codeIndent: spaces/tabs
// - dateFormat: ISO8601/US/EU
// - numberFormat: 1,000.00/1.000,00

CategoryWorkflow - 工作流偏好

pref := &memory.Preference{
    UserID:     "user-123",
    Category:   memory.CategoryWorkflow,
    Key:        "autoSave",
    Value:      "true",
    Strength:   0.6,
    Confidence: 0.7,
}

// 常见的工作流偏好
// - autoSave: true/false
// - confirmBeforeAction: true/false
// - verbosity: minimal/normal/detailed

核心操作

添加偏好

pref := &memory.Preference{
    UserID:     "user-123",
    Category:   memory.CategoryUI,
    Key:        "theme",
    Value:      "dark",
    Strength:   0.8,
    Confidence: 0.9,
    Metadata: map[string]string{
        "source": "user_message",
        "date":   "2024-01-15",
    },
}

err := manager.AddPreference(ctx, pref)

获取偏好

pref, err := manager.GetPreference(
    ctx,
    "user-123",
    memory.CategoryUI,
    "theme",
)

if err != nil {
    log.Fatal(err)
}

fmt.Printf("用户主题偏好: %s (强度: %.2f)\n", pref.Value, pref.Strength)

列出偏好

// 列出所有偏好
allPrefs, err := manager.ListPreferences(ctx, "user-123", "")

// 列出特定类别的偏好
uiPrefs, err := manager.ListPreferences(
    ctx,
    "user-123",
    memory.CategoryUI,
)

for _, pref := range uiPrefs {
    fmt.Printf("%s = %s\n", pref.Key, pref.Value)
}

更新偏好

err := manager.UpdatePreference(
    ctx,
    "user-123",
    memory.CategoryUI,
    "theme",
    "light",  // 新值
)

删除偏好

err := manager.DeletePreference(
    ctx,
    "user-123",
    memory.CategoryUI,
    "theme",
)

获取 TOP 偏好

// 获取强度最高的 10 个偏好
topPrefs, err := manager.GetTopPreferences(ctx, "user-123", 10)

for i, pref := range topPrefs {
    fmt.Printf("%d. %s:%s = %s (强度: %.2f)\n",
        i+1, pref.Category, pref.Key, pref.Value, pref.Strength)
}

冲突解决策略

ConflictKeepLatest - 保留最新

config := memory.DefaultPreferenceManagerConfig()
config.ConflictStrategy = memory.ConflictKeepLatest

// 添加第一个偏好
pref1 := &memory.Preference{
    UserID:   "user-123",
    Category: memory.CategoryUI,
    Key:      "theme",
    Value:    "dark",
}
manager.AddPreference(ctx, pref1)

// 添加冲突的偏好(相同 category + key)
pref2 := &memory.Preference{
    UserID:   "user-123",
    Category: memory.CategoryUI,
    Key:      "theme",
    Value:    "light",  // 不同的值
}
manager.AddPreference(ctx, pref2)

// 结果:保留 "light"(最新的值)

ConflictKeepStronger - 保留强度更高的

config.ConflictStrategy = memory.ConflictKeepStronger

// 强度 0.9 的偏好
pref1 := &memory.Preference{
    UserID:   "user-123",
    Category: memory.CategoryUI,
    Key:      "theme",
    Value:    "dark",
    Strength: 0.9,
}
manager.AddPreference(ctx, pref1)

// 强度 0.6 的冲突偏好
pref2 := &memory.Preference{
    UserID:   "user-123",
    Category: memory.CategoryUI,
    Key:      "theme",
    Value:    "light",
    Strength: 0.6,
}
manager.AddPreference(ctx, pref2)

// 结果:保留 "dark"(强度更高)

ConflictMerge - 合并

config.ConflictStrategy = memory.ConflictMerge

// 第一个偏好
pref1 := &memory.Preference{
    UserID:   "user-123",
    Category: memory.CategoryUI,
    Key:      "theme",
    Value:    "dark",
    Strength: 0.6,
}
manager.AddPreference(ctx, pref1)

// 第二个偏好
pref2 := &memory.Preference{
    UserID:   "user-123",
    Category: memory.CategoryUI,
    Key:      "theme",
    Value:    "dark",
    Strength: 0.8,
}
manager.AddPreference(ctx, pref2)

// 结果:强度合并为 (0.6 + 0.8) / 2 = 0.7

强度衰减

config := memory.DefaultPreferenceManagerConfig()
config.StrengthDecay = 0.01  // 每天衰减 1%
config.MinStrength = 0.1     // 最小强度阈值

manager := memory.NewPreferenceManager(config)

// 添加偏好
pref := &memory.Preference{
    UserID:   "user-123",
    Category: memory.CategoryUI,
    Key:      "theme",
    Value:    "dark",
    Strength: 0.5,
}
manager.AddPreference(ctx, pref)

// 一段时间后应用衰减
removed := manager.ApplyDecay(ctx)
fmt.Printf("清理了 %d 个弱偏好\n", removed)

// 如果偏好 10 天未更新:
// 强度 = 0.5 * (1 - 0.01 * 10) = 0.5 * 0.9 = 0.45
// 如果低于 MinStrength (0.1),会被删除

统计信息

stats := manager.GetStats("user-123")

fmt.Printf("总偏好数: %d\n", stats.TotalPreferences)
fmt.Printf("平均强度: %.2f\n", stats.AverageStrength)
fmt.Printf("高强度偏好: %d\n", stats.HighStrengthCount)    // >= 0.7
fmt.Printf("中等强度偏好: %d\n", stats.MediumStrengthCount) // >= 0.4
fmt.Printf("低强度偏好: %d\n", stats.LowStrengthCount)     // < 0.4

// 类别分布
for category, count := range stats.CategoryDistribution {
    fmt.Printf("%s: %d\n", category, count)
}

持久化存储

文件存储

// 创建文件存储
storage, err := memory.NewFilePreferenceStorage("/data/preferences")
if err != nil {
    log.Fatal(err)
}

// 创建持久化管理器
manager := memory.NewPersistentPreferenceManager(
    memory.DefaultPreferenceManagerConfig(),
    storage,
)

// 添加偏好
pref := &memory.Preference{
    UserID:   "user-123",
    Category: memory.CategoryUI,
    Key:      "theme",
    Value:    "dark",
}
manager.AddPreference(ctx, pref)

// 保存到磁盘
manager.SaveUser(ctx, "user-123")

加载偏好

// 加载单个用户
err := manager.LoadUser(ctx, "user-123")
if err != nil {
    log.Fatal(err)
}

// 加载所有用户
err = manager.LoadAll(ctx)
if err != nil {
    log.Fatal(err)
}

自动保存

// 启动自动保存(每 60 秒)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

go manager.AutoSave(ctx, 60)

// 程序运行期间会自动保存
// 取消 context 时会执行最后一次保存

高级用法

1. 自定义提取规则

type CustomExtractor struct {
    *memory.PreferenceExtractor
}

func (e *CustomExtractor) ExtractFromMessage(
    ctx context.Context,
    userID string,
    message agentext.Message,
) ([]*memory.Preference, error) {
    prefs := []*memory.Preference{}

    content := strings.ToLower(message.Content)

    // 自定义提取逻辑
    if strings.Contains(content, "verbose") {
        prefs = append(prefs, &memory.Preference{
            UserID:     userID,
            Category:   memory.CategoryWorkflow,
            Key:        "verbosity",
            Value:      "detailed",
            Strength:   0.7,
            Confidence: 0.8,
        })
    }

    // 添加到管理器
    for _, pref := range prefs {
        e.manager.AddPreference(ctx, pref)
    }

    return prefs, nil
}

2. 偏好驱动的行为调整

// 根据偏好调整 Agent 行为
func adjustBehavior(manager *memory.PreferenceManager, userID string) {
    // 获取语言偏好
    langPref, err := manager.GetPreference(
        ctx,
        userID,
        memory.CategoryLanguage,
        "language",
    )

    if err == nil && langPref.Value == "zh" {
        // 使用中文响应
        setResponseLanguage("zh")
    }

    // 获取详细程度偏好
    verbPref, err := manager.GetPreference(
        ctx,
        userID,
        memory.CategoryWorkflow,
        "verbosity",
    )

    if err == nil && verbPref.Value == "minimal" {
        // 使用简洁的响应
        setResponseStyle("minimal")
    }
}

3. 偏好推荐

// 基于历史偏好推荐新设置
func recommendPreferences(
    manager *memory.PreferenceManager,
    userID string,
) []string {
    recommendations := []string{}

    // 获取 TOP 偏好
    topPrefs, _ := manager.GetTopPreferences(ctx, userID, 10)

    // 分析模式
    hasDarkTheme := false
    for _, pref := range topPrefs {
        if pref.Key == "theme" && pref.Value == "dark" {
            hasDarkTheme = true
        }
    }

    // 推荐相关设置
    if hasDarkTheme {
        recommendations = append(recommendations, "建议:启用高对比度配色方案")
    }

    return recommendations
}

4. 批量导入/导出

// 导出用户偏好
func exportPreferences(
    manager *memory.PersistentPreferenceManager,
    userID string,
) ([]byte, error) {
    prefs, _ := manager.ListPreferences(ctx, userID, "")
    return json.MarshalIndent(prefs, "", "  ")
}

// 导入用户偏好
func importPreferences(
    manager *memory.PersistentPreferenceManager,
    userID string,
    data []byte,
) error {
    var prefs []*memory.Preference
    if err := json.Unmarshal(data, &prefs); err != nil {
        return err
    }

    for _, pref := range prefs {
        pref.UserID = userID  // 确保用户 ID 正确
        manager.AddPreference(ctx, pref)
    }

    return manager.SaveUser(ctx, userID)
}

最佳实践

1. 设置合理的强度阈值

config := memory.DefaultPreferenceManagerConfig()

// ✅ 根据业务调整阈值
config.MinStrength = 0.2        // 低频偏好也保留
config.StrengthDecay = 0.005    // 慢衰减(每天 0.5%)

// ❌ 避免过于激进
// config.MinStrength = 0.8     // 太高,会丢失有用偏好
// config.StrengthDecay = 0.1   // 太快,偏好很快消失

2. 定期清理

// 定期应用衰减清理弱偏好
ticker := time.NewTicker(24 * time.Hour)
go func() {
    for range ticker.C {
        removed := manager.ApplyDecay(ctx)
        log.Printf("清理了 %d 个弱偏好", removed)
    }
}()

3. 选择合适的冲突策略

// ✅ UI 偏好:保留最新
uiConfig := memory.DefaultPreferenceManagerConfig()
uiConfig.ConflictStrategy = memory.ConflictKeepLatest

// ✅ 工作流偏好:合并增强
workflowConfig := memory.DefaultPreferenceManagerConfig()
workflowConfig.ConflictStrategy = memory.ConflictMerge

// ✅ 关键设置:保留强度高的
criticalConfig := memory.DefaultPreferenceManagerConfig()
criticalConfig.ConflictStrategy = memory.ConflictKeepStronger

4. 验证偏好值

func validatePreference(pref *memory.Preference) error {
    switch pref.Category {
    case memory.CategoryUI:
        if pref.Key == "theme" {
            validValues := []string{"dark", "light", "auto"}
            if !contains(validValues, pref.Value) {
                return fmt.Errorf("invalid theme value: %s", pref.Value)
            }
        }

    case memory.CategoryLanguage:
        if pref.Key == "language" {
            validLangs := []string{"zh", "en", "ja", "es", "fr"}
            if !contains(validLangs, pref.Value) {
                return fmt.Errorf("invalid language: %s", pref.Value)
            }
        }
    }

    return nil
}

使用场景

场景 1:个性化助手

// 学习用户的交互习惯
extractor := memory.NewPreferenceExtractor(manager)

// 从每条消息中学习
messages := []agentext.Message{
    {Role: "user", Content: "Use dark mode please"},
    {Role: "user", Content: "I prefer Chinese responses"},
    {Role: "user", Content: "Keep it brief"},
}

for _, msg := range messages {
    extractor.ExtractFromMessage(ctx, "user-123", msg)
}

// 应用学到的偏好
adjustBehavior(manager, "user-123")

场景 2:多设备同步

// 在设备 A 上学习偏好
manager.AddPreference(ctx, &memory.Preference{
    UserID:   "user-123",
    Category: memory.CategoryUI,
    Key:      "theme",
    Value:    "dark",
})

// 保存到云端
manager.SaveUser(ctx, "user-123")

// 在设备 B 上加载
newManager := memory.NewPersistentPreferenceManager(config, cloudStorage)
newManager.LoadUser(ctx, "user-123")

// 偏好自动同步

场景 3:A/B 测试

// 记录用户对不同功能的偏好
func recordFeaturePreference(userID, feature string, liked bool) {
    value := "disliked"
    strength := 0.3

    if liked {
        value = "liked"
        strength = 0.7
    }

    manager.AddPreference(ctx, &memory.Preference{
        UserID:   userID,
        Category: memory.CategoryWorkflow,
        Key:      feature,
        Value:    value,
        Strength: strength,
    })
}

// 分析偏好趋势
stats := manager.GetStats(userID)

测试

运行偏好学习测试:

# 运行所有偏好测试
go test ./pkg/memory/... -run TestPreference -v

# 运行存储测试
go test ./pkg/memory/... -run TestPersistent -v

相关文档

参考资源