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 的目录收口口径
This commit is contained in:
Losita
2026-05-05 19:31:39 +08:00
parent d7184b776b
commit 2a96f4c6f9
72 changed files with 2775 additions and 291 deletions

View File

@@ -0,0 +1,16 @@
package model
import "time"
// AuditLogDTO 是审计日志领域对象。
type AuditLogDTO struct {
ID int64
MemoryID int64
UserID int
Operation string
OperatorType string
Reason string
BeforeJSON string
AfterJSON string
CreatedAt *time.Time
}

View File

@@ -0,0 +1,134 @@
package model
import (
"strings"
"time"
)
const (
// MemoryReadModeLegacy 表示读取侧沿用“RAG 优先,失败再走 legacy”旧链路。
MemoryReadModeLegacy = "legacy"
// MemoryReadModeHybrid 表示读取侧走“结构化强约束 + 语义候选”混合链路。
MemoryReadModeHybrid = "hybrid"
// MemoryInjectRenderModeFlat 表示沿用扁平列表渲染。
MemoryInjectRenderModeFlat = "flat"
// MemoryInjectRenderModeTypedV2 表示按记忆类型分段渲染。
MemoryInjectRenderModeTypedV2 = "typed_v2"
// DefaultReadConstraintLimit 是 constraint 默认预算上限。
DefaultReadConstraintLimit = 5
// DefaultReadPreferenceLimit 是 preference 默认预算上限。
DefaultReadPreferenceLimit = 5
// DefaultReadFactLimit 是 fact 默认预算上限。
DefaultReadFactLimit = 5
)
// Config 是记忆模块配置对象Day1 首版)。
//
// 职责边界:
// 1. 只承载模块运行参数,不承载业务状态;
// 2. 允许启动期统一注入,避免业务层直接依赖配置中心。
type Config struct {
Enabled bool
RAGEnabled bool
ReadMode string
ReadConstraintLimit int
ReadPreferenceLimit int
ReadFactLimit int
InjectRenderMode string
ExtractPrompt string
DecisionPrompt string
Threshold float64
EnableReranker bool
LLMTemperature float64
LLMTopP float64
JobMaxRetry int
WorkerPollEvery time.Duration
WorkerClaimBatch int
// 决策层配置。
// 说明:
// 1. DecisionEnabled 控制是否启用"召回→比对→汇总"决策流程;
// 2. 默认关闭,旧路径完全保留,回滚无风险;
// 3. DecisionFallbackMode 仅在决策流程整体报错时生效,不影响单条 LLM 比对失败(单条失败视为 unrelated
DecisionEnabled bool
DecisionCandidateTopK int // Milvus 语义召回候选数上限
DecisionCandidateMinScore float64 // Milvus 语义召回最低相似度
DecisionFallbackMode string // "legacy_add"(退回旧路径直接新增)/ "drop"(丢弃)
WriteMode string // "legacy"(旧路径)/ "decision"(决策流程),仅 DecisionEnabled=true 时生效
// 写入置信度阈值。
// 说明:
// 1. 抽取结果 confidence 低于此值直接丢弃,不做入库;
// 2. 默认 0.5,与"守门员"prompt 的 confidence>=0.5 输出规则配合;
// 3. fallback 路径 confidence 设为 0.45低于默认阈值LLM 不可用时不写入。
WriteMinConfidence float64
// 记忆模块 LLM 调用是否开启 thinking由 config.yaml 的 agent.thinking.memory 注入。
LLMThinking bool
}
// NormalizeReadMode 统一读取模式字符串。
func NormalizeReadMode(mode string) string {
switch strings.ToLower(strings.TrimSpace(mode)) {
case MemoryReadModeHybrid:
return MemoryReadModeHybrid
default:
return MemoryReadModeLegacy
}
}
// NormalizeInjectRenderMode 统一注入渲染模式字符串。
func NormalizeInjectRenderMode(mode string) string {
switch strings.ToLower(strings.TrimSpace(mode)) {
case MemoryInjectRenderModeTypedV2:
return MemoryInjectRenderModeTypedV2
default:
return MemoryInjectRenderModeFlat
}
}
// EffectiveReadConstraintLimit 返回 constraint 生效预算。
func (c Config) EffectiveReadConstraintLimit() int {
return normalizePositiveLimit(c.ReadConstraintLimit, DefaultReadConstraintLimit)
}
// EffectiveReadPreferenceLimit 返回 preference 生效预算。
func (c Config) EffectiveReadPreferenceLimit() int {
return normalizePositiveLimit(c.ReadPreferenceLimit, DefaultReadPreferenceLimit)
}
// EffectiveReadFactLimit 返回 fact 生效预算。
func (c Config) EffectiveReadFactLimit() int {
return normalizePositiveLimit(c.ReadFactLimit, DefaultReadFactLimit)
}
// EffectiveReadMode 返回生效读取模式。
func (c Config) EffectiveReadMode() string {
return NormalizeReadMode(c.ReadMode)
}
// EffectiveInjectRenderMode 返回生效渲染模式。
func (c Config) EffectiveInjectRenderMode() string {
return NormalizeInjectRenderMode(c.InjectRenderMode)
}
// TotalReadBudget 返回三类记忆的总预算上限。
func (c Config) TotalReadBudget() int {
return c.EffectiveReadConstraintLimit() +
c.EffectiveReadPreferenceLimit() +
c.EffectiveReadFactLimit()
}
func normalizePositiveLimit(value int, defaultValue int) int {
if value <= 0 {
return defaultValue
}
return value
}

