Files
Cyrene/backend/ai-core/internal/model/memory.go
T
AskaEth b6ec36886c feat: 第四轮功能增强 - LLM 思维记忆优化、DevTools 记忆UI、9个新工具、5分钟自我思考
- 优化 LLM 思维方式和记忆方法(类别/重要性/关键词/相似度合并/衰减)
- DevTools 记忆查询 UI 重新设计(类别筛选/排序/星标/搜索)
- 新增 9 个 LLM 工具:calculator, datetime, file_ops, http_request, json_ops, text, random, crypto, markdown
- 管理员主对话 5 分钟自我思考增强(工具调用/记忆提取/记忆维护)
2026-05-18 12:13:49 +08:00

202 lines
5.6 KiB
Go

package model
import (
"encoding/json"
"time"
)
// MemoryPriority 记忆优先级
type MemoryPriority int
const (
MemoryTemp MemoryPriority = 0 // 临时记忆 (会话内)
MemoryNormal MemoryPriority = 1 // 普通记忆
MemoryImportant MemoryPriority = 2 // 重要记忆
MemoryCore MemoryPriority = 3 // 核心记忆 (永远保留)
)
// String 返回优先级的中文描述
func (p MemoryPriority) String() string {
switch p {
case MemoryCore:
return "核心"
case MemoryImportant:
return "重要"
case MemoryNormal:
return "普通"
case MemoryTemp:
return "临时"
default:
return "未知"
}
}
// MemoryCategory 记忆分类
type MemoryCategory string
const (
CategoryUserPreference MemoryCategory = "user_preference" // 用户偏好 (食物、颜色、习惯)
CategoryPersonalInfo MemoryCategory = "personal_info" // 个人信息 (姓名、年龄、职业)
CategoryConversation MemoryCategory = "conversation" // 对话摘要
CategoryKnowledge MemoryCategory = "knowledge" // 知识性信息
CategoryEvent MemoryCategory = "event" // 事件记录
CategoryTask MemoryCategory = "task" // 任务/计划
CategoryRelationship MemoryCategory = "relationship" // 关系信息
// 向后兼容的旧分类别名
CategoryPreference = CategoryUserPreference
CategoryFact = CategoryPersonalInfo
CategoryHabit = CategoryUserPreference
CategoryOther = CategoryKnowledge
)
// CategoryDisplayName 返回分类的中文显示名
func (c MemoryCategory) DisplayName() string {
switch c {
case CategoryUserPreference:
return "用户偏好"
case CategoryPersonalInfo:
return "个人信息"
case CategoryConversation:
return "对话摘要"
case CategoryKnowledge:
return "知识信息"
case CategoryEvent:
return "事件记录"
case CategoryTask:
return "任务计划"
case CategoryRelationship:
return "关系情感"
default:
return "其他"
}
}
// MemoryEntry 记忆条目
type MemoryEntry struct {
ID string `json:"id" db:"id"`
UserID string `json:"user_id" db:"user_id"`
Content string `json:"content" db:"content"`
Summary string `json:"summary" db:"summary"` // 简短摘要
Category MemoryCategory `json:"category" db:"category"`
Priority MemoryPriority `json:"priority" db:"priority"`
Importance int `json:"importance" db:"importance"` // 重要程度 1-10
Keywords []string `json:"keywords" db:"keywords"` // 关键词标签
SessionID string `json:"session_id" db:"session_id"` // 来源会话
Source string `json:"source" db:"source"` // 来源 (conversation/thinking)
Embedding []float32 `json:"-" db:"embedding"` // 向量 (pgvector)
AccessCount int `json:"access_count" db:"access_count"`
LastAccess time.Time `json:"last_access" db:"last_access"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"` // 最后更新时间
ExpiresAt *time.Time `json:"expires_at,omitempty" db:"expires_at"` // 临时记忆过期时间
}
// KeywordsJSON 将关键词序列化为 JSON 字符串(用于数据库存储)
func (e *MemoryEntry) KeywordsJSON() string {
if len(e.Keywords) == 0 {
return "[]"
}
data, _ := json.Marshal(e.Keywords)
return string(data)
}
// ParseKeywords 从 JSON 字符串解析关键词
func ParseKeywords(raw string) []string {
if raw == "" || raw == "[]" {
return nil
}
var keywords []string
if err := json.Unmarshal([]byte(raw), &keywords); err != nil {
return nil
}
return keywords
}
// SimilarityScore 计算两个记忆条目的简单文本相似度(基于词汇重叠)
// 返回值 0.0 - 1.0
func (e *MemoryEntry) SimilarityScore(other *MemoryEntry) float64 {
if e.Content == other.Content {
return 1.0
}
// 基于关键词的重叠度
if len(e.Keywords) > 0 && len(other.Keywords) > 0 {
keywordSet := make(map[string]bool, len(e.Keywords))
for _, k := range e.Keywords {
keywordSet[k] = true
}
overlap := 0
for _, k := range other.Keywords {
if keywordSet[k] {
overlap++
}
}
keywordScore := float64(overlap) / float64(max(len(e.Keywords), len(other.Keywords)))
if keywordScore > 0.6 {
return keywordScore
}
}
// 基于内容的字符级 Jaccard 相似度
return jaccardSimilarity(e.Content, other.Content)
}
// jaccardSimilarity 计算两个字符串的 Jaccard 相似度
func jaccardSimilarity(a, b string) float64 {
if a == b {
return 1.0
}
if len(a) == 0 || len(b) == 0 {
return 0.0
}
// 使用 bigram 分词
bigramsA := make(map[string]int)
runesA := []rune(a)
for i := 0; i < len(runesA)-1; i++ {
bigramsA[string(runesA[i:i+2])]++
}
bigramsB := make(map[string]int)
runesB := []rune(b)
for i := 0; i < len(runesB)-1; i++ {
bigramsB[string(runesB[i:i+2])]++
}
intersection := 0
for bg, countA := range bigramsA {
if countB, ok := bigramsB[bg]; ok {
intersection += min(countA, countB)
}
}
union := 0
allBigrams := make(map[string]bool)
for bg := range bigramsA {
allBigrams[bg] = true
}
for bg := range bigramsB {
allBigrams[bg] = true
}
for bg := range allBigrams {
union += max(bigramsA[bg], bigramsB[bg])
}
if union == 0 {
return 0.0
}
return float64(intersection) / float64(union)
}
// MemoryQuery 记忆查询参数
type MemoryQuery struct {
UserID string
Query string // 查询文本
Category MemoryCategory
Priority MemoryPriority
MinImportance int // 最低重要程度筛选
Limit int
Offset int
}