cmpnd 0.58.0__tar.gz → 0.60.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.
- {cmpnd-0.58.0 → cmpnd-0.60.0}/PKG-INFO +1 -1
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/commands/programs.py +124 -18
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/commands/top.py +132 -2
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/verbs.py +1 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/optimization.py +61 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/docs/cli.md +55 -10
- {cmpnd-0.58.0 → cmpnd-0.60.0}/pyproject.toml +1 -1
- {cmpnd-0.58.0 → cmpnd-0.60.0}/scripts/test-in-linux.sh +1 -1
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/span_reference_fixture.py +24 -0
- cmpnd-0.60.0/tests/test_authored_binding_credentials.py +328 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_main.py +3 -1
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_programs.py +139 -26
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_top.py +65 -12
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_span_references.py +57 -4
- {cmpnd-0.58.0 → cmpnd-0.60.0}/uv.lock +1 -1
- {cmpnd-0.58.0 → cmpnd-0.60.0}/.gitignore +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/AGENTS.md +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/CLAUDE.md +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/README.md +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/__init__.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/_assessments.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/_authored_import.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/_deployments.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/_gepa_patch.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/_http.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/_runtime_tools.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/_traces.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/_valsets.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/authored.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/callback.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/__init__.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/commands/admin.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/commands/assessments.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/commands/deployments.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/commands/evals.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/commands/org.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/commands/signatures.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/commands/traces.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/commands/valsets.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/shell.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/trace_render.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/cli/tree_help.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/client.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/configuration.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/context.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/credentials.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/dataset_sync.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/datasets.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/decorators.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/deployment.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/deployment_metadata.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/deployment_runs.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/execution.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/exporter.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/helpers.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/identity.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/imaging.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/ir/reflection.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/lm_configuration.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/mcp_servers.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/models.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/named_programs.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/optimizers/gepa.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/optimizers/gepa_callback.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/optimizers/tracker.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/packaging.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/programs.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/references.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/retry.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/cmpnd/traces.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/docs/2026-04-02-code-review.md +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/docs/client.md +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/docs/custom-proposer.md +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/docs/reading-traces.md +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/docs/sdk-instrumentation.md +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/examples/local_ollama.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/ingest/README.md +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/bodies.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/client_floor.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/program_names.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/protocols.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/ingest/pyproject.toml +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/scripts/capture_review_run.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/scripts/check-pip-install.sh +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/scripts/install-test-env.sh +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/scripts/release-stress.sh +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/scripts/require_version_bump.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/scripts/seed_review_run.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/scripts/selfhost_compat.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/stubs/dspy/utils/dummies.pyi +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/__init__.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/_credential_route.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/_deployment_signature_helpers.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/_fake_clock.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/_resolve_refs.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/_traced_run.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/conftest.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/committee_task.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/helpers.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/target_capabilities.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_assessments_cli.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_composite_module.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_deploy_execute.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_deploy_pandas.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_deploy_programs.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_optimize_failures.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_parallel_export.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_project_attribution.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_traces_read.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_user_attribution.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/e2e/test_valsets_cli.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/integration/__init__.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/integration/authored_optimize/judge.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/integration/conftest.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/integration/hosted_optimize_probe.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/integration/test_logs_renders_traces.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/integration/test_unhappy_path.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/load/__init__.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/load/__main__.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/load/drain.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/load/ingest.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/load/rlm_shape.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/load/rlm_size_report.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/load/sweep.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/load/test_rlm_size_report.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/parity/__init__.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/parity/conftest.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/parity/test_health.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/parity/test_trace_streaming.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/parity/test_traces.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/stress/__init__.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/stress/_provider.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/stress/_readiness.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/stress/_reliability.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/stress/_result.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/stress/mint_bedrock_token.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/stress/test_deploy_programs.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/stress/test_multi_client_stress.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/stress/test_optimize_client_committee.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/stress/test_optimize_loop.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/stress/test_optimize_stress.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/stress/test_provider_roles.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/stress/test_readiness.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/stress/test_real_provider_twins.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/stress/test_reliability.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/stress/test_reliability_classify.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/stress/test_rlm_wasm.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/stress/test_trace_search.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_adapter_reentrancy.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_assessments_api.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_authored.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_authored_bundle_collisions.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_authored_metric_contract.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_authored_named_models.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_authored_optimize.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_callback.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_admin.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_api_client.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_assessments.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_deployment_runs.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_deployments.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_evals.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_org.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_parity.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_provider_credentials.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_schema_sample.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_shell.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_signatures.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_traces.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_traces_search_wire.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_tree_help.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cli/test_valsets.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_client.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_client_floor.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_client_program_names.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_configuration.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_context.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_context_propagation.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_credential_kwargs.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_credential_selection.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_credential_values.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_dataset_sync.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_datasets.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_decision_client_spans.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_decorators.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_deploy.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_deploy_program_id.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_deployment_metadata.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_deployment_runs.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_deployments_read.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_eval.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_eval_example_attribution.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_eval_start.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_eval_trace_grouping.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_execute.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_execute_run_identity.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_exporter.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_exporter_program_names.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_exporter_queue.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_exporter_real.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_gepa_output_normalization.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_identity.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_imaging.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_import_weight.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_ingest_conformance.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_integration.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_interpreter_callback.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_lm15_native_calls.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_lm_configuration.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_lm_usage_shapes.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_mcp_tools.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_models.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_multi_client_stress_readiness.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_optimize.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_optimize_lms.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_optimize_reflection_lm.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_packaging.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_packaging_mcp_tools.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_packaging_program_source.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_poll_until.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_program_id_deprecation.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_program_identity.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_program_lineage_persist.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_program_names_in_deploy.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_programs_create.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_project_payload.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_real_provider_twins_readiness.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_reflector_properties.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_reflector_smoke.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_release_stress_script.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_replaced_names.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_repro_reactv2_bugs.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_retry.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_rlm_patch.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_run_attribution_paths.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_runtime_tools.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_schemas.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_seal_lm_credential.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_signature_inputs.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_span_sent_once.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_system_one.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_trace_context_concurrency.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_trace_fact_wire_contract.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_trace_facts.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_trace_render.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_traces_read.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_tracked_gepa.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_truncation.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_upload_auth_headers.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_valsets_api.py +0 -0
- {cmpnd-0.58.0 → cmpnd-0.60.0}/tests/test_xxh64.py +0 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
|
+
import json
|
|
6
7
|
import sys
|
|
7
8
|
import urllib.parse
|
|
8
9
|
from argparse import ArgumentParser, _SubParsersAction
|
|
@@ -12,8 +13,8 @@ from .. import api_client, pagination
|
|
|
12
13
|
from . import provider_credentials
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
#
|
|
16
|
-
# which the routes resolve as a *compiled* program hash — they look it up
|
|
16
|
+
# The hash that show, traces, stats, usage and select take is the value `programs optimized list`
|
|
17
|
+
# prints as `program_hash`, which the routes resolve as a *compiled* program hash — they look it up
|
|
17
18
|
# against optimization runs and map it to a program signature hash internally.
|
|
18
19
|
# The two hashes are different values, so a real signature hash 404s here. The
|
|
19
20
|
# argument help says which one it wants rather than naming the concept the
|
|
@@ -48,34 +49,74 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
48
49
|
create.add_argument("--json", action="store_true", help="Emit the created program as JSON instead of its id")
|
|
49
50
|
create.set_defaults(func=handle_create)
|
|
50
51
|
|
|
51
|
-
ls = sub.add_parser(
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
ls = sub.add_parser(
|
|
53
|
+
"list",
|
|
54
|
+
help="List your organization's programs",
|
|
55
|
+
description=(
|
|
56
|
+
"List your organization's live programs, newest first, with each program's name and p_ id. A program "
|
|
57
|
+
"lists from its creation on, before anything runs under it. With --trashed, list the Trash instead."
|
|
58
|
+
),
|
|
59
|
+
)
|
|
54
60
|
ls.add_argument(
|
|
55
61
|
"--trashed",
|
|
56
62
|
action="store_true",
|
|
57
63
|
help="List the programs in the Trash instead, with when each is purged",
|
|
58
64
|
)
|
|
59
|
-
ls.add_argument("--order-by", default="run_count", help="Sort field (default: run_count)")
|
|
60
|
-
ls.add_argument("--order", default="desc", choices=["asc", "desc"], help="Sort direction")
|
|
61
65
|
pagination.add_args(ls, with_page_aliases=True, default_limit=50)
|
|
66
|
+
# The optimized-program filters and sort moved to `programs optimized list`. They stay declared so that a
|
|
67
|
+
# command that passes one gets an error naming the command that takes it.
|
|
68
|
+
for flag, dest in [
|
|
69
|
+
("--name", "program_name"),
|
|
70
|
+
("--model", "model_name"),
|
|
71
|
+
("--order-by", "order_by"),
|
|
72
|
+
("--order", "order"),
|
|
73
|
+
]:
|
|
74
|
+
ls.add_argument(flag, dest=dest, default=None, help=argparse.SUPPRESS)
|
|
62
75
|
ls.set_defaults(func=handle_list)
|
|
63
76
|
|
|
77
|
+
# The optimized programs are the compiled programs optimization runs produced, grouped by compiled program
|
|
78
|
+
# hash. They are different records from the org's programs, so they nest, as the registry does.
|
|
79
|
+
optimized = sub.add_parser("optimized", help="Browse the optimized programs that optimization runs produced")
|
|
80
|
+
optimized_sub = optimized.add_subparsers(dest="optimized_action")
|
|
81
|
+
opt_list = optimized_sub.add_parser(
|
|
82
|
+
"list",
|
|
83
|
+
help="List optimized programs by compiled program hash",
|
|
84
|
+
description=(
|
|
85
|
+
"List the compiled programs your optimization runs produced, one row per compiled program hash, with "
|
|
86
|
+
"their run counts and scores. `programs show`, `traces`, `stats` and `usage` take the hash from the "
|
|
87
|
+
"program_hash column."
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
opt_list.add_argument("--name", dest="program_name", help="Filter by program name")
|
|
91
|
+
opt_list.add_argument("--model", dest="model_name", help="Filter by model name")
|
|
92
|
+
opt_list.add_argument("--order-by", default="run_count", help="Sort field (default: run_count)")
|
|
93
|
+
opt_list.add_argument("--order", default="desc", choices=["asc", "desc"], help="Sort direction")
|
|
94
|
+
pagination.add_args(opt_list, with_page_aliases=True, default_limit=50)
|
|
95
|
+
opt_list.set_defaults(func=handle_optimized_list)
|
|
96
|
+
|
|
64
97
|
show = sub.add_parser("show", help="Show program detail")
|
|
65
|
-
show.add_argument(
|
|
98
|
+
show.add_argument(
|
|
99
|
+
"id", help="Compiled program hash, as `programs optimized list` prints it in the program_hash column"
|
|
100
|
+
)
|
|
66
101
|
show.set_defaults(func=handle_show)
|
|
67
102
|
|
|
68
103
|
tr = sub.add_parser("traces", help="List traces for a program")
|
|
69
|
-
tr.add_argument(
|
|
104
|
+
tr.add_argument(
|
|
105
|
+
"id", help="Compiled program hash, as `programs optimized list` prints it in the program_hash column"
|
|
106
|
+
)
|
|
70
107
|
pagination.add_args(tr, with_page_aliases=False, default_limit=50)
|
|
71
108
|
tr.set_defaults(func=handle_traces)
|
|
72
109
|
|
|
73
110
|
stats = sub.add_parser("stats", help="Show a program's execution statistics")
|
|
74
|
-
stats.add_argument(
|
|
111
|
+
stats.add_argument(
|
|
112
|
+
"id", help="Compiled program hash, as `programs optimized list` prints it in the program_hash column"
|
|
113
|
+
)
|
|
75
114
|
stats.set_defaults(func=handle_stats)
|
|
76
115
|
|
|
77
116
|
usage = sub.add_parser("usage", help="Show a program's trace volume, tokens and cost over time")
|
|
78
|
-
usage.add_argument(
|
|
117
|
+
usage.add_argument(
|
|
118
|
+
"id", help="Compiled program hash, as `programs optimized list` prints it in the program_hash column"
|
|
119
|
+
)
|
|
79
120
|
usage.add_argument("--hours", type=int, default=24, help="Rolling window in hours (default %(default)s)")
|
|
80
121
|
# Constrained on purpose: an unrecognised period buckets hourly server-side
|
|
81
122
|
# rather than failing, so a typo would answer a different question silently.
|
|
@@ -132,7 +173,9 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
132
173
|
purge.set_defaults(func=handle_purge)
|
|
133
174
|
|
|
134
175
|
select = sub.add_parser("select", help="Pin an optimization run as this program's chosen version")
|
|
135
|
-
select.add_argument(
|
|
176
|
+
select.add_argument(
|
|
177
|
+
"id", help="Compiled program hash, as `programs optimized list` prints it in the program_hash column"
|
|
178
|
+
)
|
|
136
179
|
select.add_argument("--run", dest="run_id", required=True, help="Optimization run ID (op_...) to pin")
|
|
137
180
|
select.set_defaults(func=handle_select)
|
|
138
181
|
|
|
@@ -178,14 +221,24 @@ def handle_create(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
178
221
|
|
|
179
222
|
|
|
180
223
|
def handle_list(client: api_client.ApiClient, args: Any) -> Any:
|
|
224
|
+
"""List the org's live programs by name and p_ id, or the Trash with ``--trashed``."""
|
|
225
|
+
for attr, flag in [
|
|
226
|
+
("program_name", "--name"),
|
|
227
|
+
("model_name", "--model"),
|
|
228
|
+
("order_by", "--order-by"),
|
|
229
|
+
("order", "--order"),
|
|
230
|
+
]:
|
|
231
|
+
if getattr(args, attr, None) is not None:
|
|
232
|
+
from . import top
|
|
233
|
+
|
|
234
|
+
raise api_client.CliError(
|
|
235
|
+
f"{flag} filters the optimized programs, which `{top.hint_programs('optimized', 'list')}` lists. "
|
|
236
|
+
f"`{top.hint_programs('list')}` lists your organization's programs by name and p_ id."
|
|
237
|
+
)
|
|
181
238
|
if getattr(args, "trashed", False):
|
|
182
|
-
# The Trash is its own short list, unpaged and newest first, so
|
|
183
|
-
#
|
|
239
|
+
# The Trash is its own short list, unpaged and newest first, so paging does not apply. A flag left at its
|
|
240
|
+
# default cannot be told apart from one not passed.
|
|
184
241
|
for attr, flag, default in [
|
|
185
|
-
("program_name", "--name", None),
|
|
186
|
-
("model_name", "--model", None),
|
|
187
|
-
("order_by", "--order-by", "run_count"),
|
|
188
|
-
("order", "--order", "desc"),
|
|
189
242
|
("limit", "--limit", 50),
|
|
190
243
|
("offset", "--offset", 0),
|
|
191
244
|
("per_page", "--per-page", None),
|
|
@@ -194,6 +247,59 @@ def handle_list(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
194
247
|
if getattr(args, attr, default) != default:
|
|
195
248
|
raise api_client.CliError(f"{flag} does not apply to --trashed, which lists the whole Trash")
|
|
196
249
|
return client.get("/api/v1/programs/trash")
|
|
250
|
+
params = pagination.wire(args, "limit_offset", as_str=True)
|
|
251
|
+
# Read as text: a server from before the program list, such as self-host release 2026-001, redirects this
|
|
252
|
+
# path to its dispatcher, and the redirect's body is HTML.
|
|
253
|
+
try:
|
|
254
|
+
raw = client.get_text("/api/v1/programs", params=params)
|
|
255
|
+
except api_client.CliError as exc:
|
|
256
|
+
if exc.status_code not in (404, 405):
|
|
257
|
+
raise
|
|
258
|
+
return _list_recorded(client, params)
|
|
259
|
+
try:
|
|
260
|
+
listed = json.loads(raw)
|
|
261
|
+
except ValueError:
|
|
262
|
+
listed = None
|
|
263
|
+
if isinstance(listed, dict) and "programs" in listed:
|
|
264
|
+
return listed
|
|
265
|
+
return _list_recorded(client, params)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# GET /api/v1/modules on a server from before the program list answers at most this many programs
|
|
269
|
+
# (modulesPageCap in self-host release 2026-001), and nothing pages past them.
|
|
270
|
+
_RECORDED_PROGRAMS_CAP = 200
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _list_recorded(client: api_client.ApiClient, params: dict[str, str]) -> dict[str, Any]:
|
|
274
|
+
"""List the programs a server from before the program list recorded traces for, paged here.
|
|
275
|
+
|
|
276
|
+
GET /api/v1/modules answers them in one array, by name and legacy hash, up to its cap. A program with no
|
|
277
|
+
traces yet is not in it. A full array may be missing programs past the cap, so the answer then has no total
|
|
278
|
+
and says it is incomplete.
|
|
279
|
+
"""
|
|
280
|
+
rows = client.get("/api/v1/modules")
|
|
281
|
+
if not isinstance(rows, list):
|
|
282
|
+
raise api_client.CliError(
|
|
283
|
+
"this cmpnd server answered neither the program list nor the list of recorded programs"
|
|
284
|
+
)
|
|
285
|
+
complete = len(rows) < _RECORDED_PROGRAMS_CAP
|
|
286
|
+
note = "note: this cmpnd server predates the program list, so this lists the programs it recorded traces for."
|
|
287
|
+
if not complete:
|
|
288
|
+
note += f" It answers at most {_RECORDED_PROGRAMS_CAP} of them, so programs past those are missing."
|
|
289
|
+
print(note + " Upgrade the server to list every program by name and p_ id.", file=sys.stderr)
|
|
290
|
+
limit, offset = int(params["limit"]), int(params["offset"])
|
|
291
|
+
return {
|
|
292
|
+
"programs": rows[offset : offset + limit],
|
|
293
|
+
"total": len(rows) if complete else None,
|
|
294
|
+
"complete": complete,
|
|
295
|
+
"limit": limit,
|
|
296
|
+
"offset": offset,
|
|
297
|
+
"has_more": offset + limit < len(rows),
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def handle_optimized_list(client: api_client.ApiClient, args: Any) -> Any:
|
|
302
|
+
"""List the optimized programs, one row per compiled program hash."""
|
|
197
303
|
params: dict[str, str] = pagination.wire(args, "per_page", as_str=True)
|
|
198
304
|
params["order_by"] = args.order_by
|
|
199
305
|
params["order_direction"] = args.order
|
|
@@ -890,6 +890,34 @@ def _program_args(args: Any, verb: str, *, needs_name: bool = True) -> str | Non
|
|
|
890
890
|
return name
|
|
891
891
|
|
|
892
892
|
|
|
893
|
+
class _CredentialLookup:
|
|
894
|
+
"""Turn a credential name a flag gave into the organization's ``cmpnd.Credential``.
|
|
895
|
+
|
|
896
|
+
Every command that takes a credential name looks it up here, before it stages or
|
|
897
|
+
sends anything. The lookup is ``cmpnd.credentials.get``, the line a Python caller
|
|
898
|
+
writes, so a mistyped name fails on the command line with the organization's names
|
|
899
|
+
and the nearest one. Serve checks the name again when the deploy or run names it,
|
|
900
|
+
which catches a credential revoked in between. Each distinct name is read once.
|
|
901
|
+
"""
|
|
902
|
+
|
|
903
|
+
def __init__(self, client: api_client.ApiClient) -> None:
|
|
904
|
+
self._client = client
|
|
905
|
+
self._found: dict[str, Any] = {}
|
|
906
|
+
|
|
907
|
+
def __call__(self, flag: str, name: str) -> Any:
|
|
908
|
+
"""The credential ``name``. ``flag`` is how the command line spelled it, for the error."""
|
|
909
|
+
from ... import credentials
|
|
910
|
+
|
|
911
|
+
if name not in self._found:
|
|
912
|
+
try:
|
|
913
|
+
self._found[name] = credentials.get(
|
|
914
|
+
name, api_key=self._client._api_key, endpoint=self._client._endpoint
|
|
915
|
+
)
|
|
916
|
+
except credentials.CredentialNotFound as exc:
|
|
917
|
+
raise api_client.CliError(f"{flag}: {exc}") from exc
|
|
918
|
+
return self._found[name]
|
|
919
|
+
|
|
920
|
+
|
|
893
921
|
def cmd_deploy(client: api_client.ApiClient, args: Any) -> None:
|
|
894
922
|
"""Stage authored Python and deploy the checked bundle.
|
|
895
923
|
|
|
@@ -904,12 +932,18 @@ def cmd_deploy(client: api_client.ApiClient, args: Any) -> None:
|
|
|
904
932
|
dspy.configure(lm=lm)
|
|
905
933
|
program = dspy.Predict("question -> answer")
|
|
906
934
|
PY
|
|
935
|
+
|
|
936
|
+
``--credential NAME`` names the credential the program's model spends, and is looked
|
|
937
|
+
up before the source is staged.
|
|
907
938
|
"""
|
|
908
939
|
from pathlib import Path
|
|
909
940
|
|
|
910
941
|
from ... import authored, deployment
|
|
911
942
|
|
|
912
943
|
program_name = _program_args(args, "deploy")
|
|
944
|
+
credential_name = getattr(args, "credential", None)
|
|
945
|
+
if credential_name is not None:
|
|
946
|
+
credential_name = _CredentialLookup(client)(f"--credential {credential_name}", credential_name).name
|
|
913
947
|
try:
|
|
914
948
|
if args.source == "-" or args.source is None:
|
|
915
949
|
prepared = authored.prepare_stdin(
|
|
@@ -939,7 +973,7 @@ def cmd_deploy(client: api_client.ApiClient, args: Any) -> None:
|
|
|
939
973
|
metadata=metadata,
|
|
940
974
|
program_name=program_name,
|
|
941
975
|
config=sdk_config(client),
|
|
942
|
-
credential_name=
|
|
976
|
+
credential_name=credential_name,
|
|
943
977
|
)
|
|
944
978
|
except ValueError as exc:
|
|
945
979
|
raise api_client.CliError(str(exc)) from exc
|
|
@@ -978,6 +1012,67 @@ def _optimizer_arg(value: str) -> str:
|
|
|
978
1012
|
return value
|
|
979
1013
|
|
|
980
1014
|
|
|
1015
|
+
def _lm_credential_arg(value: str) -> tuple[str, str]:
|
|
1016
|
+
"""Parse ``--lm-credential BINDING=NAME`` into the binding and the credential's name.
|
|
1017
|
+
|
|
1018
|
+
The program's own model is refused here, at parse time, because it spends the run's
|
|
1019
|
+
credential and ``--credential`` already names that.
|
|
1020
|
+
"""
|
|
1021
|
+
binding, sep, name = value.partition("=")
|
|
1022
|
+
if not sep or not binding or not name:
|
|
1023
|
+
raise ArgumentTypeError(
|
|
1024
|
+
f"{value!r} is not BINDING=NAME. Give the name of an LM the optimize file's `lms` binds, then a "
|
|
1025
|
+
"credential `cmpnd credentials list` shows, as in judge=typesafe"
|
|
1026
|
+
)
|
|
1027
|
+
from ... import optimization
|
|
1028
|
+
|
|
1029
|
+
if binding == optimization.DEFAULT_LM_BINDING:
|
|
1030
|
+
raise ArgumentTypeError(
|
|
1031
|
+
f"{binding} is the program's own model, which spends the run's credential. Name it with --credential"
|
|
1032
|
+
)
|
|
1033
|
+
return binding, name
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
def _optimize_credentials(
|
|
1037
|
+
client: api_client.ApiClient, args: Any
|
|
1038
|
+
) -> tuple[Any, str | Any | None, dict[str, Any] | None]:
|
|
1039
|
+
"""Look up every credential an optimize names, before the file is staged.
|
|
1040
|
+
|
|
1041
|
+
Returns the run's credential, the reflection LM to pass (the model name, wrapped in
|
|
1042
|
+
``cmpnd.HostedLM`` when ``--reflection-credential`` names its credential) and the
|
|
1043
|
+
credential per binding. :class:`_CredentialLookup` turns each name into a
|
|
1044
|
+
``cmpnd.Credential``, so a mistyped one fails before the import and the deploy a run
|
|
1045
|
+
makes ahead of its first step.
|
|
1046
|
+
"""
|
|
1047
|
+
from ...lm_configuration import HostedLM
|
|
1048
|
+
|
|
1049
|
+
pairs: list[tuple[str, str]] = list(getattr(args, "lm_credential", None) or [])
|
|
1050
|
+
seen: set[str] = set()
|
|
1051
|
+
for binding, _ in pairs:
|
|
1052
|
+
if binding in seen:
|
|
1053
|
+
raise api_client.CliError(
|
|
1054
|
+
f"--lm-credential names the binding {binding!r} more than once. Give each binding one credential."
|
|
1055
|
+
)
|
|
1056
|
+
seen.add(binding)
|
|
1057
|
+
reflection_model = getattr(args, "reflection_model", None)
|
|
1058
|
+
reflection_name = getattr(args, "reflection_credential", None)
|
|
1059
|
+
if reflection_name is not None and reflection_model is None:
|
|
1060
|
+
raise api_client.CliError(
|
|
1061
|
+
"--reflection-credential names the credential the reflection model spends, so it needs --reflection-model."
|
|
1062
|
+
)
|
|
1063
|
+
|
|
1064
|
+
lookup = _CredentialLookup(client)
|
|
1065
|
+
run_name = getattr(args, "credential", None)
|
|
1066
|
+
run = lookup(f"--credential {run_name}", run_name) if run_name is not None else None
|
|
1067
|
+
reflection_lm: str | HostedLM | None = reflection_model
|
|
1068
|
+
if reflection_name is not None and reflection_model is not None:
|
|
1069
|
+
reflection_lm = HostedLM(
|
|
1070
|
+
reflection_model, credential=lookup(f"--reflection-credential {reflection_name}", reflection_name)
|
|
1071
|
+
)
|
|
1072
|
+
lm_credentials = {binding: lookup(f"--lm-credential {binding}={name}", name) for binding, name in pairs}
|
|
1073
|
+
return run, reflection_lm, lm_credentials or None
|
|
1074
|
+
|
|
1075
|
+
|
|
981
1076
|
def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
|
|
982
1077
|
"""Optimize the program, metric and proposer one authored file binds.
|
|
983
1078
|
|
|
@@ -1001,6 +1096,11 @@ def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
1001
1096
|
return dspy.Prediction(score=float(ok),
|
|
1002
1097
|
feedback="" if ok else f"expected {gold.answer}")
|
|
1003
1098
|
PY
|
|
1099
|
+
|
|
1100
|
+
The file cannot name a credential, because the kernel that imports it reaches no
|
|
1101
|
+
platform. ``--credential`` names the run's, ``--reflection-credential`` the
|
|
1102
|
+
reflection model's and ``--lm-credential BINDING=NAME`` one of the file's ``lms``.
|
|
1103
|
+
Each name is looked up before the file is staged.
|
|
1004
1104
|
"""
|
|
1005
1105
|
from pathlib import Path
|
|
1006
1106
|
|
|
@@ -1008,6 +1108,7 @@ def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
1008
1108
|
|
|
1009
1109
|
deployment_id = getattr(args, "deployment_id", None)
|
|
1010
1110
|
program_name = _program_args(args, "optimize", needs_name=deployment_id is None)
|
|
1111
|
+
credential, reflection_lm, lm_credentials = _optimize_credentials(client, args)
|
|
1011
1112
|
try:
|
|
1012
1113
|
if args.source == "-" or args.source is None:
|
|
1013
1114
|
prepared = authored.prepare_optimize_stdin(
|
|
@@ -1041,7 +1142,9 @@ def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
1041
1142
|
# The flag is named for what a command line can carry — a model name,
|
|
1042
1143
|
# never an LM object — and the parameter for what the Python surface
|
|
1043
1144
|
# accepts. Each layer's name says what that layer takes.
|
|
1044
|
-
reflection_lm=
|
|
1145
|
+
reflection_lm=reflection_lm,
|
|
1146
|
+
credential=credential,
|
|
1147
|
+
lm_credentials=lm_credentials,
|
|
1045
1148
|
max_metric_calls=getattr(args, "max_metric_calls", None),
|
|
1046
1149
|
auto=getattr(args, "auto", None),
|
|
1047
1150
|
max_steps=getattr(args, "max_steps", 10),
|
|
@@ -1402,6 +1505,8 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
1402
1505
|
"--reflection-model openai/gpt-4o --auto light\n"
|
|
1403
1506
|
" cmpnd optimize gepa - capital-qa --reflection-model openai/gpt-4o "
|
|
1404
1507
|
"--max-metric-calls 60 < task.py\n"
|
|
1508
|
+
" cmpnd optimize gepa task.py capital-qa --reflection-model openai/gpt-4o --auto light "
|
|
1509
|
+
"--credential openai --lm-credential judge=typesafe\n"
|
|
1405
1510
|
" cmpnd optimize gepa task.py --reflection-model openai/gpt-4o --auto light --deployment-id d_a1b2c3d4\n"
|
|
1406
1511
|
),
|
|
1407
1512
|
formatter_class=raw,
|
|
@@ -1438,6 +1543,31 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
1438
1543
|
"instructions (required for gepa; the server will not substitute the "
|
|
1439
1544
|
"deployment's own model)",
|
|
1440
1545
|
)
|
|
1546
|
+
opt.add_argument(
|
|
1547
|
+
"--credential",
|
|
1548
|
+
metavar="NAME",
|
|
1549
|
+
default=None,
|
|
1550
|
+
help="The provider credential the run spends: the program's model, and every model that names no "
|
|
1551
|
+
"credential of its own (default: the organization's default credential). "
|
|
1552
|
+
"`cmpnd credentials list` shows the names",
|
|
1553
|
+
)
|
|
1554
|
+
opt.add_argument(
|
|
1555
|
+
"--reflection-credential",
|
|
1556
|
+
dest="reflection_credential",
|
|
1557
|
+
metavar="NAME",
|
|
1558
|
+
default=None,
|
|
1559
|
+
help="The credential the reflection model spends instead of the run's",
|
|
1560
|
+
)
|
|
1561
|
+
opt.add_argument(
|
|
1562
|
+
"--lm-credential",
|
|
1563
|
+
dest="lm_credential",
|
|
1564
|
+
metavar="BINDING=NAME",
|
|
1565
|
+
type=_lm_credential_arg,
|
|
1566
|
+
action="append",
|
|
1567
|
+
default=None,
|
|
1568
|
+
help="The credential one LM of the file's `lms` spends instead of the run's, as judge=typesafe. Repeat it "
|
|
1569
|
+
"for each binding",
|
|
1570
|
+
)
|
|
1441
1571
|
# A budget is mandatory and the optimizer will not choose one — see
|
|
1442
1572
|
# `cmpnd.optimization._apply_budget`. Two flags rather than one because a
|
|
1443
1573
|
# caller who does not yet know what a run costs reaches for `--auto`, and one
|
|
@@ -185,6 +185,7 @@ RELATION_NOUNS: dict[str, str] = {
|
|
|
185
185
|
"failed-examples": "the examples a compared candidate got wrong",
|
|
186
186
|
"examples": "the examples belonging to a dataset or an eval run",
|
|
187
187
|
"registry": "the registered programs or signatures, as distinct from the observed ones",
|
|
188
|
+
"optimized": "the compiled programs optimization runs produced, as distinct from the organization's programs",
|
|
188
189
|
"stats": "a program's execution statistics",
|
|
189
190
|
"usage": "a program's usage over time",
|
|
190
191
|
"openapi": "the OpenAPI document a deployment serves",
|
|
@@ -508,6 +508,62 @@ def _lms_config(config: dict[str, Any], lms: Any) -> None:
|
|
|
508
508
|
config["lms"] = declared
|
|
509
509
|
|
|
510
510
|
|
|
511
|
+
def _lm_credentials_config(config: dict[str, Any], lm_credentials: Any) -> None:
|
|
512
|
+
"""Name the credential each of the run's declared LM bindings spends.
|
|
513
|
+
|
|
514
|
+
An authored file declares its ``lms`` and cannot name a credential for one, because
|
|
515
|
+
the kernel that imports it again reaches no platform to look one up. So the call that
|
|
516
|
+
starts the run names it, by binding name, and the entry gains the ``credential`` field
|
|
517
|
+
that ``cmpnd.HostedLM(lm, credential=)`` and ``cmpnd.SystemOne(model, credential=)``
|
|
518
|
+
give it in a live-object call. The wire shape is the same either way.
|
|
519
|
+
|
|
520
|
+
Each binding must be one the run declares, and must not already name a credential:
|
|
521
|
+
a second credential for one binding is two answers to one question, the refusal
|
|
522
|
+
:func:`_reflection_config` makes of ``reflection_credential``. The program's own
|
|
523
|
+
model spends the run's ``credential=``, so ``default-lm`` is refused here.
|
|
524
|
+
|
|
525
|
+
``config["lms"]`` is replaced rather than edited, because it may be the dict the
|
|
526
|
+
import child reported, which the caller's prepared file still holds.
|
|
527
|
+
"""
|
|
528
|
+
if not lm_credentials:
|
|
529
|
+
return
|
|
530
|
+
if not isinstance(lm_credentials, dict):
|
|
531
|
+
raise ValueError(f"lm_credentials must map binding names to credentials (got {type(lm_credentials).__name__})")
|
|
532
|
+
declared: dict[str, Any] = config.get("lms") or {}
|
|
533
|
+
named: dict[str, str] = {}
|
|
534
|
+
for binding, credential in lm_credentials.items():
|
|
535
|
+
if binding == DEFAULT_LM_BINDING:
|
|
536
|
+
raise ValueError(
|
|
537
|
+
f"{DEFAULT_LM_BINDING!r} is the program's own model, and it spends the run's credential. "
|
|
538
|
+
"Name that credential for the run instead of for a binding."
|
|
539
|
+
)
|
|
540
|
+
name = credentials.name_of(credential, f"lm_credentials[{binding!r}]")
|
|
541
|
+
if name is None:
|
|
542
|
+
continue
|
|
543
|
+
if binding not in declared:
|
|
544
|
+
if not declared:
|
|
545
|
+
raise ValueError(
|
|
546
|
+
f"a credential is named for the LM binding {binding!r}, and the optimize file binds no "
|
|
547
|
+
"`lms`, so the run has no binding to spend it."
|
|
548
|
+
)
|
|
549
|
+
raise ValueError(
|
|
550
|
+
f"a credential is named for the LM binding {binding!r}, and the optimize file's `lms` binds no "
|
|
551
|
+
f"such name. It binds {', '.join(repr(each) for each in sorted(declared))}."
|
|
552
|
+
)
|
|
553
|
+
existing = declared[binding].get("credential")
|
|
554
|
+
if existing is not None:
|
|
555
|
+
raise ValueError(
|
|
556
|
+
f"the LM binding {binding!r} already names the credential {existing!r}, and a second one was "
|
|
557
|
+
f"named for it: these are two answers to which credential it spends. Keep one."
|
|
558
|
+
)
|
|
559
|
+
named[binding] = name
|
|
560
|
+
if named:
|
|
561
|
+
config["lms"] = {
|
|
562
|
+
binding: {**entry, "credential": named[binding]} if binding in named else entry
|
|
563
|
+
for binding, entry in declared.items()
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
|
|
511
567
|
def _check_metric_call(metric: Callable[..., Any], optimizer: str) -> None: # pyright: ignore[reportUnusedFunction] # called by the authored import child
|
|
512
568
|
"""Check the authored metric against the kernel's positional and named arguments."""
|
|
513
569
|
signature = inspect.signature(metric)
|
|
@@ -938,6 +994,7 @@ def optimize_authored(
|
|
|
938
994
|
config: dict[str, Any] | None = None,
|
|
939
995
|
reflection_lm: str | Any | None = None,
|
|
940
996
|
lms: dict[str, Any] | None = None,
|
|
997
|
+
lm_credentials: dict[str, credentials.Credential] | None = None,
|
|
941
998
|
credential: credentials.Credential | None = None,
|
|
942
999
|
max_metric_calls: int | None = None,
|
|
943
1000
|
auto: str | None = None,
|
|
@@ -972,6 +1029,9 @@ def optimize_authored(
|
|
|
972
1029
|
resolves, because that deployment already has its program.
|
|
973
1030
|
|
|
974
1031
|
``credential`` is the run's credential, as on :func:`optimize_deployment`.
|
|
1032
|
+
``lm_credentials`` maps a binding the file's ``lms`` declares to the credential it
|
|
1033
|
+
spends instead, which the file itself cannot name. A ``cmpnd.HostedLM`` or
|
|
1034
|
+
``cmpnd.SystemOne`` names one in a live-object call.
|
|
975
1035
|
"""
|
|
976
1036
|
check_optimizer(optimizer)
|
|
977
1037
|
credential_name = credentials.name_of(credential, "optimize_authored(credential=)")
|
|
@@ -985,6 +1045,7 @@ def optimize_authored(
|
|
|
985
1045
|
config["lms"] = prepared.facts.lms
|
|
986
1046
|
_reflection_config(config, reflection_lm)
|
|
987
1047
|
_lms_config(config, lms)
|
|
1048
|
+
_lm_credentials_config(config, lm_credentials)
|
|
988
1049
|
_apply_budget(config, max_metric_calls=max_metric_calls, auto=auto)
|
|
989
1050
|
|
|
990
1051
|
if prepared.program_bundle is None and deployment_id is not None and program_name is not None:
|
|
@@ -41,6 +41,27 @@ The CLI refuses `--program-id`, `cmpnd programs create` without a program
|
|
|
41
41
|
name, and its `--name`. These inputs are deprecated, and the error shows the
|
|
42
42
|
program name form.
|
|
43
43
|
|
|
44
|
+
`cmpnd programs list` lists your organization's live programs, newest first,
|
|
45
|
+
with each program's `name` and `program_id`. It reads `GET /api/v1/programs`,
|
|
46
|
+
which lists a program from its creation on, before anything runs under it. A
|
|
47
|
+
program with no name has a null `name`. The answer also holds `total`, the
|
|
48
|
+
count of live programs, and `has_more`. The command pages with `--limit` and
|
|
49
|
+
`--offset`, and needs `traces:read`.
|
|
50
|
+
|
|
51
|
+
A server from before the program list, such as self-host release 2026-001, has
|
|
52
|
+
no `GET /api/v1/programs`. Against one, `cmpnd programs list` says so on stderr
|
|
53
|
+
and lists the programs that server recorded traces for, from
|
|
54
|
+
`GET /api/v1/modules`, by name and legacy hash. A program with no traces yet is
|
|
55
|
+
not in that list. That route answers at most 200 programs, so when it is full
|
|
56
|
+
the answer has a null `total`, `complete` is false, and the note says programs
|
|
57
|
+
are missing.
|
|
58
|
+
|
|
59
|
+
`cmpnd programs optimized list` lists the compiled programs that optimization
|
|
60
|
+
runs produced, one row per compiled program hash, from
|
|
61
|
+
`GET /api/v1/programs/v2`. It takes `--name`, `--model`, `--order-by` and
|
|
62
|
+
`--order`. `cmpnd programs list` refuses those flags with an error that names
|
|
63
|
+
`cmpnd programs optimized list`.
|
|
64
|
+
|
|
44
65
|
## The Trash
|
|
45
66
|
|
|
46
67
|
A program in the Trash leaves every view, and its traces with it, until it is
|
|
@@ -194,9 +215,11 @@ model and the field, never the value. Seal the key with
|
|
|
194
215
|
`cmpnd credentials create` instead.
|
|
195
216
|
|
|
196
217
|
`--credential NAME` names the provider credential the program's model spends.
|
|
197
|
-
Without it, the deployment spends the organization's default. The CLI
|
|
198
|
-
name
|
|
199
|
-
|
|
218
|
+
Without it, the deployment spends the organization's default. The CLI looks
|
|
219
|
+
the name up before it stages the source. A name the organization does not hold
|
|
220
|
+
fails with the names it does hold and the nearest one, and nothing is sent.
|
|
221
|
+
The platform checks the name again at the deploy, which catches a credential
|
|
222
|
+
an owner revoked in between. The deploy prints the protocol, model and
|
|
200
223
|
credential of each binding, such as
|
|
201
224
|
`default-lm: chat_completions openai/deepseek-4.1-flash → luna-exec (https://gw.lunaroute.com)`.
|
|
202
225
|
|
|
@@ -279,10 +302,10 @@ Some older spellings still run as deprecated aliases and print a warning:
|
|
|
279
302
|
- `credentials add` for `create`
|
|
280
303
|
|
|
281
304
|
Every list command pages with `--limit` and `--offset`. The `traces`, `evals`,
|
|
282
|
-
`optimizations`, `deployments` and `programs` lists also
|
|
283
|
-
`--per-page` as deprecated aliases.
|
|
284
|
-
|
|
285
|
-
offsets. `deployments runs list` still accepts the deprecated `--cursor`, which
|
|
305
|
+
`optimizations`, `deployments`, `programs` and `programs optimized` lists also
|
|
306
|
+
accept `--page` and `--per-page` as deprecated aliases. Every one of those
|
|
307
|
+
except `programs list` pages by page number on the wire, so its `--offset` must
|
|
308
|
+
be a multiple of `--limit`, and the error names the nearest valid offsets. `deployments runs list` still accepts the deprecated `--cursor`, which
|
|
286
309
|
pages from a previous response's `next_cursor` and cannot be combined with
|
|
287
310
|
`--offset`.
|
|
288
311
|
|
|
@@ -331,9 +354,10 @@ The `signatures` and `programs` groups name programs differently.
|
|
|
331
354
|
hash, which identifies a program's inputs and outputs and everything recorded
|
|
332
355
|
under them: traces, examples, evals and revisions. The CLI passes an id
|
|
333
356
|
through unchanged, and serve tells the two apart by the `p_` prefix.
|
|
334
|
-
`cmpnd programs
|
|
335
|
-
|
|
336
|
-
|
|
357
|
+
`cmpnd programs show`, `traces`, `stats`, `usage` and `select` take a compiled
|
|
358
|
+
program hash, the value in the `program_hash` column of
|
|
359
|
+
`cmpnd programs optimized list`. `cmpnd programs list` prints names and ids, and
|
|
360
|
+
the Trash commands take a program name or id ("The Trash" above). Its routes resolve the hash
|
|
337
361
|
through optimization runs, so they do not find a signature hash. Each group's
|
|
338
362
|
`--help` names the column its IDs come from.
|
|
339
363
|
|
|
@@ -618,6 +642,27 @@ organization must hold provider credentials for every model the run uses.
|
|
|
618
642
|
Named model declarations hold model names and sampling settings, and the CLI
|
|
619
643
|
refuses a provider key on them.
|
|
620
644
|
|
|
645
|
+
A run spends the organization's default credential unless the command names
|
|
646
|
+
another. `--credential NAME` names the run's credential. The program's model
|
|
647
|
+
spends it, and so does every model that names no credential of its own.
|
|
648
|
+
`--reflection-credential NAME` names the credential the reflection model
|
|
649
|
+
spends. `--lm-credential BINDING=NAME` names the credential one model in the
|
|
650
|
+
file's `lms` spends, and repeats once per binding:
|
|
651
|
+
|
|
652
|
+
```bash
|
|
653
|
+
cmpnd optimize gepa task.py capital-qa --reflection-model openai/gpt-5 --auto light \
|
|
654
|
+
--credential openai --lm-credential judge=typesafe
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
The file cannot name a credential itself, because the kernel imports it again
|
|
658
|
+
and reaches no platform there. These flags send what `credential=`,
|
|
659
|
+
`cmpnd.HostedLM(lm, credential=)` and `cmpnd.SystemOne(model, credential=)`
|
|
660
|
+
send from Python. The CLI looks up every name before it stages the file. A
|
|
661
|
+
name the organization does not hold fails with the names it does hold and the
|
|
662
|
+
nearest one. The CLI also refuses `--lm-credential` for a binding the file's
|
|
663
|
+
`lms` does not declare, for one binding twice and for `default-lm`. The
|
|
664
|
+
program's own model spends the run's credential, so `--credential` names it.
|
|
665
|
+
|
|
621
666
|
`cmpnd.Client.optimize_deployment(deployment, metric=fn)` in a notebook packages
|
|
622
667
|
live objects and takes named models through its `lms` argument. An authored file's `lms`
|
|
623
668
|
binding cannot be combined with `lms` in the parent call or `config["lms"]`.
|
|
@@ -5,7 +5,7 @@ name = "cmpnd"
|
|
|
5
5
|
# it's new) — no git tags. Bump with `uv version --bump {minor,patch}` or by
|
|
6
6
|
# hand. Runtime `cmpnd.__version__` reads installed metadata, so it tracks
|
|
7
7
|
# this automatically. See docs/plans/2026-07-02-sdk-pypi-tag-free-publish.md.
|
|
8
|
-
version = "0.
|
|
8
|
+
version = "0.60.0"
|
|
9
9
|
description = "DSPy observability and deployment SDK for cmpnd"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.11"
|
|
@@ -33,7 +33,7 @@ set -euo pipefail
|
|
|
33
33
|
|
|
34
34
|
SDK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
35
35
|
REPO_DIR="$(cd "${SDK_DIR}/.." && pwd)"
|
|
36
|
-
IMAGE="${SDK_TEST_LINUX_IMAGE:-ghcr.io/astral-sh/uv:python3.12-
|
|
36
|
+
IMAGE="${SDK_TEST_LINUX_IMAGE:-ghcr.io/astral-sh/uv:python3.12-trixie-slim}"
|
|
37
37
|
ARGS=("$@")
|
|
38
38
|
if [ ${#ARGS[@]} -eq 0 ]; then
|
|
39
39
|
ARGS=(tests/ -k "not e2e")
|