mslxdff 0.1.124 → 0.1.125

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mslxdff",
3
- "version": "0.1.124",
3
+ "version": "0.1.125",
4
4
  "description": "测试项目,请勿使用。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,6 +8,12 @@ import { compatFetch, timeoutSignal, getUndici } from "../compat.js";
8
8
  const PEER_TIMEOUT_MS = 30_000;
9
9
  const PEER_STATUS_TIMEOUT_MS = 2_000;
10
10
  const DEFAULT_PEER_CONNECT_TIMEOUT_MS = 3_000;
11
+ // SSE 响应体数据间隔超时(undici bodyTimeout)。慢模型首块后可静默思考远超 30s,
12
+ // 沿用 30s 会把组内转发的中继活流掐成 "terminated"(实测首块后 ~31s 必断,本地直连
13
+ // 无此限制——组内因此比直连"不流畅")。与应用层 MAX_STREAM_MS(120s) 对齐:
14
+ // undici 层只做最后兜底,掐流交给应用层的首块/总时长策略。
15
+ const PEER_BODY_TIMEOUT_MS = 120_000;
16
+ export { PEER_BODY_TIMEOUT_MS };
11
17
 
12
18
  function peerConnectTimeoutMs() {
13
19
  const n = Number(process.env.MSLXDFF_PEER_CONNECT_TIMEOUT_MS);
@@ -25,7 +31,7 @@ function getPeerDispatcher() {
25
31
  peerDispatcher = new Agent({
26
32
  connect: { timeout: peerConnectTimeoutMs() },
27
33
  headersTimeout: PEER_TIMEOUT_MS,
28
- bodyTimeout: PEER_TIMEOUT_MS,
34
+ bodyTimeout: PEER_BODY_TIMEOUT_MS,
29
35
  keepAliveTimeout: 30_000,
30
36
  keepAliveMaxTimeout: 60_000,
31
37
  });
@@ -104,8 +110,9 @@ export async function peerHealthyModels(peer, { timeoutMs = PEER_STATUS_TIMEOUT_
104
110
  return p;
105
111
  }
106
112
 
107
- async function forwardToPeer(peer, body, model, hops) {
108
- const controller = new AbortController();
113
+ // controller 由调用方持有:赛跑 winner 确定后可主动 abort 其余 in-flight,
114
+ // 避免迟到流挂满 bodyTimeout(并给对方省下无用负载)。30s timer 仅覆盖"等响应头"。
115
+ async function forwardToPeer(peer, body, model, hops, controller = new AbortController()) {
109
116
  const timer = setTimeout(() => controller.abort(), PEER_TIMEOUT_MS);
110
117
  try {
111
118
  const headers = {
@@ -178,23 +185,39 @@ export async function racePeerCandidates(candidates, ctx) {
178
185
  const completed = await new Promise((resolve) => {
179
186
  const order = [];
180
187
  const total = prepared.length;
188
+ const ctrls = new Map(); // url -> controller(winner 后统一取消其余)
181
189
  let settled = false;
182
190
  const finish = (winner) => {
183
191
  if (settled) return;
184
192
  settled = true;
193
+ if (winner) {
194
+ for (const c of ctrls.values()) {
195
+ if (c === winner.ctrl) continue;
196
+ c.killedByUs = true;
197
+ c.abort();
198
+ }
199
+ }
185
200
  resolve({ list: order, winner });
186
201
  };
187
202
  for (const { peer, target } of prepared) {
188
203
  tried.add(peer.url);
204
+ const ctrl = new AbortController();
205
+ ctrls.set(peer.url, ctrl);
189
206
  ctx.evt("peer-request", { peer: peer.url, model: target, hops: ctx.hops + 1 });
190
207
  // 插件 hook:peer:beforeForward — 转发给组员前观察
191
208
  if (ctx.plugins?.length) {
192
209
  runHook(ctx.plugins, "peer:beforeForward", { reqId: ctx.reqId, peer: peer.url, model: target, hops: ctx.hops + 1 }).catch(() => {});
193
210
  }
194
211
  const t0 = performance.now();
195
- forwardToPeer(peer, ctx.body, target, ctx.hops).then((res) => {
212
+ forwardToPeer(peer, ctx.body, target, ctx.hops, ctrl).then((res) => {
196
213
  const latencyMs = Math.round(performance.now() - t0);
197
214
  const failed = res instanceof Error || res.status >= 400;
215
+ // winner 已定后被我们主动取消:静默,不记账不报错(否则污染健康统计与错误日志)
216
+ if (res instanceof Error && ctrl.killedByUs) {
217
+ order.push({ ok: false, peer, target, res, status: 0, killed: true });
218
+ if (order.length === total) finish(null);
219
+ return;
220
+ }
198
221
  ctx.evt("peer-forward", { peer: peer.url, model: target, hops: ctx.hops + 1, latencyMs, ok: !failed });
199
222
  // 插件 hook:peer:result — 组员响应后观察
200
223
  if (ctx.plugins?.length) {
@@ -211,7 +234,7 @@ export async function racePeerCandidates(candidates, ctx) {
211
234
  recordLater(() => ctx.peers.recordResult(peer.url, { ok: false }));
212
235
  if (order.length === total) finish(null);
213
236
  } else {
214
- const entry = { ok: true, peer, target, res, latencyMs };
237
+ const entry = { ok: true, peer, target, res, latencyMs, ctrl };
215
238
  order.push(entry);
216
239
  if (!settled) {
217
240
  // 第一个成功立即返回:不等慢/黑洞候选(迟到者的记账由各分支自理)