feat(server): add inference reliability - tool-shim and loop detectors
- ToolShim recovers XML/JSON tool calls from plain-text model output - detectContentRepeat catches same-content loops - detectToolLoop catches repeated tool invocations - detectDoomLoop combines both detectors
This commit is contained in:
68
apps/server/src/services/inference/loop-detectors.ts
Normal file
68
apps/server/src/services/inference/loop-detectors.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
// Loop detectors — detects repetitive patterns in assistant output
|
||||
// that indicate a model is stuck in a loop.
|
||||
|
||||
export interface LoopDetectionResult {
|
||||
isLoop: boolean;
|
||||
reason?: string;
|
||||
confidence: number; // 0-1
|
||||
}
|
||||
|
||||
const REPEATED_PHRASE_MIN_COUNT = 4;
|
||||
const REPEATED_TOOL_MIN_COUNT = 3;
|
||||
|
||||
export function detectContentRepeat(messages: string[]): LoopDetectionResult {
|
||||
if (messages.length < REPEATED_PHRASE_MIN_COUNT) {
|
||||
return { isLoop: false, confidence: 0 };
|
||||
}
|
||||
|
||||
const recent = messages.slice(-REPEATED_PHRASE_MIN_COUNT);
|
||||
const unique = new Set(recent);
|
||||
|
||||
if (unique.size === 1) {
|
||||
return {
|
||||
isLoop: true,
|
||||
reason: `Same content repeated ${REPEATED_PHRASE_MIN_COUNT} times`,
|
||||
confidence: 0.9,
|
||||
};
|
||||
}
|
||||
|
||||
if (unique.size <= 2 && recent.length >= 4) {
|
||||
return {
|
||||
isLoop: true,
|
||||
reason: 'Content oscillating between two variants',
|
||||
confidence: 0.7,
|
||||
};
|
||||
}
|
||||
|
||||
return { isLoop: false, confidence: 0 };
|
||||
}
|
||||
|
||||
export function detectToolLoop(toolNames: string[]): LoopDetectionResult {
|
||||
if (toolNames.length < REPEATED_TOOL_MIN_COUNT) return { isLoop: false, confidence: 0 };
|
||||
|
||||
const recent = toolNames.slice(-REPEATED_TOOL_MIN_COUNT);
|
||||
const unique = new Set(recent);
|
||||
|
||||
if (unique.size === 1) {
|
||||
return {
|
||||
isLoop: true,
|
||||
reason: `Same tool "${recent[0]}" called ${REPEATED_TOOL_MIN_COUNT} times consecutively`,
|
||||
confidence: 0.85,
|
||||
};
|
||||
}
|
||||
|
||||
return { isLoop: false, confidence: 0 };
|
||||
}
|
||||
|
||||
export function detectDoomLoop(
|
||||
messages: string[],
|
||||
toolNames: string[],
|
||||
): LoopDetectionResult {
|
||||
const contentResult = detectContentRepeat(messages);
|
||||
if (contentResult.isLoop) return contentResult;
|
||||
|
||||
const toolResult = detectToolLoop(toolNames);
|
||||
if (toolResult.isLoop) return toolResult;
|
||||
|
||||
return { isLoop: false, confidence: 0 };
|
||||
}
|
||||
Reference in New Issue
Block a user