feat: 记忆体系统 v0.3.0 完成
Some checks failed
Release / build (push) Failing after 4m28s

## 核心功能
- 双记忆系统合并:picoclaw MEMORY.md + hxclaw 会话摘要
- 独立上下文系统:不依赖 picoclaw session
- 向量检索:硅基流动 BGE-M3 API
- 三重检测:关键词/向量相似度/命令

## 数据库
- libSQL (TursoDB) 存储
- sessions + chats 表设计
- 向量存储使用 binary 编码

## 查询场景
- RecallHistory: 查询所有会话摘要
- RecallTopic: 按话题向量检索
- RecallSession: 指定会话详情
- RecallWithinSession: 会话内检索

## 导出
- MongoDB 风格:~/.config/hxclaw/export-data.json
- chats 嵌套在 sessions 下
- 增量导出,同 session 累加

## UI 优化
- 合并状态显示(耗时 · 状态 · 消息数)
- 颜色设计:金色图标 + 暗绿色/暗红色状态

## 配置项
- memory.recall: keywords, auto_recall, similarity_threshold
- memory.vector: max_search_results
- memory.auto_export
This commit is contained in:
2026-04-27 06:16:19 +08:00
parent 88a110e87e
commit 5d9498f687
12 changed files with 1583 additions and 52 deletions

View File

