后端: 1. 阶段 1.5/1.6 收口 llm-service / rag-service,统一模型出口与检索基础设施入口,清退 backend/infra/llm 与 backend/infra/rag 旧实现; 2. 同步更新相关调用链与微服务迁移计划文档
84 lines
3.3 KiB
Go
84 lines
3.3 KiB
Go
package service
|
||
|
||
import (
|
||
"time"
|
||
|
||
memorymodel "github.com/LoveLosita/smartflow/backend/memory/model"
|
||
ragservice "github.com/LoveLosita/smartflow/backend/services/rag"
|
||
)
|
||
|
||
// buildReadScopedItemQuery 构造读侧统一使用的 MySQL 查询条件。
|
||
//
|
||
// 职责边界:
|
||
// 1. 只负责把 RetrieveRequest 映射成“读侧作用域”查询参数;
|
||
// 2. 不负责真正查库,也不负责排序、裁剪或注入;
|
||
// 3. conversation_id 字段在这里刻意不参与过滤,仅保留在记忆记录元数据里供审计与溯源使用。
|
||
//
|
||
// 步骤化说明:
|
||
// 1. 读侧始终按 user_id 作为硬隔离边界,避免跨用户串记忆。
|
||
// 2. assistant_id / run_id 仍允许参与过滤,因为它们表达的是助手实例与执行轮次边界,而不是“是否跨对话召回”的问题。
|
||
// 3. conversation_id 明确置空,原因是聊天上下文窗口已经覆盖同对话信息;记忆读侧的价值主要在跨对话补充。
|
||
func buildReadScopedItemQuery(
|
||
req memorymodel.RetrieveRequest,
|
||
now time.Time,
|
||
statuses []string,
|
||
limit int,
|
||
) memorymodel.ItemQuery {
|
||
return memorymodel.ItemQuery{
|
||
UserID: req.UserID,
|
||
ConversationID: "",
|
||
AssistantID: req.AssistantID,
|
||
RunID: req.RunID,
|
||
Statuses: statuses,
|
||
MemoryTypes: normalizeRetrieveMemoryTypes(req.MemoryTypes),
|
||
IncludeGlobal: true,
|
||
OnlyUnexpired: true,
|
||
Limit: limit,
|
||
Now: now,
|
||
}
|
||
}
|
||
|
||
// buildReadScopedRAGRequest 构造读侧统一使用的 RAG 检索请求。
|
||
//
|
||
// 职责边界:
|
||
// 1. 只负责生成 memory 检索请求,不负责执行向量检索;
|
||
// 2. 不负责阈值外的重排、fallback 或去重;
|
||
// 3. conversation_id 字段同样只保留在文档 metadata 中,不再作为聊天读侧的硬过滤条件。
|
||
//
|
||
// 步骤化说明:
|
||
// 1. user_id 仍是唯一必须保留的硬过滤条件,确保召回范围限定在当前用户。
|
||
// 2. conversation_id 明确置空,避免旧对话记忆在进入相似度计算前就被 metadata filter 提前挡掉。
|
||
// 3. assistant_id / run_id 保持透传,方便后续若存在多助手场景时继续做更细粒度隔离。
|
||
func buildReadScopedRAGRequest(
|
||
req memorymodel.RetrieveRequest,
|
||
topK int,
|
||
threshold float64,
|
||
) ragservice.MemoryRetrieveRequest {
|
||
return ragservice.MemoryRetrieveRequest{
|
||
Query: req.Query,
|
||
TopK: topK,
|
||
Threshold: threshold,
|
||
Action: "search",
|
||
UserID: req.UserID,
|
||
ConversationID: "",
|
||
AssistantID: req.AssistantID,
|
||
RunID: req.RunID,
|
||
MemoryTypes: normalizeRetrieveMemoryTypes(req.MemoryTypes),
|
||
}
|
||
}
|
||
|
||
// shouldReturnSemanticRAGResult 判断当前是否可以直接采用 RAG 结果。
|
||
//
|
||
// 职责边界:
|
||
// 1. 只负责表达“RAG 是否足以短路后续 MySQL fallback”这一条业务规则;
|
||
// 2. 不负责执行任何检索,也不负责日志记录;
|
||
// 3. 返回 false 不代表错误,只代表调用方应继续尝试数据库兜底。
|
||
//
|
||
// 步骤化说明:
|
||
// 1. RAG 报错时,一定不能短路,必须继续走 MySQL fallback。
|
||
// 2. RAG 0 命中时,同样不能短路;否则会把“成功执行但没有候选”误当成最终结果。
|
||
// 3. 只有“无报错且结果非空”时,才允许直接返回 RAG 结果。
|
||
func shouldReturnSemanticRAGResult(items []memorymodel.ItemDTO, err error) bool {
|
||
return err == nil && len(items) > 0
|
||
}
|