| |
| |
| |
| |
| |
|
|
| import fs from 'node:fs'; |
| import path from 'node:path'; |
| import { execSync } from 'node:child_process'; |
| import os from 'node:os'; |
|
|
| |
| |
| |
| export 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; |
| } |
|
|
| |
| |
| |
| export function getModelFromPath(reportPath) { |
| const parts = reportPath.split(path.sep); |
| |
| const artifactDir = parts.find((p) => p.startsWith('eval-logs-')); |
| if (!artifactDir) return 'unknown'; |
|
|
| const match = artifactDir.match(/^eval-logs-(.+)-(\d+)$/); |
| return match ? match[1] : 'unknown'; |
| } |
|
|
| |
| |
| |
| export function escapeRegex(string) { |
| return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); |
| } |
|
|
| |
| |
| |
| |
| export function getStatsFromReports(reports) { |
| 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) { |
| const filePath = testResult.name; |
| for (const assertion of testResult.assertionResults) { |
| const name = assertion.title; |
| if (!testStats[name]) { |
| testStats[name] = { passed: 0, total: 0, file: filePath }; |
| } |
| testStats[name].total++; |
| if (assertion.status === 'passed') { |
| testStats[name].passed++; |
| } |
| } |
| } |
| } catch (error) { |
| console.error(`Error processing report at ${reportPath}:`, error.message); |
| } |
| } |
| return statsByModel; |
| } |
|
|
| |
| |
| |
| |
| export function fetchNightlyHistory(lookbackCount) { |
| const history = []; |
| try { |
| const cmd = `gh run list --workflow evals-nightly.yml --branch main --limit ${ |
| lookbackCount + 2 |
| } --json databaseId,status`; |
| const runsJson = execSync(cmd, { encoding: 'utf-8' }); |
| let runs = JSON.parse(runsJson); |
|
|
| |
| runs = runs.filter((r) => r.status === 'completed').slice(0, lookbackCount); |
|
|
| for (const run of runs) { |
| const tmpDir = fs.mkdtempSync( |
| path.join(os.tmpdir(), `gemini-evals-hist-${run.databaseId}-`), |
| ); |
| try { |
| execSync( |
| `gh run download ${run.databaseId} -p "eval-logs-*" -D "${tmpDir}"`, |
| { stdio: 'ignore' }, |
| ); |
|
|
| const runReports = findReports(tmpDir); |
| if (runReports.length > 0) { |
| history.push({ |
| runId: run.databaseId, |
| stats: getStatsFromReports(runReports), |
| }); |
| } |
| } catch (error) { |
| console.error( |
| `Failed to process artifacts for run ${run.databaseId}:`, |
| error.message, |
| ); |
| } finally { |
| fs.rmSync(tmpDir, { recursive: true, force: true }); |
| } |
| } |
| } catch (error) { |
| console.error('Failed to fetch history:', error.message); |
| } |
| return history; |
| } |
|
|