package memory import ( "encoding/json" "strings" ) // SourceRef 是 source_conversation 落库值的结构化形状(裁决 // 04.4.3,design/03 §2 溯源):一条记忆由哪次会话的哪条消息 // 提取而来。两个 id 都由持久化层(提取调用点)从运行上下文 // 盖真实值,绝不由模型生成,因此「可回溯到哪次对话」是 // 机器可验证的,而不是 LLM 自由文本。 type SourceRef struct { // SessionID 是提取发生时的会话 id(Run 的 sessionID:Web // 主/次级会话或渠道会话)。 SessionID string `json:"session_id"` // MessageID 是触发提取的消息标识:gateway 在 Run 前挂载的 // turn id;调用方未挂载时为空,此时溯源只到会话粒度。 MessageID string `json:"message_id"` } // FormatSourceConversation 把真实 session/message id 编码为 // source_conversation 的落库值:固定键序的紧凑 JSON,可被 // ParseSourceConversation 解回。两个 id 都为空时返回 ""(无 // 溯源)。纯字符串字段结构的 Marshal 不会失败;防御性兜底 // 返回 ""。 func FormatSourceConversation(sessionID, messageID string) string { ref := SourceRef{ SessionID: strings.TrimSpace(sessionID), MessageID: strings.TrimSpace(messageID), } if ref.SessionID == "" && ref.MessageID == "" { return "" } b, err := json.Marshal(ref) if err != nil { return "" } return string(b) } // ParseSourceConversation 解码落库的 source_conversation。ok 为 // false 表示该值不是结构化溯源 —— 裁决前由 LLM 自由文本填写 // 的遗留行会落在这里 —— 调用方据此区分可机器回溯的行与 // 遗留行,而不是把自由文本误当真实 id。解出的两个字段都为 // 空同样返回 false:没有真实 id 的行不构成溯源。 func ParseSourceConversation(s string) (SourceRef, bool) { s = strings.TrimSpace(s) if !strings.HasPrefix(s, "{") { return SourceRef{}, false } var ref SourceRef if err := json.Unmarshal([]byte(s), &ref); err != nil { return SourceRef{}, false } if ref.SessionID == "" && ref.MessageID == "" { return SourceRef{}, false } return ref, true }