111 lines
7.1 KiB
Python
111 lines
7.1 KiB
Python
"""Freeze the exact code, data, graph and parameters after complete DEV evaluation."""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
from .reproducibility import content_fingerprints, sha256_file
|
|
|
|
|
|
def make_freeze(dev: dict, questions: list[dict], fingerprints: dict, audit: dict,
|
|
*, purpose: str = 'independent_holdout') -> dict:
|
|
if purpose not in {'independent_holdout','historical_regression'}:
|
|
raise ValueError('Unknown evaluation purpose.')
|
|
expected_ids = {q['id'] for q in questions if q['split'] == 'dev'}
|
|
actual_ids = [row['id'] for row in dev['questions']]
|
|
if dev['configuration']['split'] != 'dev' or set(actual_ids) != expected_ids or len(actual_ids) != len(expected_ids):
|
|
raise RuntimeError('Freeze requires the complete DEV split, without duplicates.')
|
|
if dev['summary']['error_count'] or any(row.get('error') for row in dev['questions']):
|
|
raise RuntimeError('Freeze requires DEV without runtime errors.')
|
|
if dev['run_metadata']['fingerprints'] != fingerprints:
|
|
raise RuntimeError('Code or data changed after DEV; rerun DEV before freezing.')
|
|
if fingerprints.get('retrieval_policy') and any(
|
|
row.get('source_checked') is not True or row.get('all_facts_supported') is not True
|
|
for row in dev['questions']):
|
|
raise RuntimeError('Every DEV result must pass independent validation of all returned facts.')
|
|
if audit['dataset_sha256'] != fingerprints['dataset_sha256'] or audit['inputs'] != fingerprints['inputs']:
|
|
raise RuntimeError('Source audit does not match the benchmark and inputs.')
|
|
if len(audit['questions']) != len(questions) or {r['id'] for r in audit['questions']} != {q['id'] for q in questions}:
|
|
raise RuntimeError('Source audit must cover every question exactly once.')
|
|
schema3 = any(question.get('graph', {}).get('schema_version') == 3 for question in questions)
|
|
partition = None
|
|
if schema3:
|
|
if audit.get('source_corpus') != fingerprints.get('source_corpus'):
|
|
raise RuntimeError('Independent audit does not match original Markdown sources.')
|
|
if any(not row.get('source_consistent') for row in audit['questions']):
|
|
raise RuntimeError('Resolve source-inconsistent annotations before freezing v2.')
|
|
profiles = {split: {profile for question in questions if question['split'] == split
|
|
for profile in question.get('graph', {}).get('source_profiles', [])}
|
|
for split in ('dev', 'test')}
|
|
policy = audit.get('partition_policy', {})
|
|
if purpose == 'historical_regression':
|
|
partition = {'method': 'Previously exposed questions; regression, not independent TEST',
|
|
'dev_profile_count': len(profiles['dev']),
|
|
'test_profile_count': len(profiles['test']),
|
|
'source_profile_overlap': len(profiles['dev'] & profiles['test'])}
|
|
elif policy.get('method') == 'disjoint logical query groups':
|
|
# This mode explicitly reports known profiles. It must never be
|
|
# presented as the stronger, historic unseen-profile experiment.
|
|
from .benchmark_v4 import logical_group
|
|
from .source_oracle import norm
|
|
groups = {split: {logical_group(q) for q in questions if q['split'] == split}
|
|
for split in ('dev', 'test')}
|
|
if any(q['graph'].get('partition_group') != logical_group(q) for q in questions):
|
|
raise RuntimeError('Declared logical group does not match the question constraints.')
|
|
if not groups['dev'] or not groups['test'] or groups['dev'] & groups['test']:
|
|
raise RuntimeError('DEV/TEST logical groups overlap or are missing.')
|
|
exposed = set(policy.get('previously_exposed_groups', []))
|
|
if not exposed or groups['test'] & exposed:
|
|
raise RuntimeError('TEST contains previously exposed logical groups.')
|
|
texts = {split: {norm(q['question']) for q in questions if q['split'] == split}
|
|
for split in ('dev', 'test')}
|
|
if texts['dev'] & texts['test']:
|
|
raise RuntimeError('DEV/TEST question texts overlap.')
|
|
partition = {'method': policy['method'], 'logical_group_overlap': 0,
|
|
'previously_exposed_test_groups': 0,
|
|
'dev_group_count': len(groups['dev']), 'test_group_count': len(groups['test']),
|
|
'dev_profile_count': len(profiles['dev']), 'test_profile_count': len(profiles['test']),
|
|
'source_profile_overlap': len(profiles['dev'] & profiles['test']),
|
|
'limitations': policy['limitations']}
|
|
elif profiles['dev'] & profiles['test']:
|
|
raise RuntimeError('DEV/TEST source profiles overlap; independent holdout refused.')
|
|
if not profiles['dev'] or not profiles['test']:
|
|
raise RuntimeError('Primary v2 freeze requires declared DEV and TEST source profiles.')
|
|
if partition is None:
|
|
partition = {'method': 'disjoint source profiles',
|
|
'dev_profile_count': len(profiles['dev']),
|
|
'test_profile_count': len(profiles['test']), 'overlap': 0}
|
|
return {'schema_version': 2 if schema3 else 1, 'evaluation_kind':purpose,
|
|
'partition': partition, 'frozen_at_utc': datetime.now(timezone.utc).isoformat(),
|
|
'fingerprints': fingerprints, 'graph': dev['run_metadata']['graph'],
|
|
'parameters': {key: dev['configuration'][key] for key in ['limit', 'max_entities']},
|
|
'dev_summary': dev['summary'], 'source_audit_summary': audit['by_split'],
|
|
'policy': 'Report all questions including negatives. No exclusions or holdout tuning.'}
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--dev-results', type=Path, required=True)
|
|
parser.add_argument('--questions', type=Path, default=Path(__file__).parent / 'graphrag_questions_v4.json')
|
|
parser.add_argument('--audit', type=Path, required=True)
|
|
parser.add_argument('--output', type=Path, required=True)
|
|
parser.add_argument('--purpose', choices=['independent_holdout','historical_regression'],
|
|
default='independent_holdout')
|
|
args = parser.parse_args()
|
|
if args.output.exists():
|
|
parser.error('Freeze manifest already exists; choose a new version/output path.')
|
|
manifest = make_freeze(json.loads(args.dev_results.read_text()), json.loads(args.questions.read_text()),
|
|
content_fingerprints(args.questions), json.loads(args.audit.read_text()), purpose=args.purpose)
|
|
manifest.update({'dev_results_sha256': sha256_file(args.dev_results),
|
|
'audit_sha256': sha256_file(args.audit)})
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
with args.output.open('x', encoding='utf-8') as output:
|
|
output.write(json.dumps(manifest, ensure_ascii=False, indent=2) + '\n')
|
|
print(f'Frozen GraphRAG: {args.output}')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|