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
@@ -83,6 +83,9 @@ func (h *hub) streamChat(ctx context.Context, payload any) SubmitResult {
|
||||
// clientMsgId 被判重(design/16 §4.5、§5.1;协同 G4)。空 clientMsgId 是
|
||||
// 空操作,因此老客户端的行为逐字不变。
|
||||
ctx = withClientMsgHint(withMode(withDeviceID(ctx, req.DeviceID), req.Mode), clientMsgHint{ClientMsgID: req.ClientMsgID})
|
||||
// 可选的并行声明走同一条 ctx 通道(design/15 §2.5):false 是空操作,
|
||||
// 省略 `parallel` 的老客户端与加入该字段之前逐字相同。
|
||||
ctx = withParallelTurn(ctx, req.Parallel)
|
||||
env := h.envResolver.owner(ctx, req.SessionID)
|
||||
if h.orchestrator != nil {
|
||||
return h.orchestrator.Submit(ctx, req.SessionID, msgs, env)
|
||||
@@ -98,11 +101,20 @@ func (h *hub) streamChat(ctx context.Context, payload any) SubmitResult {
|
||||
// message.*/thinking.*/tool_call.* 事件扇出,因此只有非流式路径才发出
|
||||
// 一整段文本 chunk 以维持流契约。
|
||||
func (h *hub) runTurn(ctx context.Context, sessionID string, msgs []model.Message, env loop.SessionEnv) (*loop.Result, error) {
|
||||
turnID := newTurnID()
|
||||
// 该路的 turnId 优先取 ctx 上已有的那个(design/15 §2.5.9 的 ⚠️ 必改项):
|
||||
// 编排器在**启动该路之前**就生成了它,并把同一份 id 交给 runTurn —— 因为它
|
||||
// 发出(后续切片的)`turn_begin` 帧时必须报出这个 id,而客户端按 turnId
|
||||
// 两级分桶。若这里再自己生成一个,两处 id 不同,分桶会立即失配。
|
||||
//
|
||||
// 未挂载(子代理 / 后台思考 / 未接编排器的旧回退路径)时才新铸一个,因此
|
||||
// 单路路径的帧上仍是一个**每回合全新的随机 id** ⇒ 对客户端无可见差异。
|
||||
turnID := loop.TurnIDFrom(ctx)
|
||||
if turnID == "" {
|
||||
turnID = newTurnID()
|
||||
}
|
||||
// 把轮次身份串进运行上下文,使 loop 把它盖在它发出的每个流式
|
||||
// 事件上(message.*/thinking.*/tool_call.*);hub 随后把它放到
|
||||
// 下行帧上。每一轮都取全新的 id —— 一个会话最多一个在途轮次
|
||||
// —— 因此客户端能把一串增量关联到同一个助手轮次
|
||||
// 下行帧上,客户端因此能把一串增量关联到同一个助手轮次
|
||||
// (design/16 §4.2)。
|
||||
ctx = loop.WithTurnID(ctx, turnID)
|
||||
if h.runner == nil || len(msgs) == 0 {
|
||||
@@ -222,6 +234,12 @@ type chatStreamRequest struct {
|
||||
// 上行帧仍有 4096 字节硬上限 ⇒ 文档实际上走 REST;这里保持同形解析,
|
||||
// 使两条入口不会分叉(解析见 resolveDocuments)。
|
||||
Documents []chatDocumentRef `json:"documents,omitempty"`
|
||||
// Parallel 是「把本条作为**独立一路**处理」的显式声明(design/15 §2.5,
|
||||
// 主人 2026-10-05 裁决 B):它可选,只有会话正忙且并发额度未满时才被照准
|
||||
// (额度满 ⇒ 落回排队并回 `pending_ack{reason:"parallel_limit"}`)。
|
||||
// 省略它时行为与加入该字段之前逐字相同 —— 服务端**不做**启发式自动升级,
|
||||
// 触发权在客户端。
|
||||
Parallel bool `json:"parallel,omitempty"`
|
||||
}
|
||||
|
||||
// resolveDocuments 解析本回合携带的文档引用(design/24 §4.4/§6.1)。一条都不带
|
||||
|
||||
Reference in new issue
Block a user