From 4bd6066dda451e92b1986eb41d12e76f61a23e30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=B5=B7?= <7836246@qq.com> Date: Thu, 19 Mar 2026 09:14:29 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=A3=80=E6=B5=8B=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E9=80=80=E5=8C=96=E5=BE=AA=E7=8E=AF=E8=BE=93=E5=87=BA=E5=B9=B6?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E4=B8=AD=E6=AD=A2=E6=B5=81=20(#66)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 当 Cursor 后端模型陷入退化循环时(不断重复输出 、
等 短文本标记),会导致内容输出一点就停止、后台日志充斥大量 重复标记的现象。 修复方案:在 cursor-client.ts SSE 解析层加入退化重复检测器: - 跟踪连续相同的 text-delta(仅检测 ≤20 字符的短 token) - 同一 delta 连续出现 ≥8 次时判定为退化循环 - 立即中止流(reader.cancel),保留已收到的有效内容 - 外层 sendCursorRequest 识别 DEGENERATE_LOOP_ABORTED 后 不再重试(重试也会重蹈覆辙) 所有流式路径(Anthropic handler、OpenAI handler、续写) 自动受保护,无需逐个修改。 --- src/cursor-client.ts | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/cursor-client.ts b/src/cursor-client.ts index bea6c96..829366a 100644 --- a/src/cursor-client.ts +++ b/src/cursor-client.ts @@ -58,6 +58,8 @@ export async function sendCursorRequest( } catch (err) { // 外部主动中止不重试 if (externalSignal?.aborted) throw err; + // ★ 退化循环中止不重试 — 已有的内容是有效的,重试也会重蹈覆辙 + if (err instanceof Error && err.message === 'DEGENERATE_LOOP_ABORTED') return; const msg = err instanceof Error ? err.message : String(err); console.error(`[Cursor] 请求失败 (${attempt}/${maxRetries}): ${msg.substring(0, 100)}`); if (attempt < maxRetries) { @@ -126,6 +128,14 @@ async function sendCursorRequestInner( const decoder = new TextDecoder(); let buffer = ''; + // ★ 退化重复检测器 (#66) + // 模型有时会陷入循环,不断输出 、
等无意义标记 + // 检测原理:跟踪最近的连续相同 delta,超过阈值则中止流 + let lastDelta = ''; + let repeatCount = 0; + const REPEAT_THRESHOLD = 8; // 同一 delta 连续出现 8 次 → 退化 + let degenerateAborted = false; + while (true) { const { done, value } = await reader.read(); if (done) break; @@ -144,11 +154,44 @@ async function sendCursorRequestInner( try { const event: CursorSSEEvent = JSON.parse(data); + + // ★ 退化重复检测:当模型重复输出同一短文本片段时中止 + if (event.type === 'text-delta' && event.delta) { + const trimmedDelta = event.delta.trim(); + // 只检测短 token(长文本重复是正常的,比如重复的代码行) + if (trimmedDelta.length > 0 && trimmedDelta.length <= 20) { + if (trimmedDelta === lastDelta) { + repeatCount++; + if (repeatCount >= REPEAT_THRESHOLD) { + console.warn(`[Cursor] ⚠️ 检测到退化循环: "${trimmedDelta}" 已连续重复 ${repeatCount} 次,中止流`); + degenerateAborted = true; + // 不再转发此 delta,直接中止 + reader.cancel(); + break; + } + } else { + lastDelta = trimmedDelta; + repeatCount = 1; + } + } else { + // 长文本或空白 → 重置计数 + lastDelta = ''; + repeatCount = 0; + } + } + onChunk(event); } catch { // 非 JSON 数据,忽略 } } + + if (degenerateAborted) break; + } + + // ★ 退化循环中止后,抛出特殊错误让外层 sendCursorRequest 不再重试 + if (degenerateAborted) { + throw new Error('DEGENERATE_LOOP_ABORTED'); } // 处理剩余 buffer