cmpnd 0.62.0__tar.gz → 0.64.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.62.0 → cmpnd-0.64.0}/PKG-INFO +40 -46
- {cmpnd-0.62.0 → cmpnd-0.64.0}/README.md +39 -45
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/_assessments.py +6 -5
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/_traces.py +11 -14
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/_valsets.py +9 -7
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/callback.py +25 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/__init__.py +0 -2
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/commands/assessments.py +11 -2
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/commands/evals.py +1 -1
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/commands/org.py +71 -4
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/commands/programs.py +59 -16
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/commands/top.py +21 -48
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/commands/traces.py +8 -14
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/commands/valsets.py +6 -25
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/verbs.py +5 -5
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/client.py +32 -9
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/programs.py +22 -1
- {cmpnd-0.62.0 → cmpnd-0.64.0}/docs/cli.md +67 -55
- {cmpnd-0.62.0 → cmpnd-0.64.0}/docs/client.md +18 -10
- {cmpnd-0.62.0 → cmpnd-0.64.0}/docs/reading-traces.md +2 -3
- {cmpnd-0.62.0 → cmpnd-0.64.0}/docs/sdk-instrumentation.md +16 -3
- {cmpnd-0.62.0 → cmpnd-0.64.0}/pyproject.toml +1 -1
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_traces_read.py +1 -1
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_valsets_cli.py +11 -10
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_assessments_api.py +15 -5
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_assessments.py +34 -7
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_evals.py +1 -1
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_org.py +160 -4
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_parity.py +0 -4
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_programs.py +90 -4
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_schema_sample.py +12 -8
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_shell.py +7 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_top.py +77 -7
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_traces.py +0 -4
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_traces_program_and_module.py +4 -5
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_traces_search_wire.py +28 -16
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_valsets.py +18 -14
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_client.py +12 -2
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_client_program_names.py +142 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_traces_read.py +0 -2
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_valsets_api.py +34 -8
- {cmpnd-0.62.0 → cmpnd-0.64.0}/uv.lock +1 -1
- cmpnd-0.62.0/cmpnd/cli/commands/signatures.py +0 -164
- cmpnd-0.62.0/tests/test_cli/test_signatures.py +0 -225
- {cmpnd-0.62.0 → cmpnd-0.64.0}/.gitignore +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/AGENTS.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/CLAUDE.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/_authored_import.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/_deployments.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/_gepa_patch.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/_http.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/_runtime_tools.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/authored.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/commands/admin.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/commands/deployments.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/shell.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/trace_render.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/cli/tree_help.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/configuration.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/context.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/credentials.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/dataset_sync.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/datasets.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/decorators.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/deployment.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/deployment_metadata.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/deployment_runs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/execution.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/exporter.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/helpers.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/identity.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/imaging.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/ir/reflection.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/lm_configuration.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/mcp_servers.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/models.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/named_programs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/optimization.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/optimizers/gepa.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/optimizers/gepa_callback.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/optimizers/tracker.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/packaging.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/references.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/retry.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/cmpnd/traces.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/docs/2026-04-02-code-review.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/docs/custom-proposer.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/examples/local_ollama.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/ingest/README.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/bodies.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/client_floor.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/program_names.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/protocols.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/ingest/pyproject.toml +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/scripts/capture_review_run.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/scripts/check-pip-install.sh +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/scripts/install-test-env.sh +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/scripts/release-stress.sh +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/scripts/require_version_bump.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/scripts/seed_review_run.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/scripts/selfhost_compat.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/scripts/test-in-linux.sh +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/stubs/dspy/utils/dummies.pyi +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/_credential_route.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/_deployment_signature_helpers.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/_fake_clock.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/_resolve_refs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/_traced_run.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/conftest.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/committee_task.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/helpers.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/target_capabilities.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_assessments_cli.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_composite_module.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_deploy_execute.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_deploy_pandas.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_deploy_programs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_optimize_authored_metric.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_optimize_custom_proposer.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_optimize_failures.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_parallel_export.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_project_attribution.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/e2e/test_user_attribution.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/integration/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/integration/authored_optimize/judge.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/integration/conftest.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/integration/hosted_optimize_probe.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/integration/test_logs_renders_traces.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/integration/test_unhappy_path.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/load/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/load/__main__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/load/drain.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/load/ingest.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/load/rlm_shape.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/load/rlm_size_report.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/load/sweep.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/load/test_rlm_size_report.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/parity/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/parity/conftest.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/parity/test_health.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/parity/test_trace_streaming.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/parity/test_traces.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/span_reference_fixture.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/_provider.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/_readiness.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/_reliability.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/_result.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/mint_bedrock_token.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/test_bedrock_converse.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/test_deploy_programs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/test_multi_client_stress.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/test_optimize_client_committee.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/test_optimize_loop.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/test_optimize_stress.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/test_provider_roles.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/test_readiness.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/test_real_provider_twins.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/test_reliability.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/test_reliability_classify.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/test_rlm_wasm.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/stress/test_trace_search.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_adapter_reentrancy.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_authored.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_authored_binding_credentials.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_authored_bundle_collisions.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_authored_metric_contract.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_authored_named_models.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_authored_optimize.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_callback.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_admin.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_api_client.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_deployment_runs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_deployments.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_main.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_provider_credentials.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cli/test_tree_help.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_client_floor.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_configuration.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_context.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_context_propagation.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_credential_kwargs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_credential_selection.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_credential_values.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_dataset_sync.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_datasets.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_decision_client_spans.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_decorators.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_deploy.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_deploy_program_id.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_deployment_metadata.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_deployment_runs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_deployments_read.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_eval.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_eval_example_attribution.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_eval_start.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_eval_trace_grouping.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_execute.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_execute_run_identity.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_exporter.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_exporter_program_names.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_exporter_queue.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_exporter_real.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_gepa_output_normalization.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_identity.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_imaging.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_import_weight.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_ingest_conformance.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_integration.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_interpreter_callback.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_lm15_native_calls.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_lm_configuration.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_lm_usage_shapes.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_mcp_tools.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_models.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_multi_client_stress_readiness.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_optimize.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_optimize_lms.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_optimize_reflection_lm.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_packaging.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_packaging_mcp_tools.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_packaging_program_source.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_poll_until.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_program_id_deprecation.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_program_identity.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_program_lineage_persist.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_program_names_in_deploy.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_programs_create.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_project_payload.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_real_provider_twins_readiness.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_reflector_properties.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_reflector_smoke.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_release_stress_script.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_replaced_names.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_repro_reactv2_bugs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_retry.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_rlm_patch.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_run_attribution_paths.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_runtime_tools.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_schemas.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_seal_lm_credential.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_signature_inputs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_span_references.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_span_sent_once.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_system_one.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_trace_context_concurrency.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_trace_fact_wire_contract.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_trace_facts.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_trace_render.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_tracked_gepa.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_truncation.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_upload_auth_headers.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.64.0}/tests/test_xxh64.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cmpnd
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.64.0
|
|
4
4
|
Summary: DSPy observability and deployment SDK for cmpnd
|
|
5
5
|
Project-URL: Homepage, https://cmpnd.ai
|
|
6
6
|
Author-email: cmpnd <hello@cmpnd.ai>
|
|
@@ -51,9 +51,9 @@ Description-Content-Type: text/markdown
|
|
|
51
51
|
|
|
52
52
|
# cmpnd
|
|
53
53
|
|
|
54
|
-
The `cmpnd` package traces
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
The `cmpnd` package traces, deploys and optimizes DSPy programs on the cmpnd
|
|
55
|
+
platform. You give a program a name, and a client records its traces,
|
|
56
|
+
deployments and optimization runs under that name.
|
|
57
57
|
|
|
58
58
|
## Installation
|
|
59
59
|
|
|
@@ -66,46 +66,15 @@ uv add cmpnd # or: pip install cmpnd
|
|
|
66
66
|
The `cmpnd` library works with the cmpnd service at
|
|
67
67
|
[platform.cmpnd.ai](https://platform.cmpnd.ai). Create an API key on the
|
|
68
68
|
platform under Settings → API keys, and pass it as `api_key=` to
|
|
69
|
-
`cmpnd.
|
|
69
|
+
`cmpnd.Client()` or `cmpnd.configure()`. For a self-hosted cmpnd install, also
|
|
70
70
|
pass the install's URL as `endpoint=`.
|
|
71
71
|
|
|
72
72
|
## Quick start
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
```python
|
|
79
|
-
import cmpnd
|
|
80
|
-
import dspy
|
|
81
|
-
|
|
82
|
-
cmpnd.configure(api_key="ck_xxx", project_tags="my-project")
|
|
83
|
-
cmpnd.auto_instrument()
|
|
84
|
-
|
|
85
|
-
dspy.configure(lm=dspy.LM("openai/gpt-6-luna"))
|
|
86
|
-
|
|
87
|
-
cot = dspy.ChainOfThought("question -> answer")
|
|
88
|
-
result = cot(question="What is DSPy?") # the trace goes to cmpnd
|
|
89
|
-
|
|
90
|
-
cmpnd.trace_id(result) # "a1b2c3d4-...", or None outside instrumentation
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
`cmpnd.trace_id()` links a result to its platform trace, for example to attach
|
|
94
|
-
feedback later. Traces recorded this way have no program name. The platform
|
|
95
|
-
groups them by the structure of the program's modules. The API reference
|
|
96
|
-
below lists the other options of `cmpnd.configure()`.
|
|
97
|
-
|
|
98
|
-
## Program names
|
|
99
|
-
|
|
100
|
-
When you’re ready to name programs, evolve them, and share them with your team, switch
|
|
101
|
-
to `cmpnd.Client`. A client replaces `configure()` and `auto_instrument()` and offers
|
|
102
|
-
more features. A client records only the programs you run through it, and each of
|
|
103
|
-
those programs has a program name. A client takes most of the same arguments as `configure()`.
|
|
104
|
-
|
|
105
|
-
A program name is the name your organization gives a program, such as `question-answerer`.
|
|
106
|
-
Traces, deployments and optimization runs under one program name go to one
|
|
107
|
-
program. The platform makes the program the first time your organization uses
|
|
108
|
-
the program name.
|
|
74
|
+
A program name is the name your organization gives a program, such as
|
|
75
|
+
`question-answerer`. Traces, deployments and optimization runs under one
|
|
76
|
+
program name go to one program. The platform makes the program the first time
|
|
77
|
+
your organization uses the program name.
|
|
109
78
|
|
|
110
79
|
`@cmpnd.program(name)` above a `dspy.Module` class gives the class a program
|
|
111
80
|
name. Building the class takes its own arguments and a required `lm=`. The
|
|
@@ -128,17 +97,23 @@ class QA(dspy.Predict):
|
|
|
128
97
|
qa1 = QA(lm=dspy.LM("openai/gpt-6-luna"))
|
|
129
98
|
```
|
|
130
99
|
|
|
131
|
-
A
|
|
100
|
+
A `cmpnd.Client` runs the program and records its trace under the program
|
|
101
|
+
name:
|
|
132
102
|
|
|
133
103
|
```python
|
|
134
104
|
gepa = dspy.GEPA(metric=metric, reflection_lm=dspy.LM("openai/gpt-6-luna"), auto="light")
|
|
135
105
|
|
|
136
106
|
with cmpnd.Client(api_key="ck_xxx", project_tags="my-project") as sdk:
|
|
137
|
-
# the trace
|
|
107
|
+
# the trace goes to the program "question-answerer"
|
|
138
108
|
result = sdk.run_program(qa1, question="What is DSPy?")
|
|
139
109
|
qa2 = sdk.optimize_program(qa1, gepa, trainset=train, valset=val)
|
|
110
|
+
|
|
111
|
+
cmpnd.trace_id(result) # "a1b2c3d4-...", the trace of this run
|
|
140
112
|
```
|
|
141
113
|
|
|
114
|
+
`cmpnd.trace_id()` links a result to its platform trace, for example to attach
|
|
115
|
+
feedback later. A client records only the programs you run through it.
|
|
116
|
+
|
|
142
117
|
A client is active from construction. The `with` form flushes the client's
|
|
143
118
|
traces and closes it on exit. Without `with`, the client stays active until
|
|
144
119
|
you call `close()` or the process exits, and its traces go out at exit:
|
|
@@ -167,8 +142,26 @@ qa3 = cmpnd.Program(
|
|
|
167
142
|
- `optimize_program` runs the optimizer in your process and returns a copy of
|
|
168
143
|
the program that holds the optimized module. The copy keeps the program name
|
|
169
144
|
and the LM.
|
|
170
|
-
|
|
171
|
-
|
|
145
|
+
|
|
146
|
+
## Sketching with `auto_instrument()`
|
|
147
|
+
|
|
148
|
+
`cmpnd.auto_instrument()` records a trace of every DSPy call in the process,
|
|
149
|
+
with no other change to your code. Use it to sketch a
|
|
150
|
+
[DSPy program](https://dspy.ai/) and see what it does:
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
cmpnd.configure(api_key="ck_xxx", project_tags="my-project")
|
|
154
|
+
cmpnd.auto_instrument()
|
|
155
|
+
|
|
156
|
+
dspy.configure(lm=dspy.LM("openai/gpt-6-luna"))
|
|
157
|
+
dspy.ChainOfThought("question -> answer")(question="What is DSPy?")
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
These traces carry no program, so the platform files them under Unattributed.
|
|
161
|
+
The first one in a process logs a warning that points to `@cmpnd.program` and
|
|
162
|
+
`cmpnd.Client`. Calling a `cmpnd.Program` directly while `auto_instrument()` is
|
|
163
|
+
on raises a `TypeError` that points to the client. The API reference below
|
|
164
|
+
lists the options of `cmpnd.configure()`.
|
|
172
165
|
|
|
173
166
|
## Deployed programs
|
|
174
167
|
|
|
@@ -531,7 +524,7 @@ describes one:
|
|
|
531
524
|
|
|
532
525
|
```
|
|
533
526
|
usage: cmpnd [-h] [--version] [--api-key API_KEY] [--endpoint ENDPOINT]
|
|
534
|
-
{auth,login,logout,org,credentials,tool-servers,optimizations,programs,
|
|
527
|
+
{auth,login,logout,org,credentials,tool-servers,optimizations,programs,traces,evals,datasets,deployments,ps,status,logs,inspect,schema,sample,run,deploy,optimize}
|
|
535
528
|
...
|
|
536
529
|
|
|
537
530
|
CMPND platform CLI — deploy, run, and inspect DSPy programs.
|
|
@@ -625,7 +618,8 @@ captured input and output size limits, 65,536 bytes each by default.
|
|
|
625
618
|
|
|
626
619
|
### `cmpnd.auto_instrument()`
|
|
627
620
|
|
|
628
|
-
Registers the callback with DSPy, so every DSPy call records a trace.
|
|
621
|
+
Registers the callback with DSPy, so every DSPy call records a trace. The
|
|
622
|
+
traces carry no program and land in Unattributed.
|
|
629
623
|
|
|
630
624
|
### `cmpnd.Client()`
|
|
631
625
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# cmpnd
|
|
2
2
|
|
|
3
|
-
The `cmpnd` package traces
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
The `cmpnd` package traces, deploys and optimizes DSPy programs on the cmpnd
|
|
4
|
+
platform. You give a program a name, and a client records its traces,
|
|
5
|
+
deployments and optimization runs under that name.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -15,46 +15,15 @@ uv add cmpnd # or: pip install cmpnd
|
|
|
15
15
|
The `cmpnd` library works with the cmpnd service at
|
|
16
16
|
[platform.cmpnd.ai](https://platform.cmpnd.ai). Create an API key on the
|
|
17
17
|
platform under Settings → API keys, and pass it as `api_key=` to
|
|
18
|
-
`cmpnd.
|
|
18
|
+
`cmpnd.Client()` or `cmpnd.configure()`. For a self-hosted cmpnd install, also
|
|
19
19
|
pass the install's URL as `endpoint=`.
|
|
20
20
|
|
|
21
21
|
## Quick start
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
```python
|
|
28
|
-
import cmpnd
|
|
29
|
-
import dspy
|
|
30
|
-
|
|
31
|
-
cmpnd.configure(api_key="ck_xxx", project_tags="my-project")
|
|
32
|
-
cmpnd.auto_instrument()
|
|
33
|
-
|
|
34
|
-
dspy.configure(lm=dspy.LM("openai/gpt-6-luna"))
|
|
35
|
-
|
|
36
|
-
cot = dspy.ChainOfThought("question -> answer")
|
|
37
|
-
result = cot(question="What is DSPy?") # the trace goes to cmpnd
|
|
38
|
-
|
|
39
|
-
cmpnd.trace_id(result) # "a1b2c3d4-...", or None outside instrumentation
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
`cmpnd.trace_id()` links a result to its platform trace, for example to attach
|
|
43
|
-
feedback later. Traces recorded this way have no program name. The platform
|
|
44
|
-
groups them by the structure of the program's modules. The API reference
|
|
45
|
-
below lists the other options of `cmpnd.configure()`.
|
|
46
|
-
|
|
47
|
-
## Program names
|
|
48
|
-
|
|
49
|
-
When you’re ready to name programs, evolve them, and share them with your team, switch
|
|
50
|
-
to `cmpnd.Client`. A client replaces `configure()` and `auto_instrument()` and offers
|
|
51
|
-
more features. A client records only the programs you run through it, and each of
|
|
52
|
-
those programs has a program name. A client takes most of the same arguments as `configure()`.
|
|
53
|
-
|
|
54
|
-
A program name is the name your organization gives a program, such as `question-answerer`.
|
|
55
|
-
Traces, deployments and optimization runs under one program name go to one
|
|
56
|
-
program. The platform makes the program the first time your organization uses
|
|
57
|
-
the program name.
|
|
23
|
+
A program name is the name your organization gives a program, such as
|
|
24
|
+
`question-answerer`. Traces, deployments and optimization runs under one
|
|
25
|
+
program name go to one program. The platform makes the program the first time
|
|
26
|
+
your organization uses the program name.
|
|
58
27
|
|
|
59
28
|
`@cmpnd.program(name)` above a `dspy.Module` class gives the class a program
|
|
60
29
|
name. Building the class takes its own arguments and a required `lm=`. The
|
|
@@ -77,17 +46,23 @@ class QA(dspy.Predict):
|
|
|
77
46
|
qa1 = QA(lm=dspy.LM("openai/gpt-6-luna"))
|
|
78
47
|
```
|
|
79
48
|
|
|
80
|
-
A
|
|
49
|
+
A `cmpnd.Client` runs the program and records its trace under the program
|
|
50
|
+
name:
|
|
81
51
|
|
|
82
52
|
```python
|
|
83
53
|
gepa = dspy.GEPA(metric=metric, reflection_lm=dspy.LM("openai/gpt-6-luna"), auto="light")
|
|
84
54
|
|
|
85
55
|
with cmpnd.Client(api_key="ck_xxx", project_tags="my-project") as sdk:
|
|
86
|
-
# the trace
|
|
56
|
+
# the trace goes to the program "question-answerer"
|
|
87
57
|
result = sdk.run_program(qa1, question="What is DSPy?")
|
|
88
58
|
qa2 = sdk.optimize_program(qa1, gepa, trainset=train, valset=val)
|
|
59
|
+
|
|
60
|
+
cmpnd.trace_id(result) # "a1b2c3d4-...", the trace of this run
|
|
89
61
|
```
|
|
90
62
|
|
|
63
|
+
`cmpnd.trace_id()` links a result to its platform trace, for example to attach
|
|
64
|
+
feedback later. A client records only the programs you run through it.
|
|
65
|
+
|
|
91
66
|
A client is active from construction. The `with` form flushes the client's
|
|
92
67
|
traces and closes it on exit. Without `with`, the client stays active until
|
|
93
68
|
you call `close()` or the process exits, and its traces go out at exit:
|
|
@@ -116,8 +91,26 @@ qa3 = cmpnd.Program(
|
|
|
116
91
|
- `optimize_program` runs the optimizer in your process and returns a copy of
|
|
117
92
|
the program that holds the optimized module. The copy keeps the program name
|
|
118
93
|
and the LM.
|
|
119
|
-
|
|
120
|
-
|
|
94
|
+
|
|
95
|
+
## Sketching with `auto_instrument()`
|
|
96
|
+
|
|
97
|
+
`cmpnd.auto_instrument()` records a trace of every DSPy call in the process,
|
|
98
|
+
with no other change to your code. Use it to sketch a
|
|
99
|
+
[DSPy program](https://dspy.ai/) and see what it does:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
cmpnd.configure(api_key="ck_xxx", project_tags="my-project")
|
|
103
|
+
cmpnd.auto_instrument()
|
|
104
|
+
|
|
105
|
+
dspy.configure(lm=dspy.LM("openai/gpt-6-luna"))
|
|
106
|
+
dspy.ChainOfThought("question -> answer")(question="What is DSPy?")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
These traces carry no program, so the platform files them under Unattributed.
|
|
110
|
+
The first one in a process logs a warning that points to `@cmpnd.program` and
|
|
111
|
+
`cmpnd.Client`. Calling a `cmpnd.Program` directly while `auto_instrument()` is
|
|
112
|
+
on raises a `TypeError` that points to the client. The API reference below
|
|
113
|
+
lists the options of `cmpnd.configure()`.
|
|
121
114
|
|
|
122
115
|
## Deployed programs
|
|
123
116
|
|
|
@@ -480,7 +473,7 @@ describes one:
|
|
|
480
473
|
|
|
481
474
|
```
|
|
482
475
|
usage: cmpnd [-h] [--version] [--api-key API_KEY] [--endpoint ENDPOINT]
|
|
483
|
-
{auth,login,logout,org,credentials,tool-servers,optimizations,programs,
|
|
476
|
+
{auth,login,logout,org,credentials,tool-servers,optimizations,programs,traces,evals,datasets,deployments,ps,status,logs,inspect,schema,sample,run,deploy,optimize}
|
|
484
477
|
...
|
|
485
478
|
|
|
486
479
|
CMPND platform CLI — deploy, run, and inspect DSPy programs.
|
|
@@ -574,7 +567,8 @@ captured input and output size limits, 65,536 bytes each by default.
|
|
|
574
567
|
|
|
575
568
|
### `cmpnd.auto_instrument()`
|
|
576
569
|
|
|
577
|
-
Registers the callback with DSPy, so every DSPy call records a trace.
|
|
570
|
+
Registers the callback with DSPy, so every DSPy call records a trace. The
|
|
571
|
+
traces carry no program and land in Unattributed.
|
|
578
572
|
|
|
579
573
|
### `cmpnd.Client()`
|
|
580
574
|
|
|
@@ -13,7 +13,7 @@ import os
|
|
|
13
13
|
from collections.abc import Sequence
|
|
14
14
|
from typing import Any, Literal
|
|
15
15
|
|
|
16
|
-
from . import _http, configuration
|
|
16
|
+
from . import _http, configuration, programs
|
|
17
17
|
|
|
18
18
|
DEFAULT_ENDPOINT = "https://platform.cmpnd.ai"
|
|
19
19
|
|
|
@@ -58,22 +58,23 @@ def list(
|
|
|
58
58
|
) -> dict[str, Any]:
|
|
59
59
|
"""List assessments by program or by trace, newest first.
|
|
60
60
|
|
|
61
|
-
Pass exactly one of ``program`` (every assessment of that program
|
|
62
|
-
``trace_id`` (only that trace's assessments). Returns
|
|
61
|
+
Pass exactly one of ``program`` (every assessment of that program, named by its
|
|
62
|
+
name or ``p_`` id) or ``trace_id`` (only that trace's assessments). Returns
|
|
63
63
|
``{"assessments": [...], "total": N, "limit", "offset", "has_more"}``.
|
|
64
64
|
``limit`` (max rows) and ``offset`` (skip this many) page the full set — the
|
|
65
65
|
server clamps ``limit`` to at most 200.
|
|
66
66
|
"""
|
|
67
67
|
if program is not None and trace_id is not None:
|
|
68
68
|
raise ValueError("pass exactly one of program or trace_id")
|
|
69
|
+
client = _client(api_key, endpoint)
|
|
69
70
|
if trace_id is not None:
|
|
70
71
|
scope: dict[str, str] = {"trace_id": trace_id}
|
|
71
72
|
elif program is not None:
|
|
72
|
-
scope = {"program": program}
|
|
73
|
+
scope = {"program": programs.program_id_for(client, program)}
|
|
73
74
|
else:
|
|
74
75
|
raise ValueError("pass exactly one of program or trace_id")
|
|
75
76
|
params = {**scope, "limit": str(limit), "offset": str(offset)}
|
|
76
|
-
return
|
|
77
|
+
return client.get("/api/v1/assessments", params=params)
|
|
77
78
|
|
|
78
79
|
|
|
79
80
|
def get(assessment_id: str, *, api_key: str | None = None, endpoint: str | None = None) -> dict[str, Any]:
|
|
@@ -4,7 +4,7 @@ The implementation of ``list`` and ``show`` over the shared ``_http`` transport.
|
|
|
4
4
|
:class:`cmpnd.Client`'s ``list_traces`` / ``get_trace`` call these directly; the
|
|
5
5
|
deprecated :mod:`cmpnd.traces` shim wraps them with a warning. The filter and
|
|
6
6
|
verb names match the ``cmpnd traces`` CLI vocabulary — ``list`` filtered by
|
|
7
|
-
``query`` / ``program`` / ``module`` / ``
|
|
7
|
+
``query`` / ``program`` / ``module`` / ``deployment`` / ``since`` / ``until`` /
|
|
8
8
|
``all_history``, and ``show`` for one trace — so the surfaces do not drift.
|
|
9
9
|
"""
|
|
10
10
|
|
|
@@ -29,7 +29,6 @@ def list(
|
|
|
29
29
|
model: str | None = None,
|
|
30
30
|
program: str | None = None,
|
|
31
31
|
module: str | None = None,
|
|
32
|
-
hash: str | None = None,
|
|
33
32
|
deployment: str | None = None,
|
|
34
33
|
metadata: dict[str, str] | None = None,
|
|
35
34
|
tags: list[str] | None = None,
|
|
@@ -49,11 +48,10 @@ def list(
|
|
|
49
48
|
``ignored_fields`` naming any request field an older server did not understand.
|
|
50
49
|
|
|
51
50
|
The filters mirror ``cmpnd traces list``: ``query`` searches indexed span text;
|
|
52
|
-
``program`` names the program by name
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
``since``;
|
|
51
|
+
``program`` names the program by its name or its program id (``p_…``);
|
|
52
|
+
``module`` is the root module's class, matched exactly. No index serves the
|
|
53
|
+
class, so without ``program`` it reads every trace in the time range and slows
|
|
54
|
+
as the range grows. Pair it with a program or a shorter ``since``;
|
|
57
55
|
``deployment`` is a deployment id; ``metadata`` is matched by containment (every
|
|
58
56
|
pair must be present); ``tags`` matches any of the given tags. ``since`` /
|
|
59
57
|
``until`` accept an ISO 8601 timestamp, a bare date (``YYYY-MM-DD``), or a
|
|
@@ -71,15 +69,14 @@ def list(
|
|
|
71
69
|
("status", status),
|
|
72
70
|
("q", query),
|
|
73
71
|
("model", model),
|
|
74
|
-
("program_name", program),
|
|
75
72
|
("module_class", module),
|
|
76
73
|
("deployment_id", deployment),
|
|
77
74
|
("tags", tags),
|
|
78
75
|
):
|
|
79
76
|
if val is not None:
|
|
80
77
|
body[wire_key] = val
|
|
81
|
-
if
|
|
82
|
-
body.update(program_filter(
|
|
78
|
+
if program is not None:
|
|
79
|
+
body.update(program_filter(program))
|
|
83
80
|
if metadata is not None:
|
|
84
81
|
body["metadata"] = dict(metadata)
|
|
85
82
|
if since is not None:
|
|
@@ -92,14 +89,14 @@ def list(
|
|
|
92
89
|
def program_filter(value: str) -> dict[str, str]:
|
|
93
90
|
"""The search-body key for one program filter value.
|
|
94
91
|
|
|
95
|
-
A program id (``p_…``) goes in ``program_id`` and
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
A program id (``p_…``) goes in ``program_id`` and anything else is a program
|
|
93
|
+
name, since no name starts with ``p_``. One helper for the CLI and the SDK,
|
|
94
|
+
so the two cannot split.
|
|
98
95
|
"""
|
|
99
96
|
text = value.strip()
|
|
100
97
|
if identity.is_program_id(text):
|
|
101
98
|
return {"program_id": text}
|
|
102
|
-
return {"
|
|
99
|
+
return {"program_name": value}
|
|
103
100
|
|
|
104
101
|
|
|
105
102
|
def show(trace_id: str, *, api_key: str | None = None, endpoint: str | None = None) -> dict[str, Any]:
|
|
@@ -15,7 +15,7 @@ import datetime
|
|
|
15
15
|
import os
|
|
16
16
|
from typing import Any
|
|
17
17
|
|
|
18
|
-
from . import _http, configuration
|
|
18
|
+
from . import _http, configuration, programs
|
|
19
19
|
|
|
20
20
|
DEFAULT_ENDPOINT = "https://platform.cmpnd.ai"
|
|
21
21
|
|
|
@@ -30,22 +30,24 @@ def default_name() -> str:
|
|
|
30
30
|
def create(
|
|
31
31
|
program: str, *, name: str | None = None, api_key: str | None = None, endpoint: str | None = None
|
|
32
32
|
) -> dict[str, Any]:
|
|
33
|
-
"""Create a
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
"""Create a valset owned by ``program``, a program name or ``p_`` id. ``name`` is auto-assigned when omitted."""
|
|
34
|
+
client = _client(api_key, endpoint)
|
|
35
|
+
payload = {"program": programs.program_id_for(client, program), "name": name or default_name()}
|
|
36
|
+
return client.post("/api/v1/valsets", json=payload)
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
def list(
|
|
39
40
|
program: str, *, limit: int = 50, offset: int = 0, api_key: str | None = None, endpoint: str | None = None
|
|
40
41
|
) -> dict[str, Any]:
|
|
41
|
-
"""List a program
|
|
42
|
+
"""List the valsets of ``program``, a program name or ``p_`` id, newest first.
|
|
42
43
|
|
|
43
44
|
Returns ``{"valsets": [...], "total": N, "limit", "offset", "has_more"}``.
|
|
44
45
|
``limit`` / ``offset`` page the full set — the server clamps ``limit`` to at
|
|
45
46
|
most 200.
|
|
46
47
|
"""
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
client = _client(api_key, endpoint)
|
|
49
|
+
params = {"program": programs.program_id_for(client, program), "limit": str(limit), "offset": str(offset)}
|
|
50
|
+
return client.get("/api/v1/valsets", params=params)
|
|
49
51
|
|
|
50
52
|
|
|
51
53
|
def get(valset_id: str, *, api_key: str | None = None, endpoint: str | None = None) -> dict[str, Any]:
|
|
@@ -39,6 +39,27 @@ def _declared_program_name() -> str | None:
|
|
|
39
39
|
return name if isinstance(name, str) else None
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
# Set once the process has warned that a trace names no program, so the warning
|
|
43
|
+
# appears once per process, whichever callback records the trace.
|
|
44
|
+
_warned_unattributed = False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _warn_once_if_unattributed(trace: models.TraceBuilder) -> None:
|
|
48
|
+
"""Warn the first time a trace in this process names neither a program name nor a program id.
|
|
49
|
+
|
|
50
|
+
Such a trace belongs to no program, and the platform files it under Unattributed.
|
|
51
|
+
"""
|
|
52
|
+
global _warned_unattributed
|
|
53
|
+
if _warned_unattributed or trace.declared_name is not None or trace.program_id is not None:
|
|
54
|
+
return
|
|
55
|
+
_warned_unattributed = True
|
|
56
|
+
logger.warning(
|
|
57
|
+
"cmpnd: this trace names no program, so the platform files it under Unattributed. Give the program a "
|
|
58
|
+
"name with @cmpnd.program(name) and run it through cmpnd.Client, and its traces go to that program. "
|
|
59
|
+
"This warning appears once per process."
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
42
63
|
def trace_id(prediction: Any) -> str | None:
|
|
43
64
|
"""Return the cmpnd trace id for a prediction returned by an instrumented program.
|
|
44
65
|
|
|
@@ -1193,6 +1214,10 @@ class _ModuleMixin(_CallbackBase):
|
|
|
1193
1214
|
module_class=instance.__class__.__name__,
|
|
1194
1215
|
)
|
|
1195
1216
|
self._stamp(trace)
|
|
1217
|
+
# A root module call is where a program runs, so it is where an unnamed one
|
|
1218
|
+
# is caught. GEPA's own LM calls (_emit_optimization_lm_trace) belong to the
|
|
1219
|
+
# optimization run, which names the program, so they never warn.
|
|
1220
|
+
_warn_once_if_unattributed(trace)
|
|
1196
1221
|
|
|
1197
1222
|
# Attach the run ids this trace starts under, from this callback's own
|
|
1198
1223
|
# state right now — not at finalize (on_module_end), where the
|
|
@@ -24,7 +24,6 @@ from .commands import (
|
|
|
24
24
|
org,
|
|
25
25
|
programs,
|
|
26
26
|
provider_credentials,
|
|
27
|
-
signatures,
|
|
28
27
|
tool_servers,
|
|
29
28
|
top,
|
|
30
29
|
traces,
|
|
@@ -164,7 +163,6 @@ def main() -> None:
|
|
|
164
163
|
# gates each read on scope, so a caller without it gets a 403, not a
|
|
165
164
|
# hidden command.
|
|
166
165
|
programs.register(subparsers)
|
|
167
|
-
signatures.register(subparsers)
|
|
168
166
|
traces.register(subparsers)
|
|
169
167
|
evals.register(subparsers)
|
|
170
168
|
datasets.register(subparsers)
|
|
@@ -56,7 +56,8 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
56
56
|
scope.add_argument(
|
|
57
57
|
"program",
|
|
58
58
|
nargs="?",
|
|
59
|
-
|
|
59
|
+
metavar="PROGRAM",
|
|
60
|
+
help="Program name or p_ id",
|
|
60
61
|
)
|
|
61
62
|
scope.add_argument("--trace", dest="trace_id", metavar="TRACE_ID", help="A single trace's assessments")
|
|
62
63
|
pagination.add_args(ls, with_page_aliases=False, default_limit=50)
|
|
@@ -88,7 +89,15 @@ def handle_create(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
88
89
|
|
|
89
90
|
|
|
90
91
|
def handle_list(client: api_client.ApiClient, args: Any) -> Any:
|
|
91
|
-
|
|
92
|
+
if args.trace_id:
|
|
93
|
+
scope = {"trace_id": args.trace_id}
|
|
94
|
+
else:
|
|
95
|
+
from . import programs
|
|
96
|
+
|
|
97
|
+
program_id, _ = programs.resolve(
|
|
98
|
+
client, args.program, old_server_remedy="Upgrade the server to name a program."
|
|
99
|
+
)
|
|
100
|
+
scope = {"program": program_id}
|
|
92
101
|
params = {**scope, **pagination.wire(args, "limit_offset", as_str=True)}
|
|
93
102
|
return client.get("/api/v1/assessments", params=params)
|
|
94
103
|
|
|
@@ -50,7 +50,7 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
50
50
|
# `evals compare` is deliberately absent. The route requires a
|
|
51
51
|
# `dataset_hash`, and no eval read returns one: neither the list nor the
|
|
52
52
|
# detail projection carries it, and nor does the compare response itself.
|
|
53
|
-
# The only surface that emits one is `
|
|
53
|
+
# The only surface that emits one is `programs evals`, so the command
|
|
54
54
|
# would ship with a required argument its own noun cannot produce. It goes
|
|
55
55
|
# in once the projection carries the hash.
|
|
56
56
|
|
|
@@ -29,17 +29,21 @@ adds one) and `switch` picks which stored key later commands use, the way the
|
|
|
29
29
|
web app switches a session's active org. `list` reads
|
|
30
30
|
/api/v1/auth/organizations and marks the orgs you hold a key for and the
|
|
31
31
|
active one. `switch` needs no network call when the org's key is already
|
|
32
|
-
stored.
|
|
33
|
-
|
|
32
|
+
stored. When none is stored for an org you are an approved member of, it
|
|
33
|
+
offers to mint one with the current key's scopes (`--mint` skips the
|
|
34
|
+
question), stores it and switches to it. `cmpnd org leave` removes your own
|
|
35
|
+
membership and drops that org's stored key.
|
|
34
36
|
|
|
35
37
|
Always registered (no env gate); the server is the authority.
|
|
36
38
|
"""
|
|
37
39
|
|
|
38
40
|
from __future__ import annotations
|
|
39
41
|
|
|
42
|
+
import socket
|
|
40
43
|
import sys
|
|
41
44
|
import urllib.parse
|
|
42
45
|
from argparse import ArgumentParser, _SubParsersAction
|
|
46
|
+
from datetime import datetime, timezone
|
|
43
47
|
from typing import Any
|
|
44
48
|
|
|
45
49
|
from .. import api_client, credentials
|
|
@@ -91,6 +95,11 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
91
95
|
|
|
92
96
|
switch = sub.add_parser("switch", help="Use the stored key for another of your organizations")
|
|
93
97
|
switch.add_argument("org", help="Organization slug or id (from `org list`)")
|
|
98
|
+
switch.add_argument(
|
|
99
|
+
"--mint",
|
|
100
|
+
action="store_true",
|
|
101
|
+
help="When no key is stored for the org, mint one with the current key's scopes without asking",
|
|
102
|
+
)
|
|
94
103
|
switch.set_defaults(func=handle_switch, _skip_resolve_config=True)
|
|
95
104
|
|
|
96
105
|
leave = sub.add_parser("leave", help="Leave one of your organizations and drop its stored key")
|
|
@@ -214,12 +223,70 @@ def handle_switch(_unused_client: api_client.ApiClient | None, args: Any) -> Non
|
|
|
214
223
|
api_key, _ = api_client.resolve_config(args)
|
|
215
224
|
except api_client.CliError:
|
|
216
225
|
raise _no_key_stored(f"'{target}'", endpoint, target) from None
|
|
217
|
-
|
|
226
|
+
client = api_client.ApiClient(api_key, endpoint)
|
|
227
|
+
for row in _memberships(client):
|
|
218
228
|
if target in (row.get("id"), row.get("slug")):
|
|
219
|
-
|
|
229
|
+
_mint_and_switch(client, row, target, ask=not getattr(args, "mint", False))
|
|
230
|
+
return
|
|
220
231
|
raise api_client.CliError(f"You are not a member of an organization '{target}' on {endpoint}.")
|
|
221
232
|
|
|
222
233
|
|
|
234
|
+
def _mint_and_switch(client: api_client.ApiClient, row: dict[str, Any], target: str, *, ask: bool) -> None:
|
|
235
|
+
"""Mint a key for a member org that has none stored, store it and switch.
|
|
236
|
+
|
|
237
|
+
The request names no scopes, so serve grants the calling key's own
|
|
238
|
+
non-admin scopes and the new key is never broader than the one in use.
|
|
239
|
+
serve refuses with 400 or 403 when the calling key cannot mint, and the
|
|
240
|
+
browser login is then the way to get the key.
|
|
241
|
+
"""
|
|
242
|
+
endpoint = client._endpoint
|
|
243
|
+
label = str(row.get("name") or row.get("slug") or target)
|
|
244
|
+
if row.get("approved") is False:
|
|
245
|
+
raise api_client.CliError(
|
|
246
|
+
f"Your membership in {label} is not approved yet, so no key can be minted for it. "
|
|
247
|
+
"An owner of the org approves it."
|
|
248
|
+
)
|
|
249
|
+
if ask:
|
|
250
|
+
if not sys.stdin.isatty():
|
|
251
|
+
raise api_client.CliError(
|
|
252
|
+
f"No key is stored for {label} on {endpoint}. Run {top.hint_org_switch(target, mint=True)} "
|
|
253
|
+
f"to mint one with the current key's scopes, or {top.hint_login(target)}."
|
|
254
|
+
)
|
|
255
|
+
try:
|
|
256
|
+
answer = input(f"No key is stored for {label}. Mint one with the current key's scopes? [Y/n] ")
|
|
257
|
+
except EOFError:
|
|
258
|
+
# Ctrl-D at the prompt declines.
|
|
259
|
+
print(file=sys.stderr)
|
|
260
|
+
answer = "n"
|
|
261
|
+
if answer.strip().lower() not in ("", "y", "yes"):
|
|
262
|
+
raise _no_key_stored(label, endpoint, target)
|
|
263
|
+
|
|
264
|
+
hostname = socket.gethostname() or "unknown"
|
|
265
|
+
name = f"CLI: {hostname} {datetime.now(timezone.utc).strftime('%Y-%m-%d')}"
|
|
266
|
+
try:
|
|
267
|
+
created = client.post("/api/v1/org/keys", json={"name": name, "org_id": row.get("id") or target})
|
|
268
|
+
except api_client.CliError as exc:
|
|
269
|
+
if exc.status_code in (400, 403):
|
|
270
|
+
raise api_client.CliError(
|
|
271
|
+
f"The current key could not mint a key for {label}: {exc}. Run {top.hint_login(target)} to store one."
|
|
272
|
+
) from exc
|
|
273
|
+
raise
|
|
274
|
+
token = created.get("token") if isinstance(created, dict) else None
|
|
275
|
+
org_id = created.get("org_id") if isinstance(created, dict) else None
|
|
276
|
+
if not isinstance(token, str) or not token or not isinstance(org_id, str) or not org_id:
|
|
277
|
+
raise api_client.CliError(f"The response did not include a key for {label}. Nothing was stored.")
|
|
278
|
+
name_out = created.get("org_name")
|
|
279
|
+
slug_out = created.get("org_slug")
|
|
280
|
+
credentials.save(
|
|
281
|
+
endpoint,
|
|
282
|
+
token,
|
|
283
|
+
org_id=org_id,
|
|
284
|
+
org_name=name_out if isinstance(name_out, str) else None,
|
|
285
|
+
org_slug=slug_out if isinstance(slug_out, str) else None,
|
|
286
|
+
)
|
|
287
|
+
print(f"Minted a key for {name_out or slug_out or org_id} ({org_id}) and switched to it on {endpoint}")
|
|
288
|
+
|
|
289
|
+
|
|
223
290
|
def _say_active_moved(endpoint: str, moved_to: str | None) -> None:
|
|
224
291
|
if moved_to:
|
|
225
292
|
k = {k.org_id: k for k in credentials.stored_keys(endpoint)}.get(moved_to)
|