benchmax 0.1.2.dev36__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 (261) hide show
  1. benchmax-0.2.0/.gitignore +81 -0
  2. benchmax-0.2.0/PKG-INFO +193 -0
  3. benchmax-0.2.0/README.md +178 -0
  4. benchmax-0.2.0/pyproject.toml +35 -0
  5. benchmax-0.2.0/pytest.ini +36 -0
  6. benchmax-0.2.0/src/benchmax/auth.py +109 -0
  7. benchmax-0.2.0/src/benchmax/bundle.py +1024 -0
  8. benchmax-0.2.0/src/benchmax/envs/README.md +25 -0
  9. benchmax-0.2.0/src/benchmax/envs/__init__.py +46 -0
  10. benchmax-0.2.0/src/benchmax/envs/base/README.md +74 -0
  11. benchmax-0.2.0/src/benchmax/envs/base/__init__.py +14 -0
  12. benchmax-0.2.0/src/benchmax/envs/base/dataset.py +75 -0
  13. benchmax-0.2.0/src/benchmax/envs/base/env.py +544 -0
  14. benchmax-0.2.0/src/benchmax/envs/base/openai_types.py +18 -0
  15. benchmax-0.2.0/src/benchmax/envs/dataset.py +25 -0
  16. benchmax-0.2.0/src/benchmax/envs/environment.py +381 -0
  17. benchmax-0.2.0/src/benchmax/envs/harbor/README.md +156 -0
  18. benchmax-0.2.0/src/benchmax/envs/harbor/__init__.py +25 -0
  19. benchmax-0.2.0/src/benchmax/envs/harbor/bundled_agent.py +286 -0
  20. benchmax-0.2.0/src/benchmax/envs/harbor/credentials.py +215 -0
  21. benchmax-0.2.0/src/benchmax/envs/harbor/dataset.py +158 -0
  22. benchmax-0.2.0/src/benchmax/envs/harbor/dep_check.py +17 -0
  23. benchmax-0.2.0/src/benchmax/envs/harbor/env.py +631 -0
  24. benchmax-0.2.0/src/benchmax/envs/harbor/types.py +79 -0
  25. benchmax-0.2.0/src/benchmax/envs/identity.py +63 -0
  26. benchmax-0.2.0/src/benchmax/envs/logging.py +90 -0
  27. benchmax-0.2.0/src/benchmax/envs/shared_types.py +144 -0
  28. benchmax-0.2.0/src/benchmax/rewards/__init__.py +80 -0
  29. benchmax-0.2.0/src/benchmax/rewards/adaptive.py +238 -0
  30. benchmax-0.2.0/src/benchmax/rewards/deterministic.py +175 -0
  31. benchmax-0.2.0/src/benchmax/rewards/diversity.py +213 -0
  32. benchmax-0.2.0/src/benchmax/rewards/judge.py +191 -0
  33. {benchmax-0.1.2.dev36/src/benchmax/rubrics → benchmax-0.2.0/src/benchmax/rewards}/prompts.py +166 -0
  34. benchmax-0.2.0/src/benchmax/rewards/rubric.py +418 -0
  35. benchmax-0.2.0/src/benchmax/rewards/scoring.py +244 -0
  36. benchmax-0.2.0/tests/conftest.py +29 -0
  37. benchmax-0.2.0/tests/unit/bundle/test_artifact.py +181 -0
  38. benchmax-0.2.0/tests/unit/bundle/test_source_capture.py +593 -0
  39. benchmax-0.2.0/tests/unit/envs/test_base_dataset.py +62 -0
  40. benchmax-0.2.0/tests/unit/envs/test_base_env_group.py +912 -0
  41. benchmax-0.2.0/tests/unit/envs/test_contract_types.py +61 -0
  42. benchmax-0.2.0/tests/unit/envs/test_environment_group.py +465 -0
  43. benchmax-0.2.0/tests/unit/envs/test_example_id.py +46 -0
  44. benchmax-0.2.0/tests/unit/fakes/model_server.py +179 -0
  45. benchmax-0.2.0/tests/unit/harbor/test_bundled_agent.py +328 -0
  46. benchmax-0.2.0/tests/unit/harbor/test_harbor_dataset.py +210 -0
  47. benchmax-0.2.0/tests/unit/harbor/test_harbor_env.py +855 -0
  48. benchmax-0.2.0/tests/unit/rewards/conftest.py +60 -0
  49. benchmax-0.2.0/tests/unit/rewards/test_adaptive.py +134 -0
  50. benchmax-0.2.0/tests/unit/rewards/test_deterministic.py +81 -0
  51. benchmax-0.2.0/tests/unit/rewards/test_diversity.py +134 -0
  52. benchmax-0.2.0/tests/unit/rewards/test_diversity_env.py +238 -0
  53. benchmax-0.2.0/tests/unit/rewards/test_judge.py +117 -0
  54. benchmax-0.2.0/tests/unit/rewards/test_rubric.py +185 -0
  55. benchmax-0.2.0/tests/unit/rewards/test_rubric_rewards.py +125 -0
  56. benchmax-0.2.0/tests/unit/test_auth.py +48 -0
  57. benchmax-0.1.2.dev36/LICENSE +0 -201
  58. benchmax-0.1.2.dev36/PKG-INFO +0 -77
  59. benchmax-0.1.2.dev36/README.md +0 -21
  60. benchmax-0.1.2.dev36/pyproject.toml +0 -89
  61. benchmax-0.1.2.dev36/setup.cfg +0 -4
  62. benchmax-0.1.2.dev36/src/benchmax/bundle.py +0 -377
  63. benchmax-0.1.2.dev36/src/benchmax/cli/__init__.py +0 -73
  64. benchmax-0.1.2.dev36/src/benchmax/cli/_auth.py +0 -65
  65. benchmax-0.1.2.dev36/src/benchmax/cli/_client.py +0 -49
  66. benchmax-0.1.2.dev36/src/benchmax/cli/_output.py +0 -151
  67. benchmax-0.1.2.dev36/src/benchmax/cli/_preflight.py +0 -104
  68. benchmax-0.1.2.dev36/src/benchmax/cli/_project.py +0 -188
  69. benchmax-0.1.2.dev36/src/benchmax/cli/_providers.py +0 -60
  70. benchmax-0.1.2.dev36/src/benchmax/cli/control.py +0 -28
  71. benchmax-0.1.2.dev36/src/benchmax/cli/corpus.py +0 -330
  72. benchmax-0.1.2.dev36/src/benchmax/cli/data.py +0 -562
  73. benchmax-0.1.2.dev36/src/benchmax/cli/dataview.py +0 -436
  74. benchmax-0.1.2.dev36/src/benchmax/cli/doctor.py +0 -110
  75. benchmax-0.1.2.dev36/src/benchmax/cli/help.py +0 -233
  76. benchmax-0.1.2.dev36/src/benchmax/cli/launch.py +0 -445
  77. benchmax-0.1.2.dev36/src/benchmax/cli/runs.py +0 -392
  78. benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/CLAUDE.md +0 -162
  79. benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/STARTER.md +0 -98
  80. benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/__init__.py +0 -0
  81. benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/generic_eval_dataset.jsonl +0 -2
  82. benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/generic_main.py +0 -301
  83. benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/generic_train_dataset.jsonl +0 -3
  84. benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/rag_eval_dataset.jsonl +0 -1
  85. benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/rag_main.py +0 -508
  86. benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/rag_train_dataset.jsonl +0 -2
  87. benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/skills/design-environment/SKILL.md +0 -380
  88. benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/skills/generate-data/SKILL.md +0 -195
  89. benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/skills/launch-run/SKILL.md +0 -130
  90. benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/skills/verify-environment/SKILL.md +0 -228
  91. benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/skills/view-progress/SKILL.md +0 -173
  92. benchmax-0.1.2.dev36/src/benchmax/cli/setup.py +0 -360
  93. benchmax-0.1.2.dev36/src/benchmax/cli/templates/viewer.html +0 -485
  94. benchmax-0.1.2.dev36/src/benchmax/cli/validate.py +0 -775
  95. benchmax-0.1.2.dev36/src/benchmax/config.py +0 -55
  96. benchmax-0.1.2.dev36/src/benchmax/envs/__init__.py +0 -0
  97. benchmax-0.1.2.dev36/src/benchmax/envs/base_env.py +0 -254
  98. benchmax-0.1.2.dev36/src/benchmax/envs/crm/crm_env.py +0 -107
  99. benchmax-0.1.2.dev36/src/benchmax/envs/crm/workdir/reward_fn.py +0 -167
  100. benchmax-0.1.2.dev36/src/benchmax/envs/example_id.py +0 -149
  101. benchmax-0.1.2.dev36/src/benchmax/envs/excel/data_utils.py +0 -28
  102. benchmax-0.1.2.dev36/src/benchmax/envs/excel/excel_env.py +0 -230
  103. benchmax-0.1.2.dev36/src/benchmax/envs/excel/workdir/__init__.py +0 -0
  104. benchmax-0.1.2.dev36/src/benchmax/envs/excel/workdir/excel_code_runner_mcp.py +0 -60
  105. benchmax-0.1.2.dev36/src/benchmax/envs/excel/workdir/excel_utils.py +0 -254
  106. benchmax-0.1.2.dev36/src/benchmax/envs/excel/workdir/reward_fn.py +0 -56
  107. benchmax-0.1.2.dev36/src/benchmax/envs/logging.py +0 -122
  108. benchmax-0.1.2.dev36/src/benchmax/envs/math/math_env.py +0 -72
  109. benchmax-0.1.2.dev36/src/benchmax/envs/math/workdir/reward_fn.py +0 -46
  110. benchmax-0.1.2.dev36/src/benchmax/envs/mcp/__init__.py +0 -12
  111. benchmax-0.1.2.dev36/src/benchmax/envs/mcp/example_workdir/demo_mcp_server.py +0 -131
  112. benchmax-0.1.2.dev36/src/benchmax/envs/mcp/example_workdir/reward_fn.py +0 -135
  113. benchmax-0.1.2.dev36/src/benchmax/envs/mcp/parallel_mcp_env.py +0 -604
  114. benchmax-0.1.2.dev36/src/benchmax/envs/mcp/provisioners/__init__.py +0 -20
  115. benchmax-0.1.2.dev36/src/benchmax/envs/mcp/provisioners/base_provisioner.py +0 -50
  116. benchmax-0.1.2.dev36/src/benchmax/envs/mcp/provisioners/local_provisioner.py +0 -319
  117. benchmax-0.1.2.dev36/src/benchmax/envs/mcp/provisioners/manual_provisioner.py +0 -70
  118. benchmax-0.1.2.dev36/src/benchmax/envs/mcp/provisioners/skypilot_provisioner.py +0 -222
  119. benchmax-0.1.2.dev36/src/benchmax/envs/mcp/provisioners/utils.py +0 -106
  120. benchmax-0.1.2.dev36/src/benchmax/envs/mcp/proxy_server.py +0 -471
  121. benchmax-0.1.2.dev36/src/benchmax/envs/mcp/server_pool.py +0 -493
  122. benchmax-0.1.2.dev36/src/benchmax/envs/mcp/utils.py +0 -217
  123. benchmax-0.1.2.dev36/src/benchmax/envs/postgres_search/__init__.py +0 -0
  124. benchmax-0.1.2.dev36/src/benchmax/envs/postgres_search/linker_env.py +0 -237
  125. benchmax-0.1.2.dev36/src/benchmax/envs/postgres_search/search_env.py +0 -671
  126. benchmax-0.1.2.dev36/src/benchmax/envs/reward_helpers.py +0 -215
  127. benchmax-0.1.2.dev36/src/benchmax/envs/telestich/example.py +0 -663
  128. benchmax-0.1.2.dev36/src/benchmax/envs/telestich/telestich_env.py +0 -1320
  129. benchmax-0.1.2.dev36/src/benchmax/envs/types.py +0 -137
  130. benchmax-0.1.2.dev36/src/benchmax/envs/wikipedia/utils.py +0 -95
  131. benchmax-0.1.2.dev36/src/benchmax/envs/wikipedia/wiki_env.py +0 -274
  132. benchmax-0.1.2.dev36/src/benchmax/multi_model/__init__.py +0 -0
  133. benchmax-0.1.2.dev36/src/benchmax/multi_model/caller.py +0 -504
  134. benchmax-0.1.2.dev36/src/benchmax/multi_model/clients.py +0 -144
  135. benchmax-0.1.2.dev36/src/benchmax/multi_model/example_usage.py +0 -325
  136. benchmax-0.1.2.dev36/src/benchmax/multi_model/inspector.py +0 -140
  137. benchmax-0.1.2.dev36/src/benchmax/multi_model/models.py +0 -108
  138. benchmax-0.1.2.dev36/src/benchmax/multi_model/pricing.py +0 -67
  139. benchmax-0.1.2.dev36/src/benchmax/platform/__init__.py +0 -25
  140. benchmax-0.1.2.dev36/src/benchmax/platform/browser.py +0 -32
  141. benchmax-0.1.2.dev36/src/benchmax/platform/client.py +0 -1596
  142. benchmax-0.1.2.dev36/src/benchmax/platform/credentials.py +0 -379
  143. benchmax-0.1.2.dev36/src/benchmax/platform/device_auth.py +0 -81
  144. benchmax-0.1.2.dev36/src/benchmax/platform/exceptions.py +0 -46
  145. benchmax-0.1.2.dev36/src/benchmax/platform/login.py +0 -70
  146. benchmax-0.1.2.dev36/src/benchmax/platform/training_run.py +0 -173
  147. benchmax-0.1.2.dev36/src/benchmax/platform/validation.py +0 -960
  148. benchmax-0.1.2.dev36/src/benchmax/prompts/__init__.py +0 -0
  149. benchmax-0.1.2.dev36/src/benchmax/prompts/tools.py +0 -92
  150. benchmax-0.1.2.dev36/src/benchmax/rag/chunkers/__init__.py +0 -0
  151. benchmax-0.1.2.dev36/src/benchmax/rag/chunkers/email.py +0 -793
  152. benchmax-0.1.2.dev36/src/benchmax/rag/chunkers/inspector.py +0 -230
  153. benchmax-0.1.2.dev36/src/benchmax/rag/chunkers/markdown.py +0 -347
  154. benchmax-0.1.2.dev36/src/benchmax/rag/chunkers/models.py +0 -253
  155. benchmax-0.1.2.dev36/src/benchmax/rag/chunkers/storage.py +0 -78
  156. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/__init__.py +0 -1
  157. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/chroma/__init__.py +0 -0
  158. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/chroma/client.py +0 -536
  159. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/chroma/files.py +0 -162
  160. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/chroma/filter_mapper.py +0 -149
  161. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/chroma/search.py +0 -207
  162. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/chroma/source.py +0 -792
  163. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/embed.py +0 -54
  164. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/pinecone/__init__.py +0 -0
  165. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/pinecone/files.py +0 -159
  166. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/pinecone/filter_mapper.py +0 -194
  167. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/pinecone/index_client.py +0 -460
  168. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/pinecone/search.py +0 -128
  169. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/pinecone/source.py +0 -559
  170. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/postgres/__init__.py +0 -0
  171. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/postgres/client.py +0 -799
  172. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/postgres/exceptions.py +0 -53
  173. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/postgres/filter_mapper.py +0 -119
  174. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/postgres/models.py +0 -63
  175. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/postgres/search.py +0 -113
  176. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/postgres/source.py +0 -479
  177. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/search_client.py +0 -62
  178. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/search_schema/__init__.py +0 -0
  179. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/search_schema/builders.py +0 -58
  180. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/search_schema/dsl_parser.py +0 -47
  181. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/search_schema/search_exceptions.py +0 -63
  182. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/search_schema/search_types.py +0 -176
  183. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/source.py +0 -127
  184. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/turbopuffer/__init__.py +0 -0
  185. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/turbopuffer/files.py +0 -175
  186. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/turbopuffer/filter_mapper.py +0 -139
  187. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/turbopuffer/namespace.py +0 -344
  188. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/turbopuffer/search.py +0 -215
  189. benchmax-0.1.2.dev36/src/benchmax/rag/corpus/turbopuffer/source.py +0 -722
  190. benchmax-0.1.2.dev36/src/benchmax/rag/preprocess/__init__.py +0 -0
  191. benchmax-0.1.2.dev36/src/benchmax/rag/preprocess/email/__init__.py +0 -0
  192. benchmax-0.1.2.dev36/src/benchmax/rag/preprocess/email/clean_bodies.py +0 -513
  193. benchmax-0.1.2.dev36/src/benchmax/rag/preprocess/email/dedupe.py +0 -799
  194. benchmax-0.1.2.dev36/src/benchmax/rag/preprocess/email/filter_automated_email_qas.py +0 -320
  195. benchmax-0.1.2.dev36/src/benchmax/rag/preprocess/email/filter_automated_emails.py +0 -560
  196. benchmax-0.1.2.dev36/src/benchmax/rag/preprocess/email/mbox.py +0 -257
  197. benchmax-0.1.2.dev36/src/benchmax/rag/preprocess/email/schema.py +0 -180
  198. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/__init__.py +0 -64
  199. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/anchor_selector.py +0 -17
  200. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/auto_tune.py +0 -255
  201. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/batch_processor.py +0 -500
  202. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/checkpoint.py +0 -294
  203. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/corpus_capabilities.py +0 -94
  204. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/corpus_profile.py +0 -1688
  205. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/filters/__init__.py +0 -21
  206. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/filters/deterministic_guards.py +0 -225
  207. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/filters/env_rollout.py +0 -288
  208. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/filters/grounding_llm.py +0 -589
  209. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/filters/hop_count_validity.py +0 -1078
  210. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/filters/quality_gate.py +0 -243
  211. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/filters/retrieval_llm.py +0 -896
  212. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/formatters/__init__.py +0 -5
  213. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/formatters/train_eval.py +0 -123
  214. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/generated_qa.py +0 -125
  215. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/generators/__init__.py +0 -5
  216. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/generators/direct_llm.py +0 -765
  217. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/helpers.py +0 -133
  218. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/metadata_linker.py +0 -940
  219. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/metrics.py +0 -95
  220. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/models.py +0 -36
  221. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/pipeline.py +0 -2748
  222. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/pipeline_config.py +0 -1204
  223. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/protocols.py +0 -66
  224. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/query_rewriter.py +0 -149
  225. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/response_parsers.py +0 -63
  226. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/retrieval_query.py +0 -76
  227. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/scoring.py +0 -114
  228. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/search_agent_linker.py +0 -662
  229. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/storage.py +0 -142
  230. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/style_controls.py +0 -230
  231. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/transformers/__init__.py +0 -7
  232. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/transformers/base.py +0 -125
  233. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/transformers/dedup.py +0 -195
  234. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/wiki_builder.py +0 -539
  235. benchmax-0.1.2.dev36/src/benchmax/rag/qa_generation/wiki_chunk_linker.py +0 -331
  236. benchmax-0.1.2.dev36/src/benchmax/rewards/__init__.py +0 -0
  237. benchmax-0.1.2.dev36/src/benchmax/rewards/diversity.py +0 -305
  238. benchmax-0.1.2.dev36/src/benchmax/rubrics/__init__.py +0 -17
  239. benchmax-0.1.2.dev36/src/benchmax/rubrics/_utils.py +0 -51
  240. benchmax-0.1.2.dev36/src/benchmax/rubrics/adaptive.py +0 -137
  241. benchmax-0.1.2.dev36/src/benchmax/rubrics/cache.py +0 -178
  242. benchmax-0.1.2.dev36/src/benchmax/rubrics/reward_fns.py +0 -349
  243. benchmax-0.1.2.dev36/src/benchmax/rubrics/rubric.py +0 -463
  244. benchmax-0.1.2.dev36/src/benchmax/traces/__init__.py +0 -8
  245. benchmax-0.1.2.dev36/src/benchmax/traces/adapter.py +0 -351
  246. benchmax-0.1.2.dev36/src/benchmax/traces/braintrust/__init__.py +0 -0
  247. benchmax-0.1.2.dev36/src/benchmax/traces/braintrust/adapter.py +0 -322
  248. benchmax-0.1.2.dev36/src/benchmax/traces/braintrust/message_extraction.py +0 -245
  249. benchmax-0.1.2.dev36/src/benchmax/traces/http.py +0 -90
  250. benchmax-0.1.2.dev36/src/benchmax/traces/pipeline.py +0 -278
  251. benchmax-0.1.2.dev36/src/benchmax/traces/pivot.py +0 -664
  252. benchmax-0.1.2.dev36/src/benchmax/traces/processing.py +0 -776
  253. benchmax-0.1.2.dev36/src/benchmax/traces/registry.py +0 -32
  254. benchmax-0.1.2.dev36/src/benchmax/utils/__init__.py +0 -14
  255. benchmax-0.1.2.dev36/src/benchmax/utils/checkpoint.py +0 -87
  256. benchmax-0.1.2.dev36/src/benchmax.egg-info/PKG-INFO +0 -77
  257. benchmax-0.1.2.dev36/src/benchmax.egg-info/SOURCES.txt +0 -204
  258. benchmax-0.1.2.dev36/src/benchmax.egg-info/dependency_links.txt +0 -1
  259. benchmax-0.1.2.dev36/src/benchmax.egg-info/entry_points.txt +0 -2
  260. benchmax-0.1.2.dev36/src/benchmax.egg-info/requires.txt +0 -55
  261. benchmax-0.1.2.dev36/src/benchmax.egg-info/top_level.txt +0 -1
