Download scripts/eval.ts from Ryukijano/conjugate: direct link, hf CLI and curl.
- Browser
- Download file 11 kB
-
https://huggingface.co/spaces/Ryukijano/conjugate/resolve/main/scripts/eval.ts
- Command line
-
hf download hf://spaces/Ryukijano/conjugate/scripts/eval.ts
-
curl -L -o eval.ts https://huggingface.co/spaces/Ryukijano/conjugate/resolve/main/scripts/eval.ts
11 kB
| /** | |
| * Software evals for the research audit pipeline. Not a clinical benchmark: | |
| * no clinician cases, answer keys or scoring of medical correctness. Every | |
| * expected value here is the app's own server-side mapping, so these numbers | |
| * measure whether the verifier enforces its contract, not whether the science | |
| * is right. Output: evals/results.json (read by the web Evals view). | |
| */ | |
| import { writeFileSync, mkdirSync } from 'node:fs'; | |
| import { performance } from 'node:perf_hooks'; | |
| import { type ResearchDraft, type ResearchRequest, type ResearchResult } from '@her2/shared'; | |
| import { auditResearchDraft, runResearch } from '../apps/api/src/research.js'; | |
| import { | |
| allowedTools, DATASET_SHA256, derivedSourceId, expectedClaim, labelSourceId, readResearchTool, workbookSourceId | |
| } from '../apps/api/src/research-evidence.js'; | |
| import { CLAUDE_MODEL } from '../apps/api/src/evidence.js'; | |
| const PRODUCTS = ['DRG0CYMEB', 'DRG0ERKBH'] as const; | |
| const QUESTIONS = ['composition', 'linker_release', 'payload_risk_transfer', 'workbook_safety', 'label_identity'] as const; | |
| const POLICIES = ['all', 'workbook_only'] as const; | |
| const DRILLS = ['cross_product_citation', 'derived_as_primary', 'invented_source'] as const; | |
| type Scope = { product_id: ResearchRequest['product_id']; question_id: ResearchRequest['question_id']; evidence_policy: ResearchRequest['evidence_policy'] }; | |
| const scopes: Scope[] = PRODUCTS.flatMap(product_id => QUESTIONS.flatMap(question_id => POLICIES.map(evidence_policy => ({ product_id, question_id, evidence_policy })))); | |
| function request(scope: Scope, engine: ResearchRequest['engine'], integrity_drill: ResearchRequest['integrity_drill'] = 'none'): ResearchRequest { | |
| return { ...scope, engine, integrity_drill, synthetic_confirmed: true }; | |
| } | |
| function invariantsHold(result: ResearchResult) { | |
| return result.clinical_status === 'draft_pending_pharmacist' && result.needs_human === true && result.eligibility === 'not_assessed' | |
| && result.guardrail.status === 'blocked' && result.answer_correctness_probability === null && result.omission_probability === null; | |
| } | |
| function traceMs(result: ResearchResult, stage: ResearchResult['trace'][number]['stage']) { | |
| return Math.round(result.trace.filter(step => step.stage === stage).reduce((sum, step) => sum + step.duration_ms, 0)); | |
| } | |
| // Grid A: rules-only runs, no drill. Verdict per scope and the shift when the label is withheld. | |
| type VerdictRow = Scope & { verdict: string; source_ids: string[]; receipts_read: string[]; invariants: boolean }; | |
| async function gridVerdicts() { | |
| const rows: VerdictRow[] = []; | |
| for (const scope of scopes) { | |
| const result = await runResearch(request(scope, 'evidence')); | |
| rows.push({ ...scope, verdict: result.claims[0]?.verdict ?? 'no_claim', source_ids: result.claims[0]?.source_ids ?? [], | |
| receipts_read: result.receipts.map(r => r.id), invariants: invariantsHold(result) }); | |
| } | |
| const shifts = PRODUCTS.flatMap(product_id => QUESTIONS.map(question_id => { | |
| const all = rows.find(r => r.product_id === product_id && r.question_id === question_id && r.evidence_policy === 'all')!; | |
| const wb = rows.find(r => r.product_id === product_id && r.question_id === question_id && r.evidence_policy === 'workbook_only')!; | |
| return { product_id, question_id, with_label: all.verdict, workbook_only: wb.verdict, changed: all.verdict !== wb.verdict }; | |
| })); | |
| return { rows, shifts, changed_count: shifts.filter(s => s.changed).length, total: shifts.length }; | |
| } | |
| // Grid B: injected bad citations after drafting. The verifier must reject every one. | |
| type DrillRow = Scope & { drill: string; rejected: boolean; caught_by: string[]; invariants: boolean }; | |
| async function gridDrills() { | |
| const rows: DrillRow[] = []; | |
| for (const scope of scopes) for (const drill of DRILLS) { | |
| const result = await runResearch(request(scope, 'evidence', drill)); | |
| const caught = result.challenges.filter(c => c.outcome === 'caught').map(c => c.code); | |
| rows.push({ ...scope, drill, rejected: result.draft_integrity === 'rejected' && result.claims.length === 0 && result.omitted_claim_ids.length > 0, | |
| caught_by: caught, invariants: invariantsHold(result) }); | |
| } | |
| return { rows, rejected_count: rows.filter(r => r.rejected).length, total: rows.length }; | |
| } | |
| // Grid C: scripted drafter strategies that would score well under a naive metric. | |
| // These are hand-written drafts, not model behaviour. | |
| const STRATEGIES = { | |
| abstain_all: 'Return no claims at all (would max out an "abstention" metric).', | |
| cite_everything: 'Cite every retrieved source, including the author-derived note.', | |
| duplicate_claim: 'Repeat the same claim twice with the right sources.', | |
| other_product_label: "Cite the other product's label summary.", | |
| invented_source: 'Cite a source id that was never retrieved.', | |
| swap_product: 'Keep the sources but declare the other product id.', | |
| honest_expected: 'Cite exactly the independently expected sources (control).' | |
| } as const; | |
| type Strategy = keyof typeof STRATEGIES; | |
| function strategyDraft(strategy: Strategy, req: ResearchRequest, receipts: ReturnType<typeof readResearchTool>): ResearchDraft { | |
| const expected = expectedClaim(req, receipts); | |
| const other = req.product_id === 'DRG0ERKBH' ? 'DRG0CYMEB' : 'DRG0ERKBH'; | |
| const claim = (source_ids: string[]) => ({ claim_id: req.question_id, source_ids }); | |
| switch (strategy) { | |
| case 'abstain_all': return { product_id: req.product_id, claims: [] }; | |
| case 'cite_everything': return { product_id: req.product_id, claims: [claim(receipts.map(r => r.id))] }; | |
| case 'duplicate_claim': return { product_id: req.product_id, claims: [claim(expected.source_ids), claim(expected.source_ids)] }; | |
| case 'other_product_label': return { product_id: req.product_id, claims: [claim([labelSourceId(other)])] }; | |
| case 'invented_source': return { product_id: req.product_id, claims: [claim(['ADCDB-PRIMARY-ASSAY-2024'])] }; | |
| case 'swap_product': return { product_id: other, claims: [claim(expected.source_ids)] }; | |
| case 'honest_expected': return { product_id: req.product_id, claims: [claim(expected.source_ids)] }; | |
| } | |
| } | |
| type StrategyRow = Scope & { strategy: Strategy; accepted: boolean; caught_by: string[]; omitted: string[] }; | |
| function gridStrategies() { | |
| const rows: StrategyRow[] = []; | |
| for (const scope of scopes) for (const strategy of Object.keys(STRATEGIES) as Strategy[]) { | |
| const req = request(scope, 'evidence'); | |
| const receipts = allowedTools(req).flatMap(tool => readResearchTool(tool, req)); | |
| const audit = auditResearchDraft(req, strategyDraft(strategy, req, receipts), receipts); | |
| rows.push({ ...scope, strategy, accepted: audit.accepted, caught_by: audit.challenges.filter(c => c.outcome === 'caught').map(c => c.code), | |
| omitted: audit.omitted_claim_ids }); | |
| } | |
| const byStrategy = (Object.keys(STRATEGIES) as Strategy[]).map(strategy => { | |
| const mine = rows.filter(r => r.strategy === strategy); | |
| return { strategy, description: STRATEGIES[strategy], accepted: mine.filter(r => r.accepted).length, total: mine.length, | |
| caught_by: [...new Set(mine.flatMap(r => r.caught_by))] }; | |
| }); | |
| return { rows, by_strategy: byStrategy }; | |
| } | |
| // Grid D: Claude (plan + draft) on every scope. Measures whether the model's identifier-only | |
| // output matches the independent expected selection, plus latency. Skipped without a key. | |
| async function gridClaude(apiKey: string | undefined) { | |
| if (!apiKey) return { skipped: true as const, reason: 'ANTHROPIC_API_KEY not set' }; | |
| type ClaudeOk = Scope & { ok: true; accepted: boolean; verdict: string; tools_planned: (string | null)[]; draft_source_ids: string[]; draft_claim_count: number; plan_ms: number; draft_ms: number; wall_ms: number; invariants: boolean }; | |
| type ClaudeFail = Scope & { ok: false; error_code: string; wall_ms: number }; | |
| const rows: (ClaudeOk | ClaudeFail)[] = []; | |
| for (const scope of scopes) { | |
| const started = performance.now(); | |
| try { | |
| const result = await runResearch(request(scope, 'claude'), { claude: { apiKey } }); | |
| const expected = result.claims[0]; | |
| const draftClaim = result.draft.claims[0]; | |
| rows.push({ ...scope, ok: true as const, accepted: result.draft_integrity === 'accepted', verdict: expected?.verdict ?? 'no_claim', | |
| tools_planned: result.trace.filter(s => s.stage === 'retrieve' && s.status === 'completed').map(s => s.tool), | |
| draft_source_ids: draftClaim?.source_ids ?? [], draft_claim_count: result.draft.claims.length, | |
| plan_ms: traceMs(result, 'plan'), draft_ms: traceMs(result, 'draft'), wall_ms: Math.round(performance.now() - started), invariants: invariantsHold(result) }); | |
| } catch (error) { | |
| rows.push({ ...scope, ok: false as const, error_code: (error as { code?: string }).code ?? 'UNKNOWN', wall_ms: Math.round(performance.now() - started) }); | |
| } | |
| } | |
| const ok = rows.filter((r): r is ClaudeOk => r.ok); | |
| const med = (xs: number[]) => { const s = [...xs].sort((a, b) => a - b); return s.length ? s[Math.floor(s.length / 2)] : null; }; | |
| return { skipped: false as const, model: CLAUDE_MODEL, rows, completed: ok.length, failed: rows.length - ok.length, | |
| accepted: ok.filter(r => r.accepted).length, total: rows.length, | |
| median_plan_ms: med(ok.map(r => r.plan_ms)), median_draft_ms: med(ok.map(r => r.draft_ms)), median_wall_ms: med(ok.map(r => r.wall_ms)) }; | |
| } | |
| async function main() { | |
| const verdicts = await gridVerdicts(); | |
| const drills = await gridDrills(); | |
| const strategies = gridStrategies(); | |
| const claude = await gridClaude(process.env.ANTHROPIC_API_KEY?.trim() || undefined); | |
| const out = { | |
| kind: 'software_evals_not_clinical_benchmark', | |
| generated_at: new Date().toISOString(), | |
| dataset_sha256: DATASET_SHA256, | |
| products: PRODUCTS, questions: QUESTIONS, policies: POLICIES, | |
| source_ids_example: { workbook: workbookSourceId('DRG0ERKBH'), label: labelSourceId('DRG0ERKBH'), derived: derivedSourceId('DRG0ERKBH') }, | |
| verdicts, drills, strategies, claude, | |
| notes: [ | |
| 'Expected claims and sources are the app\'s own server-side definitions; agreement measures contract enforcement, not scientific truth.', | |
| 'Grid C drafts are scripted by the authors to probe the verifier. They are not observed model behaviour.', | |
| 'Clinical release stays blocked in every run; no eval changes that gate.' | |
| ] | |
| }; | |
| mkdirSync('evals', { recursive: true }); | |
| writeFileSync('evals/results.json', JSON.stringify(out, null, 1) + '\n'); | |
| console.log(JSON.stringify({ verdict_shifts: `${verdicts.changed_count}/${verdicts.total}`, drills_rejected: `${drills.rejected_count}/${drills.total}`, | |
| strategies: strategies.by_strategy.map(s => `${s.strategy}:${s.accepted}/${s.total}`), claude: claude.skipped ? claude.reason : `${claude.accepted}/${claude.total} accepted, ${claude.failed} failed, median wall ${claude.median_wall_ms}ms` })); | |
| } | |
| main().catch(error => { console.error(error instanceof Error ? error.message : String(error)); process.exit(1); }); | |