Files
smartmate/backend/services/memory/module.go
Losita 2a96f4c6f9 Version: 0.9.76.dev.260505
后端:
1.阶段 6 agent / memory 服务化收口
- 新增 cmd/agent 独立进程入口,承载 agent zrpc server、agent outbox relay / consumer 和运行时依赖初始化
- 补齐 services/agent/rpc 的 Chat stream 与 conversation meta/list/timeline、schedule-preview、context-stats、schedule-state unary RPC
- 新增 gateway/client/agent 与 shared/contracts/agent,将 /api/v1/agent chat 和非 chat 门面切到 agent zrpc
- 收缩 gateway 本地 AgentService 装配,双 RPC 开关开启时不再初始化本地 agent 编排、LLM、RAG 和 memory reader fallback
- 将 backend/memory 物理迁入 services/memory,私有实现收入 internal,保留 module/model/observe 作为 memory 服务门面
- 调整 memory outbox、memory reader 和 agent 记忆渲染链路的 import 与服务边界,cmd/memory 独占 memory worker / consumer
- 关闭 gateway 侧 agent outbox worker 所有权,agent relay / consumer 由 cmd/agent 独占,gateway 仅保留 HTTP/SSE 门面与迁移期开关回退
- 更新阶段 6 文档,记录 agent / memory 当前切流点、smoke 结果,以及 backend/client 与 gateway/shared 的目录收口口径
2026-05-05 19:31:39 +08:00

285 lines
11 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"
"github.com/LoveLosita/smartflow/backend/model"
llmservice "github.com/LoveLosita/smartflow/backend/services/llm"
memorycleanup "github.com/LoveLosita/smartflow/backend/services/memory/internal/cleanup"
memoryorchestrator "github.com/LoveLosita/smartflow/backend/services/memory/internal/orchestrator"
memoryrepo "github.com/LoveLosita/smartflow/backend/services/memory/internal/repo"
memoryservice "github.com/LoveLosita/smartflow/backend/services/memory/internal/service"
memoryvectorsync "github.com/LoveLosita/smartflow/backend/services/memory/internal/vectorsync"
memoryworker "github.com/LoveLosita/smartflow/backend/services/memory/internal/worker"
memorymodel "github.com/LoveLosita/smartflow/backend/services/memory/model"
memoryobserve "github.com/LoveLosita/smartflow/backend/services/memory/observe"
ragservice "github.com/LoveLosita/smartflow/backend/services/rag"
"gorm.io/gorm"
)
// Module 是 memory 模块对外暴露的统一门面。
//
// 职责边界:
// 1. 负责把 repo、service、worker、orchestrator 组装成一个稳定入口;
// 2. 负责对外暴露“写入 / 读取 / 管理 / 启动 worker”这些高层意图
// 3. 不负责替代应用层 DI也不负责替代上层事务管理器事务边界仍由调用方掌控。
type Module struct {
db *gorm.DB
cfg memorymodel.Config
llmClient *llmservice.Client
ragRuntime ragservice.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 *llmservice.Client, ragRuntime ragservice.Runtime, cfg memorymodel.Config) *Module {
return NewModuleWithObserve(db, llmClient, ragRuntime, cfg, ObserveDeps{})
}
// NewModuleWithObserve 创建带观测依赖的 memory 模块门面。
func NewModuleWithObserve(
db *gorm.DB,
llmClient *llmservice.Client,
ragRuntime ragservice.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 *llmservice.Client,
ragRuntime ragservice.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,
}
}