Replace FlashDecidedToContinueEvent with NextSpeakerCheckEvent (#5257)

This commit is contained in:
Sandy Tao 2025-07-30 21:47:04 -07:00 committed by GitHub
parent 3ef2c6d198
commit 23c014e29c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 52 additions and 24 deletions

View File

@ -43,8 +43,8 @@ import { ProxyAgent, setGlobalDispatcher } from 'undici';
import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js'; import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js';
import { LoopDetectionService } from '../services/loopDetectionService.js'; import { LoopDetectionService } from '../services/loopDetectionService.js';
import { ideContext } from '../ide/ideContext.js'; import { ideContext } from '../ide/ideContext.js';
import { logFlashDecidedToContinue } from '../telemetry/loggers.js'; import { logNextSpeakerCheck } from '../telemetry/loggers.js';
import { FlashDecidedToContinueEvent } from '../telemetry/types.js'; import { NextSpeakerCheckEvent } from '../telemetry/types.js';
function isThinkingSupported(model: string) { function isThinkingSupported(model: string) {
if (model.startsWith('gemini-2.5')) return true; if (model.startsWith('gemini-2.5')) return true;
@ -415,11 +415,15 @@ export class GeminiClient {
this, this,
signal, signal,
); );
logNextSpeakerCheck(
this.config,
new NextSpeakerCheckEvent(
prompt_id,
turn.finishReason?.toString() || '',
nextSpeakerCheck?.next_speaker || '',
),
);
if (nextSpeakerCheck?.next_speaker === 'model') { if (nextSpeakerCheck?.next_speaker === 'model') {
logFlashDecidedToContinue(
this.config,
new FlashDecidedToContinueEvent(prompt_id),
);
const nextRequest = [{ text: 'Please continue.' }]; const nextRequest = [{ text: 'Please continue.' }];
// This recursive call's events will be yielded out, but the final // This recursive call's events will be yielded out, but the final
// turn object will be from the top-level call. // turn object will be from the top-level call.

View File

@ -163,6 +163,7 @@ export type ServerGeminiStreamEvent =
export class Turn { export class Turn {
readonly pendingToolCalls: ToolCallRequestInfo[]; readonly pendingToolCalls: ToolCallRequestInfo[];
private debugResponses: GenerateContentResponse[]; private debugResponses: GenerateContentResponse[];
finishReason: FinishReason | undefined;
constructor( constructor(
private readonly chat: GeminiChat, private readonly chat: GeminiChat,
@ -170,6 +171,7 @@ export class Turn {
) { ) {
this.pendingToolCalls = []; this.pendingToolCalls = [];
this.debugResponses = []; this.debugResponses = [];
this.finishReason = undefined;
} }
// The run method yields simpler events suitable for server logic // The run method yields simpler events suitable for server logic
async *run( async *run(
@ -235,6 +237,7 @@ export class Turn {
const finishReason = resp.candidates?.[0]?.finishReason; const finishReason = resp.candidates?.[0]?.finishReason;
if (finishReason) { if (finishReason) {
this.finishReason = finishReason;
yield { yield {
type: GeminiEventType.Finished, type: GeminiEventType.Finished,
value: finishReason as FinishReason, value: finishReason as FinishReason,

View File

@ -18,7 +18,7 @@ import {
ApiErrorEvent, ApiErrorEvent,
FlashFallbackEvent, FlashFallbackEvent,
LoopDetectedEvent, LoopDetectedEvent,
FlashDecidedToContinueEvent, NextSpeakerCheckEvent,
SlashCommandEvent, SlashCommandEvent,
} from '../types.js'; } from '../types.js';
import { EventMetadataKey } from './event-metadata-key.js'; import { EventMetadataKey } from './event-metadata-key.js';
@ -40,7 +40,7 @@ const api_error_event_name = 'api_error';
const end_session_event_name = 'end_session'; const end_session_event_name = 'end_session';
const flash_fallback_event_name = 'flash_fallback'; const flash_fallback_event_name = 'flash_fallback';
const loop_detected_event_name = 'loop_detected'; const loop_detected_event_name = 'loop_detected';
const flash_decided_to_continue_event_name = 'flash_decided_to_continue'; const next_speaker_check_event_name = 'next_speaker_check';
const slash_command_event_name = 'slash_command'; const slash_command_event_name = 'slash_command';
export interface LogResponse { export interface LogResponse {
@ -512,12 +512,20 @@ export class ClearcutLogger {
this.flushIfNeeded(); this.flushIfNeeded();
} }
logFlashDecidedToContinueEvent(event: FlashDecidedToContinueEvent): void { logNextSpeakerCheck(event: NextSpeakerCheckEvent): void {
const data = [ const data = [
{ {
gemini_cli_key: EventMetadataKey.GEMINI_CLI_PROMPT_ID, gemini_cli_key: EventMetadataKey.GEMINI_CLI_PROMPT_ID,
value: JSON.stringify(event.prompt_id), value: JSON.stringify(event.prompt_id),
}, },
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_RESPONSE_FINISH_REASON,
value: JSON.stringify(event.finish_reason),
},
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_NEXT_SPEAKER_CHECK_RESULT,
value: JSON.stringify(event.result),
},
{ {
gemini_cli_key: EventMetadataKey.GEMINI_CLI_SESSION_ID, gemini_cli_key: EventMetadataKey.GEMINI_CLI_SESSION_ID,
value: this.config?.getSessionId() ?? '', value: this.config?.getSessionId() ?? '',
@ -525,7 +533,7 @@ export class ClearcutLogger {
]; ];
this.enqueueLogEvent( this.enqueueLogEvent(
this.createLogEvent(flash_decided_to_continue_event_name, data), this.createLogEvent(next_speaker_check_event_name, data),
); );
this.flushIfNeeded(); this.flushIfNeeded();
} }

View File

@ -173,6 +173,16 @@ export enum EventMetadataKey {
// Logs the subcommand of the slash command. // Logs the subcommand of the slash command.
GEMINI_CLI_SLASH_COMMAND_SUBCOMMAND = 42, GEMINI_CLI_SLASH_COMMAND_SUBCOMMAND = 42,
// ==========================================================================
// Next Speaker Check Event Keys
// ===========================================================================
// Logs the finish reason of the previous streamGenerateContent response
GEMINI_CLI_RESPONSE_FINISH_REASON = 43,
// Logs the result of the next speaker check
GEMINI_CLI_NEXT_SPEAKER_CHECK_RESULT = 44,
} }
export function getEventMetadataKey( export function getEventMetadataKey(

View File

@ -13,8 +13,7 @@ export const EVENT_API_ERROR = 'gemini_cli.api_error';
export const EVENT_API_RESPONSE = 'gemini_cli.api_response'; export const EVENT_API_RESPONSE = 'gemini_cli.api_response';
export const EVENT_CLI_CONFIG = 'gemini_cli.config'; export const EVENT_CLI_CONFIG = 'gemini_cli.config';
export const EVENT_FLASH_FALLBACK = 'gemini_cli.flash_fallback'; export const EVENT_FLASH_FALLBACK = 'gemini_cli.flash_fallback';
export const EVENT_FLASH_DECIDED_TO_CONTINUE = export const EVENT_NEXT_SPEAKER_CHECK = 'gemini_cli.next_speaker_check';
'gemini_cli.flash_decided_to_continue';
export const EVENT_SLASH_COMMAND = 'gemini_cli.slash_command'; export const EVENT_SLASH_COMMAND = 'gemini_cli.slash_command';
export const METRIC_TOOL_CALL_COUNT = 'gemini_cli.tool.call.count'; export const METRIC_TOOL_CALL_COUNT = 'gemini_cli.tool.call.count';

View File

@ -15,7 +15,7 @@ import {
EVENT_TOOL_CALL, EVENT_TOOL_CALL,
EVENT_USER_PROMPT, EVENT_USER_PROMPT,
EVENT_FLASH_FALLBACK, EVENT_FLASH_FALLBACK,
EVENT_FLASH_DECIDED_TO_CONTINUE, EVENT_NEXT_SPEAKER_CHECK,
SERVICE_NAME, SERVICE_NAME,
EVENT_SLASH_COMMAND, EVENT_SLASH_COMMAND,
} from './constants.js'; } from './constants.js';
@ -27,7 +27,7 @@ import {
ToolCallEvent, ToolCallEvent,
UserPromptEvent, UserPromptEvent,
FlashFallbackEvent, FlashFallbackEvent,
FlashDecidedToContinueEvent, NextSpeakerCheckEvent,
LoopDetectedEvent, LoopDetectedEvent,
SlashCommandEvent, SlashCommandEvent,
} from './types.js'; } from './types.js';
@ -314,22 +314,22 @@ export function logLoopDetected(
logger.emit(logRecord); logger.emit(logRecord);
} }
export function logFlashDecidedToContinue( export function logNextSpeakerCheck(
config: Config, config: Config,
event: FlashDecidedToContinueEvent, event: NextSpeakerCheckEvent,
): void { ): void {
ClearcutLogger.getInstance(config)?.logFlashDecidedToContinueEvent(event); ClearcutLogger.getInstance(config)?.logNextSpeakerCheck(event);
if (!isTelemetrySdkInitialized()) return; if (!isTelemetrySdkInitialized()) return;
const attributes: LogAttributes = { const attributes: LogAttributes = {
...getCommonAttributes(config), ...getCommonAttributes(config),
...event, ...event,
'event.name': EVENT_FLASH_DECIDED_TO_CONTINUE, 'event.name': EVENT_NEXT_SPEAKER_CHECK,
}; };
const logger = logs.getLogger(SERVICE_NAME); const logger = logs.getLogger(SERVICE_NAME);
const logRecord: LogRecord = { const logRecord: LogRecord = {
body: `Flash decided to continue.`, body: `Next speaker check.`,
attributes, attributes,
}; };
logger.emit(logRecord); logger.emit(logRecord);

View File

@ -266,15 +266,19 @@ export class LoopDetectedEvent {
} }
} }
export class FlashDecidedToContinueEvent { export class NextSpeakerCheckEvent {
'event.name': 'flash_decided_to_continue'; 'event.name': 'next_speaker_check';
'event.timestamp': string; // ISO 8601 'event.timestamp': string; // ISO 8601
prompt_id: string; prompt_id: string;
finish_reason: string;
result: string;
constructor(prompt_id: string) { constructor(prompt_id: string, finish_reason: string, result: string) {
this['event.name'] = 'flash_decided_to_continue'; this['event.name'] = 'next_speaker_check';
this['event.timestamp'] = new Date().toISOString(); this['event.timestamp'] = new Date().toISOString();
this.prompt_id = prompt_id; this.prompt_id = prompt_id;
this.finish_reason = finish_reason;
this.result = result;
} }
} }
@ -302,5 +306,5 @@ export type TelemetryEvent =
| ApiResponseEvent | ApiResponseEvent
| FlashFallbackEvent | FlashFallbackEvent
| LoopDetectedEvent | LoopDetectedEvent
| FlashDecidedToContinueEvent | NextSpeakerCheckEvent
| SlashCommandEvent; | SlashCommandEvent;