Introduce loop detection service that breaks simple loop (#3919)

Co-authored-by: Scott Densmore <scottdensmore@mac.com>
Co-authored-by: N. Taylor Mullen <ntaylormullen@google.com>
This commit is contained in:
Sandy Tao
2025-07-14 20:25:16 -07:00
committed by GitHub
parent 7ffe8038ef
commit 734da8b9d2
5 changed files with 456 additions and 1 deletions

View File

@@ -141,6 +141,8 @@ export const useGeminiStream = (
[toolCalls],
);
const loopDetectedRef = useRef(false);
const onExec = useCallback(async (done: Promise<void>) => {
setIsResponding(true);
await done;
@@ -450,6 +452,16 @@ export const useGeminiStream = (
[addItem, config],
);
const handleLoopDetectedEvent = useCallback(() => {
addItem(
{
type: 'info',
text: `A potential loop was detected. This can happen due to repetitive tool calls or other model behavior. The request has been halted.`,
},
Date.now(),
);
}, [addItem]);
const processGeminiStreamEvents = useCallback(
async (
stream: AsyncIterable<GeminiEvent>,
@@ -489,6 +501,11 @@ export const useGeminiStream = (
case ServerGeminiEventType.MaxSessionTurns:
handleMaxSessionTurnsEvent();
break;
case ServerGeminiEventType.LoopDetected:
// handle later because we want to move pending history to history
// before we add loop detected message to history
loopDetectedRef.current = true;
break;
default: {
// enforces exhaustive switch-case
const unreachable: never = event;
@@ -579,6 +596,10 @@ export const useGeminiStream = (
addItem(pendingHistoryItemRef.current, userMessageTimestamp);
setPendingHistoryItem(null);
}
if (loopDetectedRef.current) {
loopDetectedRef.current = false;
handleLoopDetectedEvent();
}
} catch (error: unknown) {
if (error instanceof UnauthorizedError) {
onAuthError();
@@ -616,6 +637,7 @@ export const useGeminiStream = (
config,
startNewPrompt,
getPromptCount,
handleLoopDetectedEvent,
],
);