Files
smartmate/backend/memory/module.go
Losita fad3aed30a Version: 0.9.23.dev.260416
后端:
1. Memory 管理面 API 落地(“我的记忆”增删改查 + 恢复)
   - 补齐 List/Get/Create/Update/Delete/Restore 的 handler、请求模型与返回视图
   - 注册 `/api/v1/memory/items*` 路由并接入 MemoryHandler
   - 新增 memory item not found / invalid memory type / invalid memory content 三类管理面错误码
2. Memory Module / Service / Repo 扩展为“可管理 + 可治理”门面
   - 新增 NewModuleWithObserve / ObserveDeps,导出 GetItem / CreateItem / UpdateItem / DeleteItem / RestoreItem / RunDedupCleanup / MemoryObserver / MemoryMetrics
   - 新增手动新增、修改、恢复能力;删除链路切到 SoftDeleteByID;所有管理动作统一事务内写 audit,并桥接向量同步与管理面观测
   - 补齐 CreateItemFields / UpdateItemFields、单条 Create、管理侧字段更新、软删/恢复,以及 dedup 扫描/归档所需 repo 能力
   - 审计操作补齐 archive / restore
3. Memory 读侧与注入侧观测补齐
   - HybridRetrieve 返回 telemetry,统一记录 pinned hit / semantic hit / dedup drop / degraded / RAG fallback,并上报读取命中、去重丢弃、RAG 降级指标
   - AgentService 持有 memory observer / metrics;injectMemoryContext 对读取失败、空注入、成功注入补齐结构化日志与注入计数
4. Worker / 决策 / 向量同步链路治理增强
   - 召回结果显式携带 fallbackMode;hash 精确命中、rag→mysql 降级、最终动作统一写入决策观测
   - 接入 vectorSyncer / observer / metrics;为 job 重试、任务成功/失败、决策分布与 fallback 补齐打点;向量 upsert/delete 统一改走公共 Syncer,并收敛 parseMemoryID 解析逻辑
5. 启动层接入 Memory 观测依赖
   - 启动时创建 LoggerObserver + MetricsRegistry,并通过 NewModuleWithObserve 注入 memory 模块
前端:无
仓库:无
2026-04-16 19:34:32 +08:00