View File

@@ -0,0 +1,71 @@
package model
// RelationType 常量描述一条新 fact 与一条旧记忆之间的关系。
//
// 四种关系:
// 1. duplicate — 完全重复,新 fact 没有新信息,旧记忆无需变动;
// 2. update — 新 fact 是对旧记忆的修正、补充或更精确表述,需要合并更新;
// 3. conflict — 新 fact 与旧记忆矛盾,旧记忆已过时,应删旧增新;
// 4. unrelated — 两者说的是不同的事情,互不影响。
const (
RelationDuplicate = "duplicate"
RelationUpdate = "update"
RelationConflict = "conflict"
RelationUnrelated = "unrelated"
)
// CandidateSnapshot 是喂给 LLM 的旧记忆候选快照。
//
// 职责边界:
// 1. 只承载 LLM 做关系判断所需的最小信息;
// 2. MemoryID 是真实 memory_idLLM 不可见,仅供汇总决策时使用;
// 3. Score 是向量召回的相似度分数,用于多条 update 时选最优候选。
type CandidateSnapshot struct {
MemoryID int64
Title string
Content string
MemoryType string
Score float64 // Milvus 相似度分数0 表示来自 Hash 查询)
}
// ComparisonResult 是单次"新 fact vs 一条旧记忆"的 LLM 输出。
//
// 职责边界:
// 1. 只描述 LLM 对一对比较的结果,不包含最终决策动作;
// 2. UpdatedContent/UpdatedTitle 仅在 relation=update 时有意义;
// 3. Reason 写审计日志用,便于后续复盘 LLM 判断依据。
type ComparisonResult struct {
MemoryID int64 // 被比较的旧记忆 ID
Relation string // duplicate / update / conflict / unrelated
UpdatedContent string // 仅 relation=update 时有意义:合并后的新内容
UpdatedTitle string // 仅 relation=update 时有意义:合并后的新标题
Reason string // 判断理由(写审计日志用)
}
// FinalDecision 是汇总后的最终动作。
//
// 说明:
// 1. 由确定性代码产出,不是 LLM 产出;
// 2. Action 取值复用 status.go 中已定义的 DecisionActionAdd/Update/Delete/None 常量;
// 3. TargetID 在 UPDATE/DELETE 时指向旧记忆 IDADD/NONE 时为 0。
type FinalDecision struct {
Action string // ADD / UPDATE / DELETE / NONE
TargetID int64 // UPDATE/DELETE 时指向旧记忆 ID
Title string // UPDATE 时的新标题
Content string // UPDATE 时的新内容
Reason string // 汇总理由
}
// UpdateContentFields 是 UPDATE 动作需要更新的字段集合。
//
// 说明:
// 1. 只包含 UPDATE 动作实际需要修改的字段,避免全量覆盖;
// 2. NormalizedContent/ContentHash 由调用方重新计算,保证一致性。
type UpdateContentFields struct {
Title string
Content string
NormalizedContent string
ContentHash string
Confidence float64
Importance float64
}

View File

