benchkit-cli 0.1.2__tar.gz → 0.2.0__tar.gz

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.
Files changed (191) hide show
  1. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/PKG-INFO +3 -1
  2. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/README.md +2 -0
  3. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/pyproject.toml +1 -1
  4. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/__init__.py +14 -0
  5. benchkit_cli-0.2.0/src/benchkit/benchmarks/cruxeval.py +380 -0
  6. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/ruler_generator.py +47 -17
  7. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/README.md +14 -0
  8. benchkit_cli-0.2.0/src/benchkit/datasets/cruxeval.jsonl +800 -0
  9. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/executor.py +12 -2
  10. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit_cli.egg-info/PKG-INFO +3 -1
  11. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit_cli.egg-info/SOURCES.txt +3 -0
  12. benchkit_cli-0.2.0/tests/test_cruxeval.py +139 -0
  13. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_ruler.py +34 -0
  14. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/LICENSE +0 -0
  15. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/setup.cfg +0 -0
  16. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/__init__.py +0 -0
  17. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/_pi_proxy.py +0 -0
  18. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/answer_key_guard.ts +0 -0
  19. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/artifacts.py +0 -0
  20. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/aider_polyglot.py +0 -0
  21. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/arc.py +0 -0
  22. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/banking77.py +0 -0
  23. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/base.py +0 -0
  24. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/boolq.py +0 -0
  25. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/chartqa.py +0 -0
  26. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/code_verifier.py +0 -0
  27. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/downloads.py +0 -0
  28. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/evalplus.py +0 -0
  29. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/git_surgery.py +0 -0
  30. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/gpqa.py +0 -0
  31. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/gsm8k.py +0 -0
  32. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/hellaswag.py +0 -0
  33. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/humaneval.py +0 -0
  34. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/ifeval.py +0 -0
  35. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/ifeval_instructions.py +0 -0
  36. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/mbpp.py +0 -0
  37. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/mc_arena.py +0 -0
  38. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/mcq.py +0 -0
  39. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/medfailbench.py +0 -0
  40. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/mmlu.py +0 -0
  41. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/mmlu_pro.py +0 -0
  42. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/mmvp.py +0 -0
  43. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/ocrbench.py +0 -0
  44. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/openbookqa.py +0 -0
  45. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/patcheval.py +0 -0
  46. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/piqa.py +0 -0
  47. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/ruler.py +0 -0
  48. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/sanity.py +0 -0
  49. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/treejs_arena.py +0 -0
  50. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/truthfulqa.py +0 -0
  51. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/utils.py +0 -0
  52. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/vision.py +0 -0
  53. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/vstar.py +0 -0
  54. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/winogrande.py +0 -0
  55. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/benchmarks/xstest.py +0 -0
  56. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/browser.py +0 -0
  57. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/catalog.py +0 -0
  58. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/cli.py +0 -0
  59. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/client.py +0 -0
  60. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/config.py +0 -0
  61. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/controls.py +0 -0
  62. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/MMLU_LICENSE.txt +0 -0
  63. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/arc_challenge.jsonl +0 -0
  64. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/banking77.jsonl +0 -0
  65. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/banking77_intents.json +0 -0
  66. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/boolq.jsonl +0 -0
  67. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/code_verifier.jsonl +0 -0
  68. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/gpqa_diamond.jsonl +0 -0
  69. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/gsm8k.jsonl +0 -0
  70. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/hellaswag.jsonl +0 -0
  71. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/humaneval.jsonl +0 -0
  72. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/ifeval.jsonl +0 -0
  73. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/mbpp.jsonl +0 -0
  74. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/mc_blocks_1_20_1.jsonl +0 -0
  75. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/medfailbench_hard30.jsonl +0 -0
  76. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/medfailbench_hard30.md +0 -0
  77. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/openbookqa.jsonl +0 -0
  78. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/piqa.jsonl +0 -0
  79. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/sanity_code.jsonl +0 -0
  80. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/truthfulqa.jsonl +0 -0
  81. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/winogrande.jsonl +0 -0
  82. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/datasets/xstest.csv +0 -0
  83. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/decision.py +0 -0
  84. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/demo.py +0 -0
  85. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/dockerfiles.py +0 -0
  86. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/engine.py +0 -0
  87. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/evaluation.py +0 -0
  88. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/events.py +0 -0
  89. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/backport-release-stack/setup.sh +0 -0
  90. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/backport-release-stack/verify.sh +0 -0
  91. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/bisect-the-regression/setup.sh +0 -0
  92. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/bisect-the-regression/verify.sh +0 -0
  93. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/rebase-conflict-chain/setup.sh +0 -0
  94. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/rebase-conflict-chain/verify.sh +0 -0
  95. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/recover-complex-stash/setup.sh +0 -0
  96. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/recover-complex-stash/verify.sh +0 -0
  97. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/recover-lost-work/setup.sh +0 -0
  98. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/recover-lost-work/verify.sh +0 -0
  99. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/repair-force-pushed-remote/setup.sh +0 -0
  100. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/repair-force-pushed-remote/verify.sh +0 -0
  101. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/revert-merge-with-followups/setup.sh +0 -0
  102. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/revert-merge-with-followups/verify.sh +0 -0
  103. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/secret-in-history/README.md +0 -0
  104. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/secret-in-history/setup.sh +0 -0
  105. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/secret-in-history/verify.sh +0 -0
  106. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/split-the-mega-commit/setup.sh +0 -0
  107. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/split-the-mega-commit/verify.sh +0 -0
  108. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/untangle-nested-submodules/setup.sh +0 -0
  109. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/git_surgery/untangle-nested-submodules/verify.sh +0 -0
  110. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/history.py +0 -0
  111. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/jobs.py +0 -0
  112. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/leakage.py +0 -0
  113. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/looping.py +0 -0
  114. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/mc_render.py +0 -0
  115. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/mc_viewer/dist/atlas.png +0 -0
  116. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/mc_viewer/dist/blockStates.json.gz +0 -0
  117. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/mc_viewer/dist/version.json +0 -0
  118. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/mc_viewer/dist/viewer.html +0 -0
  119. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/mc_viewer/dist/viewer.js +0 -0
  120. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/mc_viewer/dist/viewer.js.LICENSE.txt +0 -0
  121. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/mc_viewer/dist/worker.js +0 -0
  122. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/mc_viewer/dist/worker.js.LICENSE.txt +0 -0
  123. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/metrics.py +0 -0
  124. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/ollama.py +0 -0
  125. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/perf.py +0 -0
  126. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/perf_report.py +0 -0
  127. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/perturbations.py +0 -0
  128. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/pi_agent.py +0 -0
  129. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/pi_package/package-lock.json +0 -0
  130. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/pi_package/package.json +0 -0
  131. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/report.py +0 -0
  132. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/report_markdown.py +0 -0
  133. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/results.py +0 -0
  134. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/retry.py +0 -0
  135. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/runner.py +0 -0
  136. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/sandbox.py +0 -0
  137. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/template_check.py +0 -0
  138. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/templates/arena.html +0 -0
  139. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/templates/history.html +0 -0
  140. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/templates/perf.html +0 -0
  141. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/templates/report.html +0 -0
  142. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/tui/__init__.py +0 -0
  143. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/tui/app.py +0 -0
  144. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/tui/app.tcss +0 -0
  145. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/tui/formatting.py +0 -0
  146. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/tui/screens/__init__.py +0 -0
  147. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/tui/screens/connect.py +0 -0
  148. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/tui/screens/detail.py +0 -0
  149. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/tui/screens/help.py +0 -0
  150. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/tui/screens/modals.py +0 -0
  151. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/tui/screens/results.py +0 -0
  152. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/tui/screens/run.py +0 -0
  153. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/tui/screens/setup.py +0 -0
  154. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/tui/theme.py +0 -0
  155. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit/tui/widgets.py +0 -0
  156. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit_cli.egg-info/dependency_links.txt +0 -0
  157. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit_cli.egg-info/entry_points.txt +0 -0
  158. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit_cli.egg-info/requires.txt +0 -0
  159. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/src/benchkit_cli.egg-info/top_level.txt +0 -0
  160. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_aider_polyglot.py +0 -0
  161. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_banking77.py +0 -0
  162. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_code_verifier.py +0 -0
  163. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_concurrency.py +0 -0
  164. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_config.py +0 -0
  165. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_datasets.py +0 -0
  166. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_decision.py +0 -0
  167. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_evalplus.py +0 -0
  168. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_git_surgery.py +0 -0
  169. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_gsm8k.py +0 -0
  170. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_history.py +0 -0
  171. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_humaneval.py +0 -0
  172. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_looping.py +0 -0
  173. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_mc_arena.py +0 -0
  174. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_mcq.py +0 -0
  175. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_medfailbench.py +0 -0
  176. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_medfailbench_real_outputs.py +0 -0
  177. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_medfailbench_real_outputs_escalation.py +0 -0
  178. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_medfailbench_scoring_distortion.py +0 -0
  179. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_patcheval.py +0 -0
  180. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_perturbations.py +0 -0
  181. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_pi_harness.py +0 -0
  182. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_repair.py +0 -0
  183. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_report.py +0 -0
  184. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_retry.py +0 -0
  185. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_runner.py +0 -0
  186. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_sanity.py +0 -0
  187. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_treejs_arena.py +0 -0
  188. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_unload.py +0 -0
  189. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_utils.py +0 -0
  190. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_vision.py +0 -0
  191. {benchkit_cli-0.1.2 → benchkit_cli-0.2.0}/tests/test_xstest.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: benchkit-cli
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: Benchmark your local LLMs with real eval suites
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.11
@@ -139,6 +139,8 @@ uv run benchkit --help
139
139
  | HumanEval+ TR | `humaneval-plus-tr` | 164 | HumanEval+ fully translated to Turkish, function and variable names included |
