benchmax 0.1.2.dev35__tar.gz → 0.1.2.dev36__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.
- {benchmax-0.1.2.dev35/src/benchmax.egg-info → benchmax-0.1.2.dev36}/PKG-INFO +1 -1
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/pyproject.toml +4 -2
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/cli/__init__.py +2 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/cli/_output.py +17 -0
- benchmax-0.1.2.dev36/src/benchmax/cli/_preflight.py +104 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/cli/_project.py +55 -5
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/cli/corpus.py +101 -1
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/cli/data.py +124 -3
- benchmax-0.1.2.dev36/src/benchmax/cli/dataview.py +436 -0
- benchmax-0.1.2.dev36/src/benchmax/cli/doctor.py +110 -0
- benchmax-0.1.2.dev36/src/benchmax/cli/launch.py +445 -0
- benchmax-0.1.2.dev36/src/benchmax/cli/runs.py +392 -0
- benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/CLAUDE.md +162 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/cli/scaffold/STARTER.md +16 -11
- benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/generic_eval_dataset.jsonl +2 -0
- benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/generic_main.py +301 -0
- benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/generic_train_dataset.jsonl +3 -0
- benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/rag_eval_dataset.jsonl +1 -0
- benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/rag_main.py +508 -0
- benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/rag_train_dataset.jsonl +2 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/cli/scaffold/skills/design-environment/SKILL.md +113 -60
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/cli/scaffold/skills/generate-data/SKILL.md +42 -39
- benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/skills/launch-run/SKILL.md +130 -0
- benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/skills/verify-environment/SKILL.md +228 -0
- benchmax-0.1.2.dev36/src/benchmax/cli/scaffold/skills/view-progress/SKILL.md +173 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/cli/setup.py +107 -33
- benchmax-0.1.2.dev36/src/benchmax/cli/templates/viewer.html +485 -0
- benchmax-0.1.2.dev36/src/benchmax/cli/validate.py +775 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/base_env.py +25 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/postgres_search/search_env.py +226 -107
- benchmax-0.1.2.dev36/src/benchmax/platform/browser.py +32 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/platform/client.py +123 -7
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/platform/login.py +2 -24
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/platform/validation.py +62 -6
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36/src/benchmax.egg-info}/PKG-INFO +1 -1
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax.egg-info/SOURCES.txt +11 -1
- benchmax-0.1.2.dev35/src/benchmax/cli/launch.py +0 -241
- benchmax-0.1.2.dev35/src/benchmax/cli/runs.py +0 -187
- benchmax-0.1.2.dev35/src/benchmax/cli/scaffold/CLAUDE.md +0 -132
- benchmax-0.1.2.dev35/src/benchmax/cli/scaffold/rag_run.py +0 -72
- benchmax-0.1.2.dev35/src/benchmax/cli/scaffold/skills/launch-run/SKILL.md +0 -68
- benchmax-0.1.2.dev35/src/benchmax/cli/scaffold/skills/verify-environment/SKILL.md +0 -199
- benchmax-0.1.2.dev35/src/benchmax/cli/scaffold/skills/view-progress/SKILL.md +0 -63
- benchmax-0.1.2.dev35/src/benchmax/cli/validate.py +0 -448
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/LICENSE +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/README.md +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/setup.cfg +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/bundle.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/cli/_auth.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/cli/_client.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/cli/_providers.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/cli/control.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/cli/help.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/cli/scaffold/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/config.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/crm/crm_env.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/crm/workdir/reward_fn.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/example_id.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/excel/data_utils.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/excel/excel_env.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/excel/workdir/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/excel/workdir/excel_code_runner_mcp.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/excel/workdir/excel_utils.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/excel/workdir/reward_fn.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/logging.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/math/math_env.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/math/workdir/reward_fn.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/mcp/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/mcp/example_workdir/demo_mcp_server.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/mcp/example_workdir/reward_fn.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/mcp/parallel_mcp_env.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/mcp/provisioners/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/mcp/provisioners/base_provisioner.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/mcp/provisioners/local_provisioner.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/mcp/provisioners/manual_provisioner.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/mcp/provisioners/skypilot_provisioner.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/mcp/provisioners/utils.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/mcp/proxy_server.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/mcp/server_pool.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/mcp/utils.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/postgres_search/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/postgres_search/linker_env.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/reward_helpers.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/telestich/example.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/telestich/telestich_env.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/types.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/wikipedia/utils.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/envs/wikipedia/wiki_env.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/multi_model/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/multi_model/caller.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/multi_model/clients.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/multi_model/example_usage.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/multi_model/inspector.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/multi_model/models.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/multi_model/pricing.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/platform/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/platform/credentials.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/platform/device_auth.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/platform/exceptions.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/platform/training_run.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/prompts/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/prompts/tools.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/chunkers/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/chunkers/email.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/chunkers/inspector.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/chunkers/markdown.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/chunkers/models.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/chunkers/storage.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/chroma/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/chroma/client.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/chroma/files.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/chroma/filter_mapper.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/chroma/search.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/chroma/source.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/embed.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/pinecone/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/pinecone/files.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/pinecone/filter_mapper.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/pinecone/index_client.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/pinecone/search.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/pinecone/source.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/postgres/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/postgres/client.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/postgres/exceptions.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/postgres/filter_mapper.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/postgres/models.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/postgres/search.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/postgres/source.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/search_client.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/search_schema/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/search_schema/builders.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/search_schema/dsl_parser.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/search_schema/search_exceptions.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/search_schema/search_types.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/source.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/turbopuffer/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/turbopuffer/files.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/turbopuffer/filter_mapper.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/turbopuffer/namespace.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/turbopuffer/search.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/corpus/turbopuffer/source.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/preprocess/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/preprocess/email/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/preprocess/email/clean_bodies.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/preprocess/email/dedupe.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/preprocess/email/filter_automated_email_qas.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/preprocess/email/filter_automated_emails.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/preprocess/email/mbox.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/preprocess/email/schema.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/anchor_selector.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/auto_tune.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/batch_processor.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/checkpoint.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/corpus_capabilities.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/corpus_profile.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/filters/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/filters/deterministic_guards.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/filters/env_rollout.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/filters/grounding_llm.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/filters/hop_count_validity.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/filters/quality_gate.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/filters/retrieval_llm.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/formatters/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/formatters/train_eval.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/generated_qa.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/generators/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/generators/direct_llm.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/helpers.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/metadata_linker.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/metrics.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/models.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/pipeline.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/pipeline_config.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/protocols.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/query_rewriter.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/response_parsers.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/retrieval_query.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/scoring.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/search_agent_linker.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/storage.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/style_controls.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/transformers/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/transformers/base.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/transformers/dedup.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/wiki_builder.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rag/qa_generation/wiki_chunk_linker.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rewards/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rewards/diversity.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rubrics/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rubrics/_utils.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rubrics/adaptive.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rubrics/cache.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rubrics/prompts.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rubrics/reward_fns.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/rubrics/rubric.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/traces/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/traces/adapter.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/traces/braintrust/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/traces/braintrust/adapter.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/traces/braintrust/message_extraction.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/traces/http.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/traces/pipeline.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/traces/pivot.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/traces/processing.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/traces/registry.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/utils/__init__.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax/utils/checkpoint.py +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax.egg-info/dependency_links.txt +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax.egg-info/entry_points.txt +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax.egg-info/requires.txt +0 -0
- {benchmax-0.1.2.dev35 → benchmax-0.1.2.dev36}/src/benchmax.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "benchmax"
|
|
3
|
-
version = "0.1.2.
|
|
3
|
+
version = "0.1.2.dev36"
|
|
4
4
|
description = "Framework-Agnostic RL Environments for LLM Fine-Tuning"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "castie@castform.com" }]
|
|
@@ -32,9 +32,11 @@ where = ["src"]
|
|
|
32
32
|
|
|
33
33
|
[tool.setuptools.package-data]
|
|
34
34
|
# Ship the `castform setup` scaffold templates in the wheel (read at runtime via
|
|
35
|
-
# importlib.resources): docs (*.md), the starter
|
|
35
|
+
# importlib.resources): docs (*.md), the starter main.py + seed datasets (*.py/*.jsonl),
|
|
36
36
|
# and the per-skill SKILL.md. Skill dirs are hyphenated (data, not Python packages).
|
|
37
37
|
"benchmax.cli.scaffold" = ["*.md", "*.py", "*.jsonl", "skills/*/SKILL.md"]
|
|
38
|
+
# Ship the `castform data view` HTML viewer (read at runtime via importlib.resources).
|
|
39
|
+
"benchmax.cli" = ["templates/*.html"]
|
|
38
40
|
|
|
39
41
|
[project.optional-dependencies]
|
|
40
42
|
mcp = [
|
|
@@ -18,6 +18,7 @@ from benchmax.cli import (
|
|
|
18
18
|
control,
|
|
19
19
|
corpus,
|
|
20
20
|
data,
|
|
21
|
+
doctor,
|
|
21
22
|
help,
|
|
22
23
|
launch,
|
|
23
24
|
runs,
|
|
@@ -43,6 +44,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
43
44
|
data.register(sub)
|
|
44
45
|
corpus.register(sub)
|
|
45
46
|
setup.register(sub)
|
|
47
|
+
doctor.register(sub)
|
|
46
48
|
|
|
47
49
|
# `guide` renders the getting-started walkthrough (the renderer lives in
|
|
48
50
|
# ``benchmax.cli.help``). Named `guide`, not `quickstart`, because `setup`
|
|
@@ -132,3 +132,20 @@ def fmt_value(value: Any) -> str:
|
|
|
132
132
|
if isinstance(value, float):
|
|
133
133
|
return f"{value:.4g}"
|
|
134
134
|
return str(value)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def truncate(text: object, n: int) -> str:
|
|
138
|
+
"""Collapse whitespace and clip to ``n`` chars (…-suffixed) for one-line display.
|
|
139
|
+
|
|
140
|
+
``None`` → ``""``, but a falsy-yet-real value is preserved (a gold answer of
|
|
141
|
+
``0`` must render as ``0``, not blank)."""
|
|
142
|
+
s = " ".join(("" if text is None else str(text)).split())
|
|
143
|
+
return s if len(s) <= n else s[: n - 1] + "…"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def final_answer(messages: list | None) -> str | None:
|
|
147
|
+
"""The last assistant-turn content in a transcript — the model's committed answer."""
|
|
148
|
+
for m in reversed(messages or []):
|
|
149
|
+
if isinstance(m, dict) and m.get("role") == "assistant" and m.get("content"):
|
|
150
|
+
return m["content"]
|
|
151
|
+
return None
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Preflight: turn a missing-env-dependency ImportError into a copy-paste fix.
|
|
2
|
+
|
|
3
|
+
``castform validate``/``launch`` import the project's ``main.py`` in-process (see
|
|
4
|
+
:func:`benchmax.cli._project.load_project`). A ``main.py`` that uses a corpus-provider
|
|
5
|
+
backend (turbopuffer / pinecone / chroma) or the data-generation helpers imports a
|
|
6
|
+
package that lives behind a ``castform[...]`` extra, not in base castform — so a base
|
|
7
|
+
install fails with ``ModuleNotFoundError`` mid-import. Rather than surface a raw
|
|
8
|
+
traceback, map the missing top-level module back to the extra that ships it and print
|
|
9
|
+
the exact ``uv pip install`` line.
|
|
10
|
+
|
|
11
|
+
The default *postgres* rag env imports on base castform (openai + httpx); this is for
|
|
12
|
+
the provider backends and the ``castform data qa-gen`` / chunking path, which pull the
|
|
13
|
+
heavier deps.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import importlib.util
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
# Import-name → the ``castform[<extra>]`` extra that provides it. Import names differ
|
|
22
|
+
# from the pip/extra names (scikit-learn→``sklearn``; chromadb ships under the
|
|
23
|
+
# ``[chroma]`` extra), and ``ModuleNotFoundError.name`` reports the *import* name — so
|
|
24
|
+
# this is keyed on that. Provider rows mirror ``_providers.PROVIDER_PIP``'s keys; the
|
|
25
|
+
# rest are the ``[rag]`` extra's data-generation + chunking deps.
|
|
26
|
+
_MODULE_EXTRA: dict[str, str] = {
|
|
27
|
+
# provider search backends — benchmax.rag.corpus.{turbopuffer,pinecone,chroma}
|
|
28
|
+
"turbopuffer": "turbopuffer",
|
|
29
|
+
"pinecone": "pinecone",
|
|
30
|
+
"chromadb": "chroma",
|
|
31
|
+
"snowballstemmer": "chroma",
|
|
32
|
+
# data generation (qa-gen) + chunking / ingest — the [rag] extra
|
|
33
|
+
"sentence_transformers": "rag",
|
|
34
|
+
"sklearn": "rag",
|
|
35
|
+
"keybert": "rag",
|
|
36
|
+
"nest_asyncio": "rag",
|
|
37
|
+
"langchain_text_splitters": "rag",
|
|
38
|
+
"ragas": "rag",
|
|
39
|
+
"ruamel": "rag",
|
|
40
|
+
"tqdm": "rag",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# One sentinel import name per extra — used by ``castform doctor`` to report whether an
|
|
44
|
+
# extra is installed without importing the (heavy) package itself.
|
|
45
|
+
_EXTRA_SENTINEL: dict[str, str] = {
|
|
46
|
+
"rag": "keybert",
|
|
47
|
+
"turbopuffer": "turbopuffer",
|
|
48
|
+
"pinecone": "pinecone",
|
|
49
|
+
"chroma": "chromadb",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _missing_module(exc: BaseException) -> str | None:
|
|
54
|
+
"""Top-level name of the first ``ModuleNotFoundError`` in the exception chain."""
|
|
55
|
+
seen: set[int] = set()
|
|
56
|
+
cur: BaseException | None = exc
|
|
57
|
+
while cur is not None and id(cur) not in seen:
|
|
58
|
+
seen.add(id(cur))
|
|
59
|
+
if isinstance(cur, ModuleNotFoundError) and cur.name:
|
|
60
|
+
return cur.name.split(".")[0]
|
|
61
|
+
cur = cur.__cause__ or cur.__context__
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def install_hint_for_import_error(exc: BaseException) -> str | None:
|
|
66
|
+
"""A copy-paste install line if ``exc`` (or its cause chain) is a missing import.
|
|
67
|
+
|
|
68
|
+
Returns ``None`` when the error isn't a ``ModuleNotFoundError`` — callers then
|
|
69
|
+
just print the base error and leave it at that.
|
|
70
|
+
"""
|
|
71
|
+
module = _missing_module(exc)
|
|
72
|
+
if module is None:
|
|
73
|
+
return None
|
|
74
|
+
extra = _MODULE_EXTRA.get(module)
|
|
75
|
+
if extra:
|
|
76
|
+
return f"→ install the env deps: uv pip install 'castform[{extra}]'"
|
|
77
|
+
# A third-party module the env imports that isn't one of our extras — still nudge
|
|
78
|
+
# toward installing it rather than leaving a bare "no module named …".
|
|
79
|
+
return (
|
|
80
|
+
f"→ missing dependency {module!r}: install it into this environment "
|
|
81
|
+
f"(e.g. uv pip install {module})"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def print_project_error(exc: Exception) -> None:
|
|
86
|
+
"""Print a project-load error to stderr, appending an install hint for a
|
|
87
|
+
missing env dependency (so a base-install ImportError is a one-line fix, not a
|
|
88
|
+
traceback). Shared by ``validate`` and ``launch``."""
|
|
89
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
90
|
+
hint = install_hint_for_import_error(exc)
|
|
91
|
+
if hint:
|
|
92
|
+
print(hint, file=sys.stderr)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def extra_is_installed(extra: str) -> bool:
|
|
96
|
+
"""True if ``extra``'s sentinel module resolves (no import executed)."""
|
|
97
|
+
sentinel = _EXTRA_SENTINEL.get(extra)
|
|
98
|
+
if sentinel is None:
|
|
99
|
+
return False
|
|
100
|
+
try:
|
|
101
|
+
return importlib.util.find_spec(sentinel) is not None
|
|
102
|
+
except (ImportError, ValueError):
|
|
103
|
+
# A parent package missing (or a namespace edge case) => treat as absent.
|
|
104
|
+
return False
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""Load a benchmax project (env class + datasets) from a directory.
|
|
2
2
|
|
|
3
|
-
Convention mirrors the web-app scaffold (``buildAgentContextBody``): ``
|
|
3
|
+
Convention mirrors the web-app scaffold (``buildAgentContextBody``): ``main.py``
|
|
4
4
|
defines a single :class:`BaseEnv` subclass; ``train_dataset.jsonl`` /
|
|
5
5
|
``eval_dataset.jsonl`` hold one JSON object per line. ``validate`` and ``launch``
|
|
6
6
|
share this loader. An importable module path (``--module``) is an alternative to
|
|
7
|
-
``
|
|
7
|
+
``main.py`` for shipped envs / fixtures.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
@@ -21,7 +21,7 @@ from typing import Any
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class ProjectError(Exception):
|
|
24
|
-
"""A project couldn't be loaded (missing
|
|
24
|
+
"""A project couldn't be loaded (missing main.py/dataset, or no/ambiguous env)."""
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
@dataclass
|
|
@@ -31,8 +31,56 @@ class LoadedProject:
|
|
|
31
31
|
eval_dataset: list[dict[str, Any]]
|
|
32
32
|
module: ModuleType
|
|
33
33
|
from_file: (
|
|
34
|
-
bool # loaded from a
|
|
34
|
+
bool # loaded from a main.py path (pickle env by value) vs an importable module
|
|
35
35
|
)
|
|
36
|
+
# Optional module-level config dicts baked into main.py so a run is
|
|
37
|
+
# reproducible from the file (validate/launch read these; CLI flags override).
|
|
38
|
+
launch_config: dict[str, Any]
|
|
39
|
+
validate_config: dict[str, Any]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def row_question_and_gold(row: Any) -> tuple[object, object]:
|
|
43
|
+
"""``(question, gold)`` from a dataset row, across the on-disk shapes.
|
|
44
|
+
|
|
45
|
+
Question: ``prompt`` (a chat-list prompt → its last user turn) else ``question``.
|
|
46
|
+
Gold: ``ground_truth`` else ``answer``. One definition so ``runs rollout`` and
|
|
47
|
+
``validate --reward-audit`` can't drift when a dataset field is renamed (see the
|
|
48
|
+
``castform-dataset-ondisk-shapes`` note)."""
|
|
49
|
+
if not isinstance(row, dict):
|
|
50
|
+
return None, None
|
|
51
|
+
q = row.get("prompt")
|
|
52
|
+
if not q:
|
|
53
|
+
q = row.get("question")
|
|
54
|
+
if isinstance(q, list): # chat-style prompt → last user turn
|
|
55
|
+
q = next(
|
|
56
|
+
(
|
|
57
|
+
m.get("content")
|
|
58
|
+
for m in reversed(q)
|
|
59
|
+
if isinstance(m, dict) and m.get("role") == "user" and m.get("content")
|
|
60
|
+
),
|
|
61
|
+
None,
|
|
62
|
+
)
|
|
63
|
+
gold = row.get("ground_truth")
|
|
64
|
+
if gold is None:
|
|
65
|
+
gold = row.get("answer")
|
|
66
|
+
return q, gold
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _read_config(module: ModuleType, name: str) -> dict[str, Any]:
|
|
70
|
+
"""A module-level config dict (``LAUNCH_CONFIG`` / ``VALIDATE_CONFIG``) from
|
|
71
|
+
main.py — the knobs the file bakes in so the run reproduces without remembering
|
|
72
|
+
CLI flags. Absent → ``{}`` (the block is optional); present-but-not-a-dict is a
|
|
73
|
+
user error we fail loudly on rather than silently drop (a dropped budget wastes
|
|
74
|
+
GPU with no explanation)."""
|
|
75
|
+
value = getattr(module, name, None)
|
|
76
|
+
if value is None:
|
|
77
|
+
return {}
|
|
78
|
+
if not isinstance(value, dict):
|
|
79
|
+
raise ProjectError(
|
|
80
|
+
f"{name} must be a dict (got {type(value).__name__}); it bakes the "
|
|
81
|
+
"validate/launch knobs into main.py. Fix or remove it."
|
|
82
|
+
)
|
|
83
|
+
return dict(value)
|
|
36
84
|
|
|
37
85
|
|
|
38
86
|
def _load_module_from_file(path: Path) -> ModuleType:
|
|
@@ -97,7 +145,7 @@ def _load_jsonl(path: Path) -> list[dict[str, Any]]:
|
|
|
97
145
|
def load_project(
|
|
98
146
|
*,
|
|
99
147
|
directory: str = ".",
|
|
100
|
-
run_file: str = "
|
|
148
|
+
run_file: str = "main.py",
|
|
101
149
|
module_path: str | None = None,
|
|
102
150
|
env_class_name: str | None = None,
|
|
103
151
|
train_file: str = "train_dataset.jsonl",
|
|
@@ -135,4 +183,6 @@ def load_project(
|
|
|
135
183
|
eval_dataset=eval_dataset,
|
|
136
184
|
module=module,
|
|
137
185
|
from_file=from_file,
|
|
186
|
+
launch_config=_read_config(module, "LAUNCH_CONFIG"),
|
|
187
|
+
validate_config=_read_config(module, "VALIDATE_CONFIG"),
|
|
138
188
|
)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
``corpus ingest <folder>`` chunks a local document folder and uploads it to the
|
|
4
4
|
deployed Corpora backend (BM25/lexical search), yielding a named corpus that
|
|
5
|
-
``data qa-gen`` and the rag ``
|
|
5
|
+
``data qa-gen`` and the rag ``main.py`` template resolve by name. ``corpus list`` /
|
|
6
6
|
``corpus delete`` manage them against the 5-corpus-per-user cap (the cap is why
|
|
7
7
|
``ingest`` can refuse — delete one to free a slot). Thin wrappers over
|
|
8
8
|
``benchmax.rag.corpus.postgres`` — the rag bits import lazily so users without the
|
|
@@ -18,7 +18,9 @@ under cap, or raises ``CorpusLimitError`` — surfaced here as a clean message.
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
20
|
import argparse
|
|
21
|
+
import os
|
|
21
22
|
import sys
|
|
23
|
+
import tempfile
|
|
22
24
|
from pathlib import Path
|
|
23
25
|
|
|
24
26
|
from benchmax import config
|
|
@@ -186,6 +188,74 @@ def _cmd_corpus_search(args: argparse.Namespace) -> int:
|
|
|
186
188
|
return 0
|
|
187
189
|
|
|
188
190
|
|
|
191
|
+
@handle_errors
|
|
192
|
+
def _cmd_corpus_browse(args: argparse.Namespace) -> int:
|
|
193
|
+
# Recover the wizard's "preview the corpus" step: pull the stored chunks and
|
|
194
|
+
# render them in the local HTML viewer (same one `data view` uses).
|
|
195
|
+
from benchmax.cli.dataview import build_view_model, write_html
|
|
196
|
+
from benchmax.platform.browser import maybe_open_browser
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
client = _corpus_client()
|
|
200
|
+
except ImportError as exc:
|
|
201
|
+
print(f"Error: {exc}. {_RAG_INSTALL_HINT}", file=sys.stderr)
|
|
202
|
+
return 1
|
|
203
|
+
|
|
204
|
+
corpora = client.list_corpora()
|
|
205
|
+
target = next(
|
|
206
|
+
(c for c in corpora if c.id == args.corpus or c.name == args.corpus), None
|
|
207
|
+
)
|
|
208
|
+
if target is None:
|
|
209
|
+
have = ", ".join(c.name for c in corpora) or "(none)"
|
|
210
|
+
print(f"Error: no corpus matching {args.corpus!r}. Have: {have}", file=sys.stderr)
|
|
211
|
+
return 1
|
|
212
|
+
|
|
213
|
+
# Page through chunks up to --limit (0 = all). `more` tracks whether a
|
|
214
|
+
# next-page cursor remained when we stopped, so we can say so honestly.
|
|
215
|
+
want = args.limit
|
|
216
|
+
chunks: list = []
|
|
217
|
+
cursor: str | None = None
|
|
218
|
+
while True:
|
|
219
|
+
if want and want > 0 and len(chunks) >= want:
|
|
220
|
+
break
|
|
221
|
+
page_size = 500 if not (want and want > 0) else min(500, want - len(chunks))
|
|
222
|
+
page, cursor = client.list_corpus_chunks(target.id, limit=page_size, cursor=cursor)
|
|
223
|
+
chunks.extend(page)
|
|
224
|
+
if not cursor or not page:
|
|
225
|
+
break
|
|
226
|
+
more = cursor is not None
|
|
227
|
+
|
|
228
|
+
records = [
|
|
229
|
+
{"id": c.id, "content": c.content, "metadata": c.metadata, "score": c.score}
|
|
230
|
+
for c in chunks
|
|
231
|
+
]
|
|
232
|
+
if args.json:
|
|
233
|
+
from benchmax.cli._output import print_json
|
|
234
|
+
|
|
235
|
+
print_json(records)
|
|
236
|
+
return 0
|
|
237
|
+
|
|
238
|
+
model = build_view_model(
|
|
239
|
+
records, source=f"{target.name} (chunks)", type_override="chunks", limit=0
|
|
240
|
+
)
|
|
241
|
+
if args.out:
|
|
242
|
+
out = Path(args.out)
|
|
243
|
+
else:
|
|
244
|
+
fd, tmp = tempfile.mkstemp(prefix="castform-chunks-", suffix=".html")
|
|
245
|
+
os.close(fd)
|
|
246
|
+
out = Path(tmp)
|
|
247
|
+
write_html(model, out, view=args.view)
|
|
248
|
+
|
|
249
|
+
n = len(records)
|
|
250
|
+
print(f"✓ corpus '{target.name}' — {n} chunk{'' if n == 1 else 's'}")
|
|
251
|
+
print(f" {out}")
|
|
252
|
+
if more:
|
|
253
|
+
print(f" (showing first {n}; corpus has more — re-run with --limit 0 for all)")
|
|
254
|
+
if not args.no_open:
|
|
255
|
+
maybe_open_browser(out.resolve().as_uri())
|
|
256
|
+
return 0
|
|
257
|
+
|
|
258
|
+
|
|
189
259
|
def register(sub: argparse._SubParsersAction) -> None:
|
|
190
260
|
"""Attach the `corpus` group to the top-level subparsers."""
|
|
191
261
|
corpus = sub.add_parser("corpus", help="Corpus utilities for RAG envs")
|
|
@@ -228,3 +298,33 @@ def register(sub: argparse._SubParsersAction) -> None:
|
|
|
228
298
|
)
|
|
229
299
|
p_se.add_argument("--json", action="store_true", help="Emit raw JSON")
|
|
230
300
|
p_se.set_defaults(func=_cmd_corpus_search)
|
|
301
|
+
|
|
302
|
+
p_br = corpus_sub.add_parser(
|
|
303
|
+
"browse",
|
|
304
|
+
help="Browse a corpus's chunks in an HTML viewer (opens in your browser)",
|
|
305
|
+
description="Page a corpus's stored chunks into the local HTML viewer — recovering "
|
|
306
|
+
"the wizard's 'preview corpus' step. Resolves the corpus by name or id.",
|
|
307
|
+
epilog="Examples:\n"
|
|
308
|
+
" castform corpus browse my-corpus\n"
|
|
309
|
+
" castform corpus browse my-corpus --limit 0 # all chunks\n"
|
|
310
|
+
" castform corpus browse my-corpus --json > chunks.jsonl",
|
|
311
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
312
|
+
)
|
|
313
|
+
p_br.add_argument("corpus", help="Corpus name or id")
|
|
314
|
+
p_br.add_argument(
|
|
315
|
+
"--limit", type=int, default=200, help="Max chunks to fetch (default: 200; 0 = all)"
|
|
316
|
+
)
|
|
317
|
+
p_br.add_argument(
|
|
318
|
+
"--view",
|
|
319
|
+
choices=["auto", "table", "messages", "raw"],
|
|
320
|
+
default="auto",
|
|
321
|
+
help="Initial display mode (default: auto)",
|
|
322
|
+
)
|
|
323
|
+
p_br.add_argument("--out", help="Write the HTML here (default: a temp file)")
|
|
324
|
+
p_br.add_argument(
|
|
325
|
+
"--no-open", action="store_true", help="Write the file but don't open a browser"
|
|
326
|
+
)
|
|
327
|
+
p_br.add_argument(
|
|
328
|
+
"--json", action="store_true", help="Emit raw chunk JSON instead of the viewer"
|
|
329
|
+
)
|
|
330
|
+
p_br.set_defaults(func=_cmd_corpus_browse)
|
|
@@ -15,6 +15,7 @@ import argparse
|
|
|
15
15
|
import json
|
|
16
16
|
import os
|
|
17
17
|
import sys
|
|
18
|
+
import tempfile
|
|
18
19
|
from pathlib import Path
|
|
19
20
|
from typing import Any
|
|
20
21
|
|
|
@@ -36,7 +37,7 @@ def _write_jsonl(path: Path, rows: list[dict[str, Any]]) -> None:
|
|
|
36
37
|
def _provider_qa_source_factory(provider: str) -> tuple[Any, str]:
|
|
37
38
|
"""Build a ``(source_factory, corpus_label)`` for ``qa-gen --provider``.
|
|
38
39
|
|
|
39
|
-
Reads the same ``DATA_*`` env vars the wizard /
|
|
40
|
+
Reads the same ``DATA_*`` env vars the wizard / main.py use (``DATA_api_key`` plus
|
|
40
41
|
per-provider resource fields). qa-gen reads stored chunks directly, so no ``embed_fn``
|
|
41
42
|
is wired here. Raises ``ValueError`` with a clean message on a missing env var or a
|
|
42
43
|
missing provider SDK (the caller turns that into an ``Error:`` line).
|
|
@@ -280,6 +281,32 @@ def _cmd_data_traces(args: argparse.Namespace) -> int:
|
|
|
280
281
|
print(f"Error: no traces fetched from project {project_id}.", file=sys.stderr)
|
|
281
282
|
return 1
|
|
282
283
|
|
|
284
|
+
if args.preview:
|
|
285
|
+
# Recover the wizard's "preview imported traces" step: show the NORMALIZED
|
|
286
|
+
# traces before any filtering, and stop (don't build the train/eval set).
|
|
287
|
+
from benchmax.cli.dataview import build_view_model, write_html
|
|
288
|
+
from benchmax.platform.browser import maybe_open_browser
|
|
289
|
+
|
|
290
|
+
out_dir = Path(args.out)
|
|
291
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
292
|
+
records = [t.to_dict() for t in traces]
|
|
293
|
+
snapshot = out_dir / "traces_preview.jsonl"
|
|
294
|
+
_write_jsonl(snapshot, records)
|
|
295
|
+
html = out_dir / "traces_preview.html"
|
|
296
|
+
model = build_view_model(records, source=f"{project_id} (traces preview)", limit=0)
|
|
297
|
+
write_html(model, html)
|
|
298
|
+
n = len(records)
|
|
299
|
+
if args.json:
|
|
300
|
+
print_json(
|
|
301
|
+
{"preview": True, "count": n, "snapshot": str(snapshot), "html": str(html)}
|
|
302
|
+
)
|
|
303
|
+
else:
|
|
304
|
+
print(f"✓ preview: {n} normalized trace{'' if n == 1 else 's'} (not built into a dataset)")
|
|
305
|
+
print(f" snapshot: {snapshot}")
|
|
306
|
+
print(f" viewer: {html}")
|
|
307
|
+
maybe_open_browser(html.resolve().as_uri())
|
|
308
|
+
return 0
|
|
309
|
+
|
|
283
310
|
pipeline = TracesPipeline(
|
|
284
311
|
traces=traces,
|
|
285
312
|
system_prompt=args.system_prompt,
|
|
@@ -346,15 +373,61 @@ def _cmd_data_traces(args: argparse.Namespace) -> int:
|
|
|
346
373
|
print(f" detected system prompt ({len(system_prompt)} chars): {preview}")
|
|
347
374
|
if tools:
|
|
348
375
|
print(f" detected tools: {', '.join(t.get('name', '?') for t in tools)}")
|
|
349
|
-
# The agent authors
|
|
376
|
+
# The agent authors main.py's dataset_preprocess to match the pulled rows.
|
|
350
377
|
print(
|
|
351
378
|
" Rows are {prompt_messages, ground_truth, init_rollout_args} — author "
|
|
352
|
-
"
|
|
379
|
+
"main.py's dataset_preprocess to match."
|
|
353
380
|
)
|
|
354
381
|
print(" Next: castform setup (then castform validate)")
|
|
355
382
|
return 0
|
|
356
383
|
|
|
357
384
|
|
|
385
|
+
@handle_errors
|
|
386
|
+
def _cmd_data_view(args: argparse.Namespace) -> int:
|
|
387
|
+
# Lazy import — `dataview` only pulls the traces models for trace files, and
|
|
388
|
+
# then only on demand; RAG/generic viewing stays free of the [traces] extra.
|
|
389
|
+
from benchmax.cli.dataview import load_view_model, write_html
|
|
390
|
+
from benchmax.platform.browser import maybe_open_browser
|
|
391
|
+
|
|
392
|
+
src = Path(args.file)
|
|
393
|
+
if not src.exists():
|
|
394
|
+
print(f"Error: file not found: {src}", file=sys.stderr)
|
|
395
|
+
return 1
|
|
396
|
+
|
|
397
|
+
try:
|
|
398
|
+
model = load_view_model(src, type_override=args.type, limit=args.limit)
|
|
399
|
+
except (OSError, UnicodeDecodeError) as exc: # binary / unreadable file
|
|
400
|
+
print(f"Error: could not read {src}: {exc}", file=sys.stderr)
|
|
401
|
+
return 1
|
|
402
|
+
|
|
403
|
+
if args.out:
|
|
404
|
+
out = Path(args.out)
|
|
405
|
+
else:
|
|
406
|
+
# A persistent temp file (not auto-deleted) so the browser can open it;
|
|
407
|
+
# printed below so headless users can grab the path.
|
|
408
|
+
fd, tmp = tempfile.mkstemp(prefix="castform-view-", suffix=".html")
|
|
409
|
+
os.close(fd)
|
|
410
|
+
out = Path(tmp)
|
|
411
|
+
try:
|
|
412
|
+
write_html(model, out, view=args.view)
|
|
413
|
+
except OSError as exc:
|
|
414
|
+
print(f"Error: could not write {out}: {exc}", file=sys.stderr)
|
|
415
|
+
return 1
|
|
416
|
+
|
|
417
|
+
noun = "row" if model["total"] == 1 else "rows"
|
|
418
|
+
print(
|
|
419
|
+
f"✓ {model['kind']}/{model['variant']} — "
|
|
420
|
+
f"{model['shown']} of {model['total']} {noun}"
|
|
421
|
+
)
|
|
422
|
+
print(f" {out}")
|
|
423
|
+
if model["capped"]:
|
|
424
|
+
print(f" (capped at {args.limit}; re-run with --limit 0 to embed all)")
|
|
425
|
+
|
|
426
|
+
if not args.no_open:
|
|
427
|
+
maybe_open_browser(out.resolve().as_uri())
|
|
428
|
+
return 0
|
|
429
|
+
|
|
430
|
+
|
|
358
431
|
def register(sub: argparse._SubParsersAction) -> None:
|
|
359
432
|
"""Attach the `data` group to the top-level subparsers."""
|
|
360
433
|
data = sub.add_parser("data", help="Dataset utilities")
|
|
@@ -437,5 +510,53 @@ def register(sub: argparse._SubParsersAction) -> None:
|
|
|
437
510
|
help="Detect-only: print the full detected system prompt + tools and write "
|
|
438
511
|
"nothing — confirm them before generating the dataset",
|
|
439
512
|
)
|
|
513
|
+
p_tr.add_argument(
|
|
514
|
+
"--preview",
|
|
515
|
+
action="store_true",
|
|
516
|
+
help="Open the imported (normalized) traces in the browser and exit, "
|
|
517
|
+
"without building the train/eval dataset",
|
|
518
|
+
)
|
|
440
519
|
p_tr.add_argument("--json", action="store_true", help="Emit raw JSON")
|
|
441
520
|
p_tr.set_defaults(func=_cmd_data_traces)
|
|
521
|
+
|
|
522
|
+
p_view = data_sub.add_parser(
|
|
523
|
+
"view",
|
|
524
|
+
help="Render a local dataset/traces JSONL as an HTML viewer (opens in your browser)",
|
|
525
|
+
description="Render a dataset, traces, corpus-chunk, or Claude session-transcript "
|
|
526
|
+
"JSONL as a self-contained HTML page and open it in the browser. The shape is "
|
|
527
|
+
"auto-detected; anything unrecognized falls back to a generic record table (never "
|
|
528
|
+
"errors). The path is always printed, so it works headless (open it yourself).",
|
|
529
|
+
epilog="Examples:\n"
|
|
530
|
+
" castform data view dataset.jsonl\n"
|
|
531
|
+
" castform data view traces.jsonl --view raw\n"
|
|
532
|
+
" castform data view ~/.claude/projects/<proj>/<session-id>.jsonl # a Claude session\n"
|
|
533
|
+
" castform data view data.jsonl --type chunks --out chunks.html --no-open",
|
|
534
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
535
|
+
)
|
|
536
|
+
p_view.add_argument("file", help="Local .jsonl dataset or traces file to view")
|
|
537
|
+
p_view.add_argument(
|
|
538
|
+
"--type",
|
|
539
|
+
choices=["rag", "traces", "chunks", "claude", "generic"],
|
|
540
|
+
default=None,
|
|
541
|
+
help="Force the classification (default: auto-detect from the first row; "
|
|
542
|
+
"`claude` = a Claude Code session transcript)",
|
|
543
|
+
)
|
|
544
|
+
p_view.add_argument(
|
|
545
|
+
"--view",
|
|
546
|
+
choices=["auto", "table", "messages", "raw"],
|
|
547
|
+
default="auto",
|
|
548
|
+
help="Initial display mode (default: auto — the detected kind's view)",
|
|
549
|
+
)
|
|
550
|
+
p_view.add_argument("--out", help="Write the HTML here (default: a temp file)")
|
|
551
|
+
p_view.add_argument(
|
|
552
|
+
"--limit",
|
|
553
|
+
type=int,
|
|
554
|
+
default=1000,
|
|
555
|
+
help="Max rows to embed (default: 1000; 0 = all)",
|
|
556
|
+
)
|
|
557
|
+
p_view.add_argument(
|
|
558
|
+
"--no-open",
|
|
559
|
+
action="store_true",
|
|
560
|
+
help="Write the file but don't open a browser",
|
|
561
|
+
)
|
|
562
|
+
p_view.set_defaults(func=_cmd_data_view)
|