Skip to content

Commit 6d4f5d0

Browse files
committed
Check for unproductive loop
1 parent 4031599 commit 6d4f5d0

File tree

2 files changed

+87
-0
lines changed

2 files changed

+87
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { CoreMessage } from 'ai';
2+
import { z } from 'zod';
3+
import { promptAiSdkStructured } from './vercel-ai-sdk/ai-sdk';
4+
import { models } from 'common/constants';
5+
import { getCoreMessagesSubset } from '../util/messages';
6+
import { logger } from '../util/logger';
7+
8+
const loopCheckSchema = z.object({
9+
is_loop: z.boolean().describe('Whether the assistant is in a non-productive loop.'),
10+
});
11+
12+
export async function checkForUnproductiveLoop(
13+
messages: CoreMessage[],
14+
options: {
15+
clientSessionId: string;
16+
fingerprintId: string;
17+
userInputId: string;
18+
userId: string | undefined;
19+
}
20+
): Promise<boolean> {
21+
const { clientSessionId, fingerprintId, userInputId, userId } = options;
22+
23+
const relevantMessages = getCoreMessagesSubset(messages, 0).slice(-6);
24+
25+
if (relevantMessages.length < 4) {
26+
return false;
27+
}
28+
29+
const prompt = `
30+
The following is a sequence of messages between a user and an AI assistant.
31+
Your task is to determine if the assistant is stuck in a non-productive loop.
32+
33+
A non-productive loop is defined as the assistant repeatedly taking similar actions (e.g., using the same tool or running the same terminal command) across multiple turns without making meaningful progress toward resolving the user's request. The assistant might be making small changes, but they don't fix the underlying issue, leading to a cycle of similar errors and attempts.
34+
35+
Analyze the message history and determine if a non-productive loop is occurring.
36+
37+
Respond with a JSON object matching the following schema:
38+
{
39+
"is_loop": boolean, // true if a loop is detected, false otherwise.
40+
}
41+
`;
42+
43+
try {
44+
const result = await promptAiSdkStructured({
45+
messages: [
46+
...relevantMessages,
47+
{ role: 'user', content: prompt },
48+
],
49+
schema: loopCheckSchema,
50+
model: models.gemini2_5_flash,
51+
clientSessionId,
52+
fingerprintId,
53+
userInputId,
54+
userId,
55+
temperature: 0,
56+
});
57+
58+
logger.debug({ result }, 'Unproductive loop check result');
59+
return result.is_loop;
60+
} catch (error) {
61+
logger.error({ error }, 'Error checking for unproductive loop');
62+
return false;
63+
}
64+
}

backend/src/main-prompt.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
requestRelevantFilesForTraining,
3232
} from './find-files/request-files-prompt'
3333
import { getDocumentationForQuery } from './get-documentation-for-query'
34+
import { checkForUnproductiveLoop } from './llm-apis/check-for-loop'
3435
import { processFileBlock } from './process-file-block'
3536
import { processStrReplace } from './process-str-replace'
3637
import { getAgentStream } from './prompt-agent-stream'
@@ -1078,6 +1079,28 @@ export const mainPrompt = async (
10781079

10791080
const newAgentContext = await agentContextPromise
10801081

1082+
if (
1083+
clientToolCalls.every((tool) => tool.name !== 'end_turn') &&
1084+
(agentState.consecutiveAssistantMessages ?? 0) > 2
1085+
) {
1086+
const isLoop = await checkForUnproductiveLoop(messagesWithResponse, {
1087+
clientSessionId,
1088+
fingerprintId,
1089+
userInputId: promptId,
1090+
userId,
1091+
})
1092+
if (isLoop) {
1093+
logger.warn('Detected unproductive loop, ending turn.')
1094+
onResponseChunk("\n\nHow would you like to proceed from here?\n\n")
1095+
fullResponse += getToolCallString('end_turn', {})
1096+
clientToolCalls.push({
1097+
name: 'end_turn',
1098+
parameters: {},
1099+
id: generateCompactId(),
1100+
})
1101+
}
1102+
}
1103+
10811104
let finalMessageHistory = expireMessages(messagesWithResponse, 'agentStep')
10821105

10831106
// Handle /compact command: replace message history with the summary

0 commit comments

Comments
 (0)