feat(chat): chat_messages.turn_id 列 + DTO(写入接线留待下一步)
迁移 0018:ALTER TABLE chat_messages ADD COLUMN turn_id TEXT NOT NULL DEFAULT ''
(与 persona_id / client_msg_id / document 同族:不用 NULL,空串 = 不属于任何已知回合 ⇒ 旧行零迁移、键省略)。
存储层全链路带 turn_id(列清单唯一一份 ⇒ List/ListAfter/Recent/Search/FindByClientMsgID 自动同源);
新增 history/turn_id.go:ensureTurnIDColumn(既有库幂等前滚)+ SetMessagesTurnID
(第二段补写:单事务参数化、先到先得 WHERE turn_id = ''、幂等、未知 id 忽略)。
DTO:historyMessage.TurnID(json turnId,omitempty)⇒ REST 列表 / admin 渠道日志 / WS 握手回放 /
message_persisted / channel_persisted / message_edited 一次全覆盖。
写入面:persistUserMessages 读 ctx 回合标识;persistAssistantReply 增可选尾参(既有调用点零改动);
withTurnID / turnIDFrom / assignTurnID / turnIDOfLeg。
⚠️ 编排器把 turnId 交给落库的 4 处(新起一路 / 续跑路 / 被 steer 吃进 / 助手回复)尚未接线
(那些文件当时被另一路占用);当前生产行为 turn_id 仍为 ''。清单已在切片报告里精确到文件:行。
13 新用例 + 5 条变异(INSERT 不带列 / DTO 不回填 / 去掉 omitempty / 去掉先到先得 / 不回滚前滚)。
为守 session_persist.go ≤500 行,turnId 一节拆到新文件 session_turn_id.go。
This commit is contained in:
1 parent
354b08a45f
commit
d64b26d499
12 files changed
+950
-16
No files matched your search
@@ -61,6 +61,11 @@ func modeFrom(ctx context.Context) string {
|
||||
// 与编排器预分配的消息 id:写入因此可被 (session_id, client_msg_id) 的唯一索引
|
||||
// 判重。省略幂等键时 ctx 上没有该值,写入与加入这一列之前逐字相同。
|
||||
//
|
||||
// ctx 上若带着本路的回合标识(design/15 §2.5.9,withTurnID),该标识随**写入**
|
||||
// 落进 turn_id 列 —— 这正是「这条路已经把这条消息吃进去了」要留下的配对信息。
|
||||
// 没有它(排队入队、未接线的路径)时列里留空串,由它实际被处理的那一路随后补写
|
||||
// (见 assignTurnID 与 internal/runtime/history/turn_id.go)。
|
||||
//
|
||||
// 它返回刚落库的那些消息(含存储分配的 id 与**只有引用**的 document 列),
|
||||
// 使调用方能把它们挂到本回合上:抽取结论要等回合跑起来才有,只能在回合结束后
|
||||
// 回写进同一列(design/24 §6.1,见 session_document_status.go)。这就是那条
|
||||
@@ -70,6 +75,7 @@ func (o *Orchestrator) persistUserMessages(ctx context.Context, sessionID string
|
||||
return nil
|
||||
}
|
||||
hint := clientMsgHintFrom(ctx)
|
||||
turnID := turnIDFrom(ctx)
|
||||
persisted := make([]history.Message, 0, len(messages))
|
||||
for _, m := range messages {
|
||||
if m.Role != model.RoleUser {
|
||||
@@ -85,7 +91,7 @@ func (o *Orchestrator) persistUserMessages(ctx context.Context, sessionID string
|
||||
if text == "" && image == "" && documents == "" {
|
||||
continue
|
||||
}
|
||||
msg := history.Message{SessionID: sessionID, Role: history.RoleUser, Content: text, Image: image, Document: documents, DeviceID: deviceID, Mode: modeOrDefault(mode), PersonaID: personaID}
|
||||
msg := history.Message{SessionID: sessionID, Role: history.RoleUser, Content: text, Image: image, Document: documents, DeviceID: deviceID, Mode: modeOrDefault(mode), PersonaID: personaID, TurnID: turnID}
|
||||
// 幂等键只标注**第一条**用户消息:一个回合至多只有一条用户消息,而把
|
||||
// 同一个键盖到多条上会让它们互相冲突(唯一索引把它们判成同一条)。
|
||||
if hint.ClientMsgID != "" {
|
||||
@@ -128,11 +134,18 @@ func (o *Orchestrator) persistUserMessages(ctx context.Context, sessionID string
|
||||
// 它是**裸写入**,判定属于 persistTurnReply(session_turn_reply.go):收尾路径
|
||||
// 经后者进入,由它保证「归本代次、且不是被取消的回合一定有一条可见消息」——
|
||||
// 空文本在这里仍然是空操作,因此本函数绝不自作主张编造内容。
|
||||
func (o *Orchestrator) persistAssistantReply(sessionID string, res *loop.Result, deviceID, mode, personaID string) {
|
||||
//
|
||||
// turnID 是本路的回合标识(design/15 §2.5.9),可选:给出即随**写入**落进 turn_id
|
||||
// 列,与它那条用户消息同值,客户端因此能精确配对「回复 ↔ 提问」。省略(未接线的
|
||||
// 调用路径)等价于加入该列之前的行为(列里留空串)。它刻意是可变参数而不是必填
|
||||
// 形参:既有调用点、以及「这一路没有回合标识」的旧路径都不必先传一个空值,而
|
||||
// 已给出的第一个值之外的多余值**被忽略**(一个回合只有一份标识,多给是调用方的
|
||||
// 笔误,绝不据此分叉行为)。
|
||||
func (o *Orchestrator) persistAssistantReply(sessionID string, res *loop.Result, deviceID, mode, personaID string, turnID ...string) {
|
||||
if o.history == nil || res == nil || res.Text == "" {
|
||||
return
|
||||
}
|
||||
full, err := o.history.AppendFull(context.Background(), history.Message{SessionID: sessionID, Role: history.RoleAssistant, Content: res.Text, Mood: res.Mood, DeviceID: deviceID, Mode: modeOrDefault(mode), PersonaID: personaID})
|
||||
full, err := o.history.AppendFull(context.Background(), history.Message{SessionID: sessionID, Role: history.RoleAssistant, Content: res.Text, Mood: res.Mood, DeviceID: deviceID, Mode: modeOrDefault(mode), PersonaID: personaID, TurnID: firstTurnID(turnID)})
|
||||
if err != nil {
|
||||
o.log.Error("orchestrator persist assistant", "err", err, "session", sessionID, "device_id", deviceID)
|
||||
return
|
||||
|
||||
Reference in new issue
Block a user