140
140
  | MBPP | `mbpp` | 500 | Short Python programming tasks |
141
141
  | MBPP+ | `mbpp-plus` | 378 | MBPP with expanded EvalPlus tests |
142
+ | CRUXEval-O | `cruxeval-o` | 800 | Predict what a short Python function returns (`-cot` for the step-by-step prompt) |
143
+ | CRUXEval-I | `cruxeval-i` | 800 | Find an input that produces a given output, checked by running it (`-cot` too) |
142
144
  | GSM8K | `gsm8k` | 1,319 | Multi-step grade-school math |
143
145
  | IFEval | `ifeval` | 541 | Verifiable instruction following |
144
146
  | RULER | `ruler` | 39 × 6 | Practical long-context retrieval from 4K to 128K |
@@ -121,6 +121,8 @@ uv run benchkit --help
121
121
  | HumanEval+ TR | `humaneval-plus-tr` | 164 | HumanEval+ fully translated to Turkish, function and variable names included |
122
122
  | MBPP | `mbpp` | 500 | Short Python programming tasks |
123
123
  | MBPP+ | `mbpp-plus` | 378 | MBPP with expanded EvalPlus tests |
124
+ | CRUXEval-O | `cruxeval-o` | 800 | Predict what a short Python function returns (`-cot` for the step-by-step prompt) |
125
+ | CRUXEval-I | `cruxeval-i` | 800 | Find an input that produces a given output, checked by running it (`-cot` too) |
124
126
  | GSM8K | `gsm8k` | 1,319 | Multi-step grade-school math |
