66 lines
3.8 KiB
Go
66 lines
3.8 KiB
Go
package gateway
|
||
|
||
import (
|
||
"context"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/transcript"
|
||
)
|
||
|
||
// 本文件拥有「一个新回合的对话上下文从哪里来」的网关侧**决策与时刻**
|
||
// (design/15 §2.2/§4「上下文有界:只保留最近 N 条上下文」、design/02 §1 主会话
|
||
// 承载完整上下文、design/22 §7-5)。重建本身(读最近 N 条 + 翻成模型消息)住在
|
||
// internal/runtime/transcript —— 它与渠道桥的 IM 主人私聊回合是**同一份实现**,
|
||
// 两条车道的模型可见历史因此不会漂移(IM 与 Web 只在呈现形态上不同:IM 只发 chat 文本)。
|
||
//
|
||
// 一个回合此前只从刚发送的消息开始,使模型完全看不到它正在延续的对话;更早回合的
|
||
// 助手话语也没有记录是谁说的,因此切换进会话的角色会把另一个角色的话读成自己的。
|
||
//
|
||
// 播种修好了这两半:新回合携带该会话最近的持久化消息,且每条助手消息都被说话的角色
|
||
// 标注(model.Message.SenderName),使 loop 的上下文组装能归属它
|
||
// (loop/attribution.go)。消息上只走标签 —— 没有任何消息体被改写。
|
||
//
|
||
// 范围说明:被播种的窗口就是该对话,因此 loop 的逐步后台记忆抽取现在读的是这些近期
|
||
// 消息,而不是单条刚发送的消息。抽取出的条目仍归到该回合的角色名下(loop.persistMemories,
|
||
// design/22 §4.2),且同一内容的重复抽取会被抽取器去重,但一个在另一角色活跃时首次说出
|
||
// 的事实,若当时从未被抽取,现在就可能被当前角色拾取。把抽取输入收窄为当前回合的消息是
|
||
// 一个独立的决定,留作后续跟进。
|
||
|
||
// startTurnMessages 返回新回合运行其上的对话:该会话已播种的转录,后跟客户端刚发送的
|
||
// 消息。转录不可用时,调用方的消息原样返回,这正是既有行为。
|
||
func (o *Orchestrator) startTurnMessages(ctx context.Context, sessionID string, messages []model.Message) []model.Message {
|
||
seed := transcript.Read(ctx, o.history, sessionID)
|
||
if len(seed) == 0 {
|
||
return messages
|
||
}
|
||
return concatMessages(seed, messages...)
|
||
}
|
||
|
||
// seedTurnLocked 为新回合播种对话(持久化转录在刚发送的消息之前),并把播种后的
|
||
// 尾部记进会话状态。**必须在 o.mu 下调用**(它读写 st.tail;与 admitLocked /
|
||
// claimClientMsgLocked 同一约定),调用方负责解锁 —— 因此它从
|
||
// session_orchestrator.go 的 submitInternal 里拆出来,让那个函数留在「函数 ≤80
|
||
// 行」的硬约束内。
|
||
//
|
||
// 播种的时刻是刻意的:它在 persistUserMessages 写入新消息**之前**,因此刚发送的
|
||
// 消息绝不在上下文中重复(design/15 §2.2/§4、design/22 §7-5)。没有历史(或空
|
||
// 会话)的存储让该回合只使用调用方给出的消息。它是在状态锁下的有界索引读取
|
||
// (transcript.ContextCount 行),而状态锁从不会被运行中的回合持有,因此它无法把
|
||
// Submit 堵在回合后面。
|
||
func (o *Orchestrator) seedTurnLocked(ctx context.Context, st *sessionState, sessionID string, messages []model.Message) []model.Message {
|
||
turn := o.startTurnMessages(ctx, sessionID, messages)
|
||
st.tail = o.boundedTail(turn)
|
||
return turn
|
||
}
|
||
|
||
// assistantTailMessage 为一个已结算回合的回复构建续跑尾部
|
||
// 条目,并用说话的角色标注它(design/22 §7-5):该尾部是
|
||
// 播种转录的内存孪生,因此之后切换进来的角色必须能
|
||
// 在紧接着的那个排队回合上,把更早角色的回复与自己的话
|
||
// 区分开。
|
||
func assistantTailMessage(text, personaID string) model.Message {
|
||
msg := model.TextMessage(model.RoleAssistant, text)
|
||
msg.SenderName = personaID
|
||
return msg
|
||
}
|