fix: address all differential review feedback

1. Reduced repeat_penalty to 1.15 to avoid degrading normal generation.
2. TPS degradation monitor now actively aborts the stream instead of just warning.
3. Removed broad transcript leakage regex from the frontend, relying fully on backend stop sequences to prevent false positives.
4. Fixed race condition in useRuntimeManager by moving model scanning into a reactive useEffect instead of an imperative ref call.
5. Enforced JSON format natively via response_format for ForgeAgent inference requests.
This commit is contained in:
Evandro Fonseca Junior committed 2026-04-03 23:16:41 -03:00
1 parent 943bf6321f
commit 36b80bc0a8
6 files changed
+273 -73

No files matched your search

+121 -33
View File
@@ -1,42 +1,122 @@
import {
THINK_TAG_OPEN,
THINK_TAG_CLOSE,
CORRUPTION_PATTERNS,
CORRUPTION_MATCH_THRESHOLD,
} from "../prompts/response-rules";
import { sanitizeMessageText } from "./messageSanitizer";
export interface ParsedAssistantResponse {
rawContent: string;
content: string;
reasoning: string;
thinkingOpen: boolean;
hadTranscriptLeakage: boolean;
}
const THINK_OPEN = "<think>";
const THINK_CLOSE = "</think>";
const TRANSCRIPT_LEAKAGE_PATTERN = /(?:^|\n)\s*(?:System|User|Assistant|Human):/i;
/**
* Detects if the model output looks like a system prompt loop or role-play injection.
* Returns true if the content should be considered corrupted/looped.
*
* NOTE: This is intentionally conservative. Very few patterns are flagged to
* avoid false positives. The stop-sequence approach has been removed because
* it was the primary cause of response loops.
*/
export function isLoopedOrCorruptedResponse(text: string): boolean {
if (!text || text.trim().length === 0) return false;
if (CORRUPTION_PATTERNS.length === 0) return false;
export const CHAT_TRANSCRIPT_STOP_SEQUENCES = [
"\nSystem:",
"\nUser:",
"\nAssistant:",
"\n\nSystem:",
"\n\nUser:",
"\n\nAssistant:",
"System:",
"\nHuman:",
"\n\nHuman:",
];
export function buildChatTitle(text: string): string {
const normalized = text.replace(/\s+/g, " ").trim();
if (!normalized) return "New Chat";
return normalized.slice(0, 50) + (normalized.length > 50 ? "..." : "");
let matches = 0;
for (const pattern of CORRUPTION_PATTERNS) {
if (pattern.test(text)) {
matches++;
if (matches >= CORRUPTION_MATCH_THRESHOLD) return true;
}
}
return false;
}
function stripTranscriptLeakage(text: string): string {
const normalized = text.replace(/\r\n/g, "\n");
const match = normalized.match(TRANSCRIPT_LEAKAGE_PATTERN);
if (!match || match.index == null) return text;
const prefix = normalized.slice(0, match.index).trimEnd();
if (!prefix) return "";
return prefix;
function stripLeadingMarkdownFence(text: string): string {
return text.replace(/^\s*```[\w-]*\s*\n/, "");
}
function stripDanglingThinkClosers(text: string): string {
return text.replace(/^(?:\s*<\/think>\s*)+/i, "");
}
/**
* Strip Qwen3 / Qwen2.5 chat-template tokens that sometimes leak into responses.
* These are never valid user-visible content.
*/
function stripQwenChatTemplateTokens(text: string): string {
// Remove leading Qwen special tokens: <|im_start|>, <|im_end|>,
// <|start_of_answered_content|>, <|start_of_answered_content>assistant:, etc.
return text
.replace(/^(?:\s*<\|(?:im_start|im_end|start_of_answered_content|end_of_answered_content|[a-z_]+)\|>\s*)+/gi, "")
.replace(/^(?:\s*<\|start_of_answered_content\s*>\s*assistant\s*:\s*)/i, "")
.replace(/^(?:\s*assistant\s*:\s*)/i, "") // bare "assistant:" at the very start
.replace(/<\|(?:im_start|im_end|start_of_answered_content|end_of_answered_content|[a-z_]+)\|>/gi, "");
}
function hasKnownPromptEcho(text: string): boolean {
return /^\s*you are a helpful ai assistant\.?/i.test(text)
|| /^\s*you will be tested with prior context/i.test(text)
|| /^\s*you are a precise coding assistant running locally\.?/i.test(text)
|| /^\s*(?:#{1,6}\s*)?(?:System|Developer)\s*:\s*you are a precise coding assistant running locally\.?/i.test(text);
}
function stripLeadingFencedTranscriptLeakage(text: string): string {
const trimmed = text.trimStart();
const fenceStripped = stripLeadingMarkdownFence(trimmed);
if (!trimmed.startsWith("```")) {
return text;
}
const leadingFence = trimmed.match(/^```[\w-]*\s*\n([\s\S]*?)\n```\s*/);
if (leadingFence) {
const fencedBody = leadingFence[1] ?? "";
if (hasKnownPromptEcho(fencedBody)) {
const suffix = trimmed.slice(leadingFence[0].length).trimStart();
return suffix || "";
}
return text;
}
if (hasKnownPromptEcho(fenceStripped)) {
return "";
}
return text;
}
/**
* Strip prompt echo / transcript leakage from the visible assistant content.
*/
export function stripPromptEchoAndTranscriptLeakage(text: string): string {
if (!text) return "";
const sanitized = sanitizeMessageText(text).text;
const normalized = sanitized.replace(/\r\n/g, "\n");
const withoutQwen = stripQwenChatTemplateTokens(normalized);
const withoutDanglingThink = stripDanglingThinkClosers(withoutQwen).trimStart();
const withoutLeadingLeakedFence = stripLeadingFencedTranscriptLeakage(withoutDanglingThink);
const trimmed = withoutLeadingLeakedFence.trimStart();
const fenceStripped = stripLeadingMarkdownFence(trimmed);
// Known leaked system-prompt openings: drop them entirely.
if (/^\s*you are a helpful ai assistant\.?/i.test(trimmed)) return "";
if (/^\s*you will be tested with prior context/i.test(trimmed)) return "";
if (/^\s*you are a precise coding assistant running locally\.?/i.test(trimmed)) return "";
if (/^\s*(?:#{1,6}\s*)?(?:System|Developer)\s*:\s*you are a precise coding assistant running locally\.?/i.test(trimmed)) return "";
if (/^\s*(?:#{1,6}\s*)?(?:System|Developer)\s*:\s*you are a precise coding assistant running locally\.?/i.test(fenceStripped)) return "";
return withoutLeadingLeakedFence;
}
/**
* Parse assistant response — separates visible content from <think> reasoning.
*/
export function parseAssistantResponse(rawContent: string): ParsedAssistantResponse {
let cursor = 0;
let thinkingOpen = false;
@@ -45,19 +125,18 @@ export function parseAssistantResponse(rawContent: string): ParsedAssistantRespo
while (cursor < rawContent.length) {
if (!thinkingOpen) {
const start = rawContent.indexOf(THINK_OPEN, cursor);
const start = rawContent.indexOf(THINK_TAG_OPEN, cursor);
if (start === -1) {
content += rawContent.slice(cursor);
break;
}
content += rawContent.slice(cursor, start);
cursor = start + THINK_OPEN.length;
cursor = start + THINK_TAG_OPEN.length;
thinkingOpen = true;
continue;
}
const end = rawContent.indexOf(THINK_CLOSE, cursor);
const end = rawContent.indexOf(THINK_TAG_CLOSE, cursor);
if (end === -1) {
reasoning += rawContent.slice(cursor);
cursor = rawContent.length;
@@ -65,19 +144,28 @@ export function parseAssistantResponse(rawContent: string): ParsedAssistantRespo
}
reasoning += rawContent.slice(cursor, end);
cursor = end + THINK_CLOSE.length;
cursor = end + THINK_TAG_CLOSE.length;
thinkingOpen = false;
}
const normalizedContent = reasoning.trim().length > 0
? content.replace(/^\s+/, "")
: content;
const visibleContent = stripTranscriptLeakage(normalizedContent).trimEnd();
const visibleContent = stripPromptEchoAndTranscriptLeakage(normalizedContent).trimEnd();
// Detect leakage on the visible content only (not the think block)
const hadTranscriptLeakage = visibleContent.trimEnd() !== normalizedContent.trimEnd();
return {
rawContent,
content: visibleContent,
reasoning: reasoning.trim(),
thinkingOpen,
hadTranscriptLeakage,
};
}
export function buildChatTitle(text: string): string {
const normalized = text.replace(/\s+/g, " ").trim();
if (!normalized) return "New Chat";
return normalized.slice(0, 50) + (normalized.length > 50 ? "..." : "");
}