Download scripts/aggregate_evals.js from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 7.48 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/scripts/aggregate_evals.js
- Command line
-
hf download hf://SaylorTwift/gemini-cli/scripts/aggregate_evals.js
-
curl -L -o aggregate_evals.js https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/scripts/aggregate_evals.js
7.48 kB
| /** | |
| * @license | |
| * Copyright 2026 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import fs from 'node:fs'; | |
| import path from 'node:path'; | |
| import { execSync } from 'node:child_process'; | |
| import os from 'node:os'; | |
| const artifactsDir = process.argv[2] || '.'; | |
| const MAX_HISTORY = 10; | |
| // Find all report.json files recursively | |
| function findReports(dir) { | |
| const reports = []; | |
| if (!fs.existsSync(dir)) return reports; | |
| const files = fs.readdirSync(dir); | |
| for (const file of files) { | |
| const fullPath = path.join(dir, file); | |
| const stat = fs.statSync(fullPath); | |
| if (stat.isDirectory()) { | |
| reports.push(...findReports(fullPath)); | |
| } else if (file === 'report.json') { | |
| reports.push(fullPath); | |
| } | |
| } | |
| return reports; | |
| } | |
| function getModelFromPath(reportPath) { | |
| const parts = reportPath.split(path.sep); | |
| // Find the part that starts with 'eval-logs-' | |
| const artifactDir = parts.find((p) => p.startsWith('eval-logs-')); | |
| if (!artifactDir) return 'unknown'; | |
| const matchNew = artifactDir.match(/^eval-logs-(.+)-(\d+)$/); | |
| if (matchNew) return matchNew[1]; | |
| const matchOld = artifactDir.match(/^eval-logs-(\d+)$/); | |
| if (matchOld) return 'gemini-2.5-pro'; // Legacy default | |
| return 'unknown'; | |
| } | |
| function getStats(reports) { | |
| // Structure: { [model]: { [testName]: { passed, failed, total } } } | |
| const statsByModel = {}; | |
| for (const reportPath of reports) { | |
| try { | |
| const model = getModelFromPath(reportPath); | |
| if (!statsByModel[model]) { | |
| statsByModel[model] = {}; | |
| } | |
| const testStats = statsByModel[model]; | |
| const content = fs.readFileSync(reportPath, 'utf-8'); | |
| const json = JSON.parse(content); | |
| for (const testResult of json.testResults) { | |
| for (const assertion of testResult.assertionResults) { | |
| const name = assertion.title; | |
| if (!testStats[name]) { | |
| testStats[name] = { passed: 0, failed: 0, total: 0 }; | |
| } | |
| testStats[name].total++; | |
| if (assertion.status === 'passed') { | |
| testStats[name].passed++; | |
| } else { | |
| testStats[name].failed++; | |
| } | |
| } | |
| } | |
| } catch (error) { | |
| console.error(`Error processing report at ${reportPath}:`, error); | |
| } | |
| } | |
| return statsByModel; | |
| } | |
| function fetchHistoricalData() { | |
| const history = []; | |
| try { | |
| // Determine branch | |
| const branch = 'main'; | |
| // Get recent runs | |
| const cmd = `gh run list --workflow evals-nightly.yml --branch "${branch}" --limit ${ | |
| MAX_HISTORY + 5 | |
| } --json databaseId,createdAt,url,displayTitle,status,conclusion`; | |
| const runsJson = execSync(cmd, { encoding: 'utf-8' }); | |
| let runs = JSON.parse(runsJson); | |
| // Filter out current run | |
| const currentRunId = process.env.GITHUB_RUN_ID; | |
| if (currentRunId) { | |
| runs = runs.filter((r) => r.databaseId.toString() !== currentRunId); | |
| } | |
| // Filter for runs that likely have artifacts (completed) and take top N | |
| // We accept 'failure' too because we want to see stats. | |
| runs = runs.filter((r) => r.status === 'completed').slice(0, MAX_HISTORY); | |
| // Fetch artifacts for each run | |
| for (const run of runs) { | |
| const tmpDir = fs.mkdtempSync( | |
| path.join(os.tmpdir(), `gemini-evals-${run.databaseId}-`), | |
| ); | |
| try { | |
| // Download report.json files. | |
| // The artifacts are named 'eval-logs-X' or 'eval-logs-MODEL-X'. | |
| // We use -p to match pattern. | |
| execSync( | |
| `gh run download ${run.databaseId} -p "eval-logs-*" -D "${tmpDir}"`, | |
| { stdio: 'ignore' }, | |
| ); | |
| const runReports = findReports(tmpDir); | |
| if (runReports.length > 0) { | |
| history.push({ | |
| run, | |
| stats: getStats(runReports), // Now returns stats grouped by model | |
| }); | |
| } | |
| } catch (error) { | |
| console.error( | |
| `Failed to download or process artifacts for run ${run.databaseId}:`, | |
| error, | |
| ); | |
| } finally { | |
| fs.rmSync(tmpDir, { recursive: true, force: true }); | |
| } | |
| } | |
| } catch (error) { | |
| console.error('Failed to fetch historical data:', error); | |
| } | |
| return history; | |
| } | |
| function generateMarkdown(currentStatsByModel, history) { | |
| console.log('### Evals Nightly Summary\n'); | |
| console.log( | |
| 'See [evals/README.md](https://github.com/google-gemini/gemini-cli/tree/main/evals) for more details.\n', | |
| ); | |
| // Reverse history to show oldest first | |
| const reversedHistory = [...history].reverse(); | |
| const models = Object.keys(currentStatsByModel).sort(); | |
| const getPassRate = (statsForModel) => { | |
| if (!statsForModel) return '-'; | |
| const totalStats = Object.values(statsForModel).reduce( | |
| (acc, stats) => { | |
| acc.passed += stats.passed; | |
| acc.total += stats.total; | |
| return acc; | |
| }, | |
| { passed: 0, total: 0 }, | |
| ); | |
| return totalStats.total > 0 | |
| ? ((totalStats.passed / totalStats.total) * 100).toFixed(1) + '%' | |
| : '-'; | |
| }; | |
| for (const model of models) { | |
| const currentStats = currentStatsByModel[model]; | |
| const totalPassRate = getPassRate(currentStats); | |
| console.log(`#### Model: ${model}`); | |
| console.log(`**Total Pass Rate: ${totalPassRate}**\n`); | |
| // Header | |
| let header = '| Test Name |'; | |
| let separator = '| :--- |'; | |
| let passRateRow = '| **Overall Pass Rate** |'; | |
| for (const item of reversedHistory) { | |
| header += ` [${item.run.databaseId}](${item.run.url}) |`; | |
| separator += ' :---: |'; | |
| passRateRow += ` **${getPassRate(item.stats[model])}** |`; | |
| } | |
| // Add Current column last | |
| header += ' Current |'; | |
| separator += ' :---: |'; | |
| passRateRow += ` **${totalPassRate}** |`; | |
| console.log(header); | |
| console.log(separator); | |
| console.log(passRateRow); | |
| // Collect all test names for this model | |
| const allTestNames = new Set(Object.keys(currentStats)); | |
| for (const item of reversedHistory) { | |
| if (item.stats[model]) { | |
| Object.keys(item.stats[model]).forEach((name) => | |
| allTestNames.add(name), | |
| ); | |
| } | |
| } | |
| for (const name of Array.from(allTestNames).sort()) { | |
| const searchUrl = `https://github.com/search?q=repo%3Agoogle-gemini%2Fgemini-cli%20%22${encodeURIComponent(name)}%22&type=code`; | |
| let row = `| [${name}](${searchUrl}) |`; | |
| // History | |
| for (const item of reversedHistory) { | |
| const stat = item.stats[model] ? item.stats[model][name] : null; | |
| if (stat) { | |
| const passRate = ((stat.passed / stat.total) * 100).toFixed(0) + '%'; | |
| row += ` ${passRate} |`; | |
| } else { | |
| row += ' - |'; | |
| } | |
| } | |
| // Current | |
| const curr = currentStats[name]; | |
| if (curr) { | |
| const passRate = ((curr.passed / curr.total) * 100).toFixed(0) + '%'; | |
| row += ` ${passRate} |`; | |
| } else { | |
| row += ' - |'; | |
| } | |
| console.log(row); | |
| } | |
| console.log('\n'); | |
| } | |
| } | |
| // --- Main --- | |
| const currentReports = findReports(artifactsDir); | |
| if (currentReports.length === 0) { | |
| console.log('No reports found.'); | |
| // We don't exit here because we might still want to see history if available, | |
| // but practically if current has no reports, something is wrong. | |
| // Sticking to original behavior roughly, but maybe we can continue. | |
| process.exit(0); | |
| } | |
| const currentStats = getStats(currentReports); | |
| const history = fetchHistoricalData(); | |
| generateMarkdown(currentStats, history); | |