@@ -0,0 +1,81 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual Environments
24
+ .env
25
+ .venv
26
+ .python-version
27
+ env/
28
+ venv/
29
+ ENV/
30
+
31
+ # IDE
32
+ .idea/
33
+ .vscode/
34
+ *.swp
35
+ *.swo
36
+
37
+ # Distribution / packaging
38
+ .Python
39
+ build/
40
+ develop-eggs/
41
+ dist/
42
+ downloads/
43
+ eggs/
44
+ .eggs/
45
+ lib/
46
+ lib64/
47
+ parts/
48
+ sdist/
49
+ var/
50
+ wheels/
51
+ *.egg-info/
52
+ .installed.cfg
53
+ *.egg
54
+
55
+ # Unit test / coverage reports
56
+ htmlcov/
57
+ .tox/
58
+ .coverage
59
+ .coverage.*
60
+ .cache
61
+ nosetests.xml
62
+ coverage.xml
63
+ *.cover
64
+ .hypothesis/
65
+ .pytest_cache/
66
+
67
+ # mypy
68
+ .mypy_cache/
69
+
70
+ # Jupyter Notebook
71
+ .ipynb_checkpoints
72
+
73
+ # macOS
74
+ .DS_Store
75
+
76
+ # Poetry
77
+ poetry.lock
78
+
79
+ # Generated workspaces
80
+ workspaces/
81
+ outputs/
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.4
2
+ Name: benchmax
3
+ Version: 0.2.0
4
+ Summary: Platform-independent runtime for grouped LLM environments
5
+ Author: BenchMax Authors
6
+ Classifier: Operating System :: OS Independent
7
+ Classifier: Programming Language :: Python :: 3
8
+ Requires-Python: ==3.12.*
9
+ Requires-Dist: cloudpickle>=3.0.0
10
+ Requires-Dist: openai>=2.15.0
11
+ Requires-Dist: packaging>=24.0
12
+ Provides-Extra: harbor
13
+ Requires-Dist: harbor<0.19,>=0.18.0; extra == 'harbor'
14
+ Description-Content-Type: text/markdown
15
+
16
+ # BenchMax
17
+
18
+ BenchMax is a platform-independent runtime for defining and running reinforcement-
19
+ learning environments. It owns the execution contract, ordered datasets, stable
20
+ example identities, reward helpers and bundling. It has no dependency on any
21
+ training platform.
22
+
23
+ Python 3.12 is required.
24
+
25
+ ## Define an environment
26
+
27
+ Most environments extend `BaseEnv` and declare their complete reward shape:
28
+
29
+ ```python
30
+ from pathlib import Path
31
+
32
+ from benchmax.envs import BaseEnv, BaseRollout, DatasetSplit, JsonlDataset
33
+ from benchmax.rewards import extract_completion_text
34
+
35
+
36
+ class AnswerEnv(BaseEnv):
37
+ reward_keys = ("correct",)
38
+ max_turns = 1
39
+
40
+ async def create_dataset(
41
+ self, split: DatasetSplit, base_dir: Path
42
+ ) -> JsonlDataset:
43
+ return JsonlDataset(base_dir / f"{split}.jsonl", row_to_example=...)
44
+
45
+ async def compute_reward(self, rollout: BaseRollout) -> dict[str, float]:
46
+ answer = extract_completion_text(rollout.messages)
47
+ return {"correct": float(answer == rollout.example_args["answer"])}
48
+ ```
49
+
50
+ `Dataset` is a fixed-order base class. Concrete datasets provide stable
51
+ `Example` objects and may keep lightweight references in each payload instead of
52
+ materializing large data in memory.
53
+
54
+ Each `RolloutRequest` carries a `split` (`"train"` by default) so custom
55
+ `run_rollout`/`run_group` implementations can tell training traffic from
56
+ evaluation traffic without out-of-band state.
57
+
58
+ `reward_keys` is authoritative. A successful rollout must return exactly those
59
+ keys. Operational failures keep the same shape with every value set to zero,
60
+ record the reason in `termination_reason`, and are logged without cancelling
61
+ successful siblings. Reward hooks run user code, so their defects settle the
62
+ same way under `reward_error` (per rollout) or `group_reward_error` (whole
63
+ group) instead of crashing a run; execution-contract violations such as
64
+ malformed requests or a broken reward schema still fail loudly after the
65
+ sibling group settles.
66
+
67
+ See the [BaseEnv guide](src/benchmax/envs/base/README.md) and
68
+ [Harbor adapter guide](src/benchmax/envs/harbor/README.md).
69
+
70
+ ## Define judge-backed rewards
71
+
72
+ Judge configuration is one serializable value shared by rubric, adaptive-rubric,
73
+ and semantic-diversity rewards. Authentication is resolved immediately before
74
+ each model call; bundles carry an `InjectedAuth` reference, never its token.
75
+
76
+ ```python
77
+ from benchmax.auth import InjectedAuth
78
+ from benchmax.rewards import Judge, Rubric, score_rubrics
79
+
80
+ judge = Judge(
81
+ model="judge-model",
82
+ base_url="https://models.example/v1",
83
+ auth=InjectedAuth("judge"),
84
+ )
85
+ rubrics = [
86
+ Rubric("Correctness", "The answer is factually correct."),
87
+ Rubric(
88
+ "Fabrication",
89
+ "The answer invents unsupported facts.",
90
+ polarity="negative",
91
+ ),
92
+ ]
93
+
94
+ rewards = await score_rubrics(
95
+ rollout_id,
96
+ completion,
97
+ ground_truth=reference,
98
+ rubrics=rubrics,
99
+ question=question,
100
+ judge=judge,
101
+ )
102
+ ```
103
+
104
+ `InjectedAuth("judge")` is a named reference; the runtime binds the real
105
+ provider for that name with `bind_model_auth`. Prefer it whenever the runtime
106
+ supplies the credential. For your own external judge endpoint, pass
107
+ `auth=StaticBearerAuth(api_key)` directly; note the key is then pickled into
108
+ the bundle.
109
+
110
+ `evaluate_single_rubric` and `evaluate_rubric_ranking` return typed results.
111
+ `score_rubrics`, `score_group_rubrics`, and `rank_group_rubrics` turn those
112
+ results into reward maps. Empty completions keep the declared rubric reward
113
+ shape and receive zeros without calling the judge. Invalid or out-of-set judge
114
+ output raises `JudgeError` so the environment runtime can record an operational
115
+ failure instead of trusting a fabricated score.
116
+
117
+ Adaptive rubric state is explicit and caller-owned through `RubricCache`.
118
+ Diversity backends are explicit as well: use `NgramDiversityConfig` for local
119
+ single-linkage clustering or `LLMDiversityConfig(judge=judge)` for semantic
120
+ clustering.
121
+
122
+ The rewards package follows a deep-module design:
123
+
124
+ - Callers provide domain intent; modules own prompt structure, authentication
125
+ retries, parsing, score normalization, clustering, and cache keys.
126
+ - Related values use validated types such as `Judge`, `Rubric`, and
127
+ `RankingAnchor` instead of parallel parameters or loose dictionaries.
128
+ - Module names describe capabilities (`prompts`, `scoring`, `deterministic`),
129
+ not visibility or generic “helper” status.
130
+ - A new public abstraction should hide substantially more complexity than it
131
+ adds to the interface.
132
+
133
+ ## Bundle an environment
134
+
135
+ Declare remote runtime dependencies at the script boundary:
136
+
137
+ ```python
138
+ from benchmax.bundle import bundle_digest, dump_bundle
139
+
140
+ bundle = dump_bundle(
141
+ AnswerEnv,
142
+ constructor_args={},
143
+ pip_dependencies=["httpx>=0.28,<0.29"],
144
+ )
145
+ print(bundle_digest(bundle))
146
+ ```
147
+
148
+ BenchMax automatically captures project-local Python modules reachable from the
149
+ environment. Source from a different project is never captured implicitly: pass
150
+ its module object through `local_modules=` to include it, or list its installed
151
+ distribution in `pip_dependencies` to keep it as a remote reference. External
152
+ packages are never inferred from project metadata. Dependency declarations must
153
+ be valid PEP 508 strings; BenchMax canonicalizes and stores them as an immutable,
154
+ order-independent collection in bundle metadata. Declare each distribution once,
155
+ combining its constraints and extras in that declaration; repeated targets are
156
+ rejected instead of relying on resolver-specific conflict behavior.
157
+
158
+ Keep project-local imports at module scope so source capture can see them.
159
+ BenchMax refuses method-local imports of local source, including literal
160
+ `importlib.import_module(...)` calls, because reconstructed by-value modules do
161
+ not satisfy a later Python import. Runtime-computed dynamic import names cannot
162
+ be inferred; install those modules remotely and declare their distributions in
163
+ `pip_dependencies`.
164
+
165
+ BenchMax only prepares the bundle. Uploading it and launching a hosted run belong
166
+ to the platform integration chosen by the caller. `bundle_digest` is the
167
+ artifact identity for storage and caching; it covers both the exact pickle and
168
+ canonical metadata. Execution runtimes can call `validate_bundle_compatibility`
169
+ on metadata before installing dependencies or unpickling. The Python version
170
+ must match exactly and the BenchMax version must share the runtime's
171
+ major.minor series; patch releases load each other's bundles.
172
+
173
+ ## Breaking-version policy
174
+
175
+ This reshuffle intentionally removes the old `benchmax.rubrics`,
176
+ `benchmax.envs.reward_helpers`, `benchmax.prompts`, and `FrozenDataset` import
177
+ surfaces. There are no compatibility aliases. Rebuild environments and bundles
178
+ against the new `benchmax.rewards` and `Dataset` APIs; a runtime that must execute
179
+ an older stored bundle must remain pinned to the older BenchMax version.
180
+
181
+ Rubric judges now enforce their declared score set. A binary rubric accepts only
182
+ `0` or `1`; include intermediate values explicitly in `score_map`, or use a
183
+ ranking reward when continuous relative scores are intended. An out-of-set judge
184
+ score is an operational `judge_error`, not a trusted reward.
185
+
186
+ ## Development
187
+
188
+ ```bash
189
+ uv run --project packages/benchmax pytest \
190
+ -c packages/benchmax/pytest.ini packages/benchmax/tests
191
+ ```
192
+
193
+ Apache 2.0 © 2026 CGFT Inc.
@@ -0,0 +1,178 @@
1
+ # BenchMax
2
+
3
+ BenchMax is a platform-independent runtime for defining and running reinforcement-
4
+ learning environments. It owns the execution contract, ordered datasets, stable
5
+ example identities, reward helpers and bundling. It has no dependency on any
6
+ training platform.
7
+
8
+ Python 3.12 is required.
9
+
10
+ ## Define an environment
11
+
12
+ Most environments extend `BaseEnv` and declare their complete reward shape:
13
+
14
+ ```python
15
+ from pathlib import Path
16
+
17
+ from benchmax.envs import BaseEnv, BaseRollout, DatasetSplit, JsonlDataset
18
+ from benchmax.rewards import extract_completion_text
19
+
20
+
21
+ class AnswerEnv(BaseEnv):
22
+ reward_keys = ("correct",)
23
+ max_turns = 1
24
+
25
+ async def create_dataset(
26
+ self, split: DatasetSplit, base_dir: Path
27
+ ) -> JsonlDataset:
28
+ return JsonlDataset(base_dir / f"{split}.jsonl", row_to_example=...)
29
+
30
+ async def compute_reward(self, rollout: BaseRollout) -> dict[str, float]:
31
+ answer = extract_completion_text(rollout.messages)
32
+ return {"correct": float(answer == rollout.example_args["answer"])}
33
+ ```
34
+
35
+ `Dataset` is a fixed-order base class. Concrete datasets provide stable
36
+ `Example` objects and may keep lightweight references in each payload instead of
37
+ materializing large data in memory.
38
+
39
+ Each `RolloutRequest` carries a `split` (`"train"` by default) so custom
40
+ `run_rollout`/`run_group` implementations can tell training traffic from
41
+ evaluation traffic without out-of-band state.
42
+
43
+ `reward_keys` is authoritative. A successful rollout must return exactly those
44
+ keys. Operational failures keep the same shape with every value set to zero,
45
+ record the reason in `termination_reason`, and are logged without cancelling
46
+ successful siblings. Reward hooks run user code, so their defects settle the
47
+ same way under `reward_error` (per rollout) or `group_reward_error` (whole
48
+ group) instead of crashing a run; execution-contract violations such as
49
+ malformed requests or a broken reward schema still fail loudly after the
50
+ sibling group settles.
51
+
52
+ See the [BaseEnv guide](src/benchmax/envs/base/README.md) and
53
+ [Harbor adapter guide](src/benchmax/envs/harbor/README.md).
54
+
55
+ ## Define judge-backed rewards
56
+
57
+ Judge configuration is one serializable value shared by rubric, adaptive-rubric,
58
+ and semantic-diversity rewards. Authentication is resolved immediately before
59
+ each model call; bundles carry an `InjectedAuth` reference, never its token.
60
+
61
+ ```python
62
+ from benchmax.auth import InjectedAuth
63
+ from benchmax.rewards import Judge, Rubric, score_rubrics
64
+
65
+ judge = Judge(
66
+ model="judge-model",
67
+ base_url="https://models.example/v1",
68
+ auth=InjectedAuth("judge"),
69
+ )
70
+ rubrics = [
71
+ Rubric("Correctness", "The answer is factually correct."),
72
+ Rubric(
73
+ "Fabrication",
74
+ "The answer invents unsupported facts.",
75
+ polarity="negative",
76
+ ),
77
+ ]
78
+
79
+ rewards = await score_rubrics(
80
+ rollout_id,
81
+ completion,
82
+ ground_truth=reference,
83
+ rubrics=rubrics,
84
+ question=question,
85
+ judge=judge,
86
+ )
87
+ ```
88
+
89
+ `InjectedAuth("judge")` is a named reference; the runtime binds the real
90
+ provider for that name with `bind_model_auth`. Prefer it whenever the runtime
91
+ supplies the credential. For your own external judge endpoint, pass
92
+ `auth=StaticBearerAuth(api_key)` directly; note the key is then pickled into
93
+ the bundle.
94
+
95
+ `evaluate_single_rubric` and `evaluate_rubric_ranking` return typed results.
96
+ `score_rubrics`, `score_group_rubrics`, and `rank_group_rubrics` turn those
97
+ results into reward maps. Empty completions keep the declared rubric reward
98
+ shape and receive zeros without calling the judge. Invalid or out-of-set judge
99
+ output raises `JudgeError` so the environment runtime can record an operational
100
+ failure instead of trusting a fabricated score.
101
+
102
+ Adaptive rubric state is explicit and caller-owned through `RubricCache`.
103
+ Diversity backends are explicit as well: use `NgramDiversityConfig` for local
104
+ single-linkage clustering or `LLMDiversityConfig(judge=judge)` for semantic
105
+ clustering.
106
+
107
+ The rewards package follows a deep-module design:
108
+
109
+ - Callers provide domain intent; modules own prompt structure, authentication
110
+ retries, parsing, score normalization, clustering, and cache keys.
111
+ - Related values use validated types such as `Judge`, `Rubric`, and
112
+ `RankingAnchor` instead of parallel parameters or loose dictionaries.
113
+ - Module names describe capabilities (`prompts`, `scoring`, `deterministic`),
114
+ not visibility or generic “helper” status.
115
+ - A new public abstraction should hide substantially more complexity than it
116
+ adds to the interface.
117
+
118
+ ## Bundle an environment
119
+
120
+ Declare remote runtime dependencies at the script boundary:
121
+
122
+ ```python
123
+ from benchmax.bundle import bundle_digest, dump_bundle
124
+
125
+ bundle = dump_bundle(
126
+ AnswerEnv,
127
+ constructor_args={},
128
+ pip_dependencies=["httpx>=0.28,<0.29"],
129
+ )
130
+ print(bundle_digest(bundle))
131
+ ```
132
+
133
+ BenchMax automatically captures project-local Python modules reachable from the
134
+ environment. Source from a different project is never captured implicitly: pass
135
+ its module object through `local_modules=` to include it, or list its installed
136
+ distribution in `pip_dependencies` to keep it as a remote reference. External
137
+ packages are never inferred from project metadata. Dependency declarations must
138
+ be valid PEP 508 strings; BenchMax canonicalizes and stores them as an immutable,
139
+ order-independent collection in bundle metadata. Declare each distribution once,
140
+ combining its constraints and extras in that declaration; repeated targets are
141
+ rejected instead of relying on resolver-specific conflict behavior.
142
+
143
+ Keep project-local imports at module scope so source capture can see them.
144
+ BenchMax refuses method-local imports of local source, including literal
145
+ `importlib.import_module(...)` calls, because reconstructed by-value modules do
146
+ not satisfy a later Python import. Runtime-computed dynamic import names cannot
147
+ be inferred; install those modules remotely and declare their distributions in
148
+ `pip_dependencies`.
149
+
150
+ BenchMax only prepares the bundle. Uploading it and launching a hosted run belong
151
+ to the platform integration chosen by the caller. `bundle_digest` is the
152
+ artifact identity for storage and caching; it covers both the exact pickle and
153
+ canonical metadata. Execution runtimes can call `validate_bundle_compatibility`
154
+ on metadata before installing dependencies or unpickling. The Python version
155
+ must match exactly and the BenchMax version must share the runtime's
156
+ major.minor series; patch releases load each other's bundles.
157
+
158
+ ## Breaking-version policy
159
+
160
+ This reshuffle intentionally removes the old `benchmax.rubrics`,
161
+ `benchmax.envs.reward_helpers`, `benchmax.prompts`, and `FrozenDataset` import
162
+ surfaces. There are no compatibility aliases. Rebuild environments and bundles
163
+ against the new `benchmax.rewards` and `Dataset` APIs; a runtime that must execute
164
+ an older stored bundle must remain pinned to the older BenchMax version.
165
+
166
+ Rubric judges now enforce their declared score set. A binary rubric accepts only
167
+ `0` or `1`; include intermediate values explicitly in `score_map`, or use a
168
+ ranking reward when continuous relative scores are intended. An out-of-set judge
169
+ score is an operational `judge_error`, not a trusted reward.
170
+
171
+ ## Development
172
+
173
+ ```bash
174
+ uv run --project packages/benchmax pytest \
175
+ -c packages/benchmax/pytest.ini packages/benchmax/tests
176
+ ```
177
+
178
+ Apache 2.0 © 2026 CGFT Inc.
@@ -0,0 +1,35 @@
1
+ [project]
2
+ name = "benchmax"
3
+ version = "0.2.0"
4
+ description = "Platform-independent runtime for grouped LLM environments"
5
+ readme = "README.md"
6
+ authors = [{ name = "BenchMax Authors" }]
7
+ requires-python = "==3.12.*"
8
+ dependencies = [
9
+ "cloudpickle>=3.0.0",
10
+ "openai>=2.15.0",
11
+ "packaging>=24.0",
12
+ ]
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "Operating System :: OS Independent",
16
+ ]
17
+
18
+ [build-system]
19
+ requires = ["hatchling>=1.27"]
20
+ build-backend = "hatchling.build"
21
+
22
+ [tool.hatch.build.targets.wheel]
23
+ packages = ["src/benchmax"]
24
+
25
+ [project.optional-dependencies]
26
+ harbor = ["harbor>=0.18.0,<0.19"]
27
+
28
+ [dependency-groups]
29
+ dev = [
30
+ "harbor>=0.18.0,<0.19",
31
+ "pytest>=8.4.2",
32
+ "pytest-asyncio>=1.2.0",
33
+ "python-dotenv>=1.2.1",
34
+ "ruff>=0.14.2",
35
+ ]
@@ -0,0 +1,36 @@
1
+ [pytest]
2
+ # Pytest configuration for benchmax
3
+ pythonpath = .
4
+
5
+ testpaths = tests
6
+
7
+ # Test discovery
8
+ python_files = test_*.py
9
+ python_classes = Test*
10
+ python_functions = test_*
11
+
12
+ # Asyncio mode
13
+ asyncio_mode = auto
14
+
15
+ # Markers
16
+ markers =
17
+ slow: Slow-running tests (deselect with '-m "not slow"')
18
+ remote: marks tests that require remote resources (deselect with '-m "not remote"')
19
+ unit: Fast, isolated tests
20
+ integration: Integration tests that hit live external APIs (require credentials; run with -m integration)
21
+
22
+ # Default deselection to speed up CI runs
23
+ addopts = -m "not slow and not remote and not integration" --import-mode=importlib
24
+
25
+ # Logging
26
+ log_cli = false
27
+ log_cli_level = INFO
28
+ log_cli_format = %(asctime)s [%(levelname)8s] %(message)s
29
+ log_cli_date_format = %Y-%m-%d %H:%M:%S
30
+
31
+ # Warnings
32
+ filterwarnings =
33
+ ignore::DeprecationWarning
34
+ ignore::PendingDeprecationWarning
35
+
36
+ minversion = 7.0
@@ -0,0 +1,109 @@
1
+ """Explicit, call-time authentication for model requests.
2
+
3
+ BenchMax defines only the runtime contract. Platform packages and execution
4
+ runtimes provide concrete credential sources and bind injected credentials.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Iterator, Mapping
10
+ from contextlib import contextmanager
11
+ from contextvars import ContextVar
12
+ from dataclasses import dataclass
13
+ from typing import Protocol, runtime_checkable
14
+
15
+ __all__ = [
16
+ "InjectedAuth",
17
+ "ModelAuth",
18
+ "ModelRequestContext",
19
+ "StaticBearerAuth",
20
+ "bind_model_auth",
21
+ ]
22
+
23
+
24
+ @dataclass(frozen=True, slots=True)
25
+ class ModelRequestContext:
26
+ """Identity of the model request about to be authorized."""
27
+
28
+ base_url: str
29
+ model: str
30
+ rollout_id: str
31
+
32
+
33
+ @runtime_checkable
34
+ class ModelAuth(Protocol):
35
+ """Return headers immediately before each model HTTP request."""
36
+
37
+ async def headers_for_request(
38
+ self,
39
+ context: ModelRequestContext,
40
+ ) -> Mapping[str, str]: ...
41
+
42
+
43
+ @dataclass(frozen=True, slots=True)
44
+ class StaticBearerAuth:
45
+ """Explicit bearer authentication for providers with a stable API key."""
46
+
47
+ token: str
48
+
49
+ def __post_init__(self) -> None:
50
+ if not isinstance(self.token, str) or not self.token:
51
+ raise ValueError("bearer token must be a non-empty string")
52
+
53
+ async def headers_for_request(
54
+ self,
55
+ context: ModelRequestContext,
56
+ ) -> Mapping[str, str]:
57
+ del context
58
+ return {"Authorization": f"Bearer {self.token}"}
59
+
60
+
61
+ _BOUND_MODEL_AUTH: ContextVar[Mapping[str, ModelAuth] | None] = ContextVar(
62
+ "benchmax_bound_model_auth",
63
+ default=None,
64
+ )
65
+
66
+
67
+ @dataclass(frozen=True, slots=True)
68
+ class InjectedAuth:
69
+ """Serializable reference to authentication supplied by the runtime."""
70
+
71
+ name: str
72
+
73
+ def __post_init__(self) -> None:
74
+ if not isinstance(self.name, str) or not self.name.strip():
75
+ raise ValueError("injected auth name must be a non-empty string")
76
+
77
+ async def headers_for_request(
78
+ self,
79
+ context: ModelRequestContext,
80
+ ) -> Mapping[str, str]:
81
+ providers = _BOUND_MODEL_AUTH.get()
82
+ provider = providers.get(self.name) if providers is not None else None
83
+ if provider is None:
84
+ raise RuntimeError(
85
+ f"No runtime model-auth provider was injected for {self.name!r}."
86
+ )
87
+ if isinstance(provider, InjectedAuth):
88
+ raise RuntimeError(
89
+ f"Injected model-auth provider {self.name!r} cannot reference "
90
+ "another InjectedAuth."
91
+ )
92
+ return await provider.headers_for_request(context)
93
+
94
+
95
+ @contextmanager
96
+ def bind_model_auth(providers: Mapping[str, ModelAuth]) -> Iterator[None]:
97
+ """Bind runtime providers for the current async execution context."""
98
+
99
+ normalized = dict(providers)
100
+ for name, provider in normalized.items():
101
+ if not isinstance(name, str) or not name.strip():
102
+ raise ValueError("model-auth provider names must be non-empty strings")
103
+ if not isinstance(provider, ModelAuth):
104
+ raise TypeError(f"model-auth provider {name!r} does not implement ModelAuth")
105
+ token = _BOUND_MODEL_AUTH.set(normalized)
106
+ try:
107
+ yield
108
+ finally:
109
+ _BOUND_MODEL_AUTH.reset(token)