@@ -0,0 +1,105 @@
package model
import "time"
// ItemDTO 是记忆条目对外读写 DTO。
//
// 职责边界:
// 1. 面向 memory 模块内部服务层使用;
// 2. 不直接绑定 GORM 标签,避免传输结构与存储结构强耦合。
type ItemDTO struct {
ID int64
UserID int
ConversationID string
AssistantID string
RunID string
MemoryType string
Title string
Content string
ContentHash string
Confidence float64
Importance float64
SensitivityLevel int
IsExplicit bool
Status string
TTLAt *time.Time
CreatedAt *time.Time
UpdatedAt *time.Time
}
// ItemQuery 描述 memory_items 的通用查询条件。
//
// 职责边界:
// 1. 只表达 memory 仓储层需要的过滤条件;
// 2. 不直接承载注入策略、重排策略等上层业务语义;
// 3. IncludeGlobal 用于“会话级 + 全局级”混合读取场景。
type ItemQuery struct {
UserID int
ConversationID string
AssistantID string
RunID string
Statuses []string
MemoryTypes []string
IncludeGlobal bool
OnlyUnexpired bool
Limit int
Now time.Time
}
// RetrieveRequest 描述“供提示词注入前读取”所需的最小参数。
type RetrieveRequest struct {
Query string
UserID int
ConversationID string
AssistantID string
RunID string
MemoryTypes []string
Limit int
Now time.Time
}
// ListItemsRequest 描述记忆管理页列表查询参数。
type ListItemsRequest struct {
UserID int
ConversationID string
Statuses []string
MemoryTypes []string
Limit int
}
// CreateItemFields 是 repo 层落库时真正需要的字段集合。
type CreateItemFields struct {
UserID int
ConversationID string
AssistantID string
RunID string
MemoryType string
Title string
Content string
NormalizedContent string
ContentHash string
Confidence float64
Importance float64
SensitivityLevel int
IsExplicit bool
Status string
TTLAt *time.Time
VectorStatus string
SourceMessageID *int64
SourceEventID *string
LastAccessAt *time.Time
}
// UpdateItemFields 是“用户管理侧修改记忆”时 repo 层允许更新的字段集合。
type UpdateItemFields struct {
MemoryType string
Title string
Content string
NormalizedContent string
ContentHash string
Confidence float64
Importance float64
SensitivityLevel int
IsExplicit bool
TTLAt *time.Time
}

View File

@@ -0,0 +1,45 @@
package model
import "time"
// ExtractJobPayload 是 memory_jobs.payload_json 的领域视图。
//
// 职责边界:
// 1. 只描述抽取任务执行所需字段;
// 2. 与数据库模型解耦,避免后续表结构调整污染 worker 逻辑。
type ExtractJobPayload struct {
UserID int `json:"user_id"`
ConversationID string `json:"conversation_id"`
AssistantID string `json:"assistant_id,omitempty"`
RunID string `json:"run_id,omitempty"`
SourceMessageID int64 `json:"source_message_id,omitempty"`
SourceRole string `json:"source_role"`
SourceText string `json:"source_text"`
OccurredAt time.Time `json:"occurred_at"`
TraceID string `json:"trace_id,omitempty"`
IdempotencyKey string `json:"idempotency_key"`
}
// FactCandidate 表示抽取阶段得到的候选事实。
type FactCandidate struct {
MemoryType string
Title string
Content string
Confidence float64
Importance float64
SensitivityLevel int
IsExplicit bool
}
// NormalizedFact 表示通过标准化后的可入库事实。
type NormalizedFact struct {
MemoryType string
Title string
Content string
NormalizedContent string
ContentHash string
Confidence float64
Importance float64
SensitivityLevel int
IsExplicit bool
}

View File

@@ -0,0 +1,20 @@
package model
import "time"
// UserSettingDTO 是用户记忆开关领域对象。
type UserSettingDTO struct {
UserID int
MemoryEnabled bool
ImplicitMemoryEnabled bool
SensitiveMemoryEnabled bool
UpdatedAt *time.Time
}
// UpdateUserSettingRequest 描述记忆开关写入请求。
type UpdateUserSettingRequest struct {
UserID int
MemoryEnabled bool
ImplicitMemoryEnabled bool
SensitiveMemoryEnabled bool
}

View File

@@ -0,0 +1,56 @@
package model
import "strings"
const (
// MemoryTypePreference 表示用户偏好类记忆。
MemoryTypePreference = "preference"
// MemoryTypeConstraint 表示硬约束类记忆。
MemoryTypeConstraint = "constraint"
// MemoryTypeFact 表示一般事实类记忆。
MemoryTypeFact = "fact"
)
const (
// DecisionActionAdd 表示新增记忆。
DecisionActionAdd = "ADD"
// DecisionActionUpdate 表示更新记忆。
DecisionActionUpdate = "UPDATE"
// DecisionActionDelete 表示删除记忆。
DecisionActionDelete = "DELETE"
// DecisionActionNone 表示不做写入动作。
DecisionActionNone = "NONE"
)
var validMemoryTypes = map[string]struct{}{
MemoryTypePreference: {},
MemoryTypeConstraint: {},
MemoryTypeFact: {},
}
var validDecisionActions = map[string]struct{}{
DecisionActionAdd: {},
DecisionActionUpdate: {},
DecisionActionDelete: {},
DecisionActionNone: {},
}
// NormalizeMemoryType 统一记忆类型字符串。
//
// 职责边界:
// 1. 只做字符串标准化,不做业务兜底;
// 2. 若调用方传入非法类型,返回空字符串供上游决定丢弃或降级。
func NormalizeMemoryType(memoryType string) string {
normalized := strings.ToLower(strings.TrimSpace(memoryType))
if _, ok := validMemoryTypes[normalized]; !ok {
return ""
}
return normalized
}
// IsValidDecisionAction 校验决策动作是否合法。
func IsValidDecisionAction(action string) bool {
normalized := strings.ToUpper(strings.TrimSpace(action))
_, ok := validDecisionActions[normalized]
return ok
}