mirror of
https://github.com/7836246/cursor2api.git
synced 2026-05-07 22:27:15 +08:00
fix: 检测模型退化循环输出并自动中止流 (#66)
当 Cursor 后端模型陷入退化循环时(不断重复输出 </s>、</br> 等 短文本标记),会导致内容输出一点就停止、后台日志充斥大量 重复标记的现象。 修复方案:在 cursor-client.ts SSE 解析层加入退化重复检测器: - 跟踪连续相同的 text-delta(仅检测 ≤20 字符的短 token) - 同一 delta 连续出现 ≥8 次时判定为退化循环 - 立即中止流(reader.cancel),保留已收到的有效内容 - 外层 sendCursorRequest 识别 DEGENERATE_LOOP_ABORTED 后 不再重试(重试也会重蹈覆辙) 所有流式路径(Anthropic handler、OpenAI handler、续写) 自动受保护,无需逐个修改。
This commit is contained in:
@@ -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)
|
||||
// 模型有时会陷入循环,不断输出 </s>、</br> 等无意义标记
|
||||
// 检测原理:跟踪最近的连续相同 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
|
||||
|
||||
Reference in New Issue
Block a user