b6ec36886c
- 优化 LLM 思维方式和记忆方法(类别/重要性/关键词/相似度合并/衰减) - DevTools 记忆查询 UI 重新设计(类别筛选/排序/星标/搜索) - 新增 9 个 LLM 工具:calculator, datetime, file_ops, http_request, json_ops, text, random, crypto, markdown - 管理员主对话 5 分钟自我思考增强(工具调用/记忆提取/记忆维护)
202 lines
5.6 KiB
Go
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
|
|
}
|