feat: 实现 LLM 文言文摘要生成,优化 Session 创建逻辑
Some checks failed
Release / build (push) Failing after 3h11m18s

This commit is contained in:
2026-04-27 08:58:08 +08:00
parent f41aa8b453
commit 124b0baa26
7 changed files with 126 additions and 24 deletions

View File

@@ -98,9 +98,10 @@
- 实现向量生成和相似度检索
4. **会话管理**
- 自动创建 Session首次输入时
- 手动创建(/new 命令)
- 消息摘要生成
- 自动创建 Session首次输入聊天消息时)
- 手动创建(/new 命令,只是重置 currentSession
- LLM 生成摘要(文言文风格)
- summary_timeout 配置(默认 30 秒)
5. **UI 优化**
- 合并状态显示到单行
@@ -112,7 +113,7 @@
- 合并到 hxclaw 的会话摘要上下文
- AI 同时看到长期记忆和会话摘要
5. **JSON 导出**
7. **JSON 导出**
- 退出时自动导出
- 手动导出
@@ -147,6 +148,21 @@ tts:
enabled: false # 全局开关(默认关闭)
port: 9876 # mimo-tts daemon 端口
auto: true # AI 回复后自动朗读
# 聊天记忆体配置
memory:
enabled: true # 启用开关
auto_session: true # 自动创建 Session
auto_export: true # 退出时自动导出
summary_timeout: 30 # 摘要生成超时(秒)
vector:
api_key: "" # 硅基流动 API Key
base_url: "https://api.siliconflow.cn/v1"
model: "BAAI/bge-m3"
recall:
keywords: ["之前", "聊过", "记得"]
auto_recall: true
similarity_threshold: 0.7
```
配置加载优先级:

View File

@@ -4,6 +4,18 @@
### v0.3.0 (2026-04-27)
- **Session 创建逻辑优化**
- Session 只在用户输入聊天消息时创建
- /new 命令只重置 currentSession不立即创建
- 退出后重新进入算作新会话
- 配置项auto_session默认 true
- **LLM 生成摘要**
- Chat 摘要:调用 LLM 生成极简文言文概述
- Session 摘要:每次对话后用 LLM 精简整合
- 配置项summary_timeout默认 30 秒)
- 容错处理:超时失败回退到简单截断
- **双记忆系统合并**
- 读取 picoclaw 的 MEMORY.md 作为长期记忆
- 合并到 hxclaw 的会话摘要上下文
@@ -105,6 +117,10 @@
- [x] 4 个查询场景RecallHistory, RecallTopic...
- [x] 三重检测机制
- [x] MongoDB 风格导出
- [x] Session 创建逻辑优化(输入消息时自动创建)
- [x] /new 命令(只重置 currentSession
- [x] LLM 生成摘要(文言文风格)
- [x] summary_timeout 配置
---
@@ -114,8 +130,6 @@
- [ ] 命令行参数支持(--theme, --tts 等)
- [ ] 多语言支持
- [ ] /new 命令开始新会话
- [ ] /memory list|show 命令
- [ ] 命令提示/补全功能(输入 / 显示内置命令列表)
---
@@ -137,6 +151,8 @@
- [x] 集成 libSQL 数据库
- [x] 实现独立上下文系统
- [x] UI 状态合并显示
- [x] LLM 生成摘要(文言文风格)
- [x] Session 自动创建逻辑
---

View File

@@ -68,6 +68,8 @@ type MemoryConfig struct {
AutoSession bool `yaml:"auto_session"`
// AutoExport 退出时自动导出
AutoExport bool `yaml:"auto_export"`
// SummaryTimeout 摘要生成超时时间(秒)
SummaryTimeout int `yaml:"summary_timeout"`
// Vector 向量服务配置
Vector VectorConfig `yaml:"vector"`
// Recall 检索配置
@@ -121,10 +123,11 @@ var (
Auto: true,
},
Memory: MemoryConfig{
Enabled: true,
DBPath: "",
AutoSession: true,
AutoExport: true,
Enabled: true,
DBPath: "",
AutoSession: true,
AutoExport: true,
SummaryTimeout: 20,
Vector: VectorConfig{
APIKey: "",
BaseURL: "https://api.siliconflow.cn/v1",
@@ -222,6 +225,7 @@ memory:
enabled: true
auto_session: true
auto_export: true
summary_timeout: 30
vector:
api_key: ""
base_url: "https://api.siliconflow.cn/v1"
@@ -302,6 +306,9 @@ func mergeConfig(userCfg, projCfg *Config) *Config {
if projCfg.Memory.AutoExport {
result.Memory.AutoExport = projCfg.Memory.AutoExport
}
if projCfg.Memory.SummaryTimeout > 0 {
result.Memory.SummaryTimeout = projCfg.Memory.SummaryTimeout
}
if projCfg.Memory.Vector.APIKey != "" {
result.Memory.Vector.APIKey = projCfg.Memory.Vector.APIKey
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/google/uuid"
"github.com/hxclaw/hxclaw/cmd/hxclaw/internal"
"github.com/sipeed/picoclaw/pkg/agent"
_ "github.com/tursodatabase/libsql-client-go/libsql"
)
@@ -18,8 +19,9 @@ type DB struct {
}
var (
db *DB
cfg *DBConfig
db *DB
cfg *DBConfig
summaryAgent *agent.AgentLoop
)
type DBConfig struct {
@@ -43,7 +45,9 @@ func GetDefaultDBPath() string {
return getDefaultDBPath()
}
func Init(opts ...DBSOption) error {
func Init(agentLoop *agent.AgentLoop, opts ...DBSOption) error {
summaryAgent = agentLoop
memoryCfg := internal.GetProjectConfig().Memory
cfg = &DBConfig{

View File

@@ -1,6 +1,7 @@
package memory
import (
"context"
"fmt"
"os"
"path/filepath"
@@ -14,6 +15,56 @@ var lastContext string
var ErrNeedNewSession = fmt.Errorf("需要创建新会话")
func generateChatSummary(userInput, aiReply string) string {
if summaryAgent == nil {
return GenerateSummary(userInput, aiReply)
}
memoryCfg := internal.GetProjectConfig().Memory
timeout := time.Duration(memoryCfg.SummaryTimeout) * time.Second
if timeout <= 0 {
timeout = 30 * time.Second
}
prompt := fmt.Sprintf(`用极简文言文概括对话,一句话,只包含最关键信息(如人名、地点、事件、核心结论):
问:%s
答:%s`, userInput, aiReply)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
resp, err := summaryAgent.ProcessDirect(ctx, prompt, "summary:chat")
if err != nil {
return GenerateSummary(userInput, aiReply)
}
return strings.TrimSpace(resp)
}
func generateSessionSummary(oldSummary, userInput, aiReply string) string {
if summaryAgent == nil {
return GenerateSummary(oldSummary, oldSummary+"\n"+aiReply)
}
memoryCfg := internal.GetProjectConfig().Memory
timeout := time.Duration(memoryCfg.SummaryTimeout) * time.Second
if timeout <= 0 {
timeout = 30 * time.Second
}
prompt := fmt.Sprintf(`精简整合以下历史,提取关键信息,去除冗余描述,保留核心要点:
历史:%s
新对话:问%s 答%s`, oldSummary, userInput, aiReply)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
resp, err := summaryAgent.ProcessDirect(ctx, prompt, "summary:session")
if err != nil {
return GenerateSummary(oldSummary, oldSummary+"\n"+aiReply)
}
return strings.TrimSpace(resp)
}
func GetContextPrompt(userInput string) string {
db := GetDB()
if db == nil {
@@ -227,8 +278,16 @@ func SaveChat(userInput, aiReply string, useSessionSummary bool) (int, error) {
// 获取或创建 Session
session := currentSession
if session == nil && memoryCfg.AutoSession {
// 自动创建 session
newSession, err := CreateNewSession()
if err != nil {
return 0, fmt.Errorf("自动创建会话失败: %v", err)
}
session = newSession
}
if session == nil {
// 如果没有 session返回错误让用户创建
return 0, ErrNeedNewSession
}
@@ -243,7 +302,7 @@ func SaveChat(userInput, aiReply string, useSessionSummary bool) (int, error) {
// 添加 AI 回复
chat.AddAIReply(aiReply)
chat.Summary = GenerateSummary(userInput, aiReply)
chat.Summary = generateChatSummary(userInput, aiReply)
chat.UpdatedAt = time.Now().Unix()
// 生成并保存向量
@@ -261,7 +320,7 @@ func SaveChat(userInput, aiReply string, useSessionSummary bool) (int, error) {
// 只有普通对话才更新 session summaryrecall 查询保持原 summary
if useSessionSummary {
session.Summary = GenerateSummary("", session.Summary+"\n"+aiReply)
session.Summary = generateSessionSummary(session.Summary, userInput, aiReply)
}
session.UpdatedAt = time.Now().Unix()
@@ -276,7 +335,11 @@ func SaveChat(userInput, aiReply string, useSessionSummary bool) (int, error) {
// 恢复原始 session summary避免 recall 结果污染)
session.Summary = originalSummary
currentSession = session
// 首次自动创建后赋值 currentSession
if currentSession == nil {
currentSession = session
}
return len(session.ChatIDs), nil
}

View File

@@ -72,7 +72,7 @@ func main() {
dbPath = memory.GetDefaultDBPath()
}
fmt.Printf("初始化记忆体db_path: %s\n", dbPath)
if err := memory.Init(memory.WithDBPath(dbPath)); err != nil {
if err := memory.Init(agentLoop, memory.WithDBPath(dbPath)); err != nil {
fmt.Fprintf(os.Stderr, "警告:初始化记忆体失败: %v将使用无记忆模式\n", err)
} else {
fmt.Println("记忆体初始化成功")
@@ -435,13 +435,8 @@ func handleTTSCommandSimple(input string) {
}
func handleNewSessionCommand(rl *internal.Readline, basePrompt string) {
uuid, err := memory.CreateNewSession()
if err != nil {
fmt.Printf("创建新会话失败: %v\n", err)
return
}
fmt.Printf("已创建新会话: %s\n", uuid)
currentSession = nil
fmt.Println("已重置会话,输入聊天消息后将创建新会话")
}
func handleMemoryCommand(input string) {

View File

@@ -26,6 +26,7 @@ memory:
enabled: false
auto_session: true
auto_export: true
summary_timeout: 30
vector:
base_url: "https://api.siliconflow.cn/v1"
model: "BAAI/bge-m3"