refactor(core): Use BaseLlmClient for LLM-based loop detection (#8427)

This commit is contained in:
Abhi
2025-09-13 23:07:33 -04:00
committed by GitHub
parent f11d79a931
commit 12720a9fa7
2 changed files with 50 additions and 29 deletions
@@ -50,6 +50,17 @@ const MIN_LLM_CHECK_INTERVAL = 5;
*/
const MAX_LLM_CHECK_INTERVAL = 15;
const LOOP_DETECTION_SYSTEM_PROMPT = `You are a sophisticated AI diagnostic agent specializing in identifying when a conversational AI is stuck in an unproductive state. Your task is to analyze the provided conversation history and determine if the assistant has ceased to make meaningful progress.
An unproductive state is characterized by one or more of the following patterns over the last 5 or more assistant turns:
Repetitive Actions: The assistant repeats the same tool calls or conversational responses a decent number of times. This includes simple loops (e.g., tool_A, tool_A, tool_A) and alternating patterns (e.g., tool_A, tool_B, tool_A, tool_B, ...).
Cognitive Loop: The assistant seems unable to determine the next logical step. It might express confusion, repeatedly ask the same questions, or generate responses that don't logically follow from the previous turns, indicating it's stuck and not advancing the task.
Crucially, differentiate between a true unproductive state and legitimate, incremental progress.
For example, a series of 'tool_A' or 'tool_B' tool calls that make small, distinct changes to the same file (like adding docstrings to functions one by one) is considered forward progress and is NOT a loop. A loop would be repeatedly replacing the same text with the same content, or cycling between a small set of files with no net change.`;
/**
* Service for detecting and preventing infinite loops in AI responses.
* Monitors tool call repetitions and content sentence repetitions.
@@ -375,21 +386,11 @@ export class LoopDetectionService {
const trimmedHistory = this.trimRecentHistory(recentHistory);
const prompt = `You are a sophisticated AI diagnostic agent specializing in identifying when a conversational AI is stuck in an unproductive state. Your task is to analyze the provided conversation history and determine if the assistant has ceased to make meaningful progress.
const taskPrompt = `Please analyze the conversation history to determine the possibility that the conversation is stuck in a repetitive, non-productive state. Provide your response in the requested JSON format.`;
An unproductive state is characterized by one or more of the following patterns over the last 5 or more assistant turns:
Repetitive Actions: The assistant repeats the same tool calls or conversational responses a decent number of times. This includes simple loops (e.g., tool_A, tool_A, tool_A) and alternating patterns (e.g., tool_A, tool_B, tool_A, tool_B, ...).
Cognitive Loop: The assistant seems unable to determine the next logical step. It might express confusion, repeatedly ask the same questions, or generate responses that don't logically follow from the previous turns, indicating it's stuck and not advancing the task.
Crucially, differentiate between a true unproductive state and legitimate, incremental progress.
For example, a series of 'tool_A' or 'tool_B' tool calls that make small, distinct changes to the same file (like adding docstrings to functions one by one) is considered forward progress and is NOT a loop. A loop would be repeatedly replacing the same text with the same content, or cycling between a small set of files with no net change.
Please analyze the conversation history to determine the possibility that the conversation is stuck in a repetitive, non-productive state.`;
const contents = [
...trimmedHistory,
{ role: 'user', parts: [{ text: prompt }] },
{ role: 'user', parts: [{ text: taskPrompt }] },
];
const schema: Record<string, unknown> = {
type: 'object',
@@ -409,9 +410,14 @@ Please analyze the conversation history to determine the possibility that the co
};
let result;
try {
result = await this.config
.getGeminiClient()
.generateJson(contents, schema, signal, DEFAULT_GEMINI_FLASH_MODEL);
result = await this.config.getBaseLlmClient().generateJson({
contents,
schema,
model: DEFAULT_GEMINI_FLASH_MODEL,
systemInstruction: LOOP_DETECTION_SYSTEM_PROMPT,
abortSignal: signal,
promptId: this.promptId,
});
} catch (e) {
// Do nothing, treat it as a non-loop.
this.config.getDebugMode() ? console.error(e) : console.debug(e);