@@ -20,6 +20,8 @@ type Config struct {
UI UIConfig `yaml:"ui"`
// TTS TTS 语音配置
TTS TTSConfig `yaml:"tts"`
// Memory 聊天记忆体配置
Memory MemoryConfig `yaml:"memory"`
}
// StreamingConfig 流式输出配置,控制模拟打字效果的延迟时间
@@ -56,6 +58,48 @@ type TTSConfig struct {
Auto bool `yaml:"auto"`
}
// MemoryConfig 聊天记忆体配置
type MemoryConfig struct {
// Enabled 启用开关
Enabled bool `yaml:"enabled"`
// DBPath 数据库路径
DBPath string `yaml:"db_path"`
// AutoSession 自动创建 Session
AutoSession bool `yaml:"auto_session"`
// AutoExport 退出时自动导出
AutoExport bool `yaml:"auto_export"`
// Vector 向量服务配置
Vector VectorConfig `yaml:"vector"`
// Recall 检索配置
Recall RecallConfig `yaml:"recall"`
}
// VectorConfig 向量服务配置
type VectorConfig struct {
// APIKey 硅基流动 API Key
APIKey string `yaml:"api_key"`
// BaseURL API 地址
BaseURL string `yaml:"base_url"`
// Model 向量模型
Model string `yaml:"model"`
// Dimension <20><>量维度
Dimension int `yaml:"dimension"`
// MaxSearchResults 最大检索结果数
MaxSearchResults int `yaml:"max_search_results"`
}
// RecallConfig 检索配置
type RecallConfig struct {
// Keywords 触发关键词列表
Keywords []string `yaml:"keywords"`
// AutoRecall 是否自动检测相似度
AutoRecall bool `yaml:"auto_recall"`
// SimilarityThreshold 相似度阈值
SimilarityThreshold float64 `yaml:"similarity_threshold"`
// MaxResults 最大检索结果数
MaxResults int `yaml:"max_results"`
}
var (
// defaultCfg 默认配置值,当配置文件不存在或字段为空时使用
defaultCfg = Config{
@@ -76,6 +120,25 @@ var (
Port: 9876,
Auto: true,
},
Memory: MemoryConfig{
Enabled: true,
DBPath: "",
AutoSession: true,
AutoExport: true,
Vector: VectorConfig{
APIKey: "",
BaseURL: "https://api.siliconflow.cn/v1",
Model: "BAAI/bge-m3",
Dimension: 1024,
MaxSearchResults: 10,
},
Recall: RecallConfig{
Keywords: []string{"之前", "聊过", "记得", "找找", "曾经", "谈论过", "提过"},
AutoRecall: true,
SimilarityThreshold: 0.7,
MaxResults: 5,
},
},
}
cfg *Config // 已合并的配置(用户配置 + 项目配置)
cfgLock sync.RWMutex // 配置读写锁
@@ -83,8 +146,7 @@ var (
// 用户配置文件路径常量
const (
userConfigDir = ".config/hxclaw" // 用户配置目录(相对于用户家目录)
userConfigFile = "config.yml" // 用户配置文件名
userConfigFile = "config.yml" // 用户配置文件名
)
// LoadProjectConfig 加载并合并项目配置
@@ -142,8 +204,8 @@ func createUserConfig(userPath string) error {
# Markdown 渲染配置
markdown:
theme: dark # 渲染主题dark, light, dracula, tokyo-night 等
line_width: 0 # 自动换行宽度(-1=禁用0=自动,>0=固定宽度)
theme: dark
line_width: 0
# UI 配置
ui:
@@ -152,8 +214,25 @@ ui:
# TTS 语音配置
tts:
enabled: false # 全局开关(默认关闭)
auto: true # AI 回复后自动朗读
enabled: false
auto: true
# 聊天记忆体配置
memory:
enabled: true
auto_session: true
auto_export: true
vector:
api_key: ""
base_url: "https://api.siliconflow.cn/v1"
model: "BAAI/bge-m3"
dimension: 1024
max_search_results: 10
recall:
keywords: ["之前", "聊过", "记得", "找找", "曾经", "谈论过", "提过"]
auto_recall: true
similarity_threshold: 0.7
max_results: 5
`
return os.WriteFile(userPath, []byte(defaultContent), 0644)
}
@@ -161,7 +240,7 @@ tts:
// loadProjectConfig 加载项目级配置文件
// 路径优先级:环境变量 HXCLAW_CONFIG 指定路径 > ./project.config.yml
func loadProjectConfig() *Config {
cfgPath := getProjectConfigPath()
cfgPath := GetProjectConfigPath()
if cfgPath == "" {
return &defaultCfg
}
@@ -210,6 +289,47 @@ func mergeConfig(userCfg, projCfg *Config) *Config {
if projCfg.TTS.Port > 0 {
result.TTS.Port = projCfg.TTS.Port
}
// Memory 配置
if projCfg.Memory.Enabled {
result.Memory.Enabled = projCfg.Memory.Enabled
}
if projCfg.Memory.DBPath != "" {
result.Memory.DBPath = projCfg.Memory.DBPath
}
if projCfg.Memory.AutoSession {
result.Memory.AutoSession = projCfg.Memory.AutoSession
}
if projCfg.Memory.AutoExport {
result.Memory.AutoExport = projCfg.Memory.AutoExport
}
if projCfg.Memory.Vector.APIKey != "" {
result.Memory.Vector.APIKey = projCfg.Memory.Vector.APIKey
}
if projCfg.Memory.Vector.BaseURL != "" {
result.Memory.Vector.BaseURL = projCfg.Memory.Vector.BaseURL
}
if projCfg.Memory.Vector.Model != "" {
result.Memory.Vector.Model = projCfg.Memory.Vector.Model
}
if projCfg.Memory.Vector.Dimension > 0 {
result.Memory.Vector.Dimension = projCfg.Memory.Vector.Dimension
}
if projCfg.Memory.Vector.MaxSearchResults > 0 {
result.Memory.Vector.MaxSearchResults = projCfg.Memory.Vector.MaxSearchResults
}
// Recall 配置
if len(projCfg.Memory.Recall.Keywords) > 0 {
result.Memory.Recall.Keywords = projCfg.Memory.Recall.Keywords
}
if projCfg.Memory.Recall.AutoRecall {
result.Memory.Recall.AutoRecall = projCfg.Memory.Recall.AutoRecall
}
if projCfg.Memory.Recall.SimilarityThreshold > 0 {
result.Memory.Recall.SimilarityThreshold = projCfg.Memory.Recall.SimilarityThreshold
}
if projCfg.Memory.Recall.MaxResults > 0 {
result.Memory.Recall.MaxResults = projCfg.Memory.Recall.MaxResults
}
}
// 再应用用户配置(覆盖项目配置)
@@ -238,6 +358,48 @@ func mergeConfig(userCfg, projCfg *Config) *Config {
if userCfg.TTS.Auto {
result.TTS.Auto = userCfg.TTS.Auto
}
// Memory 配置(用户配置优先)
if userCfg.Memory.Enabled {
result.Memory.Enabled = userCfg.Memory.Enabled
}
if userCfg.Memory.DBPath != "" {
result.Memory.DBPath = userCfg.Memory.DBPath
}
if userCfg.Memory.AutoSession {
result.Memory.AutoSession = userCfg.Memory.AutoSession
}
if userCfg.Memory.AutoExport {
result.Memory.AutoExport = userCfg.Memory.AutoExport
}
// 向量 API Key 只能在用户配置中指定
if userCfg.Memory.Vector.APIKey != "" {
result.Memory.Vector.APIKey = userCfg.Memory.Vector.APIKey
}
if userCfg.Memory.Vector.BaseURL != "" {
result.Memory.Vector.BaseURL = userCfg.Memory.Vector.BaseURL
}
if userCfg.Memory.Vector.Model != "" {
result.Memory.Vector.Model = userCfg.Memory.Vector.Model
}
if userCfg.Memory.Vector.Dimension > 0 {
result.Memory.Vector.Dimension = userCfg.Memory.Vector.Dimension
}
if userCfg.Memory.Vector.MaxSearchResults > 0 {
result.Memory.Vector.MaxSearchResults = userCfg.Memory.Vector.MaxSearchResults
}
// Recall 配置
if len(userCfg.Memory.Recall.Keywords) > 0 {
result.Memory.Recall.Keywords = userCfg.Memory.Recall.Keywords
}
if userCfg.Memory.Recall.AutoRecall {
result.Memory.Recall.AutoRecall = userCfg.Memory.Recall.AutoRecall
}
if userCfg.Memory.Recall.SimilarityThreshold > 0 {
result.Memory.Recall.SimilarityThreshold = userCfg.Memory.Recall.SimilarityThreshold
}
if userCfg.Memory.Recall.MaxResults > 0 {
result.Memory.Recall.MaxResults = userCfg.Memory.Recall.MaxResults
}
}
return &result
@@ -256,21 +418,16 @@ func GetProjectConfig() *Config {
// getUserConfigPath 获取用户配置文件路径
// 路径格式:~/.config/hxclaw/config.yml
// 支持 Windows (USERPROFILE) 和 Unix (HOME) 环境变量
func getUserConfigPath() string {
homeDir := os.Getenv("USERPROFILE")
if homeDir == "" {
homeDir = os.Getenv("HOME")
}
if homeDir == "" {
return ""
}
return filepath.Join(homeDir, userConfigDir, userConfigFile)
return GetConfigFile()
}
// getProjectConfigPath 获取项目配置文件路径
// 优先使用环境变量 HXCLAW_CONFIG 指定的路径,否则使用当前目录的 project.config.yml
func getProjectConfigPath() string {
// GetUserConfigDir 获取用户配置目录
func GetUserConfigDir() string {
return GetConfigDir()
}
func GetProjectConfigPath() string {
if path := os.Getenv("HXCLAW_CONFIG"); path != "" {
return path
}