- 新增记忆搜索API - 集成向量检索能力 - 引入智能摘要和预压缩机制 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
238 lines
6.1 KiB
Go
238 lines
6.1 KiB
Go
package service
|
||
|
||
import (
|
||
"encoding/json"
|
||
"strings"
|
||
"time"
|
||
|
||
"x-agents/server/internal/repository"
|
||
|
||
"x-agents/server/internal/model"
|
||
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
// MemoryService 记忆服务
|
||
type MemoryService struct {
|
||
agentRepo *repository.AgentRepository
|
||
}
|
||
|
||
// NewMemoryService 创建记忆服务
|
||
func NewMemoryService(agentRepo *repository.AgentRepository) *MemoryService {
|
||
return &MemoryService{
|
||
agentRepo: agentRepo,
|
||
}
|
||
}
|
||
|
||
// CreateMemory 创建记忆
|
||
func (s *MemoryService) CreateMemory(agentID, userID, content, memoryType, category, tags, keywords string, importance int, isPinned bool) (*model.AgentMemory, error) {
|
||
memory := &model.AgentMemory{
|
||
ID: uuid.New().String(),
|
||
AgentID: agentID,
|
||
UserID: userID,
|
||
Content: content,
|
||
MemoryType: memoryType,
|
||
Category: category,
|
||
Tags: tags,
|
||
Keywords: keywords,
|
||
Importance: importance,
|
||
IsPinned: isPinned,
|
||
CreatedAt: time.Now(),
|
||
UpdatedAt: time.Now(),
|
||
}
|
||
|
||
// 如果没有提供关键词,自动从内容中提取
|
||
if keywords == "" && content != "" {
|
||
memory.Keywords = extractKeywords(content)
|
||
}
|
||
|
||
err := s.agentRepo.CreateMemory(memory)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return memory, nil
|
||
}
|
||
|
||
// GetMemories 获取记忆列表
|
||
func (s *MemoryService) GetMemories(agentID, userID, category, memoryType string, limit, offset int) ([]model.AgentMemory, int64, error) {
|
||
return s.agentRepo.FindMemories(agentID, userID, category, memoryType, limit, offset)
|
||
}
|
||
|
||
// SearchMemories 搜索记忆
|
||
func (s *MemoryService) SearchMemories(agentID, userID, keyword, tags, category, memoryType string, minScore, limit, offset int) ([]model.AgentMemory, int64, error) {
|
||
// 如果有关键词搜索,优先使用模糊匹配
|
||
if keyword != "" {
|
||
return s.agentRepo.SearchMemories(agentID, userID, keyword, tags, category, memoryType, minScore, limit, offset)
|
||
}
|
||
// 否则使用过滤条件查询
|
||
return s.agentRepo.FindMemories(agentID, userID, category, memoryType, limit, offset)
|
||
}
|
||
|
||
// GetMemoryByID 获取记忆详情
|
||
func (s *MemoryService) GetMemoryByID(memoryID string) (*model.AgentMemory, error) {
|
||
return s.agentRepo.FindMemoryByID(memoryID)
|
||
}
|
||
|
||
// UpdateMemory 更新记忆
|
||
func (s *MemoryService) UpdateMemory(memoryID, content, memoryType, category, tags, keywords string, importance int, isPinned *bool) (*model.AgentMemory, error) {
|
||
memory, err := s.agentRepo.FindMemoryByID(memoryID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if content != "" {
|
||
memory.Content = content
|
||
}
|
||
if memoryType != "" {
|
||
memory.MemoryType = memoryType
|
||
}
|
||
if category != "" {
|
||
memory.Category = category
|
||
}
|
||
if tags != "" {
|
||
memory.Tags = tags
|
||
}
|
||
if keywords != "" {
|
||
memory.Keywords = keywords
|
||
}
|
||
if importance > 0 {
|
||
memory.Importance = importance
|
||
}
|
||
if isPinned != nil {
|
||
memory.IsPinned = *isPinned
|
||
}
|
||
memory.UpdatedAt = time.Now()
|
||
|
||
err = s.agentRepo.UpdateMemory(memory)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return memory, nil
|
||
}
|
||
|
||
// DeleteMemory 删除记忆
|
||
func (s *MemoryService) DeleteMemory(memoryID string) error {
|
||
return s.agentRepo.DeleteMemory(memoryID)
|
||
}
|
||
|
||
// ExportMemories 导出记忆
|
||
func (s *MemoryService) ExportMemories(agentID, userID, format string) (interface{}, error) {
|
||
memories, _, err := s.agentRepo.FindMemories(agentID, userID, "", "", 0, 0)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if format == "csv" {
|
||
// 生成CSV格式
|
||
var sb strings.Builder
|
||
sb.WriteString("id,agent_id,content,memory_type,category,tags,keywords,importance,is_pinned,created_at\n")
|
||
for _, m := range memories {
|
||
sb.WriteString(m.ID + ",")
|
||
sb.WriteString(m.AgentID + ",")
|
||
sb.WriteString("\"" + strings.ReplaceAll(m.Content, "\"", "\"\"") + "\",")
|
||
sb.WriteString(m.MemoryType + ",")
|
||
sb.WriteString(m.Category + ",")
|
||
sb.WriteString(m.Tags + ",")
|
||
sb.WriteString(m.Keywords + ",")
|
||
sb.WriteString(string(rune(m.Importance+'0')) + ",")
|
||
if m.IsPinned {
|
||
sb.WriteString("true")
|
||
} else {
|
||
sb.WriteString("false")
|
||
}
|
||
sb.WriteString("," + m.CreatedAt.Format(time.RFC3339) + "\n")
|
||
}
|
||
return sb.String(), nil
|
||
}
|
||
|
||
// 默认JSON格式
|
||
return memories, nil
|
||
}
|
||
|
||
// ImportMemories 导入记忆
|
||
func (s *MemoryService) ImportMemories(agentID, userID string, items []model.ImportItem) (int, error) {
|
||
count := 0
|
||
for _, item := range items {
|
||
memoryType := item.MemoryType
|
||
if memoryType == "" {
|
||
memoryType = "conversation"
|
||
}
|
||
importance := item.Importance
|
||
if importance == 0 {
|
||
importance = 5
|
||
}
|
||
keywords := item.Keywords
|
||
if keywords == "" && item.Content != "" {
|
||
keywords = extractKeywords(item.Content)
|
||
}
|
||
|
||
memory := &model.AgentMemory{
|
||
ID: uuid.New().String(),
|
||
AgentID: agentID,
|
||
UserID: userID,
|
||
Content: item.Content,
|
||
MemoryType: memoryType,
|
||
Category: item.Category,
|
||
Tags: item.Tags,
|
||
Keywords: keywords,
|
||
Importance: importance,
|
||
IsPinned: false,
|
||
CreatedAt: time.Now(),
|
||
UpdatedAt: time.Now(),
|
||
}
|
||
|
||
err := s.agentRepo.CreateMemory(memory)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
count++
|
||
}
|
||
return count, nil
|
||
}
|
||
|
||
// GetMemoryCategories 获取记忆分类列表
|
||
func (s *MemoryService) GetMemoryCategories(agentID, userID string) ([]string, error) {
|
||
return s.agentRepo.FindMemoryCategories(agentID, userID)
|
||
}
|
||
|
||
// GetMemoryTags 获取记忆标签列表
|
||
func (s *MemoryService) GetMemoryTags(agentID, userID string) ([]string, error) {
|
||
// 从所有记忆中的 tags 字段提取所有标签
|
||
memories, _, err := s.agentRepo.FindMemories(agentID, userID, "", "", 0, 0)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
tagSet := make(map[string]bool)
|
||
for _, m := range memories {
|
||
if m.Tags != "" {
|
||
var tags []string
|
||
if err := json.Unmarshal([]byte(m.Tags), &tags); err == nil {
|
||
for _, tag := range tags {
|
||
if tag != "" {
|
||
tagSet[tag] = true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
tags := make([]string, 0, len(tagSet))
|
||
for tag := range tagSet {
|
||
tags = append(tags, tag)
|
||
}
|
||
return tags, nil
|
||
}
|
||
|
||
// extractKeywords 从内容中提取关键词
|
||
func extractKeywords(content string) string {
|
||
// 简单提取:取内容的前50个字符作为关键词演示
|
||
// 实际生产中可使用分词库如 gojieba
|
||
if len(content) <= 50 {
|
||
return content
|
||
}
|
||
return content[:50]
|
||
}
|