285 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package memory
import (
"context"
"errors"
"log"
infrallm "github.com/LoveLosita/smartflow/backend/infra/llm"
infrarag "github.com/LoveLosita/smartflow/backend/infra/rag"
memorycleanup "github.com/LoveLosita/smartflow/backend/memory/cleanup"
memorymodel "github.com/LoveLosita/smartflow/backend/memory/model"
memoryobserve "github.com/LoveLosita/smartflow/backend/memory/observe"
memoryorchestrator "github.com/LoveLosita/smartflow/backend/memory/orchestrator"
memoryrepo "github.com/LoveLosita/smartflow/backend/memory/repo"
memoryservice "github.com/LoveLosita/smartflow/backend/memory/service"
memoryvectorsync "github.com/LoveLosita/smartflow/backend/memory/vectorsync"
memoryworker "github.com/LoveLosita/smartflow/backend/memory/worker"
"github.com/LoveLosita/smartflow/backend/model"
"gorm.io/gorm"
)
// Module 是 memory 模块对外暴露的统一门面。
//
// 职责边界:
// 1. 负责把 repo、service、worker、orchestrator 组装成一个稳定入口;
// 2. 负责对外暴露“写入 / 读取 / 管理 / 启动 worker”这些高层意图
// 3. 不负责替代应用层 DI也不负责替代上层事务管理器事务边界仍由调用方掌控。
type Module struct {
db *gorm.DB
cfg memorymodel.Config
llmClient *infrallm.Client
ragRuntime infrarag.Runtime
observer memoryobserve.Observer
metrics memoryobserve.MetricsRecorder
jobRepo *memoryrepo.JobRepo
itemRepo *memoryrepo.ItemRepo
auditRepo *memoryrepo.AuditRepo
settingsRepo *memoryrepo.SettingsRepo
enqueueService *memoryservice.EnqueueService
readService *memoryservice.ReadService
manageService *memoryservice.ManageService
vectorSyncer *memoryvectorsync.Syncer
dedupRunner *memorycleanup.DedupRunner
runner *memoryworker.Runner
}
// ObserveDeps 描述 memory 模块可选的观测依赖。
type ObserveDeps struct {
Observer memoryobserve.Observer
Metrics memoryobserve.MetricsRecorder
}
// LoadConfigFromViper 复用 memory 子包里的配置加载逻辑,对外收口一个统一入口。
func LoadConfigFromViper() memorymodel.Config {
return memoryservice.LoadConfigFromViper()
}
// NewModule 创建 memory 模块门面。
//
// 设计说明:
// 1. 这里做的是“轻组装”,不引入额外容器概念,方便先接进现有项目;
// 2. llmClient 允许为 nil此时写入链路会自动回退到本地 fallback 抽取;
// 3. ragRuntime 允许为 nil此时读取/向量同步自动回退旧逻辑;
// 4. 若后续接入统一 DI 容器,也应优先注册这个 Module而不是把内部 repo/service 继续向外泄漏。
func NewModule(db *gorm.DB, llmClient *infrallm.Client, ragRuntime infrarag.Runtime, cfg memorymodel.Config) *Module {
return NewModuleWithObserve(db, llmClient, ragRuntime, cfg, ObserveDeps{})
}
// NewModuleWithObserve 创建带观测依赖的 memory 模块门面。
func NewModuleWithObserve(
db *gorm.DB,
llmClient *infrallm.Client,
ragRuntime infrarag.Runtime,
cfg memorymodel.Config,
deps ObserveDeps,
) *Module {
return wireModule(db, llmClient, ragRuntime, cfg, deps)
}
// WithTx 返回绑定到指定事务连接的同构门面。
//
// 步骤化说明:
// 1. 上层事务管理器先创建 tx
// 2. 再通过 WithTx(tx) 把 memory 内部所有 repo/service 一次性切到同一个事务连接;
// 3. 这样外部无需重新 new 一堆 repo也不会破坏既有跨表事务边界。
func (m *Module) WithTx(tx *gorm.DB) *Module {
if m == nil {
return nil
}
if tx == nil {
return m
}
return wireModule(tx, m.llmClient, m.ragRuntime, m.cfg, ObserveDeps{
Observer: m.observer,
Metrics: m.metrics,
})
}
// EnqueueExtract 把一次记忆抽取请求入队到 memory_jobs。
func (m *Module) EnqueueExtract(
ctx context.Context,
payload memorymodel.ExtractJobPayload,
sourceEventID string,
) error {
if m == nil || m.enqueueService == nil {
return errors.New("memory module enqueue service is nil")
}
return m.enqueueService.EnqueueExtractJob(ctx, payload, sourceEventID)
}
// Retrieve 读取后续可供 prompt 注入使用的候选记忆。
func (m *Module) Retrieve(ctx context.Context, req memorymodel.RetrieveRequest) ([]memorymodel.ItemDTO, error) {
if m == nil || m.readService == nil {
return nil, errors.New("memory module read service is nil")
}
return m.readService.Retrieve(ctx, req)
}
// ListItems 列出用户当前可管理的记忆条目。
func (m *Module) ListItems(ctx context.Context, req memorymodel.ListItemsRequest) ([]memorymodel.ItemDTO, error) {
if m == nil || m.manageService == nil {
return nil, errors.New("memory module manage service is nil")
}
return m.manageService.ListItems(ctx, req)
}
// GetItem 返回当前用户自己的单条记忆详情。
func (m *Module) GetItem(ctx context.Context, req model.MemoryGetItemRequest) (*memorymodel.ItemDTO, error) {
if m == nil || m.manageService == nil {
return nil, errors.New("memory module manage service is nil")
}
return m.manageService.GetItem(ctx, req)
}
// CreateItem 手动新增一条用户记忆。
func (m *Module) CreateItem(ctx context.Context, req model.MemoryCreateItemRequest) (*memorymodel.ItemDTO, error) {
if m == nil || m.manageService == nil {
return nil, errors.New("memory module manage service is nil")
}
return m.manageService.CreateItem(ctx, req)
}
// UpdateItem 手动修改一条用户记忆。
func (m *Module) UpdateItem(ctx context.Context, req model.MemoryUpdateItemRequest) (*memorymodel.ItemDTO, error) {
if m == nil || m.manageService == nil {
return nil, errors.New("memory module manage service is nil")
}
return m.manageService.UpdateItem(ctx, req)
}
// DeleteItem 软删除一条记忆,并补写审计日志。
func (m *Module) DeleteItem(ctx context.Context, req model.MemoryDeleteItemRequest) (*memorymodel.ItemDTO, error) {
if m == nil || m.manageService == nil {
return nil, errors.New("memory module manage service is nil")
}
return m.manageService.DeleteItem(ctx, req)
}
// RestoreItem 恢复一条 deleted/archived 记忆。
func (m *Module) RestoreItem(ctx context.Context, req model.MemoryRestoreItemRequest) (*memorymodel.ItemDTO, error) {
if m == nil || m.manageService == nil {
return nil, errors.New("memory module manage service is nil")
}
return m.manageService.RestoreItem(ctx, req)
}
// GetUserSetting 读取用户当前生效的记忆开关。
func (m *Module) GetUserSetting(ctx context.Context, userID int) (memorymodel.UserSettingDTO, error) {
if m == nil || m.manageService == nil {
return memorymodel.UserSettingDTO{}, errors.New("memory module manage service is nil")
}
return m.manageService.GetUserSetting(ctx, userID)
}
// UpsertUserSetting 写入用户记忆开关。
func (m *Module) UpsertUserSetting(ctx context.Context, req memorymodel.UpdateUserSettingRequest) (memorymodel.UserSettingDTO, error) {
if m == nil || m.manageService == nil {
return memorymodel.UserSettingDTO{}, errors.New("memory module manage service is nil")
}
return m.manageService.UpsertUserSetting(ctx, req)
}
// RunDedupCleanup 执行一次离线 dedup 治理。
func (m *Module) RunDedupCleanup(ctx context.Context, req model.MemoryDedupCleanupRequest) (model.MemoryDedupCleanupResult, error) {
if m == nil || m.dedupRunner == nil {
return model.MemoryDedupCleanupResult{}, errors.New("memory module dedup runner is nil")
}
return m.dedupRunner.Run(ctx, req)
}
// MemoryObserver 暴露 memory 模块当前使用的 observer供注入桥接等外围能力复用。
func (m *Module) MemoryObserver() memoryobserve.Observer {
if m == nil || m.observer == nil {
return memoryobserve.NewNopObserver()
}
return m.observer
}
// MemoryMetrics 暴露 memory 模块当前使用的轻量计数器。
func (m *Module) MemoryMetrics() memoryobserve.MetricsRecorder {
if m == nil || m.metrics == nil {
return memoryobserve.NewNopMetrics()
}
return m.metrics
}
// StartWorker 启动 memory 后台 worker。
//
// 说明:
// 1. 这里只负责按当前配置拉起轮询循环;
// 2. 若 memory.enabled=false则直接记录日志并返回
// 3. 当前不做重复启动保护,生命周期仍假设由应用启动层统一掌控。
func (m *Module) StartWorker(ctx context.Context) {
if m == nil || m.runner == nil {
log.Println("Memory worker is not initialized")
return
}
if !m.cfg.Enabled {
log.Println("Memory worker is disabled")
return
}
go memoryworker.RunPollingLoop(ctx, m.runner, m.cfg.WorkerPollEvery, m.cfg.WorkerClaimBatch)
log.Println("Memory worker started")
}
func wireModule(
db *gorm.DB,
llmClient *infrallm.Client,
ragRuntime infrarag.Runtime,
cfg memorymodel.Config,
deps ObserveDeps,
) *Module {
jobRepo := memoryrepo.NewJobRepo(db)
itemRepo := memoryrepo.NewItemRepo(db)
auditRepo := memoryrepo.NewAuditRepo(db)
settingsRepo := memoryrepo.NewSettingsRepo(db)
observer := deps.Observer
if observer == nil {
observer = memoryobserve.NewLoggerObserver(log.Default())
}
metrics := deps.Metrics
if metrics == nil {
metrics = memoryobserve.NewMetricsRegistry()
}
vectorSyncer := memoryvectorsync.NewSyncer(ragRuntime, itemRepo, observer, metrics)
enqueueService := memoryservice.NewEnqueueService(jobRepo)
readService := memoryservice.NewReadService(itemRepo, settingsRepo, ragRuntime, cfg, observer, metrics)
manageService := memoryservice.NewManageService(db, itemRepo, auditRepo, settingsRepo, vectorSyncer, observer, metrics)
extractor := memoryorchestrator.NewLLMWriteOrchestrator(llmClient, cfg)
// 决策编排器:仅在 DecisionEnabled 时才创建有效实例。
// 原因cfg.DecisionEnabled=false 时Runner 不走决策路径,编排器不会使用,
// 但仍然创建以保持构造签名统一,避免上层调用方感知条件逻辑。
var decisionOrchestrator *memoryorchestrator.LLMDecisionOrchestrator
if cfg.DecisionEnabled && llmClient != nil {
decisionOrchestrator = memoryorchestrator.NewLLMDecisionOrchestrator(llmClient, cfg)
}
runner := memoryworker.NewRunner(db, jobRepo, itemRepo, auditRepo, settingsRepo, extractor, ragRuntime, cfg, decisionOrchestrator, vectorSyncer, observer, metrics)
dedupRunner := memorycleanup.NewDedupRunner(db, itemRepo, auditRepo, vectorSyncer, observer, metrics)
return &Module{
db: db,
cfg: cfg,
llmClient: llmClient,
ragRuntime: ragRuntime,
observer: observer,
metrics: metrics,
jobRepo: jobRepo,
itemRepo: itemRepo,
auditRepo: auditRepo,
settingsRepo: settingsRepo,
enqueueService: enqueueService,
readService: readService,
manageService: manageService,
vectorSyncer: vectorSyncer,
dedupRunner: dedupRunner,
runner: runner,
}
}