feat(gateway,loop): 忙时并行回合基础管线(I1′ 有条件放宽)
- turnId 由编排器生成、loop Run 复用(硬前提:否则 turn_begin 与流帧各铸一个,客户端两级分桶立即失配) - StateSink 增带 turnId 的变体(未接线时退化为现签名,不丢状态帧) - 上行 chat.parallel(WS chat 帧 + REST 三入口同形;省略 = 逐字不变) - 单会话额度默认 2 / 硬上限 3 / <2 关闭;额度满或未声明 ⇒ 落回既有排队语义 - I1′:checkpoint 归属、历史配对、忙态计数三处从会话级单例升为路级 - session_idle 收紧为「所有在途路收尾且 pending 空」(仅并行时生效;单路逐字不变) - 预留接口(形状定死、无实现无接线):tool/legfile.go 的 FileClaimer / ErrFileBusy / WithLegID - 顺手修一个真 bug:并行路 ctx 派生自提交请求作用域,REST 同步入口一写响应就把它连坐杀死(改 context.WithoutCancel 派生) - 23 个新用例(含 -race -count=3);变异 6 条(turnId 复用 / idle 时点 / 并行升级 / checkpoint 键 / 编排器交 id / pending_ack reason) - 契约变更点 10 条待监工升 v0.65(本人未改 Docs/**)
This commit is contained in:
1 parent
7d2ff74cfc
commit
89e13cf53d
23 files changed
+1921
-275
No files matched your search
@@ -77,6 +77,9 @@ func (s *Server) submitChatStream(ctx context.Context, sessionID string, req cha
|
||||
"mode": req.Mode,
|
||||
"clientMsgId": req.ClientMsgID,
|
||||
"documents": req.Documents,
|
||||
// parallel 与 WS chat 帧**同形**(design/15 §2.5.2):三条入口
|
||||
//(REST 同步 / REST stream / WS chat)不能对同一条消息有不同口径。
|
||||
"parallel": req.Parallel,
|
||||
}
|
||||
if s.hub == nil || s.hub.orchestrator == nil {
|
||||
// 未接编排器:回退到既有的总线触发路径(未接线的 hub 保持完全相同的
|
||||
@@ -123,8 +126,9 @@ func (s *Server) submitChatTurn(w http.ResponseWriter, r *http.Request, req chat
|
||||
// 来自会话的绑定(design/22 §4.5)。
|
||||
msgs := chatTurnMessages(req.Text, req.Image, docs)
|
||||
env := s.envResolver.owner(r.Context(), req.SessionID)
|
||||
// 客户端的 deviceID、行为模式与可选幂等键(协同 G4)都经 ctx 串进编排器。
|
||||
ctx := withClientMsgHint(withMode(withDeviceID(r.Context(), req.DeviceID), req.Mode), clientMsgHint{ClientMsgID: req.ClientMsgID})
|
||||
// 客户端的 deviceID、行为模式与可选幂等键(协同 G4)都经 ctx 串进编排器;
|
||||
// 可选的并行声明同理(design/15 §2.5)。
|
||||
ctx := withParallelTurn(withClientMsgHint(withMode(withDeviceID(r.Context(), req.DeviceID), req.Mode), clientMsgHint{ClientMsgID: req.ClientMsgID}), req.Parallel)
|
||||
if s.hub.orchestrator != nil {
|
||||
sr, res := s.hub.orchestrator.SubmitSync(ctx, req.SessionID, msgs, env)
|
||||
// 重复到达(同一个 clientMsgId):明确告知既有消息 id,而不是再跑一遍
|
||||
@@ -147,6 +151,13 @@ func (s *Server) submitChatTurn(w http.ResponseWriter, r *http.Request, req chat
|
||||
writeJSON(w, http.StatusAccepted, map[string]any{"queued": true, "queueDepth": sr.QueueDepth})
|
||||
return
|
||||
}
|
||||
// 升为独立一路(design/15 §2.5):第二路在后台跑,本次请求拿不到同步
|
||||
// 回复,因此同样回 202 + 明确的判定 —— 绝不回一个 res 为 nil 的 200
|
||||
//(那会让客户端以为拿到了空回复)。
|
||||
if sr.Parallel {
|
||||
writeJSON(w, http.StatusAccepted, map[string]any{"parallel": true, "turnId": sr.TurnID})
|
||||
return
|
||||
}
|
||||
if sr.Err != nil {
|
||||
s.log.Error("chat run", "err", sr.Err, "session", req.SessionID)
|
||||
writeError(w, apperr.Wrap(apperr.ErrInternal, "chat_failed", "chat failed"))
|
||||
@@ -179,11 +190,13 @@ func detachedTurnContext(parent context.Context) context.Context {
|
||||
|
||||
// writeChatStreamAck 把 stream:true 的提交判定写成响应(协同 G2 顺带项 + G4)。
|
||||
// 此前它无条件回 200 {"stream":true},于是「排队满」与「重复到达」都只能靠客户端
|
||||
// 猜;现在四种判定各有明确的状态码与载荷:
|
||||
// 猜;现在五种判定各有明确的状态码与载荷:
|
||||
// - 重复到达:200 {duplicate:true, messageId}(没有新回合、没有新帧,客户端用
|
||||
// messageId 把本地乐观气泡对齐到既有消息)
|
||||
// - 待处理队列满:409 {error:{code:"chat_busy"}}(与同步路径同一口径)
|
||||
// - 会话正忙、已续接排队:202 {stream:true, queued:true, queueDepth}
|
||||
// - 已升为独立一路:200 {stream:true, parallel:true, turnId}(design/15 §2.5;
|
||||
// 该路的帧随后经 WS 到达,turnId 就是它的权威标识)
|
||||
// - 已开始:200 {stream:true}
|
||||
func (s *Server) writeChatStreamAck(w http.ResponseWriter, sr SubmitResult, sessionID string) {
|
||||
switch {
|
||||
@@ -193,6 +206,8 @@ func (s *Server) writeChatStreamAck(w http.ResponseWriter, sr SubmitResult, sess
|
||||
writeError(w, apperr.Wrap(apperr.ErrConflict, "chat_busy", "会话正忙,待处理消息已满"))
|
||||
case sr.Pending:
|
||||
writeJSON(w, http.StatusAccepted, map[string]any{"stream": true, "queued": true, "queueDepth": sr.QueueDepth})
|
||||
case sr.Parallel:
|
||||
writeJSON(w, http.StatusOK, map[string]any{"stream": true, "parallel": true, "turnId": sr.TurnID})
|
||||
default:
|
||||
writeJSON(w, http.StatusOK, map[string]any{"stream": true})
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user