mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 12:54:07 -07:00
fix(core): Improve loop detection for longer repeating patterns (#12505)
This commit is contained in:
@@ -207,6 +207,82 @@ describe('LoopDetectionService', () => {
|
|||||||
expect(isLoop).toBe(false);
|
expect(isLoop).toBe(false);
|
||||||
expect(loggers.logLoopDetected).not.toHaveBeenCalled();
|
expect(loggers.logLoopDetected).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should detect a loop with longer repeating patterns (e.g. ~150 chars)', () => {
|
||||||
|
service.reset('');
|
||||||
|
const longPattern = createRepetitiveContent(1, 150);
|
||||||
|
expect(longPattern.length).toBe(150);
|
||||||
|
|
||||||
|
let isLoop = false;
|
||||||
|
for (let i = 0; i < CONTENT_LOOP_THRESHOLD + 2; i++) {
|
||||||
|
isLoop = service.addAndCheck(createContentEvent(longPattern));
|
||||||
|
if (isLoop) break;
|
||||||
|
}
|
||||||
|
expect(isLoop).toBe(true);
|
||||||
|
expect(loggers.logLoopDetected).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect the specific user-provided loop example', () => {
|
||||||
|
service.reset('');
|
||||||
|
const userPattern = `I will not output any text.
|
||||||
|
I will just end the turn.
|
||||||
|
I am done.
|
||||||
|
I will not do anything else.
|
||||||
|
I will wait for the user's next command.
|
||||||
|
`;
|
||||||
|
|
||||||
|
let isLoop = false;
|
||||||
|
// Loop enough times to trigger the threshold
|
||||||
|
for (let i = 0; i < CONTENT_LOOP_THRESHOLD + 5; i++) {
|
||||||
|
isLoop = service.addAndCheck(createContentEvent(userPattern));
|
||||||
|
if (isLoop) break;
|
||||||
|
}
|
||||||
|
expect(isLoop).toBe(true);
|
||||||
|
expect(loggers.logLoopDetected).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect the second specific user-provided loop example', () => {
|
||||||
|
service.reset('');
|
||||||
|
const userPattern =
|
||||||
|
'I have added all the requested logs and verified the test file. I will now mark the task as complete.\n ';
|
||||||
|
|
||||||
|
let isLoop = false;
|
||||||
|
for (let i = 0; i < CONTENT_LOOP_THRESHOLD + 5; i++) {
|
||||||
|
isLoop = service.addAndCheck(createContentEvent(userPattern));
|
||||||
|
if (isLoop) break;
|
||||||
|
}
|
||||||
|
expect(isLoop).toBe(true);
|
||||||
|
expect(loggers.logLoopDetected).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect a loop of alternating short phrases', () => {
|
||||||
|
service.reset('');
|
||||||
|
const alternatingPattern = 'Thinking... Done. ';
|
||||||
|
|
||||||
|
let isLoop = false;
|
||||||
|
// Needs more iterations because the pattern is short relative to chunk size,
|
||||||
|
// so it takes a few slides of the window to find the exact alignment.
|
||||||
|
for (let i = 0; i < CONTENT_LOOP_THRESHOLD * 3; i++) {
|
||||||
|
isLoop = service.addAndCheck(createContentEvent(alternatingPattern));
|
||||||
|
if (isLoop) break;
|
||||||
|
}
|
||||||
|
expect(isLoop).toBe(true);
|
||||||
|
expect(loggers.logLoopDetected).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect a loop of repeated complex thought processes', () => {
|
||||||
|
service.reset('');
|
||||||
|
const thoughtPattern =
|
||||||
|
'I need to check the file. The file does not exist. I will create the file. ';
|
||||||
|
|
||||||
|
let isLoop = false;
|
||||||
|
for (let i = 0; i < CONTENT_LOOP_THRESHOLD + 5; i++) {
|
||||||
|
isLoop = service.addAndCheck(createContentEvent(thoughtPattern));
|
||||||
|
if (isLoop) break;
|
||||||
|
}
|
||||||
|
expect(isLoop).toBe(true);
|
||||||
|
expect(loggers.logLoopDetected).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Content Loop Detection with Code Blocks', () => {
|
describe('Content Loop Detection with Code Blocks', () => {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { debugLogger } from '../utils/debugLogger.js';
|
|||||||
const TOOL_CALL_LOOP_THRESHOLD = 5;
|
const TOOL_CALL_LOOP_THRESHOLD = 5;
|
||||||
const CONTENT_LOOP_THRESHOLD = 10;
|
const CONTENT_LOOP_THRESHOLD = 10;
|
||||||
const CONTENT_CHUNK_SIZE = 50;
|
const CONTENT_CHUNK_SIZE = 50;
|
||||||
const MAX_HISTORY_LENGTH = 1000;
|
const MAX_HISTORY_LENGTH = 5000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of recent conversation turns to include in the history when asking the LLM to check for a loop.
|
* The number of recent conversation turns to include in the history when asking the LLM to check for a loop.
|
||||||
@@ -328,7 +328,7 @@ export class LoopDetectionService {
|
|||||||
* 2. Verify actual content matches to prevent hash collisions
|
* 2. Verify actual content matches to prevent hash collisions
|
||||||
* 3. Track all positions where this chunk appears
|
* 3. Track all positions where this chunk appears
|
||||||
* 4. A loop is detected when the same chunk appears CONTENT_LOOP_THRESHOLD times
|
* 4. A loop is detected when the same chunk appears CONTENT_LOOP_THRESHOLD times
|
||||||
* within a small average distance (≤ 1.5 * chunk size)
|
* within a small average distance (≤ 5 * chunk size)
|
||||||
*/
|
*/
|
||||||
private isLoopDetectedForChunk(chunk: string, hash: string): boolean {
|
private isLoopDetectedForChunk(chunk: string, hash: string): boolean {
|
||||||
const existingIndices = this.contentStats.get(hash);
|
const existingIndices = this.contentStats.get(hash);
|
||||||
@@ -353,7 +353,7 @@ export class LoopDetectionService {
|
|||||||
const totalDistance =
|
const totalDistance =
|
||||||
recentIndices[recentIndices.length - 1] - recentIndices[0];
|
recentIndices[recentIndices.length - 1] - recentIndices[0];
|
||||||
const averageDistance = totalDistance / (CONTENT_LOOP_THRESHOLD - 1);
|
const averageDistance = totalDistance / (CONTENT_LOOP_THRESHOLD - 1);
|
||||||
const maxAllowedDistance = CONTENT_CHUNK_SIZE * 1.5;
|
const maxAllowedDistance = CONTENT_CHUNK_SIZE * 5;
|
||||||
|
|
||||||
return averageDistance <= maxAllowedDistance;
|
return averageDistance <= maxAllowedDistance;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user