cmpnd 0.23.2__tar.gz → 0.24.1__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.23.2 → cmpnd-0.24.1}/.gitignore +1 -0
- cmpnd-0.23.2/CLAUDE.md → cmpnd-0.24.1/AGENTS.md +3 -3
- cmpnd-0.24.1/CLAUDE.md +1 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/PKG-INFO +4 -2
- cmpnd-0.24.1/cmpnd/_assessments.py +72 -0
- cmpnd-0.23.2/cmpnd/traces.py → cmpnd-0.24.1/cmpnd/_traces.py +8 -19
- cmpnd-0.24.1/cmpnd/_valsets.py +107 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/__init__.py +11 -3
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/assessments.py +12 -7
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/top.py +65 -56
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/valsets.py +31 -15
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/shell.py +38 -32
- cmpnd-0.24.1/cmpnd/cli/snippet.py +52 -0
- cmpnd-0.24.1/cmpnd/cli/tree_help.py +98 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/client.py +111 -24
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/mcp_servers.py +112 -19
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/packaging.py +157 -2
- cmpnd-0.24.1/cmpnd/traces.py +35 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/docs/2026-04-02-code-review.md +1 -1
- {cmpnd-0.23.2 → cmpnd-0.24.1}/docs/cli.md +49 -18
- {cmpnd-0.23.2 → cmpnd-0.24.1}/docs/client.md +24 -8
- {cmpnd-0.23.2 → cmpnd-0.24.1}/docs/reading-traces.md +7 -2
- {cmpnd-0.23.2 → cmpnd-0.24.1}/docs/sdk-instrumentation.md +71 -2
- {cmpnd-0.23.2 → cmpnd-0.24.1}/pyproject.toml +15 -7
- {cmpnd-0.23.2 → cmpnd-0.24.1}/scripts/require_version_bump.py +1 -1
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/helpers.py +24 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_assessments_cli.py +4 -1
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_valsets_cli.py +4 -1
- cmpnd-0.24.1/tests/test_assessments_api.py +56 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_assessments.py +22 -14
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_main.py +2 -2
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_top.py +137 -0
- cmpnd-0.24.1/tests/test_cli/test_tree_help.py +71 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_valsets.py +52 -12
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_client.py +68 -4
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_mcp_tools.py +264 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_packaging_program_source.py +291 -1
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_traces_read.py +46 -26
- cmpnd-0.24.1/tests/test_valsets_api.py +98 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/uv.lock +36 -32
- {cmpnd-0.23.2 → cmpnd-0.24.1}/README.md +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/__init__.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/_gepa_patch.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/_http.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/callback.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/admin.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/deployments.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/evals.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/org.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/programs.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/provider_credentials.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/signatures.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/traces.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/trace_render.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/verbs.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/configuration.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/context.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/dataset_sync.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/datasets.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/decorators.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/deployment.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/execution.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/exporter.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/helpers.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/identity.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/imaging.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/ir/reflection.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/models.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/optimization.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/optimizers/gepa.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/optimizers/gepa_callback.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/optimizers/tracker.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/retry.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/docs/custom-proposer.md +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/examples/local_ollama.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/README.md +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/cmpnd_ingest/bodies.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/cmpnd_ingest/exporter_http.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/cmpnd_ingest/protocols.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/pyproject.toml +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/scripts/capture_review_run.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/scripts/seed_review_run.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/scripts/test-in-linux.sh +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/__init__.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/_fake_clock.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/conftest.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/committee_task.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/target_capabilities.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_composite_module.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_deploy_execute.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_deploy_programs.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_optimize_failures.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_parallel_export.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_project_attribution.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_traces_read.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_user_attribution.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/integration/__init__.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/integration/conftest.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/integration/hosted_optimize_probe.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/integration/test_logs_renders_traces.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/integration/test_unhappy_path.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/load/__init__.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/load/__main__.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/load/drain.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/load/ingest.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/load/rlm_shape.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/load/sweep.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/__init__.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/conftest.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_health.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_trace_streaming.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_traces.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/__init__.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/_provider.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/_reliability.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/_result.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/mint_bedrock_token.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_deploy_programs.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_multi_client_stress.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_optimize_client_committee.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_optimize_loop.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_optimize_loop_wasm.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_optimize_stress.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_provider_roles.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_real_provider_twins.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_reliability.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_reliability_classify.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_rlm_wasm.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_trace_search.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_callback.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_admin.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_api_client.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_deployments.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_evals.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_org.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_parity.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_programs.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_provider_credentials.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_schema_sample.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_shell.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_signatures.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_traces.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_traces_search_wire.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_configuration.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_context.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_context_propagation.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_dataset_sync.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_datasets.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_decorators.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_deploy.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_eval.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_eval_example_attribution.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_eval_start.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_eval_trace_grouping.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_execute.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_exporter.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_exporter_queue.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_exporter_real.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_identity.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_imaging.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_import_weight.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_ingest_conformance.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_integration.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_interpreter_callback.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_lm_usage_shapes.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_models.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_optimize.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_optimize_lms.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_optimize_reflection_lm.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_packaging.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_packaging_mcp_tools.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_poll_until.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_program_lineage_persist.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_project_payload.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_reflector_properties.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_reflector_smoke.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_repro_reactv2_bugs.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_retry.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_rlm_patch.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_schemas.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_trace_render.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_tracked_gepa.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_truncation.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_xxh64.py +0 -0
- {cmpnd-0.23.2 → cmpnd-0.24.1}/todo.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Agent Instructions
|
|
2
2
|
|
|
3
|
-
Part of the CMPND monorepo. See `../
|
|
3
|
+
Part of the CMPND monorepo. See `../AGENTS.md` for cross-cutting rules (hash architecture, null handling, testing philosophy).
|
|
4
4
|
|
|
5
5
|
## This code is released publicly — no private references
|
|
6
6
|
|
|
@@ -104,7 +104,7 @@ code while the top-level `docs/` rotted: **the docs are part of the diff.**
|
|
|
104
104
|
exporter transport, the IR/hash, the optimizer tracker), or changes the wire
|
|
105
105
|
shape the SDK emits **updates the matching doc in the same commit.** If a
|
|
106
106
|
change leaves a doc describing the old behavior, the change isn't done.
|
|
107
|
-
- The `cmpnd-*` skills under
|
|
107
|
+
- The `cmpnd-*` skills under `../.agents/skills/` point at these docs rather than
|
|
108
108
|
duplicating them — keep the doc authoritative and let the skill be the thin
|
|
109
109
|
trigger. (Anchor paths are checked by `bin/check-skill-anchors`.)
|
|
110
110
|
- `docs/plans/*` is append-only design history and is exempt — don't rewrite it.
|
cmpnd-0.24.1/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cmpnd
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.24.1
|
|
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>
|
|
@@ -22,6 +22,7 @@ Requires-Dist: httpx>=0.27.0
|
|
|
22
22
|
Requires-Dist: ipython>=8.20
|
|
23
23
|
Requires-Dist: openai<2
|
|
24
24
|
Requires-Dist: pydantic>=2.0
|
|
25
|
+
Requires-Dist: typing-extensions>=4.1.0
|
|
25
26
|
Requires-Dist: xxhash>=3.0.0
|
|
26
27
|
Provides-Extra: dev
|
|
27
28
|
Requires-Dist: basedpyright>=1.39; extra == 'dev'
|
|
@@ -41,7 +42,8 @@ Requires-Dist: cmpnd-fake-provider; (python_version >= '3.11') and extra == 'fak
|
|
|
41
42
|
Provides-Extra: images
|
|
42
43
|
Requires-Dist: pillow>=10.0; extra == 'images'
|
|
43
44
|
Provides-Extra: mcp
|
|
44
|
-
Requires-Dist:
|
|
45
|
+
Requires-Dist: dspy>=3.3.1; extra == 'mcp'
|
|
46
|
+
Requires-Dist: mcp>=1.9; extra == 'mcp'
|
|
45
47
|
Description-Content-Type: text/markdown
|
|
46
48
|
|
|
47
49
|
# cmpnd
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Assessment HTTP calls behind ``cmpnd.Client``'s assessment methods.
|
|
2
|
+
|
|
3
|
+
Private helpers: the public surface is ``create_assessment`` /
|
|
4
|
+
``list_assessments`` / ``get_assessment`` on :class:`cmpnd.Client`, which
|
|
5
|
+
delegate here with the client's credentials. Each function reaches one backend
|
|
6
|
+
route over the shared ``_http`` transport. An omitted ``conclusion`` is
|
|
7
|
+
``"neutral"`` — neither affirming nor disputing the trace.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
from collections.abc import Sequence
|
|
14
|
+
from typing import Any, Literal
|
|
15
|
+
|
|
16
|
+
from . import _http, configuration
|
|
17
|
+
|
|
18
|
+
DEFAULT_ENDPOINT = "https://platform.cmpnd.ai"
|
|
19
|
+
|
|
20
|
+
Conclusion = Literal["up", "down", "neutral"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create(
|
|
24
|
+
trace_id: str,
|
|
25
|
+
*,
|
|
26
|
+
conclusion: Conclusion = "neutral",
|
|
27
|
+
body: str | None = None,
|
|
28
|
+
annotations: Sequence[tuple[str, str]] | None = None,
|
|
29
|
+
api_key: str | None = None,
|
|
30
|
+
endpoint: str | None = None,
|
|
31
|
+
) -> dict[str, Any]:
|
|
32
|
+
"""Record one judgement of a trace and return the created assessment.
|
|
33
|
+
|
|
34
|
+
``conclusion`` affirms (``"up"``), disputes (``"down"``), or neither
|
|
35
|
+
(``"neutral"``, the default). ``annotations`` are ``(span_id, note)`` pairs,
|
|
36
|
+
each attaching a note to one span of the trace; the server rejects a span id
|
|
37
|
+
that does not belong to the trace. ``api_key`` / ``endpoint`` override the
|
|
38
|
+
configured credentials for this call.
|
|
39
|
+
"""
|
|
40
|
+
payload: dict[str, Any] = {"trace_id": trace_id, "conclusion": conclusion}
|
|
41
|
+
if body is not None:
|
|
42
|
+
payload["body"] = body
|
|
43
|
+
if annotations:
|
|
44
|
+
payload["annotations"] = [{"span_id": span, "note": note} for span, note in annotations]
|
|
45
|
+
return _client(api_key, endpoint).post("/api/v1/assessments", json=payload)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def list(
|
|
49
|
+
program: str, *, limit: int = 50, offset: int = 0, api_key: str | None = None, endpoint: str | None = None
|
|
50
|
+
) -> dict[str, Any]:
|
|
51
|
+
"""List a program's assessments, newest first.
|
|
52
|
+
|
|
53
|
+
Returns ``{"assessments": [...], "total": N, "limit", "offset", "has_more"}``.
|
|
54
|
+
``limit`` (max rows) and ``offset`` (skip this many) page the full set — the
|
|
55
|
+
server clamps ``limit`` to at most 200.
|
|
56
|
+
"""
|
|
57
|
+
params = {"program": program, "limit": str(limit), "offset": str(offset)}
|
|
58
|
+
return _client(api_key, endpoint).get("/api/v1/assessments", params=params)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get(assessment_id: str, *, api_key: str | None = None, endpoint: str | None = None) -> dict[str, Any]:
|
|
62
|
+
"""Fetch one assessment with its annotations."""
|
|
63
|
+
return _client(api_key, endpoint).get(f"/api/v1/assessments/{assessment_id}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _client(api_key: str | None, endpoint: str | None) -> _http.ApiClient:
|
|
67
|
+
cfg = configuration.get_config()
|
|
68
|
+
resolved_key = api_key or (cfg.api_key if cfg else None) or os.getenv("CMPND_API_KEY")
|
|
69
|
+
if not resolved_key:
|
|
70
|
+
raise _http.ApiError("No API key. Pass api_key=, call cmpnd.configure(api_key=...), or set CMPND_API_KEY.")
|
|
71
|
+
resolved_endpoint = endpoint or (cfg.endpoint if cfg else None) or os.getenv("CMPND_ENDPOINT") or DEFAULT_ENDPOINT
|
|
72
|
+
return _http.ApiClient(resolved_key, resolved_endpoint)
|
|
@@ -1,22 +1,11 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
>>> import cmpnd
|
|
10
|
-
>>> cmpnd.configure(api_key="ct_xxx")
|
|
11
|
-
>>> page = cmpnd.traces.list(status="error", since="30d", limit=10)
|
|
12
|
-
>>> for row in page["traces"]:
|
|
13
|
-
... detail = cmpnd.traces.show(row["trace_id"])
|
|
14
|
-
|
|
15
|
-
The verbs and filter names match the ``cmpnd traces`` CLI vocabulary — ``list`` and
|
|
16
|
-
``show``, filtered by ``query``, ``program``, ``hash``, ``deployment``, ``since`` /
|
|
17
|
-
``until``, ``all_history`` — so a user moves between the two surfaces without
|
|
18
|
-
relearning names. (The CLI's ``traces search`` spelling is a deprecated alias of
|
|
19
|
-
``traces list``; this surface offers only ``list``.)
|
|
1
|
+
"""Trace-read HTTP calls behind ``cmpnd.Client``'s trace methods.
|
|
2
|
+
|
|
3
|
+
The implementation of ``list`` and ``show`` over the shared ``_http`` transport.
|
|
4
|
+
:class:`cmpnd.Client`'s ``list_traces`` / ``get_trace`` call these directly; the
|
|
5
|
+
deprecated :mod:`cmpnd.traces` shim wraps them with a warning. The filter and
|
|
6
|
+
verb names match the ``cmpnd traces`` CLI vocabulary — ``list`` filtered by
|
|
7
|
+
``query`` / ``program`` / ``hash`` / ``deployment`` / ``since`` / ``until`` /
|
|
8
|
+
``all_history``, and ``show`` for one trace — so the surfaces do not drift.
|
|
20
9
|
"""
|
|
21
10
|
|
|
22
11
|
from __future__ import annotations
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Validation-set HTTP calls behind ``cmpnd.Client``'s valset methods.
|
|
2
|
+
|
|
3
|
+
Private helpers: the public surface is ``create_valset`` / ``list_valsets`` /
|
|
4
|
+
``get_valset`` / ``list_valset_entries`` / ``create_valset_entry`` /
|
|
5
|
+
``delete_valset_entry`` on :class:`cmpnd.Client`, which delegate here with the
|
|
6
|
+
client's credentials. Each function reaches one backend route over the shared
|
|
7
|
+
``_http`` transport. An entry's input comes from exactly one source — an
|
|
8
|
+
assessment's trace (``from_assessment``) or a JSON ``inputs`` object; the
|
|
9
|
+
expected output is always the caller's.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import datetime
|
|
15
|
+
import os
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from . import _http, configuration
|
|
19
|
+
|
|
20
|
+
DEFAULT_ENDPOINT = "https://platform.cmpnd.ai"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def default_name() -> str:
|
|
24
|
+
"""The valset name assigned when the caller omits one — a human-readable
|
|
25
|
+
date label. Names are not required to be unique (the id identifies a
|
|
26
|
+
valset), so a repeated name is fine and needs no collision suffix."""
|
|
27
|
+
return f"valset-{datetime.datetime.now():%Y%m%d-%H%M%S}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def create(
|
|
31
|
+
program: str, *, name: str | None = None, api_key: str | None = None, endpoint: str | None = None
|
|
32
|
+
) -> dict[str, Any]:
|
|
33
|
+
"""Create a program-owned valset. ``name`` is auto-assigned when omitted."""
|
|
34
|
+
payload = {"program": program, "name": name or default_name()}
|
|
35
|
+
return _client(api_key, endpoint).post("/api/v1/valsets", json=payload)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def list(
|
|
39
|
+
program: str, *, limit: int = 50, offset: int = 0, api_key: str | None = None, endpoint: str | None = None
|
|
40
|
+
) -> dict[str, Any]:
|
|
41
|
+
"""List a program's valsets, newest first.
|
|
42
|
+
|
|
43
|
+
Returns ``{"valsets": [...], "total": N, "limit", "offset", "has_more"}``.
|
|
44
|
+
``limit`` / ``offset`` page the full set — the server clamps ``limit`` to at
|
|
45
|
+
most 200.
|
|
46
|
+
"""
|
|
47
|
+
params = {"program": program, "limit": str(limit), "offset": str(offset)}
|
|
48
|
+
return _client(api_key, endpoint).get("/api/v1/valsets", params=params)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get(valset_id: str, *, api_key: str | None = None, endpoint: str | None = None) -> dict[str, Any]:
|
|
52
|
+
"""Fetch one valset with its field shape and current version."""
|
|
53
|
+
return _client(api_key, endpoint).get(f"/api/v1/valsets/{valset_id}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def list_entries(
|
|
57
|
+
valset_id: str, *, limit: int = 50, offset: int = 0, api_key: str | None = None, endpoint: str | None = None
|
|
58
|
+
) -> dict[str, Any]:
|
|
59
|
+
"""List a valset's active entries.
|
|
60
|
+
|
|
61
|
+
Returns ``{"entries": [...], "total": N, "limit", "offset", "has_more"}``.
|
|
62
|
+
``limit`` / ``offset`` page the full set — the server clamps ``limit`` to at
|
|
63
|
+
most 200.
|
|
64
|
+
"""
|
|
65
|
+
params = {"limit": str(limit), "offset": str(offset)}
|
|
66
|
+
return _client(api_key, endpoint).get(f"/api/v1/valsets/{valset_id}/entries", params=params)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def create_entry(
|
|
70
|
+
valset_id: str,
|
|
71
|
+
*,
|
|
72
|
+
expected_outputs: dict[str, Any],
|
|
73
|
+
inputs: dict[str, Any] | None = None,
|
|
74
|
+
from_assessment: str | None = None,
|
|
75
|
+
api_key: str | None = None,
|
|
76
|
+
endpoint: str | None = None,
|
|
77
|
+
) -> dict[str, Any]:
|
|
78
|
+
"""Add one entry to a valset.
|
|
79
|
+
|
|
80
|
+
The input comes from exactly one source: pass ``from_assessment`` and the
|
|
81
|
+
server derives the inputs from that assessment's trace, or pass ``inputs``
|
|
82
|
+
directly. Supplying both or neither is an error. ``expected_outputs`` is
|
|
83
|
+
always required — the server never infers it.
|
|
84
|
+
"""
|
|
85
|
+
if (inputs is None) == (from_assessment is None):
|
|
86
|
+
raise _http.ApiError("pass exactly one of inputs= or from_assessment=")
|
|
87
|
+
payload: dict[str, Any] = {"expected_outputs": expected_outputs}
|
|
88
|
+
if from_assessment is not None:
|
|
89
|
+
payload["from_assessment"] = from_assessment
|
|
90
|
+
else:
|
|
91
|
+
payload["inputs"] = inputs
|
|
92
|
+
return _client(api_key, endpoint).post(f"/api/v1/valsets/{valset_id}/entries", json=payload)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def delete_entry(entry_id: str, *, api_key: str | None = None, endpoint: str | None = None) -> dict[str, Any]:
|
|
96
|
+
"""Retire an entry (versioned, not a hard delete)."""
|
|
97
|
+
_client(api_key, endpoint).delete(f"/api/v1/valsets/entries/{entry_id}")
|
|
98
|
+
return {"deleted": entry_id}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _client(api_key: str | None, endpoint: str | None) -> _http.ApiClient:
|
|
102
|
+
cfg = configuration.get_config()
|
|
103
|
+
resolved_key = api_key or (cfg.api_key if cfg else None) or os.getenv("CMPND_API_KEY")
|
|
104
|
+
if not resolved_key:
|
|
105
|
+
raise _http.ApiError("No API key. Pass api_key=, call cmpnd.configure(api_key=...), or set CMPND_API_KEY.")
|
|
106
|
+
resolved_endpoint = endpoint or (cfg.endpoint if cfg else None) or os.getenv("CMPND_ENDPOINT") or DEFAULT_ENDPOINT
|
|
107
|
+
return _http.ApiClient(resolved_key, resolved_endpoint)
|
|
@@ -7,10 +7,11 @@ import importlib.metadata
|
|
|
7
7
|
import json
|
|
8
8
|
import os
|
|
9
9
|
import sys
|
|
10
|
+
import typing
|
|
10
11
|
|
|
11
12
|
import httpx
|
|
12
13
|
|
|
13
|
-
from . import api_client, deprecation
|
|
14
|
+
from . import api_client, deprecation, tree_help
|
|
14
15
|
from .commands import (
|
|
15
16
|
admin,
|
|
16
17
|
assessments,
|
|
@@ -87,7 +88,7 @@ Auth: set CMPND_API_KEY and (optionally) CMPND_ENDPOINT, or pass --api-key /
|
|
|
87
88
|
|
|
88
89
|
|
|
89
90
|
def main() -> None:
|
|
90
|
-
parser =
|
|
91
|
+
parser = tree_help.TreeHelpParser(
|
|
91
92
|
prog="cmpnd",
|
|
92
93
|
description="CMPND platform CLI — deploy, invoke, and inspect DSPy programs.",
|
|
93
94
|
epilog=_EPILOG,
|
|
@@ -97,7 +98,14 @@ def main() -> None:
|
|
|
97
98
|
parser.add_argument("--api-key", dest="api_key", help="API key (or set CMPND_API_KEY)")
|
|
98
99
|
parser.add_argument("--endpoint", help="API endpoint (or set CMPND_ENDPOINT)")
|
|
99
100
|
|
|
100
|
-
|
|
101
|
+
# The root parser is a TreeHelpParser, so add_subparsers() is typed
|
|
102
|
+
# _SubParsersAction[TreeHelpParser]; the command modules' register() take
|
|
103
|
+
# _SubParsersAction[ArgumentParser], and that generic is invariant. A
|
|
104
|
+
# TreeHelpParser IS an ArgumentParser at runtime, so cast to the base.
|
|
105
|
+
subparsers = typing.cast(
|
|
106
|
+
"argparse._SubParsersAction[argparse.ArgumentParser]",
|
|
107
|
+
parser.add_subparsers(dest="resource"),
|
|
108
|
+
)
|
|
101
109
|
|
|
102
110
|
# Auth subcommand (part of the primary flow, always available)
|
|
103
111
|
auth.register(subparsers)
|
|
@@ -9,7 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
from argparse import ArgumentParser, _SubParsersAction
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
-
from .. import api_client
|
|
12
|
+
from .. import api_client, pagination
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
@@ -18,7 +18,7 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
18
18
|
|
|
19
19
|
create = sub.add_parser("create", help="Record a judgement of a trace")
|
|
20
20
|
create.add_argument("trace_id", help="The trace to assess (its SDK trace id)")
|
|
21
|
-
conclusion = create.add_mutually_exclusive_group(
|
|
21
|
+
conclusion = create.add_mutually_exclusive_group()
|
|
22
22
|
conclusion.add_argument(
|
|
23
23
|
"--up", dest="conclusion", action="store_const", const="up", help="Affirm the trace's output"
|
|
24
24
|
)
|
|
@@ -26,8 +26,13 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
26
26
|
"--down", dest="conclusion", action="store_const", const="down", help="Dispute the trace's output"
|
|
27
27
|
)
|
|
28
28
|
conclusion.add_argument(
|
|
29
|
-
"--neutral",
|
|
29
|
+
"--neutral",
|
|
30
|
+
dest="conclusion",
|
|
31
|
+
action="store_const",
|
|
32
|
+
const="neutral",
|
|
33
|
+
help="Neither affirm nor dispute (the default when no conclusion is given)",
|
|
30
34
|
)
|
|
35
|
+
create.set_defaults(conclusion="neutral")
|
|
31
36
|
create.add_argument("-m", "--message", dest="body", help="The prose body of the judgement")
|
|
32
37
|
create.add_argument(
|
|
33
38
|
"--annotate",
|
|
@@ -40,9 +45,8 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
40
45
|
create.set_defaults(func=handle_create)
|
|
41
46
|
|
|
42
47
|
ls = sub.add_parser("list", help="List a program's assessments")
|
|
43
|
-
ls.add_argument(
|
|
44
|
-
|
|
45
|
-
)
|
|
48
|
+
ls.add_argument("program", help="Program signature hash (16-hex, as `cmpnd signatures list` shows)")
|
|
49
|
+
pagination.add_args(ls, with_page_aliases=False, default_limit=50)
|
|
46
50
|
ls.set_defaults(func=handle_list)
|
|
47
51
|
|
|
48
52
|
show = sub.add_parser("show", help="Show one assessment with its annotations")
|
|
@@ -60,7 +64,8 @@ def handle_create(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
60
64
|
|
|
61
65
|
|
|
62
66
|
def handle_list(client: api_client.ApiClient, args: Any) -> Any:
|
|
63
|
-
|
|
67
|
+
params = {"program": args.program, **pagination.wire(args, "limit_offset", as_str=True)}
|
|
68
|
+
return client.get("/api/v1/assessments", params=params)
|
|
64
69
|
|
|
65
70
|
|
|
66
71
|
def handle_show(client: api_client.ApiClient, args: Any) -> Any:
|
|
@@ -15,7 +15,7 @@ from argparse import ArgumentParser, ArgumentTypeError, _SubParsersAction
|
|
|
15
15
|
from datetime import datetime, timezone
|
|
16
16
|
from typing import Any
|
|
17
17
|
|
|
18
|
-
from .. import api_client, pagination
|
|
18
|
+
from .. import api_client, pagination, snippet
|
|
19
19
|
from . import signatures
|
|
20
20
|
|
|
21
21
|
# --------------------------------------------------------------------------
|
|
@@ -782,30 +782,35 @@ def cmd_deploy(client: api_client.ApiClient, args: Any) -> None:
|
|
|
782
782
|
|
|
783
783
|
configuration.configure(api_key=client._api_key, endpoint=client._endpoint)
|
|
784
784
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
program = namespace.get("program")
|
|
792
|
-
if program is None:
|
|
793
|
-
# Fallback: pick the last dspy.Module instance defined.
|
|
785
|
+
# The whole packaging step stays inside the registration, not just the exec:
|
|
786
|
+
# both the annotations DSPy resolves and the class sources the packager
|
|
787
|
+
# inlines are read back out of `sys.modules` after the snippet has run.
|
|
788
|
+
with snippet.as_module("__cmpnd_deploy__", source_label) as module:
|
|
789
|
+
namespace: dict[str, Any] = module.__dict__
|
|
794
790
|
try:
|
|
795
|
-
|
|
796
|
-
except
|
|
797
|
-
raise api_client.CliError("Snippet
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
791
|
+
exec(compile(source, source_label, "exec"), namespace)
|
|
792
|
+
except Exception as exc:
|
|
793
|
+
raise api_client.CliError(f"Snippet raised {type(exc).__name__}: {exc}")
|
|
794
|
+
|
|
795
|
+
program = namespace.get("program")
|
|
796
|
+
if program is None:
|
|
797
|
+
# Fallback: pick the last dspy.Module instance defined.
|
|
798
|
+
try:
|
|
799
|
+
import dspy
|
|
800
|
+
except ImportError:
|
|
801
|
+
raise api_client.CliError("Snippet did not bind `program`, and dspy isn't importable to scan for one.")
|
|
802
|
+
candidates = [v for v in namespace.values() if isinstance(v, dspy.Module)]
|
|
803
|
+
if not candidates:
|
|
804
|
+
raise api_client.CliError(
|
|
805
|
+
"Snippet did not bind `program` to a dspy.Module instance. Add e.g. `program = dspy.Predict(...)`."
|
|
806
|
+
)
|
|
807
|
+
program = candidates[-1]
|
|
808
|
+
|
|
809
|
+
metadata = _parse_deploy_metadata(getattr(args, "metadata", None))
|
|
804
810
|
|
|
805
|
-
|
|
811
|
+
print(_c("deploying…", "dim"), file=sys.stderr)
|
|
812
|
+
deployed = deployment.deploy(program, metadata=metadata)
|
|
806
813
|
|
|
807
|
-
print(_c("deploying…", "dim"), file=sys.stderr)
|
|
808
|
-
deployed = deployment.deploy(program, metadata=metadata)
|
|
809
814
|
deployment_id = deployed.deployment_id
|
|
810
815
|
print(deployment_id)
|
|
811
816
|
|
|
@@ -882,42 +887,46 @@ def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
882
887
|
|
|
883
888
|
configuration.configure(api_key=client._api_key, endpoint=client._endpoint)
|
|
884
889
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
+
# Registered for the whole run, not just the exec: optimize deploys the
|
|
891
|
+
# program, and packaging reads the snippet's classes back through
|
|
892
|
+
# `sys.modules`. See `cmpnd.cli.snippet`.
|
|
893
|
+
with snippet.as_module("__cmpnd_optimize__", source_label) as module:
|
|
894
|
+
namespace: dict[str, Any] = module.__dict__
|
|
895
|
+
try:
|
|
896
|
+
exec(compile(source, source_label, "exec"), namespace)
|
|
897
|
+
except Exception as exc:
|
|
898
|
+
raise api_client.CliError(f"Snippet raised {type(exc).__name__}: {exc}")
|
|
890
899
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
900
|
+
missing = [name for name in ("program", "trainset", "valset", "metric") if namespace.get(name) is None]
|
|
901
|
+
if missing:
|
|
902
|
+
raise api_client.CliError(
|
|
903
|
+
f"Snippet must bind {missing} — e.g. `program = dspy.Predict('q -> a')`, "
|
|
904
|
+
"`trainset = [...]`, `valset = [...]`, "
|
|
905
|
+
"`def metric(gold, pred, trace, pred_name, pred_trace): ...` returning "
|
|
906
|
+
"`dspy.Prediction(score=, feedback=)`."
|
|
907
|
+
)
|
|
899
908
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
909
|
+
print(_c(f"optimizing with {args.optimizer}…", "dim"), file=sys.stderr)
|
|
910
|
+
try:
|
|
911
|
+
result = optimization.optimize(
|
|
912
|
+
namespace["program"],
|
|
913
|
+
optimizer=args.optimizer,
|
|
914
|
+
trainset=namespace["trainset"],
|
|
915
|
+
valset=namespace["valset"],
|
|
916
|
+
metric=namespace["metric"],
|
|
917
|
+
deployment_id=getattr(args, "deployment_id", None),
|
|
918
|
+
# The flag is named for what a command line can carry — a model name,
|
|
919
|
+
# never an LM object — and the parameter for what the Python surface
|
|
920
|
+
# accepts. Each layer's name says what that layer takes.
|
|
921
|
+
reflection_lm=getattr(args, "reflection_model", None),
|
|
922
|
+
max_metric_calls=getattr(args, "max_metric_calls", None),
|
|
923
|
+
auto=getattr(args, "auto", None),
|
|
924
|
+
max_steps=getattr(args, "max_steps", 10),
|
|
925
|
+
patience=getattr(args, "patience", 2),
|
|
926
|
+
seed=getattr(args, "seed", 0),
|
|
927
|
+
)
|
|
928
|
+
except (optimization.OptimizeError, optimization.OptimizeFailed, ValueError) as exc:
|
|
929
|
+
raise api_client.CliError(str(exc))
|
|
921
930
|
|
|
922
931
|
if getattr(args, "json", False):
|
|
923
932
|
print(
|
|
@@ -11,7 +11,8 @@ import json
|
|
|
11
11
|
from argparse import ArgumentParser, _SubParsersAction
|
|
12
12
|
from typing import Any
|
|
13
13
|
|
|
14
|
-
from
|
|
14
|
+
from ... import _valsets
|
|
15
|
+
from .. import api_client, pagination
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
@@ -20,11 +21,15 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
20
21
|
|
|
21
22
|
create = sub.add_parser("create", help="Create a validation set owned by a program")
|
|
22
23
|
create.add_argument("program_hash", help="Owning program signature hash (16-hex, as `cmpnd signatures list` shows)")
|
|
23
|
-
create.add_argument(
|
|
24
|
+
create.add_argument(
|
|
25
|
+
"--name",
|
|
26
|
+
help="Validation-set name — a memory-aid label, need not be unique; auto-assigned from the date when omitted",
|
|
27
|
+
)
|
|
24
28
|
create.set_defaults(func=handle_create)
|
|
25
29
|
|
|
26
30
|
ls = sub.add_parser("list", help="List a program's validation sets")
|
|
27
|
-
ls.add_argument("
|
|
31
|
+
ls.add_argument("program", help="Program signature hash (16-hex)")
|
|
32
|
+
pagination.add_args(ls, with_page_aliases=False, default_limit=50)
|
|
28
33
|
ls.set_defaults(func=handle_list)
|
|
29
34
|
|
|
30
35
|
show = sub.add_parser("show", help="Show a validation set")
|
|
@@ -36,12 +41,20 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
36
41
|
|
|
37
42
|
elist = esub.add_parser("list", help="List a valset's active entries")
|
|
38
43
|
elist.add_argument("valset_id", help="Valset id")
|
|
44
|
+
pagination.add_args(elist, with_page_aliases=False, default_limit=50)
|
|
39
45
|
elist.set_defaults(func=handle_entries_list)
|
|
40
46
|
|
|
41
|
-
ecreate = esub.add_parser("create", help="Add an entry from JSON
|
|
47
|
+
ecreate = esub.add_parser("create", help="Add an entry from an assessment or from JSON inputs")
|
|
42
48
|
ecreate.add_argument("valset_id", help="Valset id")
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
# The entry's input comes from exactly one source: an assessment's trace, or
|
|
50
|
+
# a JSON inputs object supplied directly. The expected output is always the
|
|
51
|
+
# caller's — the server never infers it.
|
|
52
|
+
source = ecreate.add_mutually_exclusive_group(required=True)
|
|
53
|
+
source.add_argument("--inputs", metavar="JSON", help="Inputs object; its keys are the valset's input fields")
|
|
54
|
+
source.add_argument(
|
|
55
|
+
"--from-assessment",
|
|
56
|
+
dest="from_assessment",
|
|
57
|
+
help="Source assessment id; its trace supplies the inputs and records the entry's provenance",
|
|
45
58
|
)
|
|
46
59
|
ecreate.add_argument(
|
|
47
60
|
"--expected-outputs",
|
|
@@ -50,9 +63,6 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
50
63
|
metavar="JSON",
|
|
51
64
|
help="Expected-outputs object; its keys are the valset's output fields",
|
|
52
65
|
)
|
|
53
|
-
ecreate.add_argument(
|
|
54
|
-
"--from-assessment", dest="from_assessment", help="Source assessment id, recorded as the entry's provenance"
|
|
55
|
-
)
|
|
56
66
|
ecreate.set_defaults(func=handle_entries_create)
|
|
57
67
|
|
|
58
68
|
edelete = esub.add_parser("delete", help="Retire an entry (versioned, not a hard delete)")
|
|
@@ -61,11 +71,15 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
61
71
|
|
|
62
72
|
|
|
63
73
|
def handle_create(client: api_client.ApiClient, args: Any) -> Any:
|
|
64
|
-
|
|
74
|
+
# One generator (cmpnd._valsets.default_name) for both the CLI and the
|
|
75
|
+
# Client, so the collision-avoiding suffix can't drift between them.
|
|
76
|
+
name = args.name or _valsets.default_name()
|
|
77
|
+
return client.post("/api/v1/valsets", json={"program": args.program_hash, "name": name})
|
|
65
78
|
|
|
66
79
|
|
|
67
80
|
def handle_list(client: api_client.ApiClient, args: Any) -> Any:
|
|
68
|
-
|
|
81
|
+
params = {"program": args.program, **pagination.wire(args, "limit_offset", as_str=True)}
|
|
82
|
+
return client.get("/api/v1/valsets", params=params)
|
|
69
83
|
|
|
70
84
|
|
|
71
85
|
def handle_show(client: api_client.ApiClient, args: Any) -> Any:
|
|
@@ -73,15 +87,17 @@ def handle_show(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
73
87
|
|
|
74
88
|
|
|
75
89
|
def handle_entries_list(client: api_client.ApiClient, args: Any) -> Any:
|
|
76
|
-
|
|
90
|
+
params = pagination.wire(args, "limit_offset", as_str=True)
|
|
91
|
+
return client.get(f"/api/v1/valsets/{args.valset_id}/entries", params=params)
|
|
77
92
|
|
|
78
93
|
|
|
79
94
|
def handle_entries_create(client: api_client.ApiClient, args: Any) -> Any:
|
|
80
|
-
|
|
81
|
-
expected = _parse_json_flag("--expected-outputs", args.expected_outputs)
|
|
82
|
-
body: dict[str, Any] = {"inputs": inputs, "expected_outputs": expected}
|
|
95
|
+
body: dict[str, Any] = {"expected_outputs": _parse_json_flag("--expected-outputs", args.expected_outputs)}
|
|
83
96
|
if args.from_assessment:
|
|
97
|
+
# The source assessment's trace supplies the inputs; the server derives them.
|
|
84
98
|
body["from_assessment"] = args.from_assessment
|
|
99
|
+
else:
|
|
100
|
+
body["inputs"] = _parse_json_flag("--inputs", args.inputs)
|
|
85
101
|
return client.post(f"/api/v1/valsets/{args.valset_id}/entries", json=body)
|
|
86
102
|
|
|
87
103
|
|