mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 17:38:10 +00:00
fix: 三层记忆/知识库数据泄漏修复 + 系统提示词动态化
问题修复: - distiller: Append 添加 memDB==nil 守卫,避免数据泄漏 - indexer: 启动时立即 Sync(),消除前30分钟空窗期 - 系统提示词: 移除硬编码的13项工具列表,改为动态分类说明 - doc_query: 消去 Query+Consume 重复搜索,改为 Consume 一次完成 - extractKeyTriples: 从原文 dump 改为规则提取(姓名/居住地/喜好/年龄/职业)
This commit is contained in:
@ -81,6 +81,7 @@ func main() {
|
||||
}
|
||||
|
||||
memIdx := memory.NewIndexer(memDB)
|
||||
memIdx.Sync() // 启动时立即同步,避免前30分钟空窗
|
||||
socialStore := social.New(memDB)
|
||||
|
||||
distiller := pipeline.NewDistiller(memDB, *dataDir, pipeline.DistillerConfig{
|
||||
@ -194,14 +195,14 @@ func main() {
|
||||
if err := textMem.Append(te); err != nil {
|
||||
log.Printf("[homed] text memory append: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if input != "" {
|
||||
distiller.Append("agent", "user", input)
|
||||
}
|
||||
if response != "" {
|
||||
distiller.Append("agent", "assistant", response)
|
||||
}
|
||||
if input != "" && memDB != nil {
|
||||
distiller.Append("agent", "user", input)
|
||||
}
|
||||
if response != "" && memDB != nil {
|
||||
distiller.Append("agent", "assistant", response)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -323,20 +324,16 @@ func main() {
|
||||
如需异步发送消息或通知,使用 output_send 指定通道和内容。
|
||||
使用 output_list_channels 查看可用通道及其能力。
|
||||
|
||||
你有以下核心工具:
|
||||
1. memory_recall — 查询图记忆(历史/个人信息)
|
||||
2. memory_commit — 写入图记忆(记住新信息)
|
||||
3. memory_introspect — 查看记忆统计
|
||||
4. person_query — 查询人物特质与社交关系网
|
||||
5. person_set_trait — 记录人物特质(性格/喜好等)
|
||||
6. person_relate — 建立人物间社交关系
|
||||
7. person_network — 查看社交网络
|
||||
8. knowledge_search — 搜索知识库
|
||||
9. doc_query — 查询文档记忆
|
||||
10. doc_commit — 写入文档记忆
|
||||
11. plgreload — 热重载插件
|
||||
12. llm_list_sources — 列出所有可用的 LLM 源
|
||||
13. llm_set_source — 切换到指定 LLM 源
|
||||
可用工具列表会由系统自动传入,按需使用即可。以下是你尤其需要关注的几类工具:
|
||||
- memory_* — 图记忆(长期记忆,记录和查询个人信息/事实)
|
||||
- knowledge_* — 知识库(查阅预设知识文档)
|
||||
- doc_* — 文档记忆(近期对话的存档,查询后自动清除)
|
||||
- person_* — 人物特质与社交关系网
|
||||
- llm_* — LLM 源管理(列出/切换模型提供商)
|
||||
- output_* — 输出通道管理(切换/发送消息)
|
||||
- timer_set — 设置定时提醒
|
||||
- plgreload — 热重载插件
|
||||
- spawn_child — 生成子 Agent 执行独立任务
|
||||
|
||||
回复你的真实想法,用自然语言与用户交流。`,
|
||||
Provider: provider,
|
||||
|
||||
@ -986,12 +986,10 @@ func (a *Agent) executeDocTool(tc agentAPI.ToolCall) string {
|
||||
if query == "" {
|
||||
return "请输入查询内容"
|
||||
}
|
||||
docs := a.docStore.Query(query, topK)
|
||||
docs := a.docStore.Consume(query, topK)
|
||||
if len(docs) == 0 {
|
||||
return "未找到相关文档记忆"
|
||||
}
|
||||
// 返回内容后从冷层移除,避免后续自动注入重复
|
||||
a.docStore.Consume(query, topK)
|
||||
var parts []string
|
||||
for i, d := range docs {
|
||||
parts = append(parts, fmt.Sprintf("[%d] %s (来源: %s)", i+1, d.Summary, d.Source))
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -213,15 +214,179 @@ func (d *Distiller) cleanupRawFiles() {
|
||||
|
||||
func extractKeyTriples(userContent, assistantContent string) []memory.Triple {
|
||||
var triples []memory.Triple
|
||||
if len(userContent) > 0 && len(userContent) < 500 {
|
||||
triples = append(triples, memory.Triple{Subject: "用户", Relation: "提及", Object: truncate(userContent, 200)})
|
||||
|
||||
// 提取对话中的关键信息,而不是直接 dump 原文
|
||||
// 规则1: "我的名字是X" / "我叫X" → (用户, 姓名, X)
|
||||
if name := extractName(userContent); name != "" {
|
||||
triples = append(triples, memory.Triple{Subject: "用户", Relation: "姓名", Object: name})
|
||||
}
|
||||
if len(assistantContent) > 0 && len(assistantContent) < 500 {
|
||||
triples = append(triples, memory.Triple{Subject: "AI", Relation: "回应", Object: truncate(assistantContent, 200)})
|
||||
// 规则2: "我住在X" / "我家在X" → (用户, 居住地, X)
|
||||
if loc := extractLocation(userContent); loc != "" {
|
||||
triples = append(triples, memory.Triple{Subject: "用户", Relation: "居住地", Object: loc})
|
||||
}
|
||||
// 规则3: "我喜欢X" / "我爱X" → (用户, 喜好, X)
|
||||
if like := extractLike(userContent); like != "" {
|
||||
triples = append(triples, memory.Triple{Subject: "用户", Relation: "喜好", Object: like})
|
||||
}
|
||||
// 规则4: "我X岁" / "我的年龄是X" → (用户, 年龄, X)
|
||||
if age := extractAge(userContent); age != "" {
|
||||
triples = append(triples, memory.Triple{Subject: "用户", Relation: "年龄", Object: age})
|
||||
}
|
||||
// 规则5: "我的工作是X" / "我在X工作" → (用户, 职业, X)
|
||||
if job := extractJob(userContent); job != "" {
|
||||
triples = append(triples, memory.Triple{Subject: "用户", Relation: "职业", Object: job})
|
||||
}
|
||||
|
||||
return triples
|
||||
}
|
||||
|
||||
func extractName(s string) string {
|
||||
patterns := []struct {
|
||||
prefix string
|
||||
suffix string
|
||||
}{
|
||||
{"我叫", ""},
|
||||
{"我的名字是", ""},
|
||||
{"我是", ""},
|
||||
{"名字是", ""},
|
||||
}
|
||||
s = strings.TrimSpace(s)
|
||||
for _, p := range patterns {
|
||||
if strings.HasPrefix(s, p.prefix) {
|
||||
candidate := strings.TrimPrefix(s, p.prefix)
|
||||
if p.suffix != "" && strings.Contains(candidate, p.suffix) {
|
||||
candidate = candidate[:strings.Index(candidate, p.suffix)]
|
||||
}
|
||||
candidate = strings.TrimSpace(candidate)
|
||||
// 取第一个空格/逗号/句号前的内容
|
||||
for _, sep := range []string{",", "。", " ", ","} {
|
||||
if idx := strings.Index(candidate, sep); idx > 0 {
|
||||
candidate = candidate[:idx]
|
||||
}
|
||||
}
|
||||
if len(candidate) > 0 && len(candidate) < 20 {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractLocation(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
after := ""
|
||||
switch {
|
||||
case strings.HasPrefix(s, "我住在"):
|
||||
after = strings.TrimPrefix(s, "我住在")
|
||||
case strings.HasPrefix(s, "我家在"):
|
||||
after = strings.TrimPrefix(s, "我家在")
|
||||
case strings.HasPrefix(s, "我居住在"):
|
||||
after = strings.TrimPrefix(s, "我居住在")
|
||||
case strings.HasPrefix(s, "住在"):
|
||||
after = strings.TrimPrefix(s, "住在")
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
for _, sep := range []string{"。", ",", " ", ","} {
|
||||
if idx := strings.Index(after, sep); idx > 0 {
|
||||
after = after[:idx]
|
||||
}
|
||||
}
|
||||
if len(after) > 0 && len(after) < 50 {
|
||||
return strings.TrimSpace(after)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractLike(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
after := ""
|
||||
switch {
|
||||
case strings.HasPrefix(s, "我喜欢"):
|
||||
after = strings.TrimPrefix(s, "我喜欢")
|
||||
case strings.HasPrefix(s, "我爱"):
|
||||
after = strings.TrimPrefix(s, "我爱")
|
||||
case strings.HasPrefix(s, "我最喜欢"):
|
||||
after = strings.TrimPrefix(s, "我最喜欢")
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
for _, sep := range []string{"。", ",", " ", ","} {
|
||||
if idx := strings.Index(after, sep); idx > 0 {
|
||||
after = after[:idx]
|
||||
}
|
||||
}
|
||||
if len(after) > 0 && len(after) < 50 {
|
||||
return strings.TrimSpace(after)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractAge(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
after := ""
|
||||
switch {
|
||||
case strings.HasPrefix(s, "我"):
|
||||
rest := strings.TrimPrefix(s, "我")
|
||||
if strings.Contains(rest, "岁") {
|
||||
after = rest[:strings.Index(rest, "岁")]
|
||||
} else if strings.HasPrefix(rest, "的年龄是") {
|
||||
after = strings.TrimPrefix(rest, "的年龄是")
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
for _, sep := range []string{"。", ",", " ", ","} {
|
||||
if idx := strings.Index(after, sep); idx > 0 {
|
||||
after = after[:idx]
|
||||
}
|
||||
}
|
||||
if len(after) > 0 && len(after) < 5 {
|
||||
return strings.TrimSpace(after)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractJob(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
after := ""
|
||||
switch {
|
||||
case strings.HasPrefix(s, "我的工作是"):
|
||||
after = strings.TrimPrefix(s, "我的工作是")
|
||||
case strings.HasPrefix(s, "我在"):
|
||||
rest := strings.TrimPrefix(s, "我在")
|
||||
if strings.Contains(rest, "工作") {
|
||||
after = rest[:strings.Index(rest, "工作")]
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
case strings.HasPrefix(s, "我是"):
|
||||
rest := strings.TrimPrefix(s, "我是")
|
||||
// "我是一个程序员" / "我是老师"
|
||||
for _, keyword := range []string{"一个", "一名", "一位"} {
|
||||
if strings.HasPrefix(rest, keyword) {
|
||||
rest = strings.TrimPrefix(rest, keyword)
|
||||
break
|
||||
}
|
||||
}
|
||||
// 职业通常较短,先看看
|
||||
after = rest
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
for _, sep := range []string{"。", ",", " ", ",", "。"} {
|
||||
if idx := strings.Index(after, sep); idx > 0 {
|
||||
after = after[:idx]
|
||||
}
|
||||
}
|
||||
if len(after) > 0 && len(after) < 20 {
|
||||
return strings.TrimSpace(after)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func truncate(s string, max int) string {
|
||||
if len(s) > max {
|
||||
return s[:max] + "..."
|
||||
|
||||
Reference in New Issue
Block a user