feat: 实现 LLM 文言文摘要生成,优化 Session 创建逻辑
Some checks failed
Release / build (push) Failing after 3h11m18s
Some checks failed
Release / build (push) Failing after 3h11m18s
This commit is contained in:
24
agents.md
24
agents.md
@@ -98,9 +98,10 @@
|
|||||||
- 实现向量生成和相似度检索
|
- 实现向量生成和相似度检索
|
||||||
|
|
||||||
4. **会话管理**
|
4. **会话管理**
|
||||||
- 自动创建 Session(首次输入时)
|
- 自动创建 Session(首次输入聊天消息时)
|
||||||
- 手动创建(/new 命令)
|
- 手动创建(/new 命令,只是重置 currentSession)
|
||||||
- 消息摘要生成
|
- LLM 生成摘要(文言文风格)
|
||||||
|
- summary_timeout 配置(默认 30 秒)
|
||||||
|
|
||||||
5. **UI 优化**
|
5. **UI 优化**
|
||||||
- 合并状态显示到单行
|
- 合并状态显示到单行
|
||||||
@@ -112,7 +113,7 @@
|
|||||||
- 合并到 hxclaw 的会话摘要上下文
|
- 合并到 hxclaw 的会话摘要上下文
|
||||||
- AI 同时看到长期记忆和会话摘要
|
- AI 同时看到长期记忆和会话摘要
|
||||||
|
|
||||||
5. **JSON 导出**
|
7. **JSON 导出**
|
||||||
- 退出时自动导出
|
- 退出时自动导出
|
||||||
- 手动导出
|
- 手动导出
|
||||||
|
|
||||||
@@ -147,6 +148,21 @@ tts:
|
|||||||
enabled: false # 全局开关(默认关闭)
|
enabled: false # 全局开关(默认关闭)
|
||||||
port: 9876 # mimo-tts daemon 端口
|
port: 9876 # mimo-tts daemon 端口
|
||||||
auto: true # AI 回复后自动朗读
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
配置加载优先级:
|
配置加载优先级:
|
||||||
|
|||||||
20
changelog.md
20
changelog.md
@@ -4,6 +4,18 @@
|
|||||||
|
|
||||||
### v0.3.0 (2026-04-27)
|
### v0.3.0 (2026-04-27)
|
||||||
|
|
||||||
|
- **Session 创建逻辑优化**
|
||||||
|
- Session 只在用户输入聊天消息时创建
|
||||||
|
- /new 命令只重置 currentSession,不立即创建
|
||||||
|
- 退出后重新进入算作新会话
|
||||||
|
- 配置项:auto_session(默认 true)
|
||||||
|
|
||||||
|
- **LLM 生成摘要**
|
||||||
|
- Chat 摘要:调用 LLM 生成极简文言文概述
|
||||||
|
- Session 摘要:每次对话后用 LLM 精简整合
|
||||||
|
- 配置项:summary_timeout(默认 30 秒)
|
||||||
|
- 容错处理:超时失败回退到简单截断
|
||||||
|
|
||||||
- **双记忆系统合并**
|
- **双记忆系统合并**
|
||||||
- 读取 picoclaw 的 MEMORY.md 作为长期记忆
|
- 读取 picoclaw 的 MEMORY.md 作为长期记忆
|
||||||
- 合并到 hxclaw 的会话摘要上下文
|
- 合并到 hxclaw 的会话摘要上下文
|
||||||
@@ -105,6 +117,10 @@
|
|||||||
- [x] 4 个查询场景(RecallHistory, RecallTopic...)
|
- [x] 4 个查询场景(RecallHistory, RecallTopic...)
|
||||||
- [x] 三重检测机制
|
- [x] 三重检测机制
|
||||||
- [x] MongoDB 风格导出
|
- [x] MongoDB 风格导出
|
||||||
|
- [x] Session 创建逻辑优化(输入消息时自动创建)
|
||||||
|
- [x] /new 命令(只重置 currentSession)
|
||||||
|
- [x] LLM 生成摘要(文言文风格)
|
||||||
|
- [x] summary_timeout 配置
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -114,8 +130,6 @@
|
|||||||
|
|
||||||
- [ ] 命令行参数支持(--theme, --tts 等)
|
- [ ] 命令行参数支持(--theme, --tts 等)
|
||||||
- [ ] 多语言支持
|
- [ ] 多语言支持
|
||||||
- [ ] /new 命令开始新会话
|
|
||||||
- [ ] /memory list|show 命令
|
|
||||||
- [ ] 命令提示/补全功能(输入 / 显示内置命令列表)
|
- [ ] 命令提示/补全功能(输入 / 显示内置命令列表)
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -137,6 +151,8 @@
|
|||||||
- [x] 集成 libSQL 数据库
|
- [x] 集成 libSQL 数据库
|
||||||
- [x] 实现独立上下文系统
|
- [x] 实现独立上下文系统
|
||||||
- [x] UI 状态合并显示
|
- [x] UI 状态合并显示
|
||||||
|
- [x] LLM 生成摘要(文言文风格)
|
||||||
|
- [x] Session 自动创建逻辑
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ type MemoryConfig struct {
|
|||||||
AutoSession bool `yaml:"auto_session"`
|
AutoSession bool `yaml:"auto_session"`
|
||||||
// AutoExport 退出时自动导出
|
// AutoExport 退出时自动导出
|
||||||
AutoExport bool `yaml:"auto_export"`
|
AutoExport bool `yaml:"auto_export"`
|
||||||
|
// SummaryTimeout 摘要生成超时时间(秒)
|
||||||
|
SummaryTimeout int `yaml:"summary_timeout"`
|
||||||
// Vector 向量服务配置
|
// Vector 向量服务配置
|
||||||
Vector VectorConfig `yaml:"vector"`
|
Vector VectorConfig `yaml:"vector"`
|
||||||
// Recall 检索配置
|
// Recall 检索配置
|
||||||
@@ -121,10 +123,11 @@ var (
|
|||||||
Auto: true,
|
Auto: true,
|
||||||
},
|
},
|
||||||
Memory: MemoryConfig{
|
Memory: MemoryConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
DBPath: "",
|
DBPath: "",
|
||||||
AutoSession: true,
|
AutoSession: true,
|
||||||
AutoExport: true,
|
AutoExport: true,
|
||||||
|
SummaryTimeout: 20,
|
||||||
Vector: VectorConfig{
|
Vector: VectorConfig{
|
||||||
APIKey: "",
|
APIKey: "",
|
||||||
BaseURL: "https://api.siliconflow.cn/v1",
|
BaseURL: "https://api.siliconflow.cn/v1",
|
||||||
@@ -222,6 +225,7 @@ memory:
|
|||||||
enabled: true
|
enabled: true
|
||||||
auto_session: true
|
auto_session: true
|
||||||
auto_export: true
|
auto_export: true
|
||||||
|
summary_timeout: 30
|
||||||
vector:
|
vector:
|
||||||
api_key: ""
|
api_key: ""
|
||||||
base_url: "https://api.siliconflow.cn/v1"
|
base_url: "https://api.siliconflow.cn/v1"
|
||||||
@@ -302,6 +306,9 @@ func mergeConfig(userCfg, projCfg *Config) *Config {
|
|||||||
if projCfg.Memory.AutoExport {
|
if projCfg.Memory.AutoExport {
|
||||||
result.Memory.AutoExport = 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 != "" {
|
if projCfg.Memory.Vector.APIKey != "" {
|
||||||
result.Memory.Vector.APIKey = projCfg.Memory.Vector.APIKey
|
result.Memory.Vector.APIKey = projCfg.Memory.Vector.APIKey
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hxclaw/hxclaw/cmd/hxclaw/internal"
|
"github.com/hxclaw/hxclaw/cmd/hxclaw/internal"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/agent"
|
||||||
|
|
||||||
_ "github.com/tursodatabase/libsql-client-go/libsql"
|
_ "github.com/tursodatabase/libsql-client-go/libsql"
|
||||||
)
|
)
|
||||||
@@ -18,8 +19,9 @@ type DB struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
db *DB
|
db *DB
|
||||||
cfg *DBConfig
|
cfg *DBConfig
|
||||||
|
summaryAgent *agent.AgentLoop
|
||||||
)
|
)
|
||||||
|
|
||||||
type DBConfig struct {
|
type DBConfig struct {
|
||||||
@@ -43,7 +45,9 @@ func GetDefaultDBPath() string {
|
|||||||
return getDefaultDBPath()
|
return getDefaultDBPath()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init(opts ...DBSOption) error {
|
func Init(agentLoop *agent.AgentLoop, opts ...DBSOption) error {
|
||||||
|
summaryAgent = agentLoop
|
||||||
|
|
||||||
memoryCfg := internal.GetProjectConfig().Memory
|
memoryCfg := internal.GetProjectConfig().Memory
|
||||||
|
|
||||||
cfg = &DBConfig{
|
cfg = &DBConfig{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package memory
|
package memory
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -14,6 +15,56 @@ var lastContext string
|
|||||||
|
|
||||||
var ErrNeedNewSession = fmt.Errorf("需要创建新会话")
|
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 {
|
func GetContextPrompt(userInput string) string {
|
||||||
db := GetDB()
|
db := GetDB()
|
||||||
if db == nil {
|
if db == nil {
|
||||||
@@ -227,8 +278,16 @@ func SaveChat(userInput, aiReply string, useSessionSummary bool) (int, error) {
|
|||||||
|
|
||||||
// 获取或创建 Session
|
// 获取或创建 Session
|
||||||
session := currentSession
|
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 {
|
if session == nil {
|
||||||
// 如果没有 session,返回错误让用户创建
|
|
||||||
return 0, ErrNeedNewSession
|
return 0, ErrNeedNewSession
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +302,7 @@ func SaveChat(userInput, aiReply string, useSessionSummary bool) (int, error) {
|
|||||||
|
|
||||||
// 添加 AI 回复
|
// 添加 AI 回复
|
||||||
chat.AddAIReply(aiReply)
|
chat.AddAIReply(aiReply)
|
||||||
chat.Summary = GenerateSummary(userInput, aiReply)
|
chat.Summary = generateChatSummary(userInput, aiReply)
|
||||||
chat.UpdatedAt = time.Now().Unix()
|
chat.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
// 生成并保存向量
|
// 生成并保存向量
|
||||||
@@ -261,7 +320,7 @@ func SaveChat(userInput, aiReply string, useSessionSummary bool) (int, error) {
|
|||||||
|
|
||||||
// 只有普通对话才更新 session summary,recall 查询保持原 summary
|
// 只有普通对话才更新 session summary,recall 查询保持原 summary
|
||||||
if useSessionSummary {
|
if useSessionSummary {
|
||||||
session.Summary = GenerateSummary("", session.Summary+"\n"+aiReply)
|
session.Summary = generateSessionSummary(session.Summary, userInput, aiReply)
|
||||||
}
|
}
|
||||||
session.UpdatedAt = time.Now().Unix()
|
session.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
@@ -276,7 +335,11 @@ func SaveChat(userInput, aiReply string, useSessionSummary bool) (int, error) {
|
|||||||
|
|
||||||
// 恢复原始 session summary(避免 recall 结果污染)
|
// 恢复原始 session summary(避免 recall 结果污染)
|
||||||
session.Summary = originalSummary
|
session.Summary = originalSummary
|
||||||
currentSession = session
|
|
||||||
|
// 首次自动创建后赋值 currentSession
|
||||||
|
if currentSession == nil {
|
||||||
|
currentSession = session
|
||||||
|
}
|
||||||
|
|
||||||
return len(session.ChatIDs), nil
|
return len(session.ChatIDs), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func main() {
|
|||||||
dbPath = memory.GetDefaultDBPath()
|
dbPath = memory.GetDefaultDBPath()
|
||||||
}
|
}
|
||||||
fmt.Printf("初始化记忆体,db_path: %s\n", dbPath)
|
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)
|
fmt.Fprintf(os.Stderr, "警告:初始化记忆体失败: %v,将使用无记忆模式\n", err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("记忆体初始化成功")
|
fmt.Println("记忆体初始化成功")
|
||||||
@@ -435,13 +435,8 @@ func handleTTSCommandSimple(input string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleNewSessionCommand(rl *internal.Readline, basePrompt 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
|
currentSession = nil
|
||||||
|
fmt.Println("已重置会话,输入聊天消息后将创建新会话")
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMemoryCommand(input string) {
|
func handleMemoryCommand(input string) {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ memory:
|
|||||||
enabled: false
|
enabled: false
|
||||||
auto_session: true
|
auto_session: true
|
||||||
auto_export: true
|
auto_export: true
|
||||||
|
summary_timeout: 30
|
||||||
vector:
|
vector:
|
||||||
base_url: "https://api.siliconflow.cn/v1"
|
base_url: "https://api.siliconflow.cn/v1"
|
||||||
model: "BAAI/bge-m3"
|
model: "BAAI/bge-m3"
|
||||||
|
|||||||
Reference in New Issue
Block a user