cmpnd 0.49.0__tar.gz → 0.51.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.49.0 → cmpnd-0.51.0}/PKG-INFO +1 -1
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/_http.py +13 -11
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/__init__.py +19 -1
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/commands/programs.py +50 -9
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/commands/top.py +84 -28
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/shell.py +15 -6
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/deployment.py +109 -13
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/optimization.py +12 -3
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/packaging.py +12 -6
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/programs.py +25 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/docs/cli.md +53 -25
- {cmpnd-0.49.0 → cmpnd-0.51.0}/docs/client.md +11 -4
- {cmpnd-0.49.0 → cmpnd-0.51.0}/pyproject.toml +1 -1
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_authored_metric_contract.py +1 -1
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_authored_optimize.py +4 -2
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_api_client.py +15 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_top.py +4 -4
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_client_program_names.py +2 -2
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_deploy_program_id.py +4 -4
- cmpnd-0.51.0/tests/test_program_names_in_deploy.py +627 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/uv.lock +1 -1
- {cmpnd-0.49.0 → cmpnd-0.51.0}/.gitignore +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/AGENTS.md +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/CLAUDE.md +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/README.md +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/__init__.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/_assessments.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/_authored_import.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/_deployments.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/_gepa_patch.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/_runtime_tools.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/_traces.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/_valsets.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/authored.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/callback.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/commands/admin.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/commands/assessments.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/commands/deployments.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/commands/evals.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/commands/org.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/commands/signatures.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/commands/traces.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/commands/valsets.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/trace_render.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/tree_help.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/cli/verbs.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/client.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/configuration.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/context.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/dataset_sync.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/datasets.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/decorators.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/deployment_metadata.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/deployment_runs.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/execution.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/exporter.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/helpers.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/identity.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/imaging.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/ir/reflection.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/lm_configuration.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/mcp_servers.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/models.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/named_programs.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/optimizers/gepa.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/optimizers/gepa_callback.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/optimizers/tracker.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/references.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/retry.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/cmpnd/traces.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/docs/2026-04-02-code-review.md +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/docs/custom-proposer.md +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/docs/reading-traces.md +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/docs/sdk-instrumentation.md +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/examples/local_ollama.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/ingest/README.md +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/ingest/cmpnd_ingest/bodies.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/ingest/cmpnd_ingest/client_floor.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/ingest/cmpnd_ingest/program_names.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/ingest/cmpnd_ingest/protocols.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/ingest/pyproject.toml +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/scripts/capture_review_run.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/scripts/check-pip-install.sh +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/scripts/release-stress.sh +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/scripts/require_version_bump.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/scripts/seed_review_run.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/scripts/test-in-linux.sh +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/stubs/dspy/utils/dummies.pyi +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/__init__.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/_deployment_signature_helpers.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/_fake_clock.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/_resolve_refs.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/_traced_run.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/conftest.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/committee_task.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/helpers.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/target_capabilities.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_assessments_cli.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_composite_module.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_deploy_execute.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_deploy_pandas.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_deploy_programs.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_optimize_failures.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_parallel_export.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_project_attribution.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_traces_read.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_user_attribution.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/e2e/test_valsets_cli.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/integration/__init__.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/integration/authored_optimize/judge.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/integration/conftest.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/integration/hosted_optimize_probe.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/integration/test_logs_renders_traces.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/integration/test_unhappy_path.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/load/__init__.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/load/__main__.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/load/drain.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/load/ingest.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/load/rlm_shape.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/load/rlm_size_report.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/load/sweep.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/load/test_rlm_size_report.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/parity/__init__.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/parity/conftest.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/parity/test_health.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/parity/test_trace_streaming.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/parity/test_traces.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/span_reference_fixture.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/stress/__init__.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/stress/_provider.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/stress/_readiness.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/stress/_reliability.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/stress/_result.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/stress/mint_bedrock_token.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/stress/test_deploy_programs.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/stress/test_multi_client_stress.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/stress/test_optimize_client_committee.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/stress/test_optimize_loop.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/stress/test_optimize_stress.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/stress/test_provider_roles.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/stress/test_readiness.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/stress/test_real_provider_twins.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/stress/test_reliability.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/stress/test_reliability_classify.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/stress/test_rlm_wasm.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/stress/test_trace_search.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_adapter_reentrancy.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_assessments_api.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_authored.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_authored_bundle_collisions.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_authored_named_models.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_callback.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_admin.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_assessments.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_deployment_runs.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_deployments.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_evals.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_main.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_org.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_parity.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_programs.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_provider_credentials.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_schema_sample.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_shell.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_signatures.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_traces.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_traces_search_wire.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_tree_help.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cli/test_valsets.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_client.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_client_floor.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_configuration.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_context.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_context_propagation.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_dataset_sync.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_datasets.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_decision_client_spans.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_decorators.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_deploy.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_deployment_metadata.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_deployment_runs.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_deployments_read.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_eval.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_eval_example_attribution.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_eval_start.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_eval_trace_grouping.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_execute.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_execute_run_identity.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_exporter.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_exporter_program_names.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_exporter_queue.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_exporter_real.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_gepa_output_normalization.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_identity.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_imaging.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_import_weight.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_ingest_conformance.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_integration.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_interpreter_callback.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_lm15_native_calls.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_lm_configuration.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_lm_usage_shapes.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_mcp_tools.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_models.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_multi_client_stress_readiness.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_optimize.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_optimize_lms.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_optimize_reflection_lm.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_packaging.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_packaging_mcp_tools.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_packaging_program_source.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_poll_until.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_program_identity.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_program_lineage_persist.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_programs_create.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_project_payload.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_real_provider_twins_readiness.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_reflector_properties.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_reflector_smoke.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_release_stress_script.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_repro_reactv2_bugs.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_retry.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_rlm_patch.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_run_attribution_paths.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_runtime_tools.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_schemas.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_signature_inputs.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_span_references.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_span_sent_once.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_system_one.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_trace_context_concurrency.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_trace_fact_wire_contract.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_trace_facts.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_trace_render.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_traces_read.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_tracked_gepa.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_truncation.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_upload_auth_headers.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_valsets_api.py +0 -0
- {cmpnd-0.49.0 → cmpnd-0.51.0}/tests/test_xxh64.py +0 -0
|
@@ -234,23 +234,25 @@ class ApiClient:
|
|
|
234
234
|
# which is the difference a user needs most and had the least of.
|
|
235
235
|
raise error(server_detail(response) or "Resource not found.")
|
|
236
236
|
if response.status_code >= 500:
|
|
237
|
-
# 5xx
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
241
|
-
#
|
|
237
|
+
# 5xx means the cmpnd platform failed to handle the request — a
|
|
238
|
+
# database hiccup, an unhandled exception, a hop between its own
|
|
239
|
+
# services. A failure at something the user runs (their tool
|
|
240
|
+
# server, their provider) answers 4xx with a sentence naming it,
|
|
241
|
+
# so this hint names the platform and nothing of theirs. The cause
|
|
242
|
+
# lives in the platform's logs, which we can't reach from here.
|
|
242
243
|
body_excerpt = (response.text or "").strip()[:200]
|
|
243
244
|
msg = f"Server error ({response.status_code}) for {method} {path}"
|
|
244
245
|
if body_excerpt:
|
|
245
246
|
msg += f": {body_excerpt}"
|
|
246
|
-
msg +=
|
|
247
|
+
msg += (
|
|
248
|
+
"\n The cmpnd platform failed to handle this request. If you run this cmpnd "
|
|
249
|
+
"instance yourself, its logs hold the cause."
|
|
250
|
+
)
|
|
247
251
|
raise error(msg, exit_code=2)
|
|
248
252
|
if response.status_code >= 400:
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
detail = response.text
|
|
253
|
-
raise error(f"Request failed ({response.status_code}): {detail}")
|
|
253
|
+
# The server's own sentence: a 4xx says what to change, and the
|
|
254
|
+
# JSON envelope around it is noise.
|
|
255
|
+
raise error(f"Request failed ({response.status_code}): {server_detail(response)}")
|
|
254
256
|
|
|
255
257
|
if return_response:
|
|
256
258
|
return response
|
|
@@ -88,6 +88,24 @@ Auth: set CMPND_API_KEY and (optionally) CMPND_ENDPOINT, or pass --api-key /
|
|
|
88
88
|
"""
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
def parse(parser: argparse.ArgumentParser, argv: list[str]) -> argparse.Namespace:
|
|
92
|
+
"""Parse ``argv``, and give a late positional the one word argparse could not place.
|
|
93
|
+
|
|
94
|
+
argparse fills optional positionals from the first run of bare words, so in
|
|
95
|
+
``cmpnd deploy program.py --entrypoint qa capital-qa`` the program name comes too
|
|
96
|
+
late and is left over. A command that sets ``_late_positional`` takes one such
|
|
97
|
+
word into that argument when it is still empty. Anything else left over is the
|
|
98
|
+
usual usage error.
|
|
99
|
+
"""
|
|
100
|
+
args, extras = parser.parse_known_args(argv)
|
|
101
|
+
late = getattr(args, "_late_positional", None)
|
|
102
|
+
if late and len(extras) == 1 and not extras[0].startswith("-") and getattr(args, late) is None:
|
|
103
|
+
setattr(args, late, extras.pop())
|
|
104
|
+
if extras:
|
|
105
|
+
parser.error("unrecognized arguments: " + " ".join(extras))
|
|
106
|
+
return args
|
|
107
|
+
|
|
108
|
+
|
|
91
109
|
def main() -> None:
|
|
92
110
|
parser = tree_help.TreeHelpParser(
|
|
93
111
|
prog="cmpnd",
|
|
@@ -165,7 +183,7 @@ def main() -> None:
|
|
|
165
183
|
# Top-level Docker-style shortcuts (human-readable by default; --json opts in)
|
|
166
184
|
top.register(subparsers)
|
|
167
185
|
|
|
168
|
-
args = parser.
|
|
186
|
+
args = parse(parser, sys.argv[1:])
|
|
169
187
|
|
|
170
188
|
if not args.resource:
|
|
171
189
|
# Bare `cmpnd` drops into an interactive Python shell with platform
|
|
@@ -21,15 +21,24 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
21
21
|
|
|
22
22
|
create = sub.add_parser(
|
|
23
23
|
"create",
|
|
24
|
-
help="Create a program
|
|
24
|
+
help="Create a program with a name",
|
|
25
25
|
description=(
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
26
|
+
"Create a program with PROGRAM_NAME in your organization and print its id on stdout. "
|
|
27
|
+
"A program name that already has a program is refused. Deploy under the program name with "
|
|
28
|
+
"`cmpnd deploy SOURCE PROGRAM_NAME`, and declare it in Python with "
|
|
29
|
+
"cmpnd.Program(PROGRAM_NAME, module=..., lm=...). Without PROGRAM_NAME, --name creates an "
|
|
30
|
+
"unnamed program with a display name, as earlier releases did."
|
|
30
31
|
),
|
|
31
32
|
)
|
|
32
|
-
create.add_argument(
|
|
33
|
+
create.add_argument(
|
|
34
|
+
"name", nargs="?", default=None, metavar="PROGRAM_NAME", help="Program name, unique in your organization"
|
|
35
|
+
)
|
|
36
|
+
create.add_argument(
|
|
37
|
+
"--name",
|
|
38
|
+
dest="display_name",
|
|
39
|
+
default=None,
|
|
40
|
+
help="Display name of an unnamed program (need not be unique)",
|
|
41
|
+
)
|
|
33
42
|
create.add_argument("--json", action="store_true", help="Emit the created program as JSON instead of its id")
|
|
34
43
|
create.set_defaults(func=handle_create)
|
|
35
44
|
|
|
@@ -88,11 +97,13 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
88
97
|
|
|
89
98
|
def handle_create(client: api_client.ApiClient, args: Any) -> Any:
|
|
90
99
|
"""Create a program. Prints the bare id on stdout so a script can capture it
|
|
91
|
-
(``PROGRAM_ID=$(cmpnd programs create
|
|
92
|
-
|
|
100
|
+
(``PROGRAM_ID=$(cmpnd programs create triage)``) and says what it made on stderr;
|
|
101
|
+
``--json`` returns serve's whole answer instead, printed like the group's reads."""
|
|
93
102
|
from ... import programs
|
|
94
103
|
|
|
95
|
-
|
|
104
|
+
if args.name is not None:
|
|
105
|
+
return _create_named(client, args)
|
|
106
|
+
row = programs.send_create(client, name=args.display_name)
|
|
96
107
|
if getattr(args, "json", False):
|
|
97
108
|
return row
|
|
98
109
|
print(row["id"])
|
|
@@ -101,6 +112,36 @@ def handle_create(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
101
112
|
return None
|
|
102
113
|
|
|
103
114
|
|
|
115
|
+
def _create_named(client: api_client.ApiClient, args: Any) -> Any:
|
|
116
|
+
"""Create the program named ``args.name``, and refuse a name that already has one."""
|
|
117
|
+
from ... import programs
|
|
118
|
+
from . import top
|
|
119
|
+
|
|
120
|
+
if args.display_name is not None:
|
|
121
|
+
raise api_client.CliError("give a program name or --name, not both: --name is the older display name")
|
|
122
|
+
try:
|
|
123
|
+
answer = programs.send_resolve(client, args.name)
|
|
124
|
+
except ValueError as exc:
|
|
125
|
+
raise api_client.CliError(str(exc)) from exc
|
|
126
|
+
if answer is None:
|
|
127
|
+
raise api_client.CliError(
|
|
128
|
+
"This server has no program names, so it cannot create a program from a program name. A serve from "
|
|
129
|
+
"before program names, such as self-host release 2026-001, needs no program first: "
|
|
130
|
+
f"`{top._hint_program_name('deploy', source='SOURCE', name='PROGRAM_NAME')}` deploys there without "
|
|
131
|
+
"a program id."
|
|
132
|
+
)
|
|
133
|
+
if not answer.get("created"):
|
|
134
|
+
raise api_client.CliError(
|
|
135
|
+
f"the program name {args.name!r} already has a program, {answer['program_id']}. "
|
|
136
|
+
f"Deploy under it with `{top._hint_program_name('deploy', source='SOURCE', name=args.name)}`."
|
|
137
|
+
)
|
|
138
|
+
if getattr(args, "json", False):
|
|
139
|
+
return answer
|
|
140
|
+
print(answer["program_id"])
|
|
141
|
+
print(f"created program {answer['program_id']} named {args.name}", file=sys.stderr)
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
|
|
104
145
|
def handle_list(client: api_client.ApiClient, args: Any) -> Any:
|
|
105
146
|
params: dict[str, str] = pagination.wire(args, "per_page", as_str=True)
|
|
106
147
|
params["order_by"] = args.order_by
|
|
@@ -194,6 +194,19 @@ def _hint_run(deployment_id: str, body: str = '{"...": "..."}') -> str:
|
|
|
194
194
|
return f"cmpnd run {short} '{body}'"
|
|
195
195
|
|
|
196
196
|
|
|
197
|
+
def _hint_program_name(verb: str, *, source: str | None = None, name: str = "capital-qa") -> str:
|
|
198
|
+
"""How to name the program a deploy or optimize runs under, as this front-end spells it."""
|
|
199
|
+
if verb == "optimize":
|
|
200
|
+
source = source or "task.py"
|
|
201
|
+
if _SHELL_MODE:
|
|
202
|
+
return f'optimize("gepa", "{source}", program="{name}")'
|
|
203
|
+
return f"cmpnd optimize gepa {source} {name}"
|
|
204
|
+
source = source or "program.py"
|
|
205
|
+
if _SHELL_MODE:
|
|
206
|
+
return f'deploy("{source}", program="{name}")'
|
|
207
|
+
return f"cmpnd deploy {source} {name}"
|
|
208
|
+
|
|
209
|
+
|
|
197
210
|
def _hint_status(deployment_id: str, follow: bool = False) -> str:
|
|
198
211
|
short = _short_id(deployment_id)
|
|
199
212
|
if _SHELL_MODE:
|
|
@@ -830,15 +843,49 @@ def _program_id_arg(value: str | None) -> str | None:
|
|
|
830
843
|
raise api_client.CliError(str(exc)) from exc
|
|
831
844
|
|
|
832
845
|
|
|
846
|
+
def _program_name_arg(value: str | None) -> str | None:
|
|
847
|
+
"""The program name a deploy or optimize names, checked before a file is staged."""
|
|
848
|
+
if value is None:
|
|
849
|
+
return None
|
|
850
|
+
from ... import programs
|
|
851
|
+
|
|
852
|
+
try:
|
|
853
|
+
return programs.validate_program_name(value)
|
|
854
|
+
except ValueError as exc:
|
|
855
|
+
raise api_client.CliError(str(exc)) from exc
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
def _program_args(args: Any, verb: str, *, needs_name: bool = True) -> tuple[str | None, str | None]:
|
|
859
|
+
"""The program name and program id a deploy or optimize names, checked before a file is staged.
|
|
860
|
+
|
|
861
|
+
The name follows the source file. ``--program-id`` is the older input: it still
|
|
862
|
+
works, warns, and cannot be given with a name. A command with no name and no id
|
|
863
|
+
from either input warns that it will need a name, unless ``needs_name`` is false
|
|
864
|
+
because a deployment it reuses already names its program.
|
|
865
|
+
"""
|
|
866
|
+
name = _program_name_arg(getattr(args, "program_name", None))
|
|
867
|
+
flag = getattr(args, "program_id", None)
|
|
868
|
+
example = _hint_program_name(verb)
|
|
869
|
+
id_input = "program_id=" if _SHELL_MODE else "--program-id"
|
|
870
|
+
if name is not None and flag is not None:
|
|
871
|
+
raise api_client.CliError(f"give a program name or {id_input}, not both")
|
|
872
|
+
if flag is not None:
|
|
873
|
+
print(f"warning: {id_input} is deprecated; give a program name instead, as in `{example}`.", file=sys.stderr)
|
|
874
|
+
program_id = _program_id_arg(flag)
|
|
875
|
+
if name is None and program_id is None and needs_name:
|
|
876
|
+
print(f"warning: this command has no program name; give one, as in `{example}`.", file=sys.stderr)
|
|
877
|
+
return name, program_id
|
|
878
|
+
|
|
879
|
+
|
|
833
880
|
def cmd_deploy(client: api_client.ApiClient, args: Any) -> None:
|
|
834
881
|
"""Stage authored Python and deploy the checked bundle.
|
|
835
882
|
|
|
836
883
|
A fresh child imports the staged bundle to inspect the declared entrypoint.
|
|
837
884
|
The CLI process does not execute the file. ``--run '{json}'`` chains into
|
|
838
|
-
the new deployment as a one-shot smoke test.
|
|
839
|
-
|
|
885
|
+
the new deployment as a one-shot smoke test. The name after the source names the
|
|
886
|
+
program, which the platform makes the first time your organization uses the name:
|
|
840
887
|
|
|
841
|
-
cat <<'PY' | cmpnd deploy -
|
|
888
|
+
cat <<'PY' | cmpnd deploy - capital-qa --run '{"question": "2+2?"}'
|
|
842
889
|
import dspy
|
|
843
890
|
lm = dspy.LM("openai/gemma4:e4b", temperature=0.0, max_tokens=64)
|
|
844
891
|
dspy.configure(lm=lm)
|
|
@@ -849,7 +896,7 @@ def cmd_deploy(client: api_client.ApiClient, args: Any) -> None:
|
|
|
849
896
|
|
|
850
897
|
from ... import authored, configuration, deployment
|
|
851
898
|
|
|
852
|
-
program_id =
|
|
899
|
+
program_name, program_id = _program_args(args, "deploy")
|
|
853
900
|
configuration.configure(api_key=client._api_key, endpoint=client._endpoint, program_id=program_id)
|
|
854
901
|
try:
|
|
855
902
|
if args.source == "-" or args.source is None:
|
|
@@ -874,7 +921,9 @@ def cmd_deploy(client: api_client.ApiClient, args: Any) -> None:
|
|
|
874
921
|
metadata = _parse_deploy_metadata(getattr(args, "metadata", None))
|
|
875
922
|
print(_c("deploying…", "dim"), file=sys.stderr)
|
|
876
923
|
try:
|
|
877
|
-
deployed = deployment.deploy_bundle(
|
|
924
|
+
deployed = deployment.deploy_bundle(
|
|
925
|
+
prepared.bundle, prepared.facts, metadata=metadata, program_id=program_id, program_name=program_name
|
|
926
|
+
)
|
|
878
927
|
except ValueError as exc:
|
|
879
928
|
raise api_client.CliError(str(exc)) from exc
|
|
880
929
|
|
|
@@ -924,7 +973,7 @@ def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
924
973
|
The file binds ``metric`` always, ``trainset`` and ``valset``, ``program`` unless
|
|
925
974
|
``--deployment-id`` names one, and ``proposer`` when the run brings its own::
|
|
926
975
|
|
|
927
|
-
cat <<'PY' | cmpnd optimize gepa -
|
|
976
|
+
cat <<'PY' | cmpnd optimize gepa - capital-qa --reflection-model openai/gpt-4o --auto light
|
|
928
977
|
import dspy
|
|
929
978
|
dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))
|
|
930
979
|
program = dspy.Predict("question -> answer")
|
|
@@ -941,7 +990,7 @@ def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
941
990
|
from ... import authored, configuration, optimization
|
|
942
991
|
|
|
943
992
|
deployment_id = getattr(args, "deployment_id", None)
|
|
944
|
-
program_id =
|
|
993
|
+
program_name, program_id = _program_args(args, "optimize", needs_name=deployment_id is None)
|
|
945
994
|
configuration.configure(api_key=client._api_key, endpoint=client._endpoint, program_id=program_id)
|
|
946
995
|
try:
|
|
947
996
|
if args.source == "-" or args.source is None:
|
|
@@ -972,6 +1021,7 @@ def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
972
1021
|
optimizer=args.optimizer,
|
|
973
1022
|
deployment_id=deployment_id,
|
|
974
1023
|
program_id=program_id,
|
|
1024
|
+
program_name=program_name,
|
|
975
1025
|
# The flag is named for what a command line can carry — a model name,
|
|
976
1026
|
# never an LM object — and the parameter for what the Python surface
|
|
977
1027
|
# accepts. Each layer's name says what that layer takes.
|
|
@@ -1089,6 +1139,26 @@ def cmd_run(client: api_client.ApiClient, args: Any) -> None:
|
|
|
1089
1139
|
# --------------------------------------------------------------------------
|
|
1090
1140
|
|
|
1091
1141
|
|
|
1142
|
+
def _add_program_options(parser: ArgumentParser, role: str) -> None:
|
|
1143
|
+
"""The program name, a positional after the source, and the ``--program-id`` it replaces."""
|
|
1144
|
+
parser.add_argument(
|
|
1145
|
+
"program_name",
|
|
1146
|
+
nargs="?",
|
|
1147
|
+
metavar="PROGRAM_NAME",
|
|
1148
|
+
default=None,
|
|
1149
|
+
help=f"Program name {role}. The platform makes the program the first time "
|
|
1150
|
+
"your organization uses the program name",
|
|
1151
|
+
)
|
|
1152
|
+
parser.add_argument(
|
|
1153
|
+
"--program-id",
|
|
1154
|
+
dest="program_id",
|
|
1155
|
+
default=None,
|
|
1156
|
+
help="Deprecated; give a program name instead. Program id (p_…), defaults to CMPND_PROGRAM_ID",
|
|
1157
|
+
)
|
|
1158
|
+
# argparse leaves a name given after other options unplaced; cli.parse places it.
|
|
1159
|
+
parser.set_defaults(_late_positional="program_name")
|
|
1160
|
+
|
|
1161
|
+
|
|
1092
1162
|
def _add_source_options(parser: ArgumentParser) -> None:
|
|
1093
1163
|
"""The two options that decide what a bundle holds, on every verb that stages one.
|
|
1094
1164
|
|
|
@@ -1247,20 +1317,13 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
1247
1317
|
),
|
|
1248
1318
|
epilog=(
|
|
1249
1319
|
"Examples:\n"
|
|
1250
|
-
" cmpnd
|
|
1251
|
-
|
|
1252
|
-
' cmpnd deploy - --program-id p_0123456789Ab --run \'{"question":"2+2?"}\' < program.py\n'
|
|
1320
|
+
" cmpnd deploy program.py capital-qa\n"
|
|
1321
|
+
' cmpnd deploy - capital-qa --run \'{"question":"2+2?"}\' < program.py\n'
|
|
1253
1322
|
),
|
|
1254
1323
|
formatter_class=raw,
|
|
1255
1324
|
)
|
|
1256
1325
|
dp.add_argument("source", nargs="?", help="Path to a .py file, or '-' to read stdin")
|
|
1257
|
-
dp
|
|
1258
|
-
"--program-id",
|
|
1259
|
-
dest="program_id",
|
|
1260
|
-
default=None,
|
|
1261
|
-
help="Program id (p_…) the deployment belongs to, from `cmpnd programs create`. "
|
|
1262
|
-
"Defaults to CMPND_PROGRAM_ID. Required by every serve after self-host release 2026-001",
|
|
1263
|
-
)
|
|
1326
|
+
_add_program_options(dp, "of the deployment")
|
|
1264
1327
|
dp.add_argument(
|
|
1265
1328
|
"--entrypoint",
|
|
1266
1329
|
default="program",
|
|
@@ -1307,10 +1370,10 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
1307
1370
|
),
|
|
1308
1371
|
epilog=(
|
|
1309
1372
|
"Examples:\n"
|
|
1310
|
-
" cmpnd optimize gepa task.py
|
|
1311
|
-
" cmpnd optimize gepa task.py
|
|
1373
|
+
" cmpnd optimize gepa task.py capital-qa --reflection-model openai/gpt-4o --auto light\n"
|
|
1374
|
+
" cmpnd optimize gepa task.py capital-qa --include judge.py "
|
|
1312
1375
|
"--reflection-model openai/gpt-4o --auto light\n"
|
|
1313
|
-
" cmpnd optimize gepa -
|
|
1376
|
+
" cmpnd optimize gepa - capital-qa --reflection-model openai/gpt-4o "
|
|
1314
1377
|
"--max-metric-calls 60 < task.py\n"
|
|
1315
1378
|
" cmpnd optimize gepa task.py --reflection-model openai/gpt-4o --auto light --deployment-id d_a1b2c3d4\n"
|
|
1316
1379
|
),
|
|
@@ -1339,14 +1402,7 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
1339
1402
|
default=None,
|
|
1340
1403
|
help="Reuse an existing ready deployment as the starting program",
|
|
1341
1404
|
)
|
|
1342
|
-
opt
|
|
1343
|
-
"--program-id",
|
|
1344
|
-
dest="program_id",
|
|
1345
|
-
default=None,
|
|
1346
|
-
help="Program id (p_…) being optimized, from `cmpnd programs create`; defaults to "
|
|
1347
|
-
"CMPND_PROGRAM_ID. Required by every serve after self-host release 2026-001 when "
|
|
1348
|
-
"the file's program is deployed here, optional with --deployment-id",
|
|
1349
|
-
)
|
|
1405
|
+
_add_program_options(opt, "of the program to optimize")
|
|
1350
1406
|
opt.add_argument(
|
|
1351
1407
|
"--reflection-model",
|
|
1352
1408
|
dest="reflection_model",
|
|
@@ -486,12 +486,14 @@ def _make_namespace(client: api_client.ApiClient, endpoint: str) -> dict[str, An
|
|
|
486
486
|
source_root: str | None = None,
|
|
487
487
|
include: list[str] | None = None,
|
|
488
488
|
program_id: str | None = None,
|
|
489
|
+
program: str | None = None,
|
|
489
490
|
) -> Deployment:
|
|
490
491
|
"""Deploy authored Python from a file or ``"-"`` for stdin.
|
|
491
492
|
|
|
492
|
-
``
|
|
493
|
-
the
|
|
494
|
-
|
|
493
|
+
``program`` names the program the deployment belongs to, which the platform
|
|
494
|
+
makes the first time your organization uses the name. ``program_id``
|
|
495
|
+
(``p_…``) is the older way to name it, and defaults to ``CMPND_PROGRAM_ID``.
|
|
496
|
+
A name wins over an id. A fresh child imports the staged bundle. The shell
|
|
495
497
|
does not execute the file in its own process. Returns the
|
|
496
498
|
new Deployment entity so you can chain ``.watch()`` / ``.run({...})``
|
|
497
499
|
directly; also invalidates ``deps`` so the new deployment shows up
|
|
@@ -499,7 +501,9 @@ def _make_namespace(client: api_client.ApiClient, endpoint: str) -> dict[str, An
|
|
|
499
501
|
"""
|
|
500
502
|
from .. import authored, configuration, deployment
|
|
501
503
|
|
|
502
|
-
declared = commands.top.
|
|
504
|
+
program_name, declared = commands.top._program_args(
|
|
505
|
+
_args(program_name=program, program_id=program_id), "deploy"
|
|
506
|
+
)
|
|
503
507
|
configuration.configure(api_key=client._api_key, endpoint=client._endpoint, program_id=declared)
|
|
504
508
|
if source in ("-", None):
|
|
505
509
|
prepared = authored.prepare_stdin(
|
|
@@ -519,7 +523,9 @@ def _make_namespace(client: api_client.ApiClient, endpoint: str) -> dict[str, An
|
|
|
519
523
|
)
|
|
520
524
|
sys.stderr.write(_c("deploying…\n", "dim"))
|
|
521
525
|
sys.stderr.flush()
|
|
522
|
-
deployed = deployment.deploy_bundle(
|
|
526
|
+
deployed = deployment.deploy_bundle(
|
|
527
|
+
prepared.bundle, prepared.facts, program_id=declared, program_name=program_name
|
|
528
|
+
)
|
|
523
529
|
|
|
524
530
|
record = client.get(f"/api/v1/deployments/{deployed.deployment_id}")
|
|
525
531
|
deps.refresh() # new deployment becomes visible in the registry
|
|
@@ -536,6 +542,7 @@ def _make_namespace(client: api_client.ApiClient, endpoint: str) -> dict[str, An
|
|
|
536
542
|
patience: int = 2,
|
|
537
543
|
seed: int = 0,
|
|
538
544
|
program_id: str | None = None,
|
|
545
|
+
program: str | None = None,
|
|
539
546
|
) -> Any:
|
|
540
547
|
"""Optimize a DSPy program: ``optimize("gepa", "task.py")``.
|
|
541
548
|
|
|
@@ -546,7 +553,8 @@ def _make_namespace(client: api_client.ApiClient, endpoint: str) -> dict[str, An
|
|
|
546
553
|
than executed here, so what scores the run is the file's own bytes. ``include``
|
|
547
554
|
adds the siblings it imports and the files it reads. Returns the best
|
|
548
555
|
``DeployedProgram`` (call it directly; ``.score`` is the best score found);
|
|
549
|
-
``deps`` is refreshed so the result deployment shows up.
|
|
556
|
+
``deps`` is refreshed so the result deployment shows up. The ``program``
|
|
557
|
+
argument names the program being optimized, as ``deploy`` takes it.
|
|
550
558
|
"""
|
|
551
559
|
result = commands.top.cmd_optimize(
|
|
552
560
|
client,
|
|
@@ -555,6 +563,7 @@ def _make_namespace(client: api_client.ApiClient, endpoint: str) -> dict[str, An
|
|
|
555
563
|
optimizer=optimizer,
|
|
556
564
|
deployment_id=deployment_id,
|
|
557
565
|
program_id=program_id,
|
|
566
|
+
program_name=program,
|
|
558
567
|
reflection_model=reflection_model,
|
|
559
568
|
source_root=source_root,
|
|
560
569
|
include=list(include or []),
|
|
@@ -26,6 +26,7 @@ from . import (
|
|
|
26
26
|
mcp_servers,
|
|
27
27
|
models,
|
|
28
28
|
packaging,
|
|
29
|
+
programs,
|
|
29
30
|
retry,
|
|
30
31
|
)
|
|
31
32
|
|
|
@@ -92,8 +93,8 @@ def _raise_http_error(response: httpx.Response, action: str) -> None:
|
|
|
92
93
|
"""Convert an HTTP error response into an actionable RuntimeError.
|
|
93
94
|
|
|
94
95
|
Replaces ``response.raise_for_status()`` so callers see a useful
|
|
95
|
-
message instead of a raw httpx traceback. 5xx
|
|
96
|
-
|
|
96
|
+
message instead of a raw httpx traceback. 5xx says the platform
|
|
97
|
+
failed and points a self-hosting operator at its logs; 4xx surfaces the response
|
|
97
98
|
body so e.g. ``{"error": "Invalid 'since' timestamp"}`` round-trips
|
|
98
99
|
to the terminal.
|
|
99
100
|
"""
|
|
@@ -105,8 +106,8 @@ def _raise_http_error(response: httpx.Response, action: str) -> None:
|
|
|
105
106
|
if body:
|
|
106
107
|
msg += f"\n Body: {body}"
|
|
107
108
|
msg += (
|
|
108
|
-
"\n The
|
|
109
|
-
"If you
|
|
109
|
+
"\n The cmpnd platform failed to handle this request. "
|
|
110
|
+
"If you run this cmpnd instance yourself, its logs hold the cause."
|
|
110
111
|
)
|
|
111
112
|
else:
|
|
112
113
|
msg = f"Failed to {action}: HTTP {response.status_code}"
|
|
@@ -356,8 +357,8 @@ def _resolve_mcp_tool_references(client: httpx.Client, config, references: list[
|
|
|
356
357
|
|
|
357
358
|
|
|
358
359
|
MISSING_PROGRAM_ID = (
|
|
359
|
-
"this server requires a program
|
|
360
|
-
"cmpnd.
|
|
360
|
+
"this server requires a program name: give one after the source, as in `cmpnd deploy SOURCE PROGRAM_NAME`, or "
|
|
361
|
+
"deploy a cmpnd.Program(PROGRAM_NAME, module=..., lm=...) with cmpnd.deploy()."
|
|
361
362
|
)
|
|
362
363
|
|
|
363
364
|
# The error serve answers, with a 400, to a create that names no program. A serve
|
|
@@ -376,6 +377,55 @@ def declared_program_id(explicit: str | None, config: configuration.Config) -> s
|
|
|
376
377
|
return identity.validate_program_id(chosen) if chosen is not None else None
|
|
377
378
|
|
|
378
379
|
|
|
380
|
+
def program_for(program_id: str | None, program_name: str | None, config: configuration.Config) -> str | None:
|
|
381
|
+
"""The program a deployment is created under, when the caller may name it by name or id.
|
|
382
|
+
|
|
383
|
+
A name wins over an explicit or configured id, and the SDK warns that the id is
|
|
384
|
+
not used. The name resolves to its program id with ``PUT``, which makes the program
|
|
385
|
+
the first time the organization uses the name. On a server with no program names,
|
|
386
|
+
the deployment goes without an id, and the SDK warns.
|
|
387
|
+
"""
|
|
388
|
+
if program_name is None:
|
|
389
|
+
return declared_program_id(program_id, config)
|
|
390
|
+
programs.validate_program_name(program_name)
|
|
391
|
+
unused = program_id if program_id is not None else config.program_id
|
|
392
|
+
if unused is not None:
|
|
393
|
+
logger.warning("cmpnd: the deploy uses program name %r, and program id %s is not used.", program_name, unused)
|
|
394
|
+
return _program_by_name(program_name, config)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def _program_by_name(name: str, config: configuration.Config) -> str | None:
|
|
398
|
+
"""The program id serve gives ``name``, or None when the server has no program names."""
|
|
399
|
+
answer = programs.send_resolve(_http.ApiClient(config.api_key, config.endpoint), name)
|
|
400
|
+
if answer is None:
|
|
401
|
+
logger.warning(
|
|
402
|
+
"cmpnd: program name %r is not used: the server has no program names, so the deploy has no program id.",
|
|
403
|
+
name,
|
|
404
|
+
)
|
|
405
|
+
return None
|
|
406
|
+
return answer["program_id"]
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _program_module(program: Any) -> Any:
|
|
410
|
+
"""The module a program deploys: the module inside it, and inside each program it holds.
|
|
411
|
+
|
|
412
|
+
A program that overrides ``forward`` runs its own code around its module, and
|
|
413
|
+
packaging cannot rebuild that code yet, so it is refused.
|
|
414
|
+
"""
|
|
415
|
+
from . import named_programs
|
|
416
|
+
|
|
417
|
+
module = program
|
|
418
|
+
while isinstance(module, named_programs.Program):
|
|
419
|
+
if named_programs.runs_itself(module):
|
|
420
|
+
raise ValueError(
|
|
421
|
+
f"{type(module).__name__} overrides forward, and deploying a cmpnd.Program that runs its own "
|
|
422
|
+
"code is not supported yet. Move that code into a dspy.Module, and deploy a cmpnd.Program "
|
|
423
|
+
"that holds it."
|
|
424
|
+
)
|
|
425
|
+
module = module.module
|
|
426
|
+
return module
|
|
427
|
+
|
|
428
|
+
|
|
379
429
|
def _refused_for_missing_program_id(response: httpx.Response, deploy_body: dict[str, Any]) -> bool:
|
|
380
430
|
"""Whether serve refused a create that named no program because it needs one."""
|
|
381
431
|
if response.status_code != 400 or "program_id" in deploy_body:
|
|
@@ -393,8 +443,14 @@ def deploy(module, metric=None, timeout=300, metadata=None, *, program_id=None):
|
|
|
393
443
|
Packages the module into a source ZIP, uploads it through the platform API to the
|
|
394
444
|
deploy stack, and polls until the program is parsed and ready.
|
|
395
445
|
|
|
446
|
+
A ``cmpnd.Program`` deploys its module under the program's name and on the
|
|
447
|
+
program's LM. The name resolves to its program id, which the platform makes the
|
|
448
|
+
first time your organization uses the name. A predictor with its own LM keeps it,
|
|
449
|
+
and the deployment's model is the LM the packaged source names.
|
|
450
|
+
|
|
396
451
|
Args:
|
|
397
|
-
module: A DSPy module (e.g. dspy.Predict, dspy.ChainOfThought)
|
|
452
|
+
module: A DSPy module (e.g. dspy.Predict, dspy.ChainOfThought), or a
|
|
453
|
+
``cmpnd.Program``.
|
|
398
454
|
metric: Optional metric callable for optimization support.
|
|
399
455
|
timeout: Seconds to wait for the program to become ready (default 300),
|
|
400
456
|
measured from the moment the upload completes. A transient blip
|
|
@@ -415,7 +471,8 @@ def deploy(module, metric=None, timeout=300, metadata=None, *, program_id=None):
|
|
|
415
471
|
id (``cmpnd.configure(program_id=...)`` or ``CMPND_PROGRAM_ID``) is
|
|
416
472
|
used. With neither, the create names no program, and serve decides:
|
|
417
473
|
every serve with program ids refuses it, and a serve from before
|
|
418
|
-
them, such as self-host release 2026-001, accepts it.
|
|
474
|
+
them, such as self-host release 2026-001, accepts it. A
|
|
475
|
+
``cmpnd.Program``'s name wins over this id.
|
|
419
476
|
|
|
420
477
|
Returns:
|
|
421
478
|
A DeployedProgram that can be called with the same inputs as the
|
|
@@ -428,8 +485,29 @@ def deploy(module, metric=None, timeout=300, metadata=None, *, program_id=None):
|
|
|
428
485
|
TimeoutError: If the program isn't ready within timeout seconds.
|
|
429
486
|
"""
|
|
430
487
|
config = configuration.require_config()
|
|
431
|
-
|
|
488
|
+
from . import named_programs
|
|
489
|
+
|
|
490
|
+
if not isinstance(module, named_programs.Program):
|
|
491
|
+
return _deploy_module(module, metric, timeout, metadata, declared_program_id(program_id, config), config)
|
|
492
|
+
inner = _program_module(module)
|
|
493
|
+
program = program_for(program_id, module.name, config)
|
|
494
|
+
import dspy
|
|
495
|
+
|
|
496
|
+
# Applied the same way the program applies it when it runs, so a nested program's
|
|
497
|
+
# LM is not used, and a predictor's own LM is.
|
|
498
|
+
with dspy.context(lm=module.lm, **{named_programs.PROGRAM_LM_SETTING: module.lm}):
|
|
499
|
+
return _deploy_module(inner, metric, timeout, metadata, program, config)
|
|
432
500
|
|
|
501
|
+
|
|
502
|
+
def _deploy_module(
|
|
503
|
+
module: Any,
|
|
504
|
+
metric: Any,
|
|
505
|
+
timeout: float,
|
|
506
|
+
metadata: Any,
|
|
507
|
+
program: str | None,
|
|
508
|
+
config: configuration.Config,
|
|
509
|
+
) -> execution.DeployedProgram:
|
|
510
|
+
"""Package and deploy a live module under ``program``, which the caller already chose."""
|
|
433
511
|
# Resolve the program's MCP tool references against the org's registry, before
|
|
434
512
|
# anything is packaged and before a deployment row exists. A program with no MCP
|
|
435
513
|
# tools asks the registry nothing.
|
|
@@ -499,9 +577,8 @@ def deploy(module, metric=None, timeout=300, metadata=None, *, program_id=None):
|
|
|
499
577
|
# the LM explicitly and so rides this same path; raw AWS creds never do.
|
|
500
578
|
# When no key is forwardable, the server decides whether it can resolve a
|
|
501
579
|
# provider and fails the deploy loudly if not — we do not guess here.
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
lm = dspy.settings.lm
|
|
580
|
+
# The same LM the packaged source names: a predictor's own LM, else the context's.
|
|
581
|
+
lm = packaging._extract_lm_object(module)
|
|
505
582
|
if lm is not None:
|
|
506
583
|
if getattr(lm, "model", None):
|
|
507
584
|
deploy_body["litellm_model"] = lm.model
|
|
@@ -522,14 +599,33 @@ def deploy_bundle(
|
|
|
522
599
|
metadata: Any = None,
|
|
523
600
|
*,
|
|
524
601
|
program_id: str | None = None,
|
|
602
|
+
program_name: str | None = None,
|
|
525
603
|
) -> execution.DeployedProgram:
|
|
526
604
|
"""Upload a checked authored-source bundle without packaging a live module.
|
|
527
605
|
|
|
528
606
|
``program_id`` works as it does for :func:`deploy`: the explicit id, else the
|
|
529
607
|
configured one, else none, and serve decides whether a deployment needs one.
|
|
608
|
+
``program_name`` names the program instead, and wins over both ids.
|
|
609
|
+
"""
|
|
610
|
+
config = configuration.require_config()
|
|
611
|
+
return deploy_bundle_under(
|
|
612
|
+
zip_bytes, facts, program_for(program_id, program_name, config), timeout=timeout, metadata=metadata
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def deploy_bundle_under(
|
|
617
|
+
zip_bytes: bytes,
|
|
618
|
+
facts: authored.ProgramFacts,
|
|
619
|
+
program: str | None,
|
|
620
|
+
*,
|
|
621
|
+
timeout: float = 300,
|
|
622
|
+
metadata: Any = None,
|
|
623
|
+
) -> execution.DeployedProgram:
|
|
624
|
+
"""Upload a checked bundle under ``program``, which the caller already chose.
|
|
625
|
+
|
|
626
|
+
None deploys without a program id, with no fallback to the configured one.
|
|
530
627
|
"""
|
|
531
628
|
config = configuration.require_config()
|
|
532
|
-
program = declared_program_id(program_id, config)
|
|
533
629
|
deploy_body = facts.deploy_body()
|
|
534
630
|
if program is not None:
|
|
535
631
|
deploy_body["program_id"] = program
|