eduevidence 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +506 -0
- package/README.zh-CN.md +434 -0
- package/SKILL.md +195 -0
- package/bin/eduevidence.js +127 -0
- package/domains/education/manifest.json +183 -0
- package/domains/education/outcome_taxonomy.json +127 -0
- package/domains/manifest.json +26 -0
- package/domains/policy/frame.schema.json +234 -0
- package/domains/policy/manifest.json +10 -0
- package/domains/policy/methodology_checklist.json +109 -0
- package/domains/policy/outcome_taxonomy.json +53 -0
- package/domains/policy/references/causal-identification.md +45 -0
- package/domains/policy/references/cost-evidence.md +44 -0
- package/domains/policy/references/equity.md +42 -0
- package/domains/policy/references/evidence-hierarchy.md +41 -0
- package/domains/policy/references/implementation-evidence.md +47 -0
- package/eduevidence_cli.py +26 -0
- package/engine/__init__.py +11 -0
- package/engine/__pycache__/__init__.cpython-312.pyc +0 -0
- package/engine/__pycache__/analysis.cpython-312.pyc +0 -0
- package/engine/__pycache__/bias.cpython-312.pyc +0 -0
- package/engine/__pycache__/briefs.cpython-312.pyc +0 -0
- package/engine/__pycache__/capabilities.cpython-312.pyc +0 -0
- package/engine/__pycache__/citation_check.cpython-312.pyc +0 -0
- package/engine/__pycache__/contracts.cpython-312.pyc +0 -0
- package/engine/__pycache__/datasets.cpython-312.pyc +0 -0
- package/engine/__pycache__/events.cpython-312.pyc +0 -0
- package/engine/__pycache__/evidence_graph.cpython-312.pyc +0 -0
- package/engine/__pycache__/evidence_review.cpython-312.pyc +0 -0
- package/engine/__pycache__/evidencecore.cpython-312.pyc +0 -0
- package/engine/__pycache__/gap_lens.cpython-312.pyc +0 -0
- package/engine/__pycache__/gaps.cpython-312.pyc +0 -0
- package/engine/__pycache__/graph_store.cpython-312.pyc +0 -0
- package/engine/__pycache__/graph_validate.cpython-312.pyc +0 -0
- package/engine/__pycache__/ids.cpython-312.pyc +0 -0
- package/engine/__pycache__/library.cpython-312.pyc +0 -0
- package/engine/__pycache__/library_builtin.cpython-312.pyc +0 -0
- package/engine/__pycache__/living.cpython-312.pyc +0 -0
- package/engine/__pycache__/log.cpython-312.pyc +0 -0
- package/engine/__pycache__/meta_analysis.cpython-312.pyc +0 -0
- package/engine/__pycache__/meta_synthesis.cpython-312.pyc +0 -0
- package/engine/__pycache__/migration.cpython-312.pyc +0 -0
- package/engine/__pycache__/mode_router.cpython-312.pyc +0 -0
- package/engine/__pycache__/paths.cpython-312.pyc +0 -0
- package/engine/__pycache__/pilot.cpython-312.pyc +0 -0
- package/engine/__pycache__/planner.cpython-312.pyc +0 -0
- package/engine/__pycache__/project.cpython-312.pyc +0 -0
- package/engine/__pycache__/projections.cpython-312.pyc +0 -0
- package/engine/__pycache__/robustness.cpython-312.pyc +0 -0
- package/engine/__pycache__/run.cpython-312.pyc +0 -0
- package/engine/__pycache__/semantics.cpython-312.pyc +0 -0
- package/engine/__pycache__/study_design.cpython-312.pyc +0 -0
- package/engine/__pycache__/synthesis.cpython-312.pyc +0 -0
- package/engine/__pycache__/tribunal.cpython-312.pyc +0 -0
- package/engine/__pycache__/update.cpython-312.pyc +0 -0
- package/engine/__pycache__/versions.cpython-312.pyc +0 -0
- package/engine/analysis.py +308 -0
- package/engine/bias.py +178 -0
- package/engine/briefs.py +106 -0
- package/engine/capabilities.py +99 -0
- package/engine/citation_check.py +192 -0
- package/engine/contracts.py +117 -0
- package/engine/datasets.py +165 -0
- package/engine/events.py +67 -0
- package/engine/evidence_graph.py +571 -0
- package/engine/evidence_review.py +88 -0
- package/engine/evidencecore.py +182 -0
- package/engine/gap_lens.py +132 -0
- package/engine/gaps.py +169 -0
- package/engine/graph_store.py +335 -0
- package/engine/graph_validate.py +87 -0
- package/engine/ids.py +77 -0
- package/engine/library.py +268 -0
- package/engine/library_builtin.py +301 -0
- package/engine/living.py +671 -0
- package/engine/log.py +39 -0
- package/engine/meta_analysis.py +333 -0
- package/engine/meta_synthesis.py +111 -0
- package/engine/migration.py +397 -0
- package/engine/mode_router.py +72 -0
- package/engine/paths.py +15 -0
- package/engine/pilot.py +368 -0
- package/engine/planner.py +126 -0
- package/engine/project.py +118 -0
- package/engine/projections.py +240 -0
- package/engine/robustness.py +109 -0
- package/engine/run.py +85 -0
- package/engine/semantics.py +135 -0
- package/engine/study_design.py +87 -0
- package/engine/synthesis.py +187 -0
- package/engine/tribunal.py +408 -0
- package/engine/update.py +113 -0
- package/engine/versions.py +12 -0
- package/install.sh +510 -0
- package/integrations/__init__.py +1 -0
- package/integrations/__pycache__/__init__.cpython-312.pyc +0 -0
- package/integrations/__pycache__/agent_mcp.cpython-312.pyc +0 -0
- package/integrations/__pycache__/smart_web_fetch.cpython-312.pyc +0 -0
- package/integrations/agent_mcp.py +856 -0
- package/integrations/smart_web_fetch.py +59 -0
- package/package.json +50 -0
- package/pyproject.toml +55 -0
- package/references/applicability-policy.md +88 -0
- package/references/education-framing.md +132 -0
- package/references/effect_size_formulas.md +35 -0
- package/references/evaluation-design.md +111 -0
- package/references/evidence-quality.md +79 -0
- package/references/grade_framework.md +29 -0
- package/references/intervention-design.md +98 -0
- package/references/methodology-audit.md +103 -0
- package/references/outcome-taxonomy.md +106 -0
- package/references/retrieval-protocol.md +142 -0
- package/references/skeptic-protocol.md +93 -0
- package/references/social_science_pitfalls.md +48 -0
- package/references/source-validity.md +140 -0
- package/references/tribunal-policy.md +112 -0
- package/references/wwc_standards.md +29 -0
- package/retrieval/__init__.py +1 -0
- package/retrieval/__pycache__/__init__.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/corpus_store.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/dedupe.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/failures.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/fetch.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/search.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/source.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/validate.cpython-312.pyc +0 -0
- package/retrieval/corpus_store.py +181 -0
- package/retrieval/dedupe.py +127 -0
- package/retrieval/failures.py +90 -0
- package/retrieval/fetch.py +435 -0
- package/retrieval/search.py +493 -0
- package/retrieval/source.py +160 -0
- package/retrieval/validate.py +257 -0
- package/schemas/agent-mcp-approval.schema.json +57 -0
- package/schemas/chart-spec.schema.json +88 -0
- package/schemas/cross-model-review.schema.json +28 -0
- package/schemas/education-frame.schema.json +106 -0
- package/schemas/evaluation.schema.json +35 -0
- package/schemas/evidence.schema.json +81 -0
- package/schemas/fetch-result.schema.json +119 -0
- package/schemas/intervention.schema.json +46 -0
- package/schemas/methodology.schema.json +102 -0
- package/schemas/report-result.schema.json +381 -0
- package/schemas/report-spec.schema.json +130 -0
- package/schemas/source.schema.json +311 -0
- package/schemas/v2/analysis-plan.schema.json +28 -0
- package/schemas/v2/analysis-run.schema.json +33 -0
- package/schemas/v2/claim.schema.json +26 -0
- package/schemas/v2/dataset-asset.schema.json +40 -0
- package/schemas/v2/decision-snapshot.schema.json +53 -0
- package/schemas/v2/evidence-link.schema.json +38 -0
- package/schemas/v2/finding.schema.json +47 -0
- package/schemas/v2/graph-revision.schema.json +30 -0
- package/schemas/v2/knowledge-gap.schema.json +40 -0
- package/schemas/v2/methodology-audit.schema.json +30 -0
- package/schemas/v2/outcome.schema.json +18 -0
- package/schemas/v2/project.schema.json +31 -0
- package/schemas/v2/research-intent.schema.json +24 -0
- package/schemas/v2/run.schema.json +43 -0
- package/schemas/v2/source.schema.json +24 -0
- package/schemas/v2/study-design.schema.json +67 -0
- package/schemas/v2/study.schema.json +37 -0
- package/schemas/v3/pilot-outcome.schema.json +132 -0
- package/schemas/v3/run-manifest.schema.json +193 -0
- package/schemas/v3/synthesis.schema.json +86 -0
- package/schemas/v4/drift-report.schema.json +66 -0
- package/schemas/v4/evidence-library.schema.json +42 -0
- package/schemas/v4/living-subscription.schema.json +55 -0
- package/schemas/v4/meta-analysis.schema.json +152 -0
- package/schemas/verdict.schema.json +56 -0
- package/scripts/__init__.py +0 -0
- package/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_evaluator.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_judge.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_routing.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_v2.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_v3.cpython-312.pyc +0 -0
- package/scripts/__pycache__/build_result.cpython-312.pyc +0 -0
- package/scripts/__pycache__/claim_audit.cpython-312.pyc +0 -0
- package/scripts/__pycache__/complexity_gate.cpython-312.pyc +0 -0
- package/scripts/__pycache__/compute_confidence.cpython-312.pyc +0 -0
- package/scripts/__pycache__/dashboard_server.cpython-312.pyc +0 -0
- package/scripts/__pycache__/did_regression.cpython-312.pyc +0 -0
- package/scripts/__pycache__/effect_calculator.cpython-312.pyc +0 -0
- package/scripts/__pycache__/evidence_matrix.cpython-312.pyc +0 -0
- package/scripts/__pycache__/evidence_score.cpython-312.pyc +0 -0
- package/scripts/__pycache__/evidence_semantics.cpython-312.pyc +0 -0
- package/scripts/__pycache__/fetch_benchmark.cpython-312.pyc +0 -0
- package/scripts/__pycache__/lint_report_layout.cpython-312.pyc +0 -0
- package/scripts/__pycache__/orchestrator.cpython-312.pyc +0 -0
- package/scripts/__pycache__/pre_verdict_gate.cpython-312.pyc +0 -0
- package/scripts/__pycache__/recompute_demo_quality.cpython-312.pyc +0 -0
- package/scripts/__pycache__/render_report.cpython-312.pyc +0 -0
- package/scripts/__pycache__/render_report_html.cpython-312.pyc +0 -0
- package/scripts/__pycache__/run_workspace.cpython-312.pyc +0 -0
- package/scripts/__pycache__/skill_lint.cpython-312.pyc +0 -0
- package/scripts/__pycache__/startup_probe.cpython-312.pyc +0 -0
- package/scripts/__pycache__/sync_killer_demo_report.cpython-312.pyc +0 -0
- package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.0.2.pyc +0 -0
- package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.1.1.pyc +0 -0
- package/scripts/__pycache__/validate_schema.cpython-312.pyc +0 -0
- package/scripts/audit_dois.py +292 -0
- package/scripts/bake_pack.sh +37 -0
- package/scripts/benchmark.py +183 -0
- package/scripts/benchmark_evaluator.py +371 -0
- package/scripts/benchmark_judge.py +535 -0
- package/scripts/benchmark_routing.py +120 -0
- package/scripts/benchmark_v2.py +304 -0
- package/scripts/benchmark_v3.py +552 -0
- package/scripts/build_esl_artifacts.py +1921 -0
- package/scripts/build_evidence_library.py +307 -0
- package/scripts/build_killer_demo.py +295 -0
- package/scripts/build_result.py +311 -0
- package/scripts/check_version_consistency.py +96 -0
- package/scripts/citation_check.py +123 -0
- package/scripts/claim_audit.py +157 -0
- package/scripts/complexity_gate.py +180 -0
- package/scripts/compute_confidence.py +176 -0
- package/scripts/dashboard_server.py +536 -0
- package/scripts/did_regression.py +315 -0
- package/scripts/effect_calculator.py +99 -0
- package/scripts/enrich_projects_human_and_lieflat.py +315 -0
- package/scripts/evidence_matrix.py +129 -0
- package/scripts/evidence_score.py +234 -0
- package/scripts/evidence_semantics.py +87 -0
- package/scripts/fetch_benchmark.py +177 -0
- package/scripts/generate_metrics.py +99 -0
- package/scripts/generate_new_projects.py +686 -0
- package/scripts/generate_promo.py +192 -0
- package/scripts/lint_report_layout.py +182 -0
- package/scripts/orchestrator.py +1456 -0
- package/scripts/pre_verdict_gate.py +513 -0
- package/scripts/quickstart.py +121 -0
- package/scripts/rebake_all_5themes.py +88 -0
- package/scripts/recompute_demo_quality.py +205 -0
- package/scripts/render_report.py +270 -0
- package/scripts/render_report_html.py +356 -0
- package/scripts/retraction_watch.py +110 -0
- package/scripts/run_workspace.py +337 -0
- package/scripts/serve_web.py +54 -0
- package/scripts/skill_lint.py +150 -0
- package/scripts/startup_probe.py +265 -0
- package/scripts/sync_killer_demo_report.py +270 -0
- package/scripts/test_adversarial_empirical.py +541 -0
- package/scripts/validate_schema.py +256 -0
- package/skill/agents/education-planner.md +80 -0
- package/skill/agents/evaluation-designer.md +74 -0
- package/skill/agents/evidence-analyst.md +106 -0
- package/skill/agents/evidence-judge.md +111 -0
- package/skill/agents/evidence-retriever.md +80 -0
- package/skill/agents/intervention-designer.md +82 -0
- package/skill/agents/method-reviewer.md +104 -0
- package/skill/agents/skeptic.md +89 -0
- package/skill/sub-skills/aihot-trend-analysis/SKILL.md +31 -0
- package/skill/sub-skills/contradiction-analysis/SKILL.md +17 -0
- package/skill/sub-skills/data-analysis/SKILL.md +23 -0
- package/skill/sub-skills/ethics-review/SKILL.md +25 -0
- package/skill/sub-skills/evidence-extraction/SKILL.md +19 -0
- package/skill/sub-skills/evidence-review/SKILL.md +18 -0
- package/skill/sub-skills/gap-analysis/SKILL.md +25 -0
- package/skill/sub-skills/literature-review/SKILL.md +21 -0
- package/skill/sub-skills/methodology-audit/SKILL.md +20 -0
- package/skill/sub-skills/report-generation/SKILL.md +51 -0
- package/skill/sub-skills/research-planning/SKILL.md +21 -0
- package/skill/sub-skills/study-design/SKILL.md +16 -0
- package/skill/task-briefs/adjudicate.md +17 -0
- package/skill/task-briefs/audit.md +15 -0
- package/skill/task-briefs/challenge.md +15 -0
- package/skill/task-briefs/evaluate.md +13 -0
- package/skill/task-briefs/extract.md +16 -0
- package/skill/task-briefs/frame.md +17 -0
- package/skill/task-briefs/intervene.md +14 -0
- package/skill/task-briefs/present.md +16 -0
- package/skill/task-briefs/retrieve.md +15 -0
- package/visualization/eduevidence-report/assets/base.css +337 -0
- package/visualization/eduevidence-report/motion/motion.css +157 -0
- package/visualization/eduevidence-report/motion/motion.js +107 -0
- package/visualization/eduevidence-report/references/bilingual-style.md +7 -0
- package/visualization/eduevidence-report/references/component-catalog.md +145 -0
- package/visualization/eduevidence-report/references/evidence-expansion.md +65 -0
- package/visualization/eduevidence-report/references/full-report-outline.md +86 -0
- package/visualization/eduevidence-report/references/layout-constraints.md +63 -0
- package/visualization/eduevidence-report/references/lieflat-composition.md +79 -0
- package/visualization/eduevidence-report/references/motion-system.md +31 -0
- package/visualization/eduevidence-report/schemas/adapter-envelope.schema.json +22 -0
- package/visualization/eduevidence-report/schemas/visual-layout.schema.json +87 -0
- package/visualization/eduevidence-report/scripts/__pycache__/adapter_contract.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_artifact_manifest.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_charts.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_figures.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_infographics.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_report.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/charts_data.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/lieflat_engine.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/zh_labels.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/adapter_contract.py +72 -0
- package/visualization/eduevidence-report/scripts/build_artifact_manifest.py +70 -0
- package/visualization/eduevidence-report/scripts/build_charts.py +283 -0
- package/visualization/eduevidence-report/scripts/build_figures.py +515 -0
- package/visualization/eduevidence-report/scripts/build_infographics.py +268 -0
- package/visualization/eduevidence-report/scripts/build_report.py +3211 -0
- package/visualization/eduevidence-report/scripts/charts_data.py +617 -0
- package/visualization/eduevidence-report/scripts/check_mobile_layout.js +220 -0
- package/visualization/eduevidence-report/scripts/lieflat_engine.py +852 -0
- package/visualization/eduevidence-report/scripts/zh_labels.py +245 -0
- package/visualization/eduevidence-report/themes/academic.css +94 -0
- package/visualization/eduevidence-report/themes/claude.css +96 -0
- package/visualization/eduevidence-report/themes/datalab-dark.css +147 -0
- package/visualization/eduevidence-report/themes/datalab.css +151 -0
- package/visualization/eduevidence-report/themes/presentation.css +140 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""benchmark_v3.py — Layer B Empirical Benchmark harness (v3).
|
|
3
|
+
|
|
4
|
+
Runs B0-B4 baselines over benchmarks/questions.jsonl with REAL model calls and
|
|
5
|
+
records a full run manifest (schemas/v3/run-manifest.schema.json). The
|
|
6
|
+
deterministic simulation (benchmark_v2) remains only as harness validation and
|
|
7
|
+
is always labelled SIMULATED — empirical numbers are the only performance
|
|
8
|
+
evidence (docs/benchmark.md Layer A vs Layer B).
|
|
9
|
+
|
|
10
|
+
Baselines:
|
|
11
|
+
B0_direct_llm 直接问模型,无检索无协议
|
|
12
|
+
B1_search_llm 一次搜索再回答(驱动不支持真实搜索时降级并如实记录)
|
|
13
|
+
B2_standard_agent 有检索无教育协议
|
|
14
|
+
B3_eduevidence_single 完整 EvidenceFlow 单 Agent(精简协议)
|
|
15
|
+
B4_eduevidence_agent_mcp 增强模式(API 驱动下如实标注 agent_mcp_used=false)
|
|
16
|
+
|
|
17
|
+
Drivers:
|
|
18
|
+
api OpenAI-compatible chat completions
|
|
19
|
+
env: EDUEVIDENCE_LLM_API_KEY / EDUEVIDENCE_LLM_BASE_URL / EDUEVIDENCE_LLM_MODEL
|
|
20
|
+
sim deterministic simulation (SIMULATED) — harness validation only
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
python3 scripts/benchmark_v3.py run --baselines B2_standard_agent,B3_eduevidence_single \
|
|
24
|
+
--questions benchmarks/questions.jsonl --annotations benchmarks/annotations \
|
|
25
|
+
--repeats 3 --out benchmarks/empirical/run-20260813-120000
|
|
26
|
+
python3 scripts/benchmark_v3.py eval --run benchmarks/empirical/run-20260813-120000
|
|
27
|
+
python3 scripts/benchmark_v3.py report --run benchmarks/empirical/run-20260813-120000
|
|
28
|
+
"""
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import argparse
|
|
32
|
+
import json
|
|
33
|
+
import os
|
|
34
|
+
import sys
|
|
35
|
+
import urllib.error
|
|
36
|
+
import urllib.request
|
|
37
|
+
from datetime import datetime, timezone
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
from typing import Any
|
|
40
|
+
|
|
41
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
42
|
+
|
|
43
|
+
from benchmark import load_questions, validate_questions # noqa: E402
|
|
44
|
+
|
|
45
|
+
BASELINES = (
|
|
46
|
+
"B0_direct_llm", "B1_search_llm", "B2_standard_agent",
|
|
47
|
+
"B3_eduevidence_single", "B4_eduevidence_agent_mcp",
|
|
48
|
+
)
|
|
49
|
+
DEFAULT_BUDGET_TOKENS = 1_000_000
|
|
50
|
+
|
|
51
|
+
# ---------------------------------------------------------------- prompts
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _prompt_b0(q: dict) -> str:
|
|
55
|
+
return (
|
|
56
|
+
"请直接回答以下教育决策问题,给出明确结论(采用/试点/拒绝/证据不足)和理由。\n\n"
|
|
57
|
+
f"问题:{q['question']}\n"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _prompt_b1(q: dict) -> str:
|
|
62
|
+
return (
|
|
63
|
+
"请先使用可用检索工具搜索相关研究证据,再基于搜索结果回答以下教育决策问题,"
|
|
64
|
+
"给出明确结论(采用/试点/拒绝/证据不足)并引用来源。\n\n"
|
|
65
|
+
f"问题:{q['question']}\n"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _prompt_b2(q: dict) -> str:
|
|
70
|
+
return (
|
|
71
|
+
"你是研究助理。请检索并分析相关文献,然后回答以下教育决策问题。"
|
|
72
|
+
"必须:列出支持与反对的证据并标注来源;评估证据质量;给出结论(采用/试点/拒绝/证据不足)。\n\n"
|
|
73
|
+
f"问题:{q['question']}\n"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
_PROTOCOL_B3 = (
|
|
78
|
+
"你是 EduEvidence 教育证据决策引擎(单 Agent 完整协议)。对问题执行:\n"
|
|
79
|
+
"1) Frame:确定学习者/干预/对照/目标结果(结果必须使用枚举:knowledge_gain, concept_understanding, "
|
|
80
|
+
"retention, transfer, independent_problem_solving, completion_time, accuracy, code_quality, "
|
|
81
|
+
"assignment_score, engagement, motivation, cognitive_load, help_seeking, metacognition, ai_dependency, "
|
|
82
|
+
"over_reliance, reduced_effort, reduced_transfer, academic_integrity_risk, false_confidence);\n"
|
|
83
|
+
"2) 检索支持证据与独立反方证据(null/negative result、AI 依赖、迁移受损、新奇效应);\n"
|
|
84
|
+
"3) 方法学审查(任务完成表现 ≠ 学习效果,最高优先级);\n"
|
|
85
|
+
"4) 证据裁决(支持/反驳/中性分开;冲突不强行裁决);\n"
|
|
86
|
+
"5) 结论边界(能主张什么/不能主张什么/是否超出证据范围);\n"
|
|
87
|
+
"6) 决策动作(adopt / pilot / reject / insufficient_evidence)。\n"
|
|
88
|
+
"严格区分:任务表现提升 ≠ 学习效果提升。"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _prompt_b3(q: dict) -> str:
|
|
93
|
+
return (
|
|
94
|
+
_PROTOCOL_B3 + "\n\n" + f"问题:{q['question']}\n" +
|
|
95
|
+
"\n请以 JSON 输出:{\"frame\": {...}, \"claims\": [{\"claim\": ..., \"outcome_type\": ..., "
|
|
96
|
+
"\"direction\": \"support|contradict|neutral\", \"source\": ...}], \"contradictions\": [...], "
|
|
97
|
+
"\"scope\": {\"can_claim\": [...], \"cannot_claim\": [...], \"exceeds_boundary\": [...]}, "
|
|
98
|
+
"\"recommended_action\": \"adopt|pilot|reject|insufficient_evidence\", "
|
|
99
|
+
"\"confidence\": \"High|Moderate|Low|Insufficient\"}\n"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _prompt_b4(q: dict) -> str:
|
|
104
|
+
return (
|
|
105
|
+
_PROTOCOL_B3 + "\n" +
|
|
106
|
+
"(增强模式:检索者、反证挑战者、方法学审查者、证据裁决者由独立上下文分别执行并交叉复核。)\n\n"
|
|
107
|
+
+ f"问题:{q['question']}\n" +
|
|
108
|
+
"\n请以 JSON 输出:{\"frame\": {...}, \"claims\": [...], \"contradictions\": [...], "
|
|
109
|
+
"\"scope\": {...}, \"recommended_action\": ..., \"confidence\": ..., "
|
|
110
|
+
"\"cross_review\": {\"agreement\": \"agree|disagree\", \"final_recommendation\": ...}}\n"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def build_prompt(baseline: str, q: dict) -> str:
|
|
115
|
+
fn = {"B0_direct_llm": _prompt_b0, "B1_search_llm": _prompt_b1,
|
|
116
|
+
"B2_standard_agent": _prompt_b2, "B3_eduevidence_single": _prompt_b3,
|
|
117
|
+
"B4_eduevidence_agent_mcp": _prompt_b4}[baseline]
|
|
118
|
+
return fn(q)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ---------------------------------------------------------------- drivers
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class ApiDriver:
|
|
125
|
+
"""OpenAI-compatible chat completions driver (no SDK dependency)."""
|
|
126
|
+
|
|
127
|
+
name = "api"
|
|
128
|
+
|
|
129
|
+
def __init__(self, *, model: str | None = None,
|
|
130
|
+
base_url: str | None = None, api_key: str | None = None,
|
|
131
|
+
temperature: float = 0.0, timeout: int = 180):
|
|
132
|
+
self.model = model or os.environ.get("EDUEVIDENCE_LLM_MODEL", "")
|
|
133
|
+
self.base_url = (base_url or os.environ.get("EDUEVIDENCE_LLM_BASE_URL", "")
|
|
134
|
+
or "https://api.openai.com/v1").rstrip("/")
|
|
135
|
+
self.api_key = api_key or os.environ.get("EDUEVIDENCE_LLM_API_KEY", "")
|
|
136
|
+
self.temperature = temperature
|
|
137
|
+
self.timeout = timeout
|
|
138
|
+
|
|
139
|
+
def available(self) -> bool:
|
|
140
|
+
return bool(self.model and self.api_key)
|
|
141
|
+
|
|
142
|
+
def call(self, prompt: str) -> tuple[str, dict[str, Any]]:
|
|
143
|
+
import time
|
|
144
|
+
t0 = time.monotonic()
|
|
145
|
+
body = json.dumps({
|
|
146
|
+
"model": self.model,
|
|
147
|
+
"temperature": self.temperature,
|
|
148
|
+
"messages": [{"role": "user", "content": prompt}],
|
|
149
|
+
}).encode("utf-8")
|
|
150
|
+
req = urllib.request.Request(
|
|
151
|
+
self.base_url + "/chat/completions", data=body,
|
|
152
|
+
headers={"Content-Type": "application/json",
|
|
153
|
+
"Authorization": f"Bearer {self.api_key}"},
|
|
154
|
+
method="POST")
|
|
155
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as resp: # noqa: S310 (user-configured endpoint)
|
|
156
|
+
payload = json.loads(resp.read().decode("utf-8"))
|
|
157
|
+
usage = payload.get("usage") or {}
|
|
158
|
+
text = (payload.get("choices") or [{}])[0].get("message", {}).get("content", "")
|
|
159
|
+
latency = time.monotonic() - t0
|
|
160
|
+
usage_out = {
|
|
161
|
+
"prompt_tokens": usage.get("prompt_tokens"),
|
|
162
|
+
"completion_tokens": usage.get("completion_tokens"),
|
|
163
|
+
"latency_s": round(latency, 2),
|
|
164
|
+
}
|
|
165
|
+
return text, usage_out
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class CliDriver:
|
|
169
|
+
"""omp CLI driver - host agent runtime (user-approved).
|
|
170
|
+
|
|
171
|
+
Calls `omp -p --no-session --model=<model> <prompt>` in a scratch dir;
|
|
172
|
+
captures stdout as the response. Token usage is estimated from text
|
|
173
|
+
length and recorded as such (manifest usage fields may stay null; the
|
|
174
|
+
run manifest environment records the exact invocation).
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
name = "cli"
|
|
178
|
+
|
|
179
|
+
def __init__(self, model: str | None = None, thinking: str = "minimal",
|
|
180
|
+
timeout: int = 600):
|
|
181
|
+
self.model = model or os.environ.get("EDUEVIDENCE_LLM_MODEL", "deepseek-v4-flash")
|
|
182
|
+
self.thinking = thinking
|
|
183
|
+
self.timeout = timeout
|
|
184
|
+
|
|
185
|
+
def available(self) -> bool:
|
|
186
|
+
import shutil
|
|
187
|
+
return shutil.which("omp") is not None
|
|
188
|
+
|
|
189
|
+
def call(self, prompt: str, *, no_tools: bool = False) -> tuple[str, dict]:
|
|
190
|
+
import subprocess
|
|
191
|
+
import tempfile
|
|
192
|
+
import time
|
|
193
|
+
|
|
194
|
+
cmd = ["omp", "-p", "--no-session", f"--model={self.model}",
|
|
195
|
+
f"--thinking={self.thinking}", "--no-extensions", "--no-skills"]
|
|
196
|
+
if no_tools:
|
|
197
|
+
cmd.append("--no-tools")
|
|
198
|
+
cmd.append("--")
|
|
199
|
+
cmd.append(prompt)
|
|
200
|
+
t0 = time.monotonic()
|
|
201
|
+
with tempfile.TemporaryDirectory(prefix="eduevidence-bench-") as workdir:
|
|
202
|
+
proc = subprocess.run(cmd, capture_output=True, text=True,
|
|
203
|
+
timeout=self.timeout, cwd=workdir)
|
|
204
|
+
latency = time.monotonic() - t0
|
|
205
|
+
if proc.returncode != 0:
|
|
206
|
+
raise RuntimeError(
|
|
207
|
+
f"omp failed rc={proc.returncode}: "
|
|
208
|
+
f"{(proc.stderr or proc.stdout or '')[:300]}")
|
|
209
|
+
text = (proc.stdout or "").strip()
|
|
210
|
+
usage = {
|
|
211
|
+
"prompt_tokens": max(1, len(prompt) // 2),
|
|
212
|
+
"completion_tokens": max(1, len(text) // 2),
|
|
213
|
+
"latency_s": round(latency, 2),
|
|
214
|
+
}
|
|
215
|
+
return text, usage
|
|
216
|
+
|
|
217
|
+
class SimDriver:
|
|
218
|
+
"""Deterministic simulation — harness validation ONLY. Never performance evidence."""
|
|
219
|
+
|
|
220
|
+
name = "sim"
|
|
221
|
+
|
|
222
|
+
def __init__(self, temperature: float = 0.0):
|
|
223
|
+
self.temperature = temperature
|
|
224
|
+
|
|
225
|
+
def available(self) -> bool:
|
|
226
|
+
return True
|
|
227
|
+
|
|
228
|
+
def call(self, prompt: str, *, no_tools: bool = False) -> tuple[str, dict[str, Any]]:
|
|
229
|
+
from benchmark_v2 import simulate_question_result # noqa: PLC0415
|
|
230
|
+
|
|
231
|
+
# Deterministic pseudo-usage from prompt length; response is a stub
|
|
232
|
+
# that the evaluator must never use as model performance.
|
|
233
|
+
import random
|
|
234
|
+
rng = random.Random(len(prompt) * 7919 % 2**31)
|
|
235
|
+
usage = {
|
|
236
|
+
"prompt_tokens": len(prompt) // 4,
|
|
237
|
+
"completion_tokens": 300 + rng.randint(0, 200),
|
|
238
|
+
"latency_s": round(0.01 + rng.random() * 0.05, 3),
|
|
239
|
+
}
|
|
240
|
+
return (
|
|
241
|
+
'{"claims": [], "contradictions": [], "scope": {"can_claim": [], '
|
|
242
|
+
'"cannot_claim": [], "exceeds_boundary": []}, '
|
|
243
|
+
'"recommended_action": "insufficient_evidence", '
|
|
244
|
+
'"confidence": "Insufficient", "simulated": true}',
|
|
245
|
+
usage,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def make_driver(name: str) -> Any:
|
|
250
|
+
if name == "api":
|
|
251
|
+
return ApiDriver()
|
|
252
|
+
if name == "cli":
|
|
253
|
+
return CliDriver()
|
|
254
|
+
if name == "sim":
|
|
255
|
+
return SimDriver()
|
|
256
|
+
raise ValueError(f"unknown driver: {name}")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# ---------------------------------------------------------------- run
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _now_iso() -> str:
|
|
263
|
+
return datetime.now(timezone.utc).isoformat()
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def run_benchmark(*, questions: list[dict], baselines: list[str], repeats: int,
|
|
267
|
+
out_dir: Path, driver_name: str, budget_tokens: int | None,
|
|
268
|
+
temperature: float = 0.0, resume: bool = False) -> dict[str, Any]:
|
|
269
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
270
|
+
driver = make_driver(driver_name)
|
|
271
|
+
if not driver.available():
|
|
272
|
+
raise RuntimeError(
|
|
273
|
+
f"driver '{driver_name}' unavailable (api needs EDUEVIDENCE_LLM_MODEL "
|
|
274
|
+
"and EDUEVIDENCE_LLM_API_KEY)")
|
|
275
|
+
|
|
276
|
+
run_id = "run-" + datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
|
|
277
|
+
manifest: dict[str, Any] = {
|
|
278
|
+
"run_id": run_id,
|
|
279
|
+
"run_mode": "simulated" if driver_name == "sim" else "empirical",
|
|
280
|
+
"created_at": _now_iso(),
|
|
281
|
+
"questions_version": _questions_version(),
|
|
282
|
+
"baselines": list(baselines),
|
|
283
|
+
"repeats": repeats,
|
|
284
|
+
"budget_tokens": budget_tokens,
|
|
285
|
+
"environment": {
|
|
286
|
+
"driver": driver.name,
|
|
287
|
+
"model_family": getattr(driver, "model", "sim") or "unknown",
|
|
288
|
+
"model_version": getattr(driver, "model", "sim") or "unknown",
|
|
289
|
+
"temperature": temperature,
|
|
290
|
+
"tools": ([] if driver_name == "sim"
|
|
291
|
+
else (["host agent tools"] if driver_name == "cli"
|
|
292
|
+
else [])),
|
|
293
|
+
"search_provider": ("none" if driver_name == "sim"
|
|
294
|
+
else ("host_tools" if driver_name == "cli"
|
|
295
|
+
else "none")),
|
|
296
|
+
"agent_mcp_used": False,
|
|
297
|
+
},
|
|
298
|
+
"attempts": [],
|
|
299
|
+
"notes": ("SIMULATED: harness validation only, not model performance" if driver_name == "sim"
|
|
300
|
+
else "empirical run; see per-attempt artifacts"),
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
total_tokens = 0
|
|
304
|
+
budget_stopped = False
|
|
305
|
+
import re as _re
|
|
306
|
+
# --resume: reuse previously completed attempts (their response artifacts
|
|
307
|
+
# live in out_dir); only unfinished attempts are re-run.
|
|
308
|
+
done_ids: set[str] = set()
|
|
309
|
+
resumed: dict[str, dict[str, Any]] = {}
|
|
310
|
+
if resume and out_dir.is_dir():
|
|
311
|
+
import json as _json
|
|
312
|
+
for art in out_dir.glob("*.response.json"):
|
|
313
|
+
try:
|
|
314
|
+
data = _json.loads(art.read_text(encoding="utf-8"))
|
|
315
|
+
except (OSError, _json.JSONDecodeError):
|
|
316
|
+
continue
|
|
317
|
+
aid = data.get("attempt_id")
|
|
318
|
+
if aid and (out_dir / art.name).is_file():
|
|
319
|
+
done_ids.add(aid)
|
|
320
|
+
resumed[aid] = data
|
|
321
|
+
if done_ids:
|
|
322
|
+
print(f"resume: {len(done_ids)} attempt(s) already present, skipping")
|
|
323
|
+
for question in questions:
|
|
324
|
+
if budget_stopped:
|
|
325
|
+
break
|
|
326
|
+
if not _re.fullmatch(r"[A-Za-z0-9_-]+", question.get("id", "")):
|
|
327
|
+
raise ValueError(
|
|
328
|
+
f"question id {question.get('id')!r} contains unsafe characters "
|
|
329
|
+
"(P2-14: ids are used in artifact filenames)")
|
|
330
|
+
for baseline in baselines:
|
|
331
|
+
for attempt in range(1, repeats + 1):
|
|
332
|
+
if budget_stopped:
|
|
333
|
+
break
|
|
334
|
+
attempt_id = f"{question['id']}-{baseline}-a{attempt}"
|
|
335
|
+
if attempt_id in done_ids:
|
|
336
|
+
# Re-register resumed attempts in the manifest (status +
|
|
337
|
+
# usage read back from their artifact) so eval/report see
|
|
338
|
+
# the complete run.
|
|
339
|
+
art = out_dir / f"{attempt_id}.response.json"
|
|
340
|
+
data = resumed.get(attempt_id, {})
|
|
341
|
+
usage = data.get("usage") or {}
|
|
342
|
+
manifest["attempts"].append({
|
|
343
|
+
"attempt_id": attempt_id,
|
|
344
|
+
"question_id": question["id"],
|
|
345
|
+
"baseline": baseline,
|
|
346
|
+
"attempt": attempt,
|
|
347
|
+
"status": "completed",
|
|
348
|
+
"error": None,
|
|
349
|
+
"started_at": _now_iso(),
|
|
350
|
+
"finished_at": _now_iso(),
|
|
351
|
+
"prompt_tokens": usage.get("prompt_tokens"),
|
|
352
|
+
"completion_tokens": usage.get("completion_tokens"),
|
|
353
|
+
"latency_s": usage.get("latency_s"),
|
|
354
|
+
"cost_usd": None,
|
|
355
|
+
"artifacts": [art.name] if art.is_file() else [],
|
|
356
|
+
})
|
|
357
|
+
continue
|
|
358
|
+
started = _now_iso()
|
|
359
|
+
entry: dict[str, Any] = {
|
|
360
|
+
"attempt_id": attempt_id,
|
|
361
|
+
"question_id": question["id"],
|
|
362
|
+
"baseline": baseline,
|
|
363
|
+
"attempt": attempt,
|
|
364
|
+
"status": "completed",
|
|
365
|
+
"error": None,
|
|
366
|
+
"started_at": started,
|
|
367
|
+
"finished_at": None,
|
|
368
|
+
"prompt_tokens": None,
|
|
369
|
+
"completion_tokens": None,
|
|
370
|
+
"latency_s": None,
|
|
371
|
+
"cost_usd": None,
|
|
372
|
+
"artifacts": [],
|
|
373
|
+
}
|
|
374
|
+
try:
|
|
375
|
+
prompt = build_prompt(baseline, question)
|
|
376
|
+
text, usage = driver.call(
|
|
377
|
+
prompt, no_tools=(baseline == "B0_direct_llm"))
|
|
378
|
+
entry.update({
|
|
379
|
+
"finished_at": _now_iso(),
|
|
380
|
+
"prompt_tokens": usage.get("prompt_tokens"),
|
|
381
|
+
"completion_tokens": usage.get("completion_tokens"),
|
|
382
|
+
"latency_s": usage.get("latency_s"),
|
|
383
|
+
})
|
|
384
|
+
pt = usage.get("prompt_tokens") or 0
|
|
385
|
+
ct = usage.get("completion_tokens") or 0
|
|
386
|
+
total_tokens += pt + ct
|
|
387
|
+
artifact = out_dir / f"{attempt_id}.response.json"
|
|
388
|
+
artifact.write_text(json.dumps({
|
|
389
|
+
"attempt_id": attempt_id,
|
|
390
|
+
"prompt": prompt,
|
|
391
|
+
"response": text,
|
|
392
|
+
"usage": usage,
|
|
393
|
+
}, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
394
|
+
entry["artifacts"] = [artifact.name]
|
|
395
|
+
except (urllib.error.URLError, OSError, ValueError, KeyError,
|
|
396
|
+
subprocess.TimeoutExpired) as exc: # P2-1: a hung model call must not kill the whole run
|
|
397
|
+
entry.update({"status": "failed", "error": str(exc),
|
|
398
|
+
"finished_at": _now_iso()})
|
|
399
|
+
manifest["attempts"].append(entry)
|
|
400
|
+
|
|
401
|
+
if budget_tokens is not None and total_tokens >= budget_tokens:
|
|
402
|
+
budget_stopped = True
|
|
403
|
+
manifest["notes"] = (manifest["notes"] + " BUDGET STOPPED at "
|
|
404
|
+
f"{total_tokens} tokens.")
|
|
405
|
+
break
|
|
406
|
+
|
|
407
|
+
if budget_stopped:
|
|
408
|
+
# P2-2: record remaining attempts as budget_stopped so the report can
|
|
409
|
+
# distinguish "stopped by budget" from "never scheduled".
|
|
410
|
+
for question in questions:
|
|
411
|
+
if any(a["question_id"] == question["id"] for a in manifest["attempts"]):
|
|
412
|
+
continue
|
|
413
|
+
for baseline in baselines:
|
|
414
|
+
for attempt in range(1, repeats + 1):
|
|
415
|
+
manifest["attempts"].append({
|
|
416
|
+
"attempt_id": f"{question['id']}-{baseline}-a{attempt}",
|
|
417
|
+
"question_id": question["id"],
|
|
418
|
+
"baseline": baseline,
|
|
419
|
+
"attempt": attempt,
|
|
420
|
+
"status": "budget_stopped",
|
|
421
|
+
"error": "budget exhausted",
|
|
422
|
+
"started_at": _now_iso(),
|
|
423
|
+
"finished_at": _now_iso(),
|
|
424
|
+
"prompt_tokens": None, "completion_tokens": None,
|
|
425
|
+
"latency_s": None, "cost_usd": None, "artifacts": [],
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
manifest_path = out_dir / "manifest.json"
|
|
429
|
+
tmp = out_dir / "manifest.json.tmp"
|
|
430
|
+
tmp.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n",
|
|
431
|
+
encoding="utf-8")
|
|
432
|
+
tmp.replace(manifest_path) # atomic write (P2-4)
|
|
433
|
+
_validate_manifest(manifest_path)
|
|
434
|
+
print(f"wrote {manifest_path} (attempts={len(manifest['attempts'])}, "
|
|
435
|
+
f"mode={manifest['run_mode']}, total_tokens~{total_tokens})")
|
|
436
|
+
return manifest
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _questions_version() -> str:
|
|
440
|
+
import subprocess as _sp
|
|
441
|
+
|
|
442
|
+
repo = Path(__file__).resolve().parent.parent
|
|
443
|
+
try:
|
|
444
|
+
proc = _sp.run(["git", "-C", str(repo), "rev-parse", "--short", "HEAD"],
|
|
445
|
+
capture_output=True, text=True, timeout=10)
|
|
446
|
+
return proc.stdout.strip() or "unknown"
|
|
447
|
+
except Exception: # noqa: BLE001 - version lookup must never fail a run
|
|
448
|
+
return "unknown"
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _validate_manifest(path: Path) -> None:
|
|
452
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
453
|
+
from validate_schema import Validator, SchemaError # noqa: PLC0415
|
|
454
|
+
|
|
455
|
+
import json as _json
|
|
456
|
+
schema = _json.loads(
|
|
457
|
+
(Path(__file__).resolve().parent.parent / "schemas" / "v3"
|
|
458
|
+
/ "run-manifest.schema.json").read_text(encoding="utf-8"))
|
|
459
|
+
data = _json.loads(path.read_text(encoding="utf-8"))
|
|
460
|
+
try:
|
|
461
|
+
Validator(schema).validate(data, schema, "$")
|
|
462
|
+
except SchemaError as exc:
|
|
463
|
+
raise ValueError(f"manifest failed schema validation: {exc}") from exc
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _cmd_run(args: argparse.Namespace) -> int:
|
|
467
|
+
questions = load_questions(Path(args.questions))
|
|
468
|
+
errors = validate_questions(questions)
|
|
469
|
+
if errors:
|
|
470
|
+
print("invalid questions:", errors, file=sys.stderr)
|
|
471
|
+
return 2
|
|
472
|
+
baselines = [b for b in BASELINES if b in args.baselines.split(",")]
|
|
473
|
+
if not baselines:
|
|
474
|
+
print(f"--baselines must be a subset of {BASELINES}", file=sys.stderr)
|
|
475
|
+
return 2
|
|
476
|
+
run_benchmark(
|
|
477
|
+
questions=[q for q in questions if q["id"] in (args.ids.split(",") if args.ids else
|
|
478
|
+
[q["id"] for q in questions])],
|
|
479
|
+
baselines=baselines, repeats=args.repeats,
|
|
480
|
+
out_dir=Path(args.out), driver_name=args.driver,
|
|
481
|
+
budget_tokens=args.budget_tokens, temperature=args.temperature,
|
|
482
|
+
resume=args.resume)
|
|
483
|
+
return 0
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _cmd_report(args: argparse.Namespace) -> int:
|
|
487
|
+
from benchmark_evaluator import report_from_run # noqa: PLC0415
|
|
488
|
+
|
|
489
|
+
run_dir = Path(args.run)
|
|
490
|
+
manifest = json.loads((run_dir / "manifest.json").read_text(encoding="utf-8"))
|
|
491
|
+
markdown = report_from_run(run_dir, manifest, Path(args.out))
|
|
492
|
+
print(markdown if args.stdout else f"wrote {args.out}")
|
|
493
|
+
return 0
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def main(argv: list[str] | None = None) -> int:
|
|
497
|
+
parser = argparse.ArgumentParser(description="EduEvidence Layer B empirical benchmark (v3)")
|
|
498
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
499
|
+
|
|
500
|
+
p_run = sub.add_parser("run", help="run baselines and write a manifest")
|
|
501
|
+
p_run.add_argument("--baselines", required=True,
|
|
502
|
+
help="comma-separated subset of " + ",".join(BASELINES))
|
|
503
|
+
p_run.add_argument("--questions", default="benchmarks/questions.jsonl")
|
|
504
|
+
p_run.add_argument("--ids", default=None, help="comma-separated question ids to run")
|
|
505
|
+
p_run.add_argument("--repeats", type=int, default=3)
|
|
506
|
+
p_run.add_argument("--driver", choices=["api", "cli", "sim"], default=None,
|
|
507
|
+
help="api (needs env keys), cli (omp), or sim (harness validation only)")
|
|
508
|
+
p_run.add_argument("--out", required=True)
|
|
509
|
+
p_run.add_argument("--budget-tokens", type=int, default=DEFAULT_BUDGET_TOKENS)
|
|
510
|
+
p_run.add_argument("--temperature", type=float, default=0.0)
|
|
511
|
+
p_run.add_argument("--resume", action="store_true",
|
|
512
|
+
help="skip attempts whose response artifacts already exist in --out")
|
|
513
|
+
p_run.set_defaults(func=_cmd_run)
|
|
514
|
+
|
|
515
|
+
p_eval = sub.add_parser("eval", help="evaluate a run against gold annotations")
|
|
516
|
+
p_eval.add_argument("--run", required=True)
|
|
517
|
+
p_eval.add_argument("--annotations", default="benchmarks/annotations")
|
|
518
|
+
p_eval.add_argument("--out", default=None)
|
|
519
|
+
p_eval.set_defaults(func=_cmd_eval)
|
|
520
|
+
|
|
521
|
+
p_report = sub.add_parser("report", help="render the empirical benchmark report (markdown)")
|
|
522
|
+
p_report.add_argument("--run", required=True)
|
|
523
|
+
p_report.add_argument("--out", required=True)
|
|
524
|
+
p_report.add_argument("--stdout", action="store_true")
|
|
525
|
+
p_report.set_defaults(func=_cmd_report)
|
|
526
|
+
|
|
527
|
+
args = parser.parse_args(argv)
|
|
528
|
+
if args.command == "run" and getattr(args, "driver", None) is None:
|
|
529
|
+
if ApiDriver().available():
|
|
530
|
+
args.driver = "api"
|
|
531
|
+
elif CliDriver().available():
|
|
532
|
+
args.driver = "cli"
|
|
533
|
+
else:
|
|
534
|
+
args.driver = "sim"
|
|
535
|
+
return args.func(args)
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def _cmd_eval(args: argparse.Namespace) -> int:
|
|
539
|
+
from benchmark_evaluator import evaluate_run # noqa: PLC0415
|
|
540
|
+
|
|
541
|
+
run_dir = Path(args.run)
|
|
542
|
+
manifest = json.loads((run_dir / "manifest.json").read_text(encoding="utf-8"))
|
|
543
|
+
summary = evaluate_run(run_dir, manifest, Path(args.annotations))
|
|
544
|
+
out_path = Path(args.out) if args.out else run_dir / "evaluation.json"
|
|
545
|
+
out_path.write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n",
|
|
546
|
+
encoding="utf-8")
|
|
547
|
+
print(f"wrote {out_path}")
|
|
548
|
+
return 0
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
if __name__ == "__main__":
|
|
552
|
+
sys.exit(main())
|