mirror of
https://github.com/7836246/cursor2api.git
synced 2026-09-03 07:20:02 +08:00
fix: 修复长响应误判为拒绝 + 减少 tolerantParse 日志噪音 (v2.5.5)
- 工具模式下长文本(8654 chars)正文碰巧含拒绝关键词被误判,导致 Claude Code 死循环 - 截断响应(max_tokens)跳过拒绝检测;长响应仅检查前300字符 - tolerantParse 对非工具调用的 JSON 代码块降为 warn 级别日志
This commit is contained in:
17
CHANGELOG.md
17
CHANGELOG.md
@@ -1,5 +1,22 @@
|
||||
# Changelog
|
||||
|
||||
## v2.5.5 (2026-03-12)
|
||||
|
||||
### 🐛 修复长响应误判为拒绝
|
||||
|
||||
- **问题**:工具模式下,模型输出长文本(如 8654 字符的深度分析报告),正文中碰巧包含 `无法提供...信息`、`工具调用场景`、`即报错` 等拒绝检测关键词,导致整个响应被替换为无意义的引导文本 `"I understand the request..."`,进而 Claude Code 陷入死循环
|
||||
- **修复策略**:
|
||||
- 截断响应(`stop_reason=max_tokens`)完全跳过拒绝检测 — 8654 字符的响应不可能是拒绝
|
||||
- 长响应(≥ 500 字符)仅检查**前 300 字符**是否包含拒绝模式 — 拒绝一定在开头
|
||||
- 短响应(< 500 字符)保持全文检测 — 真正的拒绝回复通常很短
|
||||
- 流式和非流式处理均已修复
|
||||
|
||||
### 🔇 减少 tolerantParse 日志噪音
|
||||
|
||||
- 模型输出中的普通 JSON 代码块(如含正则 `[\s\S]*?` 的代码示例)不再打印 `error` 级别日志
|
||||
- 仅当内容包含 `"tool"` / `"name"` 键(疑似工具调用)时才报 error,其余降为 `warn` 级别
|
||||
|
||||
---
|
||||
## v2.5.4 (2026-03-11)
|
||||
|
||||
### 🌐 内网代理支持 (Issue #17)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cursor2api",
|
||||
"version": "2.5.4",
|
||||
"version": "2.5.5",
|
||||
"description": "Proxy Cursor docs AI to Anthropic Messages API for Claude Code",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -640,7 +640,13 @@ export function parseToolCalls(responseText: string): {
|
||||
blocksToRemove.push({ start: blockStart, end: closingPos + 3 });
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[Converter] tolerantParse 失败:', e);
|
||||
// 仅当内容看起来像工具调用时才报 error,否则可能只是普通 JSON 代码块(代码示例等)
|
||||
const looksLikeToolCall = /["'](?:tool|name)["']\s*:/.test(jsonContent);
|
||||
if (looksLikeToolCall) {
|
||||
console.error('[Converter] tolerantParse 失败(疑似工具调用):', e);
|
||||
} else {
|
||||
console.warn(`[Converter] 跳过非工具调用的 json 代码块 (${jsonContent.length} chars)`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 没有闭合 ``` — 代码块被截断,尝试解析已有内容
|
||||
|
||||
@@ -728,7 +728,14 @@ Continue EXACTLY from where you stopped. DO NOT repeat any content already gener
|
||||
// We must send the remaining unsent fullResponse.
|
||||
let textToSend = fullResponse;
|
||||
|
||||
if (isRefusal(fullResponse)) {
|
||||
// ★ 仅对短响应或开头明确匹配拒绝模式的响应进行压制
|
||||
// 长响应(如模型在写报告)中可能碰巧包含某个宽泛的拒绝关键词,不应被误判
|
||||
// 截断响应(stopReason=max_tokens)一定不是拒绝
|
||||
const isShortResponse = fullResponse.trim().length < 500;
|
||||
const startsWithRefusal = isRefusal(fullResponse.substring(0, 300));
|
||||
const isActualRefusal = stopReason !== 'max_tokens' && (isShortResponse ? isRefusal(fullResponse) : startsWithRefusal);
|
||||
|
||||
if (isActualRefusal) {
|
||||
console.log(`[Handler] Supressed complete refusal without tools: ${fullResponse.substring(0, 100)}...`);
|
||||
textToSend = 'I understand the request. Let me proceed with the appropriate action. Could you clarify what specific task you would like me to perform?';
|
||||
}
|
||||
@@ -859,7 +866,11 @@ async function handleNonStream(res: Response, cursorReq: CursorChatRequest, body
|
||||
}
|
||||
} else {
|
||||
let textToSend = fullText;
|
||||
if (isRefusal(fullText)) {
|
||||
// ★ 同样仅对短响应或开头匹配的进行拒绝压制
|
||||
const isShort = fullText.trim().length < 500;
|
||||
const startsRefusal = isRefusal(fullText.substring(0, 300));
|
||||
const isRealRefusal = stopReason !== 'max_tokens' && (isShort ? isRefusal(fullText) : startsRefusal);
|
||||
if (isRealRefusal) {
|
||||
console.log(`[Handler] Supressed pure text refusal (non-stream): ${fullText.substring(0, 100)}...`);
|
||||
textToSend = 'Let me proceed with the task.';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user