mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-20 18:14:29 -07:00
feat(core): enhance loop detection with 2-stage check (#12902)
This commit is contained in:
@@ -37,6 +37,7 @@ import type {
|
||||
RecoveryAttemptEvent,
|
||||
WebFetchFallbackAttemptEvent,
|
||||
ExtensionUpdateEvent,
|
||||
LlmLoopCheckEvent,
|
||||
} from '../types.js';
|
||||
import { EventMetadataKey } from './event-metadata-key.js';
|
||||
import type { Config } from '../../config/config.js';
|
||||
@@ -92,6 +93,7 @@ export enum EventNames {
|
||||
AGENT_FINISH = 'agent_finish',
|
||||
RECOVERY_ATTEMPT = 'recovery_attempt',
|
||||
WEB_FETCH_FALLBACK_ATTEMPT = 'web_fetch_fallback_attempt',
|
||||
LLM_LOOP_CHECK = 'llm_loop_check',
|
||||
}
|
||||
|
||||
export interface LogResponse {
|
||||
@@ -735,6 +737,14 @@ export class ClearcutLogger {
|
||||
},
|
||||
];
|
||||
|
||||
if (event.confirmed_by_model) {
|
||||
data.push({
|
||||
gemini_cli_key:
|
||||
EventMetadataKey.GEMINI_CLI_LOOP_DETECTED_CONFIRMED_BY_MODEL,
|
||||
value: event.confirmed_by_model,
|
||||
});
|
||||
}
|
||||
|
||||
this.enqueueLogEvent(this.createLogEvent(EventNames.LOOP_DETECTED, data));
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
@@ -1269,6 +1279,32 @@ export class ClearcutLogger {
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
logLlmLoopCheckEvent(event: LlmLoopCheckEvent): void {
|
||||
const data: EventValue[] = [
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_PROMPT_ID,
|
||||
value: event.prompt_id,
|
||||
},
|
||||
{
|
||||
gemini_cli_key:
|
||||
EventMetadataKey.GEMINI_CLI_LLM_LOOP_CHECK_FLASH_CONFIDENCE,
|
||||
value: event.flash_confidence.toString(),
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_LLM_LOOP_CHECK_MAIN_MODEL,
|
||||
value: event.main_model,
|
||||
},
|
||||
{
|
||||
gemini_cli_key:
|
||||
EventMetadataKey.GEMINI_CLI_LLM_LOOP_CHECK_MAIN_MODEL_CONFIDENCE,
|
||||
value: event.main_model_confidence.toString(),
|
||||
},
|
||||
];
|
||||
|
||||
this.enqueueLogEvent(this.createLogEvent(EventNames.LLM_LOOP_CHECK, data));
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds default fields to data, and returns a new data array. This fields
|
||||
* should exist on all log events.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// Defines valid event metadata keys for Clearcut logging.
|
||||
export enum EventMetadataKey {
|
||||
// Deleted enums: 24
|
||||
// Next ID: 122
|
||||
// Next ID: 129
|
||||
|
||||
GEMINI_CLI_KEY_UNKNOWN = 0,
|
||||
|
||||
@@ -476,4 +476,20 @@ export enum EventMetadataKey {
|
||||
|
||||
// Logs whether the session is interactive.
|
||||
GEMINI_CLI_INTERACTIVE = 125,
|
||||
|
||||
// ==========================================================================
|
||||
// LLM Loop Check Event Keys
|
||||
// ==========================================================================
|
||||
|
||||
// Logs the confidence score from the flash model loop check.
|
||||
GEMINI_CLI_LLM_LOOP_CHECK_FLASH_CONFIDENCE = 126,
|
||||
|
||||
// Logs the name of the main model used for the secondary loop check.
|
||||
GEMINI_CLI_LLM_LOOP_CHECK_MAIN_MODEL = 127,
|
||||
|
||||
// Logs the confidence score from the main model loop check.
|
||||
GEMINI_CLI_LLM_LOOP_CHECK_MAIN_MODEL_CONFIDENCE = 128,
|
||||
|
||||
// Logs the model that confirmed the loop.
|
||||
GEMINI_CLI_LOOP_DETECTED_CONFIRMED_BY_MODEL = 129,
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import type {
|
||||
RecoveryAttemptEvent,
|
||||
WebFetchFallbackAttemptEvent,
|
||||
ExtensionUpdateEvent,
|
||||
LlmLoopCheckEvent,
|
||||
} from './types.js';
|
||||
import {
|
||||
recordApiErrorMetrics,
|
||||
@@ -654,3 +655,18 @@ export function logWebFetchFallbackAttempt(
|
||||
};
|
||||
logger.emit(logRecord);
|
||||
}
|
||||
|
||||
export function logLlmLoopCheck(
|
||||
config: Config,
|
||||
event: LlmLoopCheckEvent,
|
||||
): void {
|
||||
ClearcutLogger.getInstance(config)?.logLlmLoopCheckEvent(event);
|
||||
if (!isTelemetrySdkInitialized()) return;
|
||||
|
||||
const logger = logs.getLogger(SERVICE_NAME);
|
||||
const logRecord: LogRecord = {
|
||||
body: event.toLogBody(),
|
||||
attributes: event.toOpenTelemetryAttributes(config),
|
||||
};
|
||||
logger.emit(logRecord);
|
||||
}
|
||||
|
||||
@@ -708,26 +708,38 @@ export class LoopDetectedEvent implements BaseTelemetryEvent {
|
||||
'event.timestamp': string;
|
||||
loop_type: LoopType;
|
||||
prompt_id: string;
|
||||
confirmed_by_model?: string;
|
||||
|
||||
constructor(loop_type: LoopType, prompt_id: string) {
|
||||
constructor(
|
||||
loop_type: LoopType,
|
||||
prompt_id: string,
|
||||
confirmed_by_model?: string,
|
||||
) {
|
||||
this['event.name'] = 'loop_detected';
|
||||
this['event.timestamp'] = new Date().toISOString();
|
||||
this.loop_type = loop_type;
|
||||
this.prompt_id = prompt_id;
|
||||
this.confirmed_by_model = confirmed_by_model;
|
||||
}
|
||||
|
||||
toOpenTelemetryAttributes(config: Config): LogAttributes {
|
||||
return {
|
||||
const attributes: LogAttributes = {
|
||||
...getCommonAttributes(config),
|
||||
'event.name': this['event.name'],
|
||||
'event.timestamp': this['event.timestamp'],
|
||||
loop_type: this.loop_type,
|
||||
prompt_id: this.prompt_id,
|
||||
};
|
||||
|
||||
if (this.confirmed_by_model) {
|
||||
attributes['confirmed_by_model'] = this.confirmed_by_model;
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
toLogBody(): string {
|
||||
return `Loop detected. Type: ${this.loop_type}.`;
|
||||
return `Loop detected. Type: ${this.loop_type}.${this.confirmed_by_model ? ` Confirmed by: ${this.confirmed_by_model}` : ''}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1417,6 +1429,48 @@ export class ModelSlashCommandEvent implements BaseTelemetryEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export const EVENT_LLM_LOOP_CHECK = 'gemini_cli.llm_loop_check';
|
||||
export class LlmLoopCheckEvent implements BaseTelemetryEvent {
|
||||
'event.name': 'llm_loop_check';
|
||||
'event.timestamp': string;
|
||||
prompt_id: string;
|
||||
flash_confidence: number;
|
||||
main_model: string;
|
||||
main_model_confidence: number;
|
||||
|
||||
constructor(
|
||||
prompt_id: string,
|
||||
flash_confidence: number,
|
||||
main_model: string,
|
||||
main_model_confidence: number,
|
||||
) {
|
||||
this['event.name'] = 'llm_loop_check';
|
||||
this['event.timestamp'] = new Date().toISOString();
|
||||
this.prompt_id = prompt_id;
|
||||
this.flash_confidence = flash_confidence;
|
||||
this.main_model = main_model;
|
||||
this.main_model_confidence = main_model_confidence;
|
||||
}
|
||||
|
||||
toOpenTelemetryAttributes(config: Config): LogAttributes {
|
||||
return {
|
||||
...getCommonAttributes(config),
|
||||
'event.name': EVENT_LLM_LOOP_CHECK,
|
||||
'event.timestamp': this['event.timestamp'],
|
||||
prompt_id: this.prompt_id,
|
||||
flash_confidence: this.flash_confidence,
|
||||
main_model: this.main_model,
|
||||
main_model_confidence: this.main_model_confidence,
|
||||
};
|
||||
}
|
||||
|
||||
toLogBody(): string {
|
||||
return this.main_model_confidence === -1
|
||||
? `LLM loop check. Flash confidence: ${this.flash_confidence.toFixed(2)}. Main model (${this.main_model}) check skipped`
|
||||
: `LLM loop check. Flash confidence: ${this.flash_confidence.toFixed(2)}. Main model (${this.main_model}) confidence: ${this.main_model_confidence.toFixed(2)}`;
|
||||
}
|
||||
}
|
||||
|
||||
export type TelemetryEvent =
|
||||
| StartSessionEvent
|
||||
| EndSessionEvent
|
||||
@@ -1446,6 +1500,7 @@ export type TelemetryEvent =
|
||||
| AgentStartEvent
|
||||
| AgentFinishEvent
|
||||
| RecoveryAttemptEvent
|
||||
| LlmLoopCheckEvent
|
||||
| WebFetchFallbackAttemptEvent;
|
||||
|
||||
export const EVENT_EXTENSION_DISABLE = 'gemini_cli.extension_disable';
|
||||
|
||||
Reference in New Issue
Block a user