cmpnd 0.40.0__tar.gz → 0.41.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.40.0 → cmpnd-0.41.0}/PKG-INFO +5 -2
- {cmpnd-0.40.0 → cmpnd-0.41.0}/README.md +1 -1
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/callback.py +11 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/commands/top.py +13 -19
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/shell.py +3 -3
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/deployment.py +40 -18
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/optimization.py +10 -22
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/programs.py +17 -1
- {cmpnd-0.40.0 → cmpnd-0.41.0}/docs/cli.md +9 -4
- {cmpnd-0.40.0 → cmpnd-0.41.0}/docs/sdk-instrumentation.md +19 -16
- {cmpnd-0.40.0 → cmpnd-0.41.0}/pyproject.toml +8 -1
- cmpnd-0.41.0/scripts/check-pip-install.sh +34 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/load/rlm_size_report.py +10 -1
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_shell.py +12 -6
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_top.py +21 -12
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_deploy_program_id.py +56 -47
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_interpreter_callback.py +31 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_programs_create.py +15 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/uv.lock +5 -1
- {cmpnd-0.40.0 → cmpnd-0.41.0}/.gitignore +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/AGENTS.md +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/CLAUDE.md +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/__init__.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/_assessments.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/_authored_import.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/_deployments.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/_gepa_patch.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/_http.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/_runtime_tools.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/_traces.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/_valsets.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/authored.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/__init__.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/commands/admin.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/commands/assessments.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/commands/deployments.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/commands/evals.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/commands/org.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/commands/programs.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/commands/signatures.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/commands/traces.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/commands/valsets.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/trace_render.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/tree_help.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/cli/verbs.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/client.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/configuration.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/context.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/dataset_sync.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/datasets.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/decorators.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/deployment_metadata.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/deployment_runs.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/execution.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/exporter.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/helpers.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/identity.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/imaging.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/ir/reflection.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/lm_configuration.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/mcp_servers.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/models.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/optimizers/gepa.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/optimizers/gepa_callback.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/optimizers/tracker.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/packaging.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/references.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/retry.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/cmpnd/traces.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/docs/2026-04-02-code-review.md +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/docs/client.md +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/docs/custom-proposer.md +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/docs/reading-traces.md +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/examples/local_ollama.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/ingest/README.md +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/ingest/cmpnd_ingest/bodies.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/ingest/cmpnd_ingest/protocols.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/ingest/pyproject.toml +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/scripts/capture_review_run.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/scripts/release-stress.sh +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/scripts/require_version_bump.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/scripts/seed_review_run.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/scripts/test-in-linux.sh +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/stubs/dspy/utils/dummies.pyi +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/__init__.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/_deployment_signature_helpers.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/_fake_clock.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/_resolve_refs.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/_traced_run.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/conftest.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/committee_task.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/helpers.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/target_capabilities.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_assessments_cli.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_composite_module.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_deploy_execute.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_deploy_pandas.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_deploy_programs.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_optimize_failures.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_parallel_export.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_project_attribution.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_traces_read.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_user_attribution.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/e2e/test_valsets_cli.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/integration/__init__.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/integration/authored_optimize/judge.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/integration/conftest.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/integration/hosted_optimize_probe.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/integration/test_logs_renders_traces.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/integration/test_unhappy_path.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/load/__init__.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/load/__main__.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/load/drain.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/load/ingest.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/load/rlm_shape.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/load/sweep.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/load/test_rlm_size_report.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/parity/__init__.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/parity/conftest.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/parity/test_health.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/parity/test_trace_streaming.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/parity/test_traces.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/span_reference_fixture.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/stress/__init__.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/stress/_provider.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/stress/_readiness.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/stress/_reliability.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/stress/_result.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/stress/mint_bedrock_token.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/stress/test_deploy_programs.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/stress/test_multi_client_stress.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/stress/test_optimize_client_committee.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/stress/test_optimize_loop.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/stress/test_optimize_stress.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/stress/test_provider_roles.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/stress/test_readiness.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/stress/test_real_provider_twins.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/stress/test_reliability.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/stress/test_reliability_classify.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/stress/test_rlm_wasm.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/stress/test_trace_search.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_adapter_reentrancy.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_assessments_api.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_authored.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_authored_bundle_collisions.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_authored_metric_contract.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_authored_named_models.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_authored_optimize.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_callback.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_admin.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_api_client.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_assessments.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_deployment_runs.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_deployments.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_evals.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_main.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_org.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_parity.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_programs.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_provider_credentials.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_schema_sample.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_signatures.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_traces.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_traces_search_wire.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_tree_help.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cli/test_valsets.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_client.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_configuration.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_context.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_context_propagation.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_dataset_sync.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_datasets.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_decorators.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_deploy.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_deployment_metadata.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_deployment_runs.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_deployments_read.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_eval.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_eval_example_attribution.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_eval_start.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_eval_trace_grouping.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_execute.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_execute_run_identity.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_exporter.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_exporter_queue.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_exporter_real.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_gepa_output_normalization.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_identity.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_imaging.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_import_weight.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_ingest_conformance.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_integration.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_lm15_native_calls.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_lm_configuration.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_lm_usage_shapes.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_mcp_tools.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_models.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_multi_client_stress_readiness.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_optimize.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_optimize_lms.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_optimize_reflection_lm.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_packaging.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_packaging_mcp_tools.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_packaging_program_source.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_poll_until.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_program_identity.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_program_lineage_persist.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_project_payload.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_real_provider_twins_readiness.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_reflector_properties.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_reflector_smoke.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_release_stress_script.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_repro_reactv2_bugs.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_retry.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_rlm_patch.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_run_attribution_paths.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_runtime_tools.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_schemas.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_signature_inputs.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_span_references.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_span_sent_once.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_trace_context_concurrency.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_trace_fact_wire_contract.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_trace_facts.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_trace_render.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_traces_read.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_tracked_gepa.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_truncation.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.0}/tests/test_valsets_api.py +0 -0
- {cmpnd-0.40.0 → cmpnd-0.41.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.41.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>
|
|
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
18
19
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
20
|
Requires-Python: >=3.10
|
|
20
21
|
Requires-Dist: dspy>=3.1.3
|
|
@@ -27,8 +28,10 @@ Requires-Dist: xxhash>=3.0.0
|
|
|
27
28
|
Provides-Extra: dev
|
|
28
29
|
Requires-Dist: basedpyright>=1.39; extra == 'dev'
|
|
29
30
|
Requires-Dist: boto3>=1.34; extra == 'dev'
|
|
31
|
+
Requires-Dist: gepa; extra == 'dev'
|
|
30
32
|
Requires-Dist: hypothesis>=6.100; extra == 'dev'
|
|
31
33
|
Requires-Dist: jsonschema>=4.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: litellm; extra == 'dev'
|
|
32
35
|
Requires-Dist: numpy>=1.26; (python_version < '3.11') and extra == 'dev'
|
|
33
36
|
Requires-Dist: numpy>=2.3; (python_version >= '3.11') and extra == 'dev'
|
|
34
37
|
Requires-Dist: pandas>=2.2; extra == 'dev'
|
|
@@ -161,7 +164,7 @@ cmpnd.deploy(
|
|
|
161
164
|
metric=None, # optional: a metric callable, packaged for optimization
|
|
162
165
|
timeout=300, # optional: seconds to wait for the parser after the upload
|
|
163
166
|
metadata=None, # optional: a JSON-serializable dict, at most 64 KiB, stored with the deployment
|
|
164
|
-
program_id="p_…", #
|
|
167
|
+
program_id="p_…", # the program the deployment belongs to; serve requires it after self-host 2026-001
|
|
165
168
|
)
|
|
166
169
|
```
|
|
167
170
|
|
|
@@ -111,7 +111,7 @@ cmpnd.deploy(
|
|
|
111
111
|
metric=None, # optional: a metric callable, packaged for optimization
|
|
112
112
|
timeout=300, # optional: seconds to wait for the parser after the upload
|
|
113
113
|
metadata=None, # optional: a JSON-serializable dict, at most 64 KiB, stored with the deployment
|
|
114
|
-
program_id="p_…", #
|
|
114
|
+
program_id="p_…", # the program the deployment belongs to; serve requires it after self-host 2026-001
|
|
115
115
|
)
|
|
116
116
|
```
|
|
117
117
|
|
|
@@ -1949,6 +1949,14 @@ class _ToolMixin(_CallbackBase):
|
|
|
1949
1949
|
exp.export_span(span)
|
|
1950
1950
|
|
|
1951
1951
|
|
|
1952
|
+
# The variable DSPy (3.4.1 and later) hands the interpreter when RLM installs its
|
|
1953
|
+
# sandboxed `dspy` stand-in, as `dspy.primitives.facade.HOST_TOKEN_VAR`. That setup runs
|
|
1954
|
+
# as the session's first cell on every forward, and it is DSPy's machinery rather than a
|
|
1955
|
+
# step the program took, so it records no span. Spelled out rather than imported because
|
|
1956
|
+
# older DSPy has no such module.
|
|
1957
|
+
SANDBOX_SETUP_VARIABLE = "__dspy_host_token"
|
|
1958
|
+
|
|
1959
|
+
|
|
1952
1960
|
class _InterpreterMixin(_CallbackBase):
|
|
1953
1961
|
"""DSPy CodeInterpreter callbacks — one span per REPL code execution.
|
|
1954
1962
|
|
|
@@ -1975,6 +1983,9 @@ class _InterpreterMixin(_CallbackBase):
|
|
|
1975
1983
|
# has no span to attach to and no trace to root, so drop it.
|
|
1976
1984
|
if context.get_current_trace() is None and context.get_current_span() is None:
|
|
1977
1985
|
return
|
|
1986
|
+
variables = inputs.get("variables")
|
|
1987
|
+
if isinstance(variables, dict) and SANDBOX_SETUP_VARIABLE in variables:
|
|
1988
|
+
return
|
|
1978
1989
|
|
|
1979
1990
|
span = self._new_span(f"{instance.__class__.__name__}.execute", models.SpanType.INTERPRETER_EXECUTE)
|
|
1980
1991
|
|
|
@@ -811,11 +811,12 @@ def _parse_deploy_metadata(raw: str | None) -> dict[str, Any] | None:
|
|
|
811
811
|
raise api_client.CliError(f"--metadata is invalid: {exc}")
|
|
812
812
|
|
|
813
813
|
|
|
814
|
-
def _program_id_arg(value: str | None
|
|
814
|
+
def _program_id_arg(value: str | None) -> str | None:
|
|
815
815
|
"""The program id a deploy or optimize names: ``--program-id``, else ``CMPND_PROGRAM_ID``.
|
|
816
816
|
|
|
817
|
-
Checked here, before a file is staged, so a
|
|
818
|
-
|
|
817
|
+
Checked here, before a file is staged, so a mistyped id is a usage error naming
|
|
818
|
+
its source rather than a refusal after work was done. serve decides whether a
|
|
819
|
+
deployment needs an id, so a missing one is not refused here.
|
|
819
820
|
"""
|
|
820
821
|
from ... import identity
|
|
821
822
|
|
|
@@ -823,12 +824,6 @@ def _program_id_arg(value: str | None, *, required: bool) -> str | None:
|
|
|
823
824
|
if value is None and os.environ.get("CMPND_PROGRAM_ID"):
|
|
824
825
|
value, source = os.environ["CMPND_PROGRAM_ID"], "CMPND_PROGRAM_ID"
|
|
825
826
|
if value is None:
|
|
826
|
-
if required:
|
|
827
|
-
raise api_client.CliError(
|
|
828
|
-
"--program-id is required: a deployment always names its program. "
|
|
829
|
-
"Create one with `cmpnd programs create`, then pass --program-id p_… "
|
|
830
|
-
"(or set CMPND_PROGRAM_ID)."
|
|
831
|
-
)
|
|
832
827
|
return None
|
|
833
828
|
try:
|
|
834
829
|
return identity.validate_program_id(value, source=source)
|
|
@@ -855,9 +850,7 @@ def cmd_deploy(client: api_client.ApiClient, args: Any) -> None:
|
|
|
855
850
|
|
|
856
851
|
from ... import authored, configuration, deployment
|
|
857
852
|
|
|
858
|
-
|
|
859
|
-
# names its program, and the server would refuse the create anyway.
|
|
860
|
-
program_id = _program_id_arg(getattr(args, "program_id", None), required=True)
|
|
853
|
+
program_id = _program_id_arg(getattr(args, "program_id", None))
|
|
861
854
|
configuration.configure(api_key=client._api_key, endpoint=client._endpoint, program_id=program_id)
|
|
862
855
|
try:
|
|
863
856
|
if args.source == "-" or args.source is None:
|
|
@@ -881,7 +874,10 @@ def cmd_deploy(client: api_client.ApiClient, args: Any) -> None:
|
|
|
881
874
|
|
|
882
875
|
metadata = _parse_deploy_metadata(getattr(args, "metadata", None))
|
|
883
876
|
print(_c("deploying…", "dim"), file=sys.stderr)
|
|
884
|
-
|
|
877
|
+
try:
|
|
878
|
+
deployed = deployment.deploy_bundle(prepared.bundle, prepared.facts, metadata=metadata, program_id=program_id)
|
|
879
|
+
except ValueError as exc:
|
|
880
|
+
raise api_client.CliError(str(exc)) from exc
|
|
885
881
|
|
|
886
882
|
deployment_id = deployed.deployment_id
|
|
887
883
|
print(deployment_id)
|
|
@@ -946,9 +942,7 @@ def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
946
942
|
from ... import authored, configuration, optimization
|
|
947
943
|
|
|
948
944
|
deployment_id = getattr(args, "deployment_id", None)
|
|
949
|
-
|
|
950
|
-
# already names its program.
|
|
951
|
-
program_id = _program_id_arg(getattr(args, "program_id", None), required=deployment_id is None)
|
|
945
|
+
program_id = _program_id_arg(getattr(args, "program_id", None))
|
|
952
946
|
configuration.configure(api_key=client._api_key, endpoint=client._endpoint, program_id=program_id)
|
|
953
947
|
try:
|
|
954
948
|
if args.source == "-" or args.source is None:
|
|
@@ -1266,7 +1260,7 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
1266
1260
|
dest="program_id",
|
|
1267
1261
|
default=None,
|
|
1268
1262
|
help="Program id (p_…) the deployment belongs to, from `cmpnd programs create`. "
|
|
1269
|
-
"Required
|
|
1263
|
+
"Defaults to CMPND_PROGRAM_ID. Required by every serve after self-host release 2026-001",
|
|
1270
1264
|
)
|
|
1271
1265
|
dp.add_argument(
|
|
1272
1266
|
"--entrypoint",
|
|
@@ -1351,8 +1345,8 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
1351
1345
|
dest="program_id",
|
|
1352
1346
|
default=None,
|
|
1353
1347
|
help="Program id (p_…) being optimized, from `cmpnd programs create`; defaults to "
|
|
1354
|
-
"CMPND_PROGRAM_ID. Required
|
|
1355
|
-
"with --deployment-id",
|
|
1348
|
+
"CMPND_PROGRAM_ID. Required by every serve after self-host release 2026-001 when "
|
|
1349
|
+
"the file's program is deployed here, optional with --deployment-id",
|
|
1356
1350
|
)
|
|
1357
1351
|
opt.add_argument(
|
|
1358
1352
|
"--reflection-model",
|
|
@@ -490,8 +490,8 @@ def _make_namespace(client: api_client.ApiClient, endpoint: str) -> dict[str, An
|
|
|
490
490
|
"""Deploy authored Python from a file or ``"-"`` for stdin.
|
|
491
491
|
|
|
492
492
|
``program_id`` (``p_…``, from ``cmpnd programs create``) names the program
|
|
493
|
-
the deployment belongs to; it defaults to ``CMPND_PROGRAM_ID
|
|
494
|
-
|
|
493
|
+
the deployment belongs to; it defaults to ``CMPND_PROGRAM_ID``. serve
|
|
494
|
+
decides whether a deployment needs one. A fresh child imports the staged bundle. The shell
|
|
495
495
|
does not execute the file in its own process. Returns the
|
|
496
496
|
new Deployment entity so you can chain ``.watch()`` / ``.run({...})``
|
|
497
497
|
directly; also invalidates ``deps`` so the new deployment shows up
|
|
@@ -499,7 +499,7 @@ def _make_namespace(client: api_client.ApiClient, endpoint: str) -> dict[str, An
|
|
|
499
499
|
"""
|
|
500
500
|
from .. import authored, configuration, deployment
|
|
501
501
|
|
|
502
|
-
declared = commands.top._program_id_arg(program_id
|
|
502
|
+
declared = commands.top._program_id_arg(program_id)
|
|
503
503
|
configuration.configure(api_key=client._api_key, endpoint=client._endpoint, program_id=declared)
|
|
504
504
|
if source in ("-", None):
|
|
505
505
|
prepared = authored.prepare_stdin(
|
|
@@ -346,21 +346,35 @@ def _resolve_mcp_tool_references(client: httpx.Client, config, references: list[
|
|
|
346
346
|
|
|
347
347
|
|
|
348
348
|
MISSING_PROGRAM_ID = (
|
|
349
|
-
"
|
|
349
|
+
"this server requires a program id: pass program_id= (`cmpnd deploy --program-id`), or set "
|
|
350
350
|
"cmpnd.configure(program_id=...) or CMPND_PROGRAM_ID. Create one with `cmpnd programs create`."
|
|
351
351
|
)
|
|
352
352
|
|
|
353
|
+
# The error serve answers, with a 400, to a create that names no program. A serve
|
|
354
|
+
# from before program ids accepts the create instead. serve pins this prefix in
|
|
355
|
+
# deploymentsapi.missingProgramIDError.
|
|
356
|
+
_SERVE_MISSING_PROGRAM_ID = "program_id is required"
|
|
353
357
|
|
|
354
|
-
|
|
358
|
+
|
|
359
|
+
def declared_program_id(explicit: str | None, config: configuration.Config) -> str | None:
|
|
355
360
|
"""The program a deployment is created under: the explicit id, else the configured one.
|
|
356
361
|
|
|
357
|
-
A
|
|
358
|
-
|
|
362
|
+
A declared id is checked for shape before anything is packaged or sent. serve
|
|
363
|
+
decides whether a deployment needs an id, so a missing one is not refused here.
|
|
359
364
|
"""
|
|
360
365
|
chosen = explicit if explicit is not None else config.program_id
|
|
361
|
-
if chosen is None
|
|
362
|
-
|
|
363
|
-
|
|
366
|
+
return identity.validate_program_id(chosen) if chosen is not None else None
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _refused_for_missing_program_id(response: httpx.Response, deploy_body: dict[str, Any]) -> bool:
|
|
370
|
+
"""Whether serve refused a create that named no program because it needs one."""
|
|
371
|
+
if response.status_code != 400 or "program_id" in deploy_body:
|
|
372
|
+
return False
|
|
373
|
+
try:
|
|
374
|
+
error = response.json().get("error")
|
|
375
|
+
except (ValueError, AttributeError):
|
|
376
|
+
return False
|
|
377
|
+
return isinstance(error, str) and error.startswith(_SERVE_MISSING_PROGRAM_ID)
|
|
364
378
|
|
|
365
379
|
|
|
366
380
|
def deploy(module, metric=None, timeout=300, metadata=None, *, program_id=None):
|
|
@@ -387,22 +401,24 @@ def deploy(module, metric=None, timeout=300, metadata=None, *, program_id=None):
|
|
|
387
401
|
with string keys, within 64 KiB. Read it back via the deployment
|
|
388
402
|
record (``cmpnd.status`` / the deployment GET API).
|
|
389
403
|
program_id: The program (``p_…``) the deployment belongs to, as
|
|
390
|
-
``cmpnd programs create`` printed it.
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
404
|
+
``cmpnd programs create`` printed it. When omitted, the configured
|
|
405
|
+
id (``cmpnd.configure(program_id=...)`` or ``CMPND_PROGRAM_ID``) is
|
|
406
|
+
used. With neither, the create names no program, and serve decides:
|
|
407
|
+
every serve with program ids refuses it, and a serve from before
|
|
408
|
+
them, such as self-host release 2026-001, accepts it.
|
|
394
409
|
|
|
395
410
|
Returns:
|
|
396
411
|
A DeployedProgram that can be called with the same inputs as the
|
|
397
412
|
original module. Has a .deployment_id attribute and str() returns it.
|
|
398
413
|
|
|
399
414
|
Raises:
|
|
400
|
-
ValueError: If
|
|
415
|
+
ValueError: If the program id is malformed, or if serve requires one and
|
|
416
|
+
none was given or configured.
|
|
401
417
|
RuntimeError: If cmpnd is not configured, or if deployment fails.
|
|
402
418
|
TimeoutError: If the program isn't ready within timeout seconds.
|
|
403
419
|
"""
|
|
404
420
|
config = configuration.require_config()
|
|
405
|
-
|
|
421
|
+
program = declared_program_id(program_id, config)
|
|
406
422
|
|
|
407
423
|
# Resolve the program's MCP tool references against the org's registry, before
|
|
408
424
|
# anything is packaged and before a deployment row exists. A program with no MCP
|
|
@@ -423,12 +439,15 @@ def deploy(module, metric=None, timeout=300, metadata=None, *, program_id=None):
|
|
|
423
439
|
program_signature_hash = identity.compute_program_hash(module) or 0
|
|
424
440
|
|
|
425
441
|
deploy_body: dict[str, Any] = {
|
|
426
|
-
"program_id": declared_program_id,
|
|
427
442
|
"program_signature_hash": identity.format_signature_hash(program_signature_hash),
|
|
428
443
|
# Declare the ZIP's exact length so the server can size-bound the upload
|
|
429
444
|
# slot and reject an oversized deploy before signing it (Slice E).
|
|
430
445
|
"declared_content_length": len(zip_bytes),
|
|
431
446
|
}
|
|
447
|
+
# Left off when undeclared, so a serve from before program ids reads the body it
|
|
448
|
+
# always read and a newer serve answers with its own refusal.
|
|
449
|
+
if program is not None:
|
|
450
|
+
deploy_body["program_id"] = program
|
|
432
451
|
# Carry the caller's metadata blob (validated and made jsonb-storable at the
|
|
433
452
|
# call site) as a reserved top-level body key, committed with the deployment
|
|
434
453
|
# record server-side.
|
|
@@ -496,13 +515,14 @@ def deploy_bundle(
|
|
|
496
515
|
) -> execution.DeployedProgram:
|
|
497
516
|
"""Upload a checked authored-source bundle without packaging a live module.
|
|
498
517
|
|
|
499
|
-
``program_id``
|
|
500
|
-
|
|
518
|
+
``program_id`` works as it does for :func:`deploy`: the explicit id, else the
|
|
519
|
+
configured one, else none, and serve decides whether a deployment needs one.
|
|
501
520
|
"""
|
|
502
521
|
config = configuration.require_config()
|
|
503
|
-
|
|
522
|
+
program = declared_program_id(program_id, config)
|
|
504
523
|
deploy_body = facts.deploy_body()
|
|
505
|
-
|
|
524
|
+
if program is not None:
|
|
525
|
+
deploy_body["program_id"] = program
|
|
506
526
|
deploy_body["source_format"] = "authored"
|
|
507
527
|
deploy_body["declared_content_length"] = len(zip_bytes)
|
|
508
528
|
if metadata is not None:
|
|
@@ -531,6 +551,8 @@ def _deploy_prepared(
|
|
|
531
551
|
may_resend_with_no_answer=retry.never_left_the_client,
|
|
532
552
|
may_resend_after_answer=lambda _: False,
|
|
533
553
|
)
|
|
554
|
+
if _refused_for_missing_program_id(created, deploy_body):
|
|
555
|
+
raise ValueError(MISSING_PROGRAM_ID)
|
|
534
556
|
_raise_http_error(created, "create deployment")
|
|
535
557
|
answer = created.json()
|
|
536
558
|
deployment_id = answer["deployment_id"]
|
|
@@ -38,7 +38,7 @@ from typing import TYPE_CHECKING, Any, Callable, cast
|
|
|
38
38
|
import httpx
|
|
39
39
|
from httpx import HTTPError
|
|
40
40
|
|
|
41
|
-
from . import _deployments, _http, authored, configuration, deployment, execution,
|
|
41
|
+
from . import _deployments, _http, authored, configuration, deployment, execution, packaging, retry
|
|
42
42
|
from .deployment_metadata import DeploymentMetadataError, DeploymentSignatureResponse
|
|
43
43
|
|
|
44
44
|
if TYPE_CHECKING:
|
|
@@ -831,10 +831,11 @@ def optimize(
|
|
|
831
831
|
deployment_id: Skip the initial deploy by reusing an existing ready
|
|
832
832
|
deployment as program A.
|
|
833
833
|
program_id: The program (``p_…``) being optimized. Defaults to the
|
|
834
|
-
configured id.
|
|
835
|
-
``deployment_id``),
|
|
836
|
-
|
|
837
|
-
the first optimize step, the one that optimizes your
|
|
834
|
+
configured id. When the program is deployed here (no
|
|
835
|
+
``deployment_id``), the deploy carries it, and serve decides whether
|
|
836
|
+
that deploy needs one. A reused deployment already names its program.
|
|
837
|
+
Sent on the first optimize step, the one that optimizes your
|
|
838
|
+
deployment.
|
|
838
839
|
reflection_lm: **Required** — the model that proposes new instructions.
|
|
839
840
|
The search is only as good as it is, so this is a strong model,
|
|
840
841
|
typically not the one being optimized. Pass the ``dspy.LM`` you would
|
|
@@ -925,7 +926,7 @@ def optimize(
|
|
|
925
926
|
_apply_budget(config, max_metric_calls=max_metric_calls, auto=auto)
|
|
926
927
|
|
|
927
928
|
cfg = configuration.require_config()
|
|
928
|
-
program_id =
|
|
929
|
+
program_id = deployment.declared_program_id(program_id, cfg)
|
|
929
930
|
|
|
930
931
|
from . import lm_configuration
|
|
931
932
|
|
|
@@ -957,19 +958,6 @@ def optimize(
|
|
|
957
958
|
return _deployed_program(best_deployment_id, cfg, score=best_score)
|
|
958
959
|
|
|
959
960
|
|
|
960
|
-
def _declared_program_id(explicit: str | None, cfg: configuration.Config, *, deploys: bool) -> str | None:
|
|
961
|
-
"""The program an optimization names: the explicit id, else the configured one.
|
|
962
|
-
|
|
963
|
-
Required when this call deploys the program, since a deployment always names its
|
|
964
|
-
program, and refused before anything is sent. Optional when an existing
|
|
965
|
-
deployment is reused, because that deployment already names one.
|
|
966
|
-
"""
|
|
967
|
-
if deploys:
|
|
968
|
-
return deployment.require_program_id(explicit, cfg)
|
|
969
|
-
chosen = explicit if explicit is not None else cfg.program_id
|
|
970
|
-
return identity.validate_program_id(chosen) if chosen is not None else None
|
|
971
|
-
|
|
972
|
-
|
|
973
961
|
def _run_steps(
|
|
974
962
|
cfg: configuration.Config,
|
|
975
963
|
bundle: bytes,
|
|
@@ -1094,8 +1082,8 @@ def optimize_authored(
|
|
|
1094
1082
|
one directory on the executor, so a path they disagree about is refused, and it is
|
|
1095
1083
|
worth refusing here where both files are in reach.
|
|
1096
1084
|
|
|
1097
|
-
``program_id`` is :func:`optimize`'s:
|
|
1098
|
-
|
|
1085
|
+
``program_id`` is :func:`optimize`'s: the deploy of the file's program carries it,
|
|
1086
|
+
and serve decides whether that deploy needs one.
|
|
1099
1087
|
"""
|
|
1100
1088
|
check_optimizer(optimizer)
|
|
1101
1089
|
config = dict(config or {})
|
|
@@ -1111,7 +1099,7 @@ def optimize_authored(
|
|
|
1111
1099
|
_apply_budget(config, max_metric_calls=max_metric_calls, auto=auto)
|
|
1112
1100
|
|
|
1113
1101
|
cfg = configuration.require_config()
|
|
1114
|
-
program_id =
|
|
1102
|
+
program_id = deployment.declared_program_id(program_id, cfg)
|
|
1115
1103
|
|
|
1116
1104
|
facts = prepared.facts.program
|
|
1117
1105
|
if prepared.program_bundle is not None:
|
|
@@ -19,6 +19,11 @@ from . import _http, configuration
|
|
|
19
19
|
|
|
20
20
|
DEFAULT_ENDPOINT = "https://platform.cmpnd.ai"
|
|
21
21
|
|
|
22
|
+
# Self-host release 2026-001 answers a programs create with 405, because it routes
|
|
23
|
+
# GET requests under /api/v1/programs/ and has no create. Every newer serve accepts
|
|
24
|
+
# the create, so on this one route a 405 means a serve from before program ids.
|
|
25
|
+
UNSUPPORTED_STATUS = 405
|
|
26
|
+
|
|
22
27
|
|
|
23
28
|
def create(
|
|
24
29
|
name: str | None = None,
|
|
@@ -61,7 +66,18 @@ def send_create(
|
|
|
61
66
|
body["name"] = name
|
|
62
67
|
if optimized_from_run_id is not None:
|
|
63
68
|
body["optimized_from_run_id"] = optimized_from_run_id
|
|
64
|
-
|
|
69
|
+
try:
|
|
70
|
+
return client.post("/api/v1/programs", json=body)
|
|
71
|
+
except _http.ApiError as exc:
|
|
72
|
+
if exc.status_code == UNSUPPORTED_STATUS:
|
|
73
|
+
raise _http.ApiError(
|
|
74
|
+
"This server does not support program ids. A serve from before program ids, such as "
|
|
75
|
+
"self-host release 2026-001, deploys without one: run `cmpnd deploy` without "
|
|
76
|
+
f"--program-id.\n {exc}",
|
|
77
|
+
exc.exit_code,
|
|
78
|
+
status_code=exc.status_code,
|
|
79
|
+
) from exc
|
|
80
|
+
raise
|
|
65
81
|
|
|
66
82
|
|
|
67
83
|
def _client(api_key: str | None, endpoint: str | None) -> _http.ApiClient:
|
|
@@ -19,10 +19,15 @@ PROGRAM_ID=$(cmpnd programs create --name triage)
|
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
The command reports what it created on stderr, and `--json` prints the whole
|
|
22
|
-
registry row instead. `cmpnd deploy`
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
registry row instead. `cmpnd deploy` takes the id as `--program-id`, and so
|
|
23
|
+
does `cmpnd optimize` when it deploys the file's program first. With
|
|
24
|
+
`--deployment-id`, the id is optional because that deployment already names its
|
|
25
|
+
program. The CLI checks a given id's shape and leaves the requirement to serve.
|
|
26
|
+
Every serve after self-host release 2026-001 refuses a deploy with no id, and
|
|
27
|
+
the CLI then names `--program-id` and `cmpnd programs create`. Self-host release
|
|
28
|
+
2026-001 has no program ids, so a deploy there omits `--program-id`, and
|
|
29
|
+
`cmpnd programs create` reports that the server does not support them. Each
|
|
30
|
+
call creates a new program. The command never
|
|
26
31
|
retries, because a second send would create a second program.
|
|
27
32
|
`cmpnd.programs.create(name=...)` makes the same call from Python and returns
|
|
28
33
|
the row.
|
|
@@ -20,11 +20,11 @@ cmpnd.auto_instrument()
|
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
- **`cmpnd.configure()`** — `cmpnd/configuration.py`. Reads `api_key`, `endpoint` and `program_id` from args or `CMPND_API_KEY` / `CMPND_ENDPOINT` / `CMPND_PROGRAM_ID` env vars, builds a `Config` dataclass (`configuration.py`), and stores it under a lock. This call does not start any threads. It also does not touch DSPy. You can call `configure()` in a library and let the user decide whether to instrument. See [The program id](#the-program-id) for what `program_id` does.
|
|
23
|
-
- **`cmpnd.auto_instrument()`** — `python-sdk/cmpnd/__init__.py
|
|
23
|
+
- **`cmpnd.auto_instrument()`** — `auto_instrument` in `python-sdk/cmpnd/__init__.py`. Requires `configure()` to have been called. It instantiates a `CmpndCallback`, appends it to `dspy.settings.callbacks` (deduplicating if already installed), and patches `dspy.GEPA.__init__` / `GEPA.compile` so optimization runs are tracked automatically. See [`./optimization-tracking.md`](../../docs/optimization-tracking.md) for what that patch does.
|
|
24
24
|
|
|
25
25
|
The exporter thread isn't started here directly. It is lazily started on first export via `ensure_exporter_started()` in `python-sdk/cmpnd/exporter.py:41`.
|
|
26
26
|
|
|
27
|
-
If you want to instrument manually (for example, to wire the callback into a non-global `dspy.Settings`), you can construct `CmpndCallback` yourself from `python-sdk/cmpnd/callback.py
|
|
27
|
+
If you want to instrument manually (for example, to wire the callback into a non-global `dspy.Settings`), you can construct `CmpndCallback` yourself from `python-sdk/cmpnd/callback.py` and hand it to DSPy. `auto_instrument` is just the convenient path.
|
|
28
28
|
|
|
29
29
|
For an explicit, scoped alternative that traces only the programs you run through it — with its own tags and credentials, orthogonal to this global path — see [`./client.md`](./client.md) (`cmpnd.Client`). `cmpnd.de_instrument()` removes this global instrumentation and restores the patched classes; it is documented there too.
|
|
30
30
|
|
|
@@ -79,12 +79,12 @@ capability lookup.
|
|
|
79
79
|
|
|
80
80
|
The hook methods, in the order you'd expect them to fire for a typical `dspy.ChainOfThought` call:
|
|
81
81
|
|
|
82
|
-
- **Module lifecycle** — `on_module_start` (`callback.py
|
|
82
|
+
- **Module lifecycle** — `on_module_start` (`callback.py`) creates a new `SpanBuilder`, and, if this is the root module call, also creates a new `TraceBuilder` via `_create_trace`. `on_module_end` closes the span, attaches outputs, and, if it was the root, exports the trace. A call is root when no span is on the stack — or when the only span above it is the `Evaluate` span, which contains many independent program runs and parents none of them, so each run under a `dspy.Evaluate` roots its own trace. Such a root span is built with `create_child_span(..., root=True)` so it does not adopt the `Evaluate` span as its parent.
|
|
83
83
|
- **LM calls.** `on_lm_start` and `on_lm_end` wrap the provider call. The callback records the model, the current span token projections, and a versioned `lm_usage` evidence object. `capture_prompts` controls whether the prompt payload is recorded.
|
|
84
84
|
- **Adapter format/parse** — `on_adapter_format_start` / `_end` and `on_adapter_parse_start` / `_end`. Both route through the shared helpers `_start_adapter_span` and `_end_adapter_span`. The callback records one span per adapter call. DSPy's `Adapter.__init_subclass__` wraps `format` and `parse` on every subclass, so a `JSONAdapter.format` call fires the hooks twice and a `BAMLAdapter.format` call three times, each with a fresh `call_id` and the same instance. `_start_adapter_span` skips a call when the current span is the same method's adapter span on the same instance, and `_end_adapter_span` returns early for the skipped `call_id`. The outermost span is kept, since its output is what reaches the LM. `tests/test_adapter_reentrancy.py` runs the real adapters under DSPy's dispatch. An adapter span's inputs are the method's arguments, so they include the predictor's `signature`. `helpers.clean_inputs` records any signature class among a span's inputs by its label, the class name or the `in1, in2 -> out1` fields of a string signature. It never records the repr, which embeds the whole instructions and every field's metadata. The same rule covers a `Predict` called with `signature=`, as DSPy's RLM does on every turn from DSPy `main` on. The span records the signature's fields and instructions once, as its own attributes.
|
|
85
|
-
- **Tools** — `on_tool_start`
|
|
86
|
-
- **Interpreter (code execution)** — `_InterpreterMixin` (`callback.py
|
|
87
|
-
- **Evaluate** — `on_evaluate_start`
|
|
85
|
+
- **Tools** — `on_tool_start` / `on_tool_end` (`callback.py`). One span per `dspy.Tool` invocation, with the tool's args as inputs.
|
|
86
|
+
- **Interpreter (code execution)** — `_InterpreterMixin` (`callback.py`), for the DSPy `CodeInterpreter` hooks DSPy >= 3.3.1 fires around a program running code in the interpreter (`RLM`, `ProgramOfThought`, `CodeAct`, `Flex` — every module reaching it through `_create_interpreter`; `ReAct` / `ReActV2` use no interpreter and fire none). `on_interpreter_execute_start` / `_end` emit one `INTERPRETER_EXECUTE` span per REPL cell — the code in, its captured stdout out, and the `SUBMIT()` result (a DSPy `FinalOutput`) recorded distinctly from stdout. On DSPy 3.4.1 and later, `RLM` runs the sandbox side of its `dspy` stand-in as each session's first cell, marked by the `__dspy_host_token` variable (`dspy.primitives.facade.HOST_TOKEN_VAR`). That cell is DSPy's machinery rather than a step the program took, so it emits no span. `on_interpreter_tool_call_start` / `_end` synthesize a `TOOL` span for a tool called from interpreter code **only when no real `dspy.Tool` span covered the call** (a user tool routes through `Tool.__call__` and fires `on_tool_*`, so its row is suppressed to avoid a duplicate; the `llm_query` builtins fire no tool hook, so they get the synthesized span). `on_interpreter_startup_*` / `on_interpreter_shutdown_*` emit no span — their duration folds into an `interpreter.startup_ms` / `interpreter.shutdown_ms` attribute on the enclosing span, and an event with no enclosing span (a pooled pre-warm) is dropped.
|
|
87
|
+
- **Evaluate** — `on_evaluate_start` / `on_evaluate_end` (`callback.py`). These are thin forwarders that delegate to `EvalHandler` (see "Eval handling" below).
|
|
88
88
|
|
|
89
89
|
During a GEPA evaluation, DSPy wraps the program prediction with its internal
|
|
90
90
|
bootstrap trace. The callback recognizes this pair only while an optimization
|
|
@@ -108,8 +108,8 @@ so the next release that adds a hook is caught by CI rather than by a user's log
|
|
|
108
108
|
|
|
109
109
|
This doc does not re-derive hash semantics — see [`./clustering.md`](../../docs/CLUSTERING.md) for why `signature_hash` exists and how xxhash maps to a signed Int64. Here's only _where_ it gets attached:
|
|
110
110
|
|
|
111
|
-
- **On a span** — `_capture_module_attributes` in `callback.py
|
|
112
|
-
- **On a trace** — `_create_trace` in `callback.py
|
|
111
|
+
- **On a span** — `_capture_module_attributes` in `callback.py`. After walking `instance.signature.input_fields` / `output_fields` and recording types and schemas, it calls `compute_signature_hash(signature)` and stores the result on `span.signature_hash`. Composite modules (multi-child `dspy.Module` subclasses) take the `compute_composite_signature_hash` branch.
|
|
112
|
+
- **On a trace** — `_create_trace` in `callback.py`. Same split: single-signature modules hash directly; composite modules walk `dspy.Predict` children and call the composite helper.
|
|
113
113
|
|
|
114
114
|
Both code paths import from `python-sdk/cmpnd/identity.py`, which is the authoritative home of every hash the SDK computes.
|
|
115
115
|
|
|
@@ -117,11 +117,11 @@ Both code paths import from `python-sdk/cmpnd/identity.py`, which is the authori
|
|
|
117
117
|
|
|
118
118
|
## Span types
|
|
119
119
|
|
|
120
|
-
`SpanType` is an enum defined in `python-sdk/cmpnd/models.py
|
|
120
|
+
`SpanType` is an enum defined in `python-sdk/cmpnd/models.py`. Grouped by hook that produces them:
|
|
121
121
|
|
|
122
122
|
| Group | Values | Triggered when... |
|
|
123
123
|
| ------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
124
|
-
| Module-ish | `MODULE`, `PREDICT`, `CHAIN_OF_THOUGHT`, `REACT`, `RLM`, `RETRIEVE` | `on_module_start` fires. The concrete type is chosen by `_get_span_type_for_module
|
|
124
|
+
| Module-ish | `MODULE`, `PREDICT`, `CHAIN_OF_THOUGHT`, `REACT`, `RLM`, `RETRIEVE` | `on_module_start` fires. The concrete type is chosen by `_get_span_type_for_module`. |
|
|
125
125
|
| LM | `LM_CALL` | `on_lm_start` fires — one per provider round-trip. |
|
|
126
126
|
| Adapter | `ADAPTER_FORMAT`, `ADAPTER_PARSE` | DSPy's adapter serializes the prompt or parses the response. |
|
|
127
127
|
| Tool | `TOOL` | `dspy.Tool` is invoked (inside a ReAct loop), or synthesized for a builtin called from interpreter code. |
|
|
@@ -660,7 +660,7 @@ Two files, both kept deliberately thin:
|
|
|
660
660
|
- **`python-sdk/cmpnd/datasets.py`** — the user-facing `load_dataset()` helper (`datasets.py:142`). Fetches a named dataset + version from the backend, applies an optional field mapping via `_apply_field_mapping` (`datasets.py:33`), and returns DSPy `Example` objects. This is how you _pull_ a dataset down.
|
|
661
661
|
- **`python-sdk/cmpnd/dataset_sync.py`** — utilities for _pushing_ a dataset up during an eval or optimization. `check_dataset_exists` at `dataset_sync.py:21` hits `GET /api/v1/datasets/check/:hash` to skip the upload if the backend already has it. `build_dataset_payload` at `dataset_sync.py:55` computes example hashes and assembles the payload.
|
|
662
662
|
|
|
663
|
-
The callers are `python-sdk/cmpnd/eval_handler.py
|
|
663
|
+
The callers are `python-sdk/cmpnd/eval_handler.py` (eval-time dataset capture) and `python-sdk/cmpnd/optimizers/tracker.py` (optimization-time capture, where it fires per compile and also per reflective dataset in GEPA). The devset never leaves the SDK unless the backend actually asks for it via the existence check — that's the whole reason these two modules are split from `datasets.py`.
|
|
664
664
|
|
|
665
665
|
---
|
|
666
666
|
|
|
@@ -757,11 +757,14 @@ the key is left off the wire rather than sent as `null`, so an undeclared trace
|
|
|
757
757
|
it did before ids existed. The finalize body carries no id, because it restates no
|
|
758
758
|
creation field.
|
|
759
759
|
|
|
760
|
-
|
|
761
|
-
`
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
760
|
+
`deploy()` and `deploy_bundle()` take `program_id=` and fall back to the configured id
|
|
761
|
+
(`deployment.declared_program_id`). A given id is checked for shape before anything is
|
|
762
|
+
packaged or sent. With no id the create body leaves the key off, and serve decides.
|
|
763
|
+
Every serve after self-host release 2026-001 refuses that create with a `400`, which the
|
|
764
|
+
SDK raises as `ValueError` naming `cmpnd programs create`. Self-host release 2026-001
|
|
765
|
+
has no program ids and accepts it. `optimize()` and `optimize_authored()` pass the id to
|
|
766
|
+
the deploy when they deploy the program themselves. A reused `deployment_id` already
|
|
767
|
+
names its program, so the id is optional there. Later steps
|
|
765
768
|
optimize a result deployment the platform created and whose program it allocated, so
|
|
766
769
|
the caller's id rides only the steps on the caller's own deployment.
|
|
767
770
|
|
|
@@ -5,7 +5,7 @@ name = "cmpnd"
|
|
|
5
5
|
# it's new) — no git tags. Bump with `uv version --bump {minor,patch}` or by
|
|
6
6
|
# hand. Runtime `cmpnd.__version__` reads installed metadata, so it tracks
|
|
7
7
|
# this automatically. See docs/plans/2026-07-02-sdk-pypi-tag-free-publish.md.
|
|
8
|
-
version = "0.
|
|
8
|
+
version = "0.41.0"
|
|
9
9
|
description = "DSPy observability and deployment SDK for cmpnd"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.10"
|
|
@@ -24,6 +24,7 @@ classifiers = [
|
|
|
24
24
|
"Programming Language :: Python :: 3.12",
|
|
25
25
|
"Programming Language :: Python :: 3.13",
|
|
26
26
|
"Programming Language :: Python :: 3.14",
|
|
27
|
+
"Programming Language :: Python :: 3.15",
|
|
27
28
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
28
29
|
]
|
|
29
30
|
|
|
@@ -95,6 +96,12 @@ dev = [
|
|
|
95
96
|
"ruff>=0.4.0",
|
|
96
97
|
"basedpyright>=1.39",
|
|
97
98
|
"jsonschema>=4.0",
|
|
99
|
+
# The tests import litellm (tests/e2e/fake_lm.py and the modules that reuse it) and
|
|
100
|
+
# gepa directly. dspy 3.4.1 installs both only below Python 3.15, because both declare
|
|
101
|
+
# requires-python <3.15. uv ignores that upper bound, so the 3.15 test leg still
|
|
102
|
+
# installs them.
|
|
103
|
+
"litellm",
|
|
104
|
+
"gepa",
|
|
98
105
|
# litellm's bedrock/ provider imports boto3 in-process, so the client-side
|
|
99
106
|
# Bedrock stress tests (committee twins) need it in the test env.
|
|
100
107
|
"boto3>=1.34",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Install cmpnd with pip's own resolver and check that the installed package
|
|
3
|
+
# imports and instruments DSPy. Called by .github/workflows/ci-sdk.yml (the
|
|
4
|
+
# sdk-test leg for Python 3.15).
|
|
5
|
+
#
|
|
6
|
+
# `pip install cmpnd` is the install PyPI advertises. pip honours the
|
|
7
|
+
# requires-python upper bounds that uv ignores, so this check fails when no
|
|
8
|
+
# dspy release admits the Python version. PYO3_USE_ABI3_FORWARD_COMPATIBILITY
|
|
9
|
+
# is set to 0, so an extension without a wheel for this Python fails the
|
|
10
|
+
# install and does not build from source.
|
|
11
|
+
#
|
|
12
|
+
# Usage: scripts/check-pip-install.sh <python-version> <dspy-version>
|
|
13
|
+
# e.g. scripts/check-pip-install.sh 3.15 3.4.1
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
if [ "$#" -ne 2 ]; then
|
|
17
|
+
echo "usage: $0 <python-version> <dspy-version>" >&2
|
|
18
|
+
exit 2
|
|
19
|
+
fi
|
|
20
|
+
python_version=$1
|
|
21
|
+
dspy_version=$2
|
|
22
|
+
|
|
23
|
+
sdk_dir=$(cd "$(dirname "$0")/.." && pwd)
|
|
24
|
+
venv_dir=$(mktemp -d)
|
|
25
|
+
trap 'rm -rf "$venv_dir"' EXIT
|
|
26
|
+
|
|
27
|
+
uv venv --seed --python "$python_version" "$venv_dir"
|
|
28
|
+
PYO3_USE_ABI3_FORWARD_COMPATIBILITY=0 \
|
|
29
|
+
"$venv_dir/bin/python" -m pip install "$sdk_dir" "dspy==$dspy_version"
|
|
30
|
+
|
|
31
|
+
# -I keeps the checkout off sys.path, so the import reads the installed
|
|
32
|
+
# package and not the source tree.
|
|
33
|
+
cd "$venv_dir"
|
|
34
|
+
"$venv_dir/bin/python" -I -c "import cmpnd; cmpnd.configure(api_key='ci'); cmpnd.auto_instrument(); print('cmpnd', cmpnd.__file__)"
|
|
@@ -44,6 +44,11 @@ from dspy.utils.dummies import DummyLM
|
|
|
44
44
|
|
|
45
45
|
ADAPTERS = {"chat": dspy.ChatAdapter, "json": dspy.JSONAdapter, "xml": dspy.XMLAdapter}
|
|
46
46
|
|
|
47
|
+
# The variable that marks RLM's sandboxed-dspy setup run on DSPy 3.4.1 and later
|
|
48
|
+
# (`dspy.primitives.facade.HOST_TOKEN_VAR`). Spelled out rather than imported from
|
|
49
|
+
# `cmpnd.callback`, because `--at` measures revisions of cmpnd that predate it.
|
|
50
|
+
SANDBOX_SETUP_VARIABLE = "__dspy_host_token"
|
|
51
|
+
|
|
47
52
|
|
|
48
53
|
def interpreter_output(turn: int, size: int) -> str:
|
|
49
54
|
"""About ``size`` characters of interpreter output only ``turn`` produces.
|
|
@@ -91,7 +96,11 @@ class FakeInterpreter:
|
|
|
91
96
|
|
|
92
97
|
@with_callbacks
|
|
93
98
|
def execute(self, code: str, variables: Any = None) -> str:
|
|
94
|
-
del code
|
|
99
|
+
del code # the answer depends on the turn alone
|
|
100
|
+
# DSPy 3.4.1's RLM runs its sandboxed-dspy setup as the session's first cell.
|
|
101
|
+
# That is no turn of the program's, so it takes no turn's output.
|
|
102
|
+
if isinstance(variables, dict) and SANDBOX_SETUP_VARIABLE in variables:
|
|
103
|
+
return ""
|
|
95
104
|
self.turn += 1
|
|
96
105
|
return self._output(self.turn)
|
|
97
106
|
|