mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-27 04:02:37 -07:00
c0b463dbcf
Established the "Heart" of the Prompt Optimization Pipeline by building a robust, extensible data infrastructure and a high-fidelity golden dataset. Key improvements: - Core Schema: Defined the `Scenario` interface in `data/schema.ts` supporting multiple negative failure modes, platform-specific shell contexts (Unix/Win32), and strict tool-call typing. - Optimization Manifest: Created `data/manifest.json` to define "No-Fly Zones" for the optimizer, protecting literal tool names and template variables, while providing descriptive context for validation. - Tool Alignment Dataset: Authored 113 scenarios in `data/tool_alignment.jsonl` across 20 tools, focusing on "Built-in over Shell" preference. Heavily weighted `replace` (12) and `write_file` (10) to enforce surgical editing. - Extensible Validator: Implemented `scripts/validate-data.ts` to provide real-time integrity checks and purpose-driven coverage reports. - Project Integration: Added `data:validate`, `data:format`, and `data:lint` scripts to package.json and updated ESLint config to cover the data directory.
167 lines
4.6 KiB
TypeScript
167 lines
4.6 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2026 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
import type { Scenario } from '../data/schema.ts';
|
|
|
|
const MANIFEST_FILE = 'data/manifest.json';
|
|
const DEFAULT_DATA_DIR = 'data';
|
|
|
|
async function validateFile(
|
|
filePath: string,
|
|
manifest: {
|
|
data_inventory: {
|
|
file_descriptions: Record<string, string>;
|
|
tools: Record<string, unknown>;
|
|
target_samples_per_tool: number;
|
|
overrides: Record<string, number>;
|
|
};
|
|
optimization_constraints: { immutable_tokens: string[] };
|
|
},
|
|
): Promise<{ success: boolean; counts: Record<string, number> }> {
|
|
const description =
|
|
manifest.data_inventory.file_descriptions?.[filePath] ||
|
|
'No description available.';
|
|
console.log(`\n🔍 Validating: ${filePath}`);
|
|
console.log(` Purpose: ${description}`);
|
|
|
|
const immutableTools = new Set(
|
|
manifest.optimization_constraints.immutable_tokens,
|
|
);
|
|
const toolCounts: Record<string, number> = {};
|
|
|
|
// Initialize counts for all known tools
|
|
Object.keys(manifest.data_inventory.tools).forEach((tool) => {
|
|
toolCounts[tool] = 0;
|
|
});
|
|
|
|
const lines = fs.readFileSync(filePath, 'utf8').split('\n').filter(Boolean);
|
|
let hasErrors = false;
|
|
|
|
lines.forEach((line, index) => {
|
|
const lineNum = index + 1;
|
|
try {
|
|
const scenario: Scenario = JSON.parse(line) as Scenario;
|
|
|
|
if (
|
|
!scenario.id ||
|
|
!scenario.input ||
|
|
!scenario.expected ||
|
|
!scenario.negatives
|
|
) {
|
|
throw new Error(
|
|
`Missing required fields in scenario ${scenario.id || 'at line ' + lineNum}`,
|
|
);
|
|
}
|
|
|
|
scenario.expected.tool_calls.forEach((tc) => {
|
|
if (!immutableTools.has(tc.name)) {
|
|
console.error(
|
|
` ❌ Line ${lineNum}: Unknown tool "${tc.name}" in expected output.`,
|
|
);
|
|
hasErrors = true;
|
|
} else {
|
|
toolCounts[tc.name]++;
|
|
}
|
|
});
|
|
|
|
scenario.negatives.forEach((neg) => {
|
|
neg.tool_calls.forEach((tc) => {
|
|
if (!immutableTools.has(tc.name)) {
|
|
console.error(
|
|
` ❌ Line ${lineNum}: Unknown tool "${tc.name}" in negative example.`,
|
|
);
|
|
hasErrors = true;
|
|
}
|
|
});
|
|
});
|
|
} catch (e) {
|
|
console.error(
|
|
` ❌ Line ${lineNum}: Invalid JSON or Schema.`,
|
|
e instanceof Error ? e.message : e,
|
|
);
|
|
hasErrors = true;
|
|
}
|
|
});
|
|
|
|
if (!hasErrors) {
|
|
console.log(` ✅ ${lines.length} scenarios validated successfully.`);
|
|
}
|
|
|
|
return { success: !hasErrors, counts: toolCounts };
|
|
}
|
|
|
|
async function run() {
|
|
console.log('📊 Starting Data Layer Validation...');
|
|
|
|
if (!fs.existsSync(MANIFEST_FILE)) {
|
|
console.error(`❌ Manifest not found: ${MANIFEST_FILE}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const manifest = JSON.parse(fs.readFileSync(MANIFEST_FILE, 'utf8'));
|
|
const targetFiles = process.argv.slice(2);
|
|
|
|
const filesToValidate =
|
|
targetFiles.length > 0
|
|
? targetFiles
|
|
: [path.join(DEFAULT_DATA_DIR, 'tool_alignment.jsonl')];
|
|
|
|
const globalToolCounts: Record<string, number> = {};
|
|
let allSuccess = true;
|
|
|
|
for (const file of filesToValidate) {
|
|
if (!fs.existsSync(file)) {
|
|
console.warn(`⚠️ File not found: ${file}`);
|
|
continue;
|
|
}
|
|
const result = await validateFile(file, manifest);
|
|
if (!result.success) {
|
|
allSuccess = false;
|
|
}
|
|
|
|
// Aggregate counts
|
|
Object.entries(result.counts).forEach(([tool, count]) => {
|
|
globalToolCounts[tool] = (globalToolCounts[tool] || 0) + count;
|
|
});
|
|
}
|
|
|
|
// Final Coverage Report
|
|
console.log('\n📈 Global Tool Coverage Report (Aggregated):');
|
|
console.log('-------------------------');
|
|
|
|
const targetInventory = manifest.data_inventory.tools;
|
|
const overrides = manifest.data_inventory.overrides || {};
|
|
let totalScenarios = 0;
|
|
|
|
Object.keys(targetInventory)
|
|
.sort()
|
|
.forEach((tool) => {
|
|
const count = globalToolCounts[tool] || 0;
|
|
const target =
|
|
overrides[tool] || manifest.data_inventory.target_samples_per_tool;
|
|
const status = count >= target ? '✅' : '⚠️';
|
|
console.log(`${status} ${tool.padEnd(25)}: ${count}/${target}`);
|
|
totalScenarios += count;
|
|
});
|
|
|
|
console.log('-------------------------');
|
|
console.log(`Total Valid Scenarios: ${totalScenarios}`);
|
|
|
|
if (!allSuccess) {
|
|
console.error('\n❌ Validation completed with errors.');
|
|
process.exit(1);
|
|
} else {
|
|
console.log('\n✅ Data integrity check passed.');
|
|
}
|
|
}
|
|
|
|
run().catch((err) => {
|
|
console.error('Fatal validation error:', err);
|
|
process.exit(1);
|
|
});
|