125
127
  | IFEval | `ifeval` | 541 | Verifiable instruction following |
126
128
  | RULER | `ruler` | 39 × 6 | Practical long-context retrieval from 4K to 128K |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "benchkit-cli"
3
- version = "0.1.2"
3
+ version = "0.2.0"
4
4
  description = "Benchmark your local LLMs with real eval suites"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -6,6 +6,12 @@ from benchkit.benchmarks.banking77 import Banking77
6
6
  from benchkit.benchmarks.boolq import BoolQ
7
7
  from benchkit.benchmarks.chartqa import ChartQA
8
8
  from benchkit.benchmarks.code_verifier import CodeVerifier
9
+ from benchkit.benchmarks.cruxeval import (
10
+ CRUXEvalInput,
11
+ CRUXEvalInputCoT,
12
+ CRUXEvalOutput,
13
+ CRUXEvalOutputCoT,
14
+ )
9
15
  from benchkit.benchmarks.evalplus import HumanEvalPlus, HumanEvalPlusTR, MBPPPlus
10
16
  from benchkit.benchmarks.git_surgery import GitSurgery
11
17
  from benchkit.benchmarks.gpqa import GPQA
@@ -44,6 +50,10 @@ REGISTRY: dict[str, type] = {
44
50
  "humaneval-plus-tr": HumanEvalPlusTR,
45
51
  "mbpp": MBPP,
46
52
  "mbpp-plus": MBPPPlus,
53
+ "cruxeval-o": CRUXEvalOutput,
54
+ "cruxeval-i": CRUXEvalInput,
55
+ "cruxeval-o-cot": CRUXEvalOutputCoT,
56
+ "cruxeval-i-cot": CRUXEvalInputCoT,
47
57
  "gsm8k": GSM8K,
48
58
  "ifeval": IFEval,
49
59
  "ruler": RULER,
@@ -82,6 +92,10 @@ DESCRIPTIONS: dict[str, str] = {
82
92
  "humaneval-plus-tr": "HumanEval+ fully translated to Turkish, names included",
83
93
  "mbpp": "short Python functions from natural-language specifications",
84
94
  "mbpp-plus": "sanitized MBPP tasks with 39k+ EvalPlus test inputs",
95
+ "cruxeval-o": "predict what a short Python function returns for a given input",
96
+ "cruxeval-i": "find an input that makes a short Python function return a given output",
97
+ "cruxeval-o-cot": "CRUXEval output prediction with the step-by-step prompt",
98
+ "cruxeval-i-cot": "CRUXEval input prediction with the step-by-step prompt",
85
99
  "gsm8k": "multi-step grade-school math problems with exact numeric answers",
86
100
  "ifeval": "prompts with code-checkable instruction-following constraints",
87
101
  "ruler": "practical long-context retrieval across all 13 RULER task families",
@@ -0,0 +1,380 @@
1
+ """CRUXEval - predict what a short Python function returns, or what it was given.
2
+
3
+ 800 functions, each with one input and the output running it produces. The
4
+ output task (``cruxeval-o``) shows the input and asks for the output; the input
5
+ task (``cruxeval-i``) shows the output and asks for any input that produces it.
6
+ Both come in the upstream direct and chain-of-thought prompts, as separate
7
+ registry keys so the gap between them shows up as two jobs side by side.
8
+
9
+ Scoring needs no judge. A predicted output is parsed with ``ast.literal_eval``
10
+ and compared with ``==`` against the recorded output, so ``'abc'`` and
11
+ ``"abc"``, or ``(1, 2)`` written with odd spacing, all match. The one fallback:
12
+ when the recorded output is a string and the prediction is not a literal at
13
+ all, the bare text is compared with that string, so ``== abc`` counts for
14
+ ``'abc'``. Anything else that is not a literal - an unsimplified expression, a
15
+ call - is wrong, which is stricter than upstream (it evaluates expressions)
16
+ and keeps model-written code out of output scoring entirely.
17
+
18
+ A predicted input is never string-matched, because many inputs can produce
19
+ the same output: the call is run against the function in the sandbox and
20
+ passes when it returns the recorded output.
21
+ """
22
+
23
+ import ast
24
+ import json
25
+ import re
26
+ import secrets
27
+ from pathlib import Path
28
+
29
+ from benchkit.benchmarks.base import Task
30
+ from benchkit.benchmarks.utils import strip_think_tags
31
+ from benchkit.evaluation import EvaluationResult
32
+ from benchkit.executor import execute_with_feedback
33
+
34
+ DATASET = Path(__file__).parent.parent / "datasets" / "cruxeval.jsonl"
35
+
36
+ # Prompts are the upstream ones from facebookresearch/cruxeval (MIT), verbatim.
37
+ DIRECT_OUTPUT = """You are given a Python function and an assertion containing an input to the function. Complete the assertion with a literal (no unsimplified expressions, no function calls) containing the output when executing the provided code on the given input, even if the function is incorrect or incomplete. Do NOT output any extra information. Provide the full assertion with the correct output in [ANSWER] and [/ANSWER] tags, following the examples.
38
+
39
+ [PYTHON]
40
+ def f(n):
41
+ return n
42
+ assert f(17) == ??
43
+ [/PYTHON]
44
+ [ANSWER]
45
+ assert f(17) == 17
46
+ [/ANSWER]
47
+
48
+ [PYTHON]
49
+ def f(s):
50
+ return s + "a"
51
+ assert f("x9j") == ??
52
+ [/PYTHON]
53
+ [ANSWER]
54
+ assert f("x9j") == "x9ja"
55
+ [/ANSWER]
56
+
57
+ [PYTHON]
58
+ {code}
59
+ assert f({input}) == ??
60
+ [/PYTHON]
61
+ [ANSWER]
62
+ """
63
+
64
+ COT_OUTPUT = """You are given a Python function and an assertion containing an input to the function. Complete the assertion with a literal (no unsimplified expressions, no function calls) containing the output when executing the provided code on the given input, even if the function is incorrect or incomplete. Do NOT output any extra information. Execute the program step by step before arriving at an answer, and provide the full assertion with the correct output in [ANSWER] and [/ANSWER] tags, following the examples.
65
+
66
+ [PYTHON]
67
+ def f(s):
68
+ s = s + s
69
+ return "b" + s + "a"
70
+ assert f("hi") == ??
71
+ [/PYTHON]
72
+ [THOUGHT]
73
+ Let's execute the code step by step:
74
+
75
+ 1. The function f is defined, which takes a single argument s.
76
+ 2. The function is called with the argument "hi", so within the function, s is initially "hi".
77
+ 3. Inside the function, s is concatenated with itself, so s becomes "hihi".
78
+ 4. The function then returns a new string that starts with "b", followed by the value of s (which is now "hihi"), and ends with "a".
79
+ 5. The return value of the function is therefore "bhihia".
80
+ [/THOUGHT]
81
+ [ANSWER]
82
+ assert f("hi") == "bhihia"
83
+ [/ANSWER]
84
+
85
+ [PYTHON]
86
+ {code}
87
+ assert f({input}) == ??
88
+ [/PYTHON]
89
+ [THOUGHT]
90
+ """
91
+
92
+ DIRECT_INPUT = """You will be given a function f and an output in the form f(??) == output. Find any input such that executing f on the input leads to the given output. There may be multiple answers, but you should only output one. In [ANSWER] and [/ANSWER] tags, complete the assertion with one such input that will produce the output when executing the function.
93
+
94
+ [PYTHON]
95
+ def f(my_list):
96
+ count = 0
97
+ for i in my_list:
98
+ if len(i) % 2 == 0:
99
+ count += 1
100
+ return count
101
+ assert f(??) == 3
102
+ [/PYTHON]
103
+ [ANSWER]
104
+ assert f(["mq", "px", "zy"]) == 3
105
+ [/ANSWER]
106
+
107
+ [PYTHON]
108
+ def f(s1, s2):
109
+ return s1 + s2
110
+ assert f(??) == "banana"
111
+ [/PYTHON]
112
+ [ANSWER]
113
+ assert f("ba", "nana") == "banana"
114
+ [/ANSWER]
115
+
116
+ [PYTHON]
117
+ {code}
118
+ assert f(??) == {output}
119
+ [/PYTHON]
120
+ [ANSWER]
121
+ """
122
+
123
+ COT_INPUT = """You will be given a function f and an output in the form f(??) == output. Your task is to find any input such that executing f on the input leads to the given output. There may be multiple answers, but only output one. First, think step by step. You MUST surround the answer with [ANSWER] and [/ANSWER] tags. Express your answer as a passing assertion containing the input and the given output.
124
+
125
+ [PYTHON]
126
+ def f(x):
127
+ return x + 1
128
+ assert f(??) == 17
129
+ [/PYTHON]
130
+ [THOUGHT]
131
+ To find an input such that executing f on the input leads to the given output, we can work backwards from the given assertion. We know that f(??) == 17.
132
+
133
+ Since the function f(x) returns x + 1, for f(??) to be equal to 17, the value of ?? should be 16.
134
+ [/THOUGHT]
135
+ [ANSWER]
136
+ assert f(16) == 17
137
+ [/ANSWER]
138
+
139
+ [PYTHON]
140
+ {code}
141
+ assert f(??) == {output}
142
+ [/PYTHON]
143
+ [THOUGHT]
144
+ """
145
+
146
+ TEMPLATES = {
147
+ ("output", "direct"): DIRECT_OUTPUT,
148
+ ("output", "cot"): COT_OUTPUT,
149
+ ("input", "direct"): DIRECT_INPUT,
150
+ ("input", "cot"): COT_INPUT,
151
+ }
152
+
153
+ NO_ASSERTION = (
154
+ "No assertion of the form assert f(...) == ... was found in the answer. "
155
+ "Reply with one such assertion inside [ANSWER] and [/ANSWER] tags."
156
+ )
157
+
158
+
159
+ def _answer_block(response: str) -> str:
160
+ """The text the model meant as its answer.
161
+
162
+ The prompt itself opens ``[ANSWER]`` (direct) or ``[THOUGHT]`` (CoT), so a
163
+ response may carry both tags, only the closing one, or neither. The last
164
+ complete block wins; a Markdown fence inside it is unwrapped.
165
+ """
166
+ text = strip_think_tags(response)
167
+ if "[ANSWER]" in text:
168
+ text = text.rsplit("[ANSWER]", 1)[1].split("[/ANSWER]", 1)[0]
169
+ elif "[/ANSWER]" in text:
170
+ text = text.rsplit("[/ANSWER]", 1)[0]
171
+ text = text.rsplit("[/THOUGHT]", 1)[-1]
172
+ fences = re.findall(r"```[\w-]*\n?(.*?)```", text, re.DOTALL)
173
+ if fences:
174
+ text = fences[-1]
175
+ return text.strip()
176
+
177
+
178
+ def _assertion(block: str) -> str | None:
179
+ """The last ``assert`` statement in the block, if any."""
180
+ lines = block.splitlines()
181
+ for index in range(len(lines) - 1, -1, -1):
182
+ if lines[index].lstrip().startswith("assert"):
183
+ tail = "\n".join(line.strip() for line in lines[index:])
184
+ return tail if _parses(tail) else lines[index].strip()
185
+ return None
186
+
187
+
188
+ def _parses(source: str) -> bool:
189
+ try:
190
+ ast.parse(source)
191
+ except SyntaxError:
192
+ return False
193
+ return True
194
+
195
+
196
+ def _is_f_call(node: ast.expr) -> bool:
197
+ return (
198
+ isinstance(node, ast.Call)
199
+ and isinstance(node.func, ast.Name)
200
+ and node.func.id == "f"
201
+ )
202
+
203
+
204
+ def _equality(source: str) -> tuple[str, ast.expr, ast.expr] | None:
205
+ """Split ``[assert] left == right`` into its parsed sides."""
206
+ text = source.strip()
207
+ if text.startswith("assert"):
208
+ text = text[len("assert") :].strip()
209
+ try:
210
+ node = ast.parse(text, mode="eval").body
211
+ except SyntaxError:
212
+ return None
213
+ if (
214
+ isinstance(node, ast.Compare)
215
+ and len(node.ops) == 1
216
+ and isinstance(node.ops[0], ast.Eq)
217
+ ):
218
+ return text, node.left, node.comparators[0]
219
+ return None
220
+
221
+
222
+ def predicted_output(response: str) -> str | None:
223
+ """The output the model predicted, as source text."""
224
+ block = _answer_block(response)
225
+ candidate = _assertion(block) or block
226
+ split = _equality(candidate)
227
+ if split is not None:
228
+ text, left, right = split
229
+ side = left if _is_f_call(right) and not _is_f_call(left) else right
230
+ return ast.get_source_segment(text, side)
231
+ # Not parseable: a bare word after ``==`` is still a usable prediction.
232
+ if "==" in candidate:
233
+ candidate = candidate.rsplit("==", 1)[1]
234
+ candidate = candidate.split("#", 1)[0].strip()
235
+ return candidate or None
236
+
237
+
238
+ def predicted_call(response: str) -> str | None:
239
+ """The ``f(...)`` call the model proposed, as source text."""
240
+ block = _answer_block(response)
241
+ candidate = _assertion(block) or block
242
+ split = _equality(candidate)
243
+ if split is not None:
244
+ text, left, right = split
245
+ for side in (left, right):
246
+ if _is_f_call(side):
247
+ return ast.get_source_segment(text, side)
248
+ return None
249
+ try:
250
+ node = ast.parse(candidate, mode="eval").body
251
+ except SyntaxError:
252
+ return None
253
+ if _is_f_call(node):
254
+ return ast.get_source_segment(candidate, node)
255
+ # A bare input, such as ``"ba", "nana"``, is the argument list itself.
256
+ return f"f({candidate})"
257
+
258
+
259
+ def outputs_match(predicted: str | None, expected: str) -> bool:
260
+ """Literal comparison of a predicted output with the recorded one."""
261
+ if predicted is None:
262
+ return False
263
+ target = ast.literal_eval(expected)
264
+ try:
265
+ value = ast.literal_eval(predicted)
266
+ except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError):
267
+ return isinstance(target, str) and predicted.strip() == target
268
+ try:
269
+ return bool(value == target)
270
+ except Exception:
271
+ return False
272
+
273
+
274
+ class _CRUXEval:
275
+ name: str
276
+ task: str # "output" or "input"
277
+ style: str # "direct" or "cot"
278
+ task_count = 800
279
+
280
+ def load_tasks(self) -> list[Task]:
281
+ tasks = []
282
+ with open(DATASET, encoding="utf-8") as f:
283
+ for line in f:
284
+ d = json.loads(line)
285
+ tasks.append(
286
+ Task(
287
+ id=d["task_id"],
288
+ prompt=d["function"],
289
+ metadata={
290
+ "input": d["input"],
291
+ "output": d["output"],
292
+ # The demo client answers with this.
293
+ "canonical_solution": (
294
+ f"[ANSWER]\nassert f({d['input']}) == "
295
+ f"{d['output']}\n[/ANSWER]"
296
+ ),
297
+ },
298
+ )
299
+ )
300
+ return tasks
301
+
302
+ def build_prompt(self, task: Task) -> str:
303
+ template = TEMPLATES[(self.task, self.style)]
304
+ return template.format(
305
+ code=task.prompt,
306
+ input=task.metadata["input"],
307
+ output=task.metadata["output"],
308
+ )
309
+
310
+ def evaluate(self, task: Task, response: str) -> bool:
311
+ return self.evaluate_with_feedback(task, response).passed
312
+
313
+ def summary_fields(self, records: list[object]) -> dict:
314
+ """Record which half and which prompt style a score came from."""
315
+ return {"cruxeval_task": self.task, "cruxeval_prompt": self.style}
316
+
317
+
318
+ class _OutputPrediction(_CRUXEval):
319
+ task = "output"
320
+
321
+ def evaluate_with_feedback(self, task: Task, response: str) -> EvaluationResult:
322
+ predicted = predicted_output(response)
323
+ if predicted is None:
324
+ return EvaluationResult(0.0, NO_ASSERTION)
325
+ if outputs_match(predicted, task.metadata["output"]):
326
+ return EvaluationResult(1.0)
327
+ return EvaluationResult(
328
+ 0.0,
329
+ "The predicted output does not equal the value the function returns. "
330
+ "Trace the code again; the answer must be a Python literal.",
331
+ )
332
+
333
+
334
+ class _InputPrediction(_CRUXEval):
335
+ task = "input"
336
+ evaluation_activity = "running the predicted input"
337
+
338
+ def evaluate_with_feedback(self, task: Task, response: str) -> EvaluationResult:
339
+ call = predicted_call(response)
340
+ if call is None:
341
+ return EvaluationResult(0.0, NO_ASSERTION)
342
+ # Same check as upstream, ``assert output == f(input)``. The input is
343
+ # model-written code, so a pass also needs a fresh sentinel printed
344
+ # after the assertion: an input that exits early cannot fake one.
345
+ sentinel = secrets.token_hex(16)
346
+ program = (
347
+ f"{task.prompt}\n\n"
348
+ f"assert {task.metadata['output']} == {call}\n"
349
+ f"print({sentinel!r})\n"
350
+ )
351
+ result = execute_with_feedback(program, timeout=5, sentinel=sentinel)
352
+ if result.passed:
353
+ return EvaluationResult(1.0)
354
+ if "assertion failed" in result.feedback:
355
+ return EvaluationResult(
356
+ 0.0,
357
+ "The proposed input ran, but the function returned a different "
358
+ "value than the given output.",
359
+ )
360
+ return EvaluationResult(0.0, result.feedback)
361
+
362
+
363
+ class CRUXEvalOutput(_OutputPrediction):
364
+ name = "cruxeval-o"
365
+ style = "direct"
366
+
367
+
368
+ class CRUXEvalOutputCoT(_OutputPrediction):
369
+ name = "cruxeval-o-cot"
370
+ style = "cot"
371
+
372
+
373
+ class CRUXEvalInput(_InputPrediction):
374
+ name = "cruxeval-i"
375
+ style = "direct"
376
+
377
+
378
+ class CRUXEvalInputCoT(_InputPrediction):
379
+ name = "cruxeval-i-cot"
380
+ style = "cot"
@@ -9,6 +9,7 @@ import string
9
9
  import urllib.request
10
10
  import uuid
11
11
  from collections.abc import Callable
12
+ from functools import lru_cache
12
13
  from pathlib import Path
13
14
 
14
15
  from benchkit.benchmarks.base import Task
@@ -45,6 +46,14 @@ TASKS_PER_BUCKET = DEFAULT_SAMPLES_PER_KIND * len(TASK_KINDS)
45
46
  OUTPUT_RESERVE_TOKENS = 256
46
47
  FALLBACK_CHARS_PER_TOKEN = 4
47
48
  FIT_TOLERANCE = 0.01
49
+ # cwe and fwe share a 2,000-word vocabulary. cwe draws one new uncommon word
50
+ # per unit (~40 chars), so its vocabulary grows with the bucket; 2,000 words
51
+ # stop at about 18k tokens.
52
+ VOCABULARY_SIZE = 2000
53
+ # Every renderer has a floor below which extra units change nothing (cwe and
54
+ # qa at one, fwe at ten, qa_2's ten gold documents), so the fallback sizes a
55
+ # unit between two counts above all of them.
56
+ _PROBE_UNITS = (16, 80)
48
57
 
49
58
  _VARIABLE_NOISE = (
50
59
  "The grass is green. The sky is blue. The sun is yellow. "
@@ -195,8 +204,9 @@ def _aggregation_task(bucket: int, sample: int, kind: str) -> Task:
195
204
  }
196
205
  if kind in {"cwe", "fwe"}:
197
206
  rng = random.Random(seed)
207
+ size = max(VOCABULARY_SIZE, bucket // 8) if kind == "cwe" else VOCABULARY_SIZE
198
208
  vocabulary = [
199
- "".join(rng.choices(string.ascii_lowercase, k=6)) for _ in range(2000)
209
+ "".join(rng.choices(string.ascii_lowercase, k=6)) for _ in range(size)
200
210
  ]
201
211
  metadata["vocabulary"] = list(dict.fromkeys(vocabulary))
202
212
  metadata["answers"] = metadata["vocabulary"][: 10 if kind == "cwe" else 3]
@@ -266,6 +276,7 @@ def _essay_words() -> list[str]:
266
276
  return text.split()
267
277
 
268
278
 
279
+ @lru_cache(maxsize=len(QA_FILES))
269
280
  def _qa_records(kind: str) -> tuple[list[dict], list[str]]:
270
281
  raw = json.loads(_qa_path(kind).read_text(encoding="utf-8"))
271
282
  if kind == "qa_1":
@@ -410,7 +421,19 @@ def _render_niah(task: Task, filler_units: int) -> str:
410
421
  target_values=list(metadata["answers"]),
411
422
  distractor_values=[p[1] for p in pairs],
412
423
  )
413
- context = "\n".join(_insert_at_positions(filler, needles, seed ^ 0xA51CE))
424
+ lines = _insert_at_positions(filler, needles, seed ^ 0xA51CE)
425
+ if metadata["haystack"] == "essay":
426
+ # Running prose, not one word per line: a newline per word costs far
427
+ # more tokens than the character estimate assumes.
428
+ needle_set = set(needles)
429
+ chunks: list[list[str]] = [[]]
430
+ for line in lines:
431
+ if line in needle_set:
432
+ chunks.extend([[line], []])
433
+ else:
434
+ chunks[-1].append(line)
435
+ lines = [" ".join(chunk) for chunk in chunks if chunk]
436
+ context = "\n".join(lines)
414
437
  return (
415
438
  "Some special magic numbers are hidden within the following text. "
416
439
  "Memorize the records because you will be asked for one key afterward.\n\n"
@@ -463,11 +486,17 @@ def render_prompt(task: Task, filler_units: int) -> str:
463
486
 
464
487
 
465
488
  def _fallback_units(task: Task, target_tokens: int) -> int:
466
- base = render_prompt(task, 0)
467
- one = render_prompt(task, 1)
468
- chars_per_unit = max(1, len(one) - len(base))
489
+ low, high = _PROBE_UNITS
490
+ low_chars = len(render_prompt(task, low))
491
+ chars_per_unit = max(
492
+ 1.0, (len(render_prompt(task, high)) - low_chars) / (high - low)
493
+ )
469
494
  target_chars = target_tokens * FALLBACK_CHARS_PER_TOKEN
470
- return max(0, (target_chars - len(base)) // chars_per_unit)
495
+ return max(0, low + math.floor((target_chars - low_chars) / chars_per_unit))
496
+
497
+
498
+ def _estimate_tokens(prompt: str) -> int:
499
+ return math.ceil(len(prompt) / FALLBACK_CHARS_PER_TOKEN)
471
500
 
472
501
 
473
502
  def fit_prompt(
@@ -479,9 +508,12 @@ def fit_prompt(
479
508
  """Fit a generated prompt just below its context bucket.
480
509
 
481
510
  When the server exposes a tokenizer, a short secant-style calibration keeps
482
- the prompt within one percent of the target. Generic servers fall back to a
483
- conservative character-to-token estimate.
511
+ the prompt within one percent of the target. Generic servers run the same
512
+ calibration against a conservative character-to-token estimate, and the
513
+ measured count comes back as ``None``.
484
514
  """
515
+ estimated = token_count is None
516
+ count = _estimate_tokens if token_count is None else token_count
485
517
  bucket = int(task.metadata["context_tokens"])
486
518
  target = max(1, bucket - OUTPUT_RESERVE_TOKENS)
487
519
  units = (
@@ -490,10 +522,7 @@ def fit_prompt(
490
522
  else _fallback_units(task, target)
491
523
  )
492
524
  prompt = render_prompt(task, units)
493
- if token_count is None:
494
- return prompt, units, None
495
-
496
- measured = token_count(prompt)
525
+ measured = count(prompt)
497
526
  if not isinstance(measured, int) or measured <= 0:
498
527
  return prompt, units, None
499
528
 
@@ -502,7 +531,7 @@ def fit_prompt(
502
531
  if measured <= target and measured >= target * (1 - FIT_TOLERANCE):
503
532
  break
504
533
  if base_tokens is None:
505
- base_tokens = token_count(render_prompt(task, 0))
534
+ base_tokens = count(render_prompt(task, 0))
506
535
  if not isinstance(base_tokens, int) or base_tokens < 0:
507
536
  base_tokens = 0
508
537
 
@@ -513,23 +542,24 @@ def fit_prompt(
513
542
  next_units = max(0, units - 1) if measured > target else units + 1
514
543
  units = next_units
515
544
  prompt = render_prompt(task, units)
516
- next_measured = token_count(prompt)
545
+ next_measured = count(prompt)
517
546
  if not isinstance(next_measured, int) or next_measured <= 0:
518
547
  return prompt, units, None
519
548
  measured = next_measured
520
549
 
521
550
  # Never knowingly send a prompt above the bucket. The linear adjustment is
522
551
  # normally exact; this final correction handles unusual tokenizer merges.
523
- for _ in range(2):
552
+ # Without a tokenizer it is cheap, so keep going until the estimate fits.
553
+ for _ in range(16 if estimated else 2):
524
554
  if measured <= target or units == 0:
525
555
  break
526
556
  excess = measured - target
527
557
  tokens_per_unit = max(1.0, measured / max(units, 1))
528
558
  units = max(0, units - math.ceil(excess / tokens_per_unit) - 1)
529
559
  prompt = render_prompt(task, units)
530
- next_measured = token_count(prompt)
560
+ next_measured = count(prompt)
531
561
  if not isinstance(next_measured, int) or next_measured <= 0:
532
562
  return prompt, units, None
533
563
  measured = next_measured
534
564
 
535
- return prompt, units, measured
565
+ return prompt, units, None if estimated else measured
@@ -79,6 +79,20 @@ them evenly. Regenerate both with `uv run python scripts/build_banking77.py`.
79
79
  Encoders" (2020), <https://arxiv.org/abs/2003.04807>
80
80
  - Dataset license: [Creative Commons Attribution 4.0 International][cc-by-4.0]
81
81
 
82
+ ## CRUXEval
83
+
84
+ `cruxeval.jsonl` is a transformed copy of the 800 functions in CRUXEval, each
85
+ with one input and the output running the function produces. Only the schema
86
+ changed - `id` became a namespaced `task_id` and `code` became `function`.
87
+ Regenerate it with `uv run python scripts/build_cruxeval.py`, which pins the
88
+ upstream commit. The prompts in `benchmarks/cruxeval.py` are the upstream
89
+ direct and chain-of-thought prompts.
90
+
91
+ - Source: <https://github.com/facebookresearch/cruxeval>
92
+ - Paper: Gu et al., "CRUXEval: A Benchmark for Code Reasoning, Understanding
93
+ and Execution" (2024), <https://arxiv.org/abs/2401.03065>
94
+ - Dataset license: MIT
95
+
82
96
  ## OpenBookQA
83
97
 
84
98
  `openbookqa.jsonl` is a transformed copy of the 500-question OpenBookQA test