mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-12 01:49:38 -05:00
* utils: document binarySearch
* nes-datagen: generate training data from continuous recordings
Continuous enhanced telemetry now ships sliding-window recordings that, unlike per-request alternative-action recordings, carry no requestTime. The datagen pipeline needs a point to split each recording into edit history before/after, so this adds a pluggable pivot strategy (starting with Random, selectable via --pivot-strategy) and a new continuous/ pipeline module that replays a recording at the chosen pivot to produce a processed row.
Along the way this consolidates the pipeline's error and index handling: a shared WithRowIndex<T> replaces the ad-hoc { originalRowIndex, ... } pairs, per-record processing returns Result<IProcessedRow, Error> instead of field-presence unions, and failures surface as original Error objects (no string round-tripping). The telemetry sender's continuous payload is now the documented IContinuousRecording type.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* nes-datagen: label alt-action replay errors by originalRowIndex
Address PR review: the alternative-action path mislabeled diagnostics when
earlier records failed to parse.
- processAllRows: push replay errors with the row's true `originalRowIndex`
instead of its position in the filtered `rows` array (parse failures make
`rows` sparse, so the two diverge).
- loadAndProduceProcessedRows: resolve `languageForRow` via an
`originalRowIndex`-keyed Map rather than positional `rows[i]`, matching how
callers pass `e.originalRowIndex`.
- Clarify the `recordCount` doc: it counts successfully-parsed records (parse
failures are counted separately in `parseErrors`).
- Add a regression spec asserting replay errors carry the row index, not the
array position.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
109 lines
3.2 KiB
TypeScript
109 lines
3.2 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { IAlternativeAction } from '../../src/extension/inlineEdits/node/nextEditProviderTelemetry';
|
|
import { ErrorUtils } from '../../src/util/common/errors';
|
|
import { streamJsonRecords } from './streamJsonRecords';
|
|
import type { WithRowIndex } from './withRowIndex';
|
|
|
|
/**
|
|
* A single row from the JSON input.
|
|
*/
|
|
export interface IInputRow {
|
|
readonly originalRowIndex: number;
|
|
readonly suggestionStatus: string;
|
|
readonly alternativeAction: IAlternativeAction;
|
|
readonly prompt: unknown[];
|
|
readonly modelResponse: string;
|
|
readonly postProcessingOutcome: {
|
|
suggestedEdit: string;
|
|
isInlineCompletion: boolean;
|
|
};
|
|
readonly activeDocumentLanguageId: string;
|
|
}
|
|
|
|
const requiredKeys = [
|
|
'status',
|
|
'action',
|
|
'input',
|
|
'response',
|
|
'outcome',
|
|
'language',
|
|
] as const;
|
|
|
|
/**
|
|
* Parse a single JSON input record into a structured row.
|
|
*/
|
|
function parseInputRecord(record: Record<string, string>, rowIndex: number): IInputRow {
|
|
for (const key of requiredKeys) {
|
|
if (!(key in record)) {
|
|
throw new Error(`Missing key: ${key}`);
|
|
}
|
|
}
|
|
|
|
const alternativeAction = JSON.parse(record['action']) as IAlternativeAction;
|
|
const prompt = JSON.parse(record['input']) as unknown[];
|
|
const postProcessingOutcome = JSON.parse(record['outcome']) as {
|
|
suggestedEdit: string;
|
|
isInlineCompletion: boolean;
|
|
};
|
|
|
|
if (!alternativeAction.recording) {
|
|
throw new Error('action.recording is missing');
|
|
}
|
|
if (!alternativeAction.recording.entries || alternativeAction.recording.entries.length === 0) {
|
|
throw new Error('action.recording.entries is empty');
|
|
}
|
|
if (!postProcessingOutcome.suggestedEdit) {
|
|
throw new Error('outcome.suggestedEdit is missing');
|
|
}
|
|
|
|
return {
|
|
originalRowIndex: rowIndex,
|
|
suggestionStatus: record['status'],
|
|
alternativeAction,
|
|
prompt,
|
|
modelResponse: record['response'],
|
|
postProcessingOutcome,
|
|
activeDocumentLanguageId: record['language'],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Stream-parse the input file (JSON array or JSON Lines) and validate each
|
|
* record into an `IInputRow`. Records that fail validation are reported in
|
|
* `errors` (with their row index) rather than aborting the load.
|
|
*
|
|
* Note: this still accumulates the fully parsed rows into memory. For very
|
|
* large inputs (multi-GB), use `runInputPipelineParallel` so each worker only
|
|
* loads its assigned slice — the parent process never holds the whole dataset.
|
|
*/
|
|
export async function loadAndParseInput(inputPath: string, verbose = false): Promise<{
|
|
rows: IInputRow[];
|
|
errors: WithRowIndex<Error>[];
|
|
}> {
|
|
const rows: IInputRow[] = [];
|
|
const errors: WithRowIndex<Error>[] = [];
|
|
|
|
let i = 0;
|
|
for await (const record of streamJsonRecords<Record<string, string>>(inputPath)) {
|
|
const rowIndex = i++;
|
|
try {
|
|
rows.push(parseInputRecord(record, rowIndex));
|
|
} catch (e) {
|
|
errors.push({
|
|
originalRowIndex: rowIndex,
|
|
value: ErrorUtils.fromUnknown(e),
|
|
});
|
|
}
|
|
}
|
|
|
|
if (verbose) {
|
|
console.log(`Read ${i} records from ${inputPath}`);
|
|
}
|
|
|
|
return { rows, errors };
|
|
}
|