cmpnd 0.57.0__tar.gz → 0.59.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.57.0 → cmpnd-0.59.0}/AGENTS.md +34 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/PKG-INFO +2 -2
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/_authored_import.py +1 -1
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/_gepa_patch.py +6 -9
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/callback.py +1 -1
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/commands/top.py +132 -2
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/optimization.py +61 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/optimizers/tracker.py +3 -5
- {cmpnd-0.57.0 → cmpnd-0.59.0}/docs/cli.md +26 -6
- {cmpnd-0.57.0 → cmpnd-0.59.0}/pyproject.toml +31 -4
- {cmpnd-0.57.0 → cmpnd-0.59.0}/scripts/check-pip-install.sh +1 -1
- cmpnd-0.59.0/scripts/install-test-env.sh +67 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/scripts/test-in-linux.sh +9 -8
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/span_reference_fixture.py +37 -7
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_adapter_reentrancy.py +2 -8
- cmpnd-0.59.0/tests/test_authored_binding_credentials.py +328 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_top.py +65 -12
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_lm_usage_shapes.py +2 -4
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_program_lineage_persist.py +4 -5
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_rlm_patch.py +19 -6
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_span_references.py +60 -7
- {cmpnd-0.57.0 → cmpnd-0.59.0}/uv.lock +421 -194
- {cmpnd-0.57.0 → cmpnd-0.59.0}/.gitignore +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/CLAUDE.md +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/README.md +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/__init__.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/_assessments.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/_deployments.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/_http.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/_runtime_tools.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/_traces.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/_valsets.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/authored.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/__init__.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/commands/admin.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/commands/assessments.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/commands/deployments.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/commands/evals.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/commands/org.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/commands/programs.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/commands/signatures.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/commands/traces.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/commands/valsets.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/shell.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/trace_render.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/tree_help.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/cli/verbs.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/client.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/configuration.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/context.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/credentials.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/dataset_sync.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/datasets.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/decorators.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/deployment.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/deployment_metadata.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/deployment_runs.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/execution.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/exporter.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/helpers.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/identity.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/imaging.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/ir/reflection.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/lm_configuration.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/mcp_servers.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/models.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/named_programs.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/optimizers/gepa.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/optimizers/gepa_callback.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/packaging.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/programs.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/references.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/retry.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/cmpnd/traces.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/docs/2026-04-02-code-review.md +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/docs/client.md +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/docs/custom-proposer.md +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/docs/reading-traces.md +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/docs/sdk-instrumentation.md +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/examples/local_ollama.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/ingest/README.md +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/bodies.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/client_floor.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/program_names.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/protocols.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/ingest/pyproject.toml +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/scripts/capture_review_run.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/scripts/release-stress.sh +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/scripts/require_version_bump.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/scripts/seed_review_run.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/scripts/selfhost_compat.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/stubs/dspy/utils/dummies.pyi +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/__init__.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/_credential_route.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/_deployment_signature_helpers.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/_fake_clock.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/_resolve_refs.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/_traced_run.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/conftest.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/committee_task.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/helpers.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/target_capabilities.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_assessments_cli.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_composite_module.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_deploy_execute.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_deploy_pandas.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_deploy_programs.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_optimize_failures.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_parallel_export.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_project_attribution.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_traces_read.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_user_attribution.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/e2e/test_valsets_cli.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/integration/__init__.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/integration/authored_optimize/judge.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/integration/conftest.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/integration/hosted_optimize_probe.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/integration/test_logs_renders_traces.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/integration/test_unhappy_path.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/load/__init__.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/load/__main__.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/load/drain.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/load/ingest.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/load/rlm_shape.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/load/rlm_size_report.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/load/sweep.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/load/test_rlm_size_report.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/parity/__init__.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/parity/conftest.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/parity/test_health.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/parity/test_trace_streaming.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/parity/test_traces.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/stress/__init__.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/stress/_provider.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/stress/_readiness.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/stress/_reliability.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/stress/_result.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/stress/mint_bedrock_token.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/stress/test_deploy_programs.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/stress/test_multi_client_stress.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/stress/test_optimize_client_committee.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/stress/test_optimize_loop.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/stress/test_optimize_stress.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/stress/test_provider_roles.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/stress/test_readiness.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/stress/test_real_provider_twins.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/stress/test_reliability.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/stress/test_reliability_classify.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/stress/test_rlm_wasm.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/stress/test_trace_search.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_assessments_api.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_authored.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_authored_bundle_collisions.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_authored_metric_contract.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_authored_named_models.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_authored_optimize.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_callback.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_admin.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_api_client.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_assessments.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_deployment_runs.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_deployments.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_evals.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_main.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_org.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_parity.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_programs.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_provider_credentials.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_schema_sample.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_shell.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_signatures.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_traces.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_traces_search_wire.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_tree_help.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cli/test_valsets.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_client.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_client_floor.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_client_program_names.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_configuration.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_context.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_context_propagation.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_credential_kwargs.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_credential_selection.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_credential_values.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_dataset_sync.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_datasets.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_decision_client_spans.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_decorators.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_deploy.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_deploy_program_id.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_deployment_metadata.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_deployment_runs.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_deployments_read.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_eval.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_eval_example_attribution.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_eval_start.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_eval_trace_grouping.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_execute.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_execute_run_identity.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_exporter.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_exporter_program_names.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_exporter_queue.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_exporter_real.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_gepa_output_normalization.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_identity.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_imaging.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_import_weight.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_ingest_conformance.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_integration.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_interpreter_callback.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_lm15_native_calls.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_lm_configuration.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_mcp_tools.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_models.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_multi_client_stress_readiness.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_optimize.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_optimize_lms.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_optimize_reflection_lm.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_packaging.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_packaging_mcp_tools.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_packaging_program_source.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_poll_until.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_program_id_deprecation.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_program_identity.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_program_names_in_deploy.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_programs_create.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_project_payload.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_real_provider_twins_readiness.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_reflector_properties.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_reflector_smoke.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_release_stress_script.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_replaced_names.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_repro_reactv2_bugs.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_retry.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_run_attribution_paths.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_runtime_tools.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_schemas.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_seal_lm_credential.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_signature_inputs.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_span_sent_once.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_system_one.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_trace_context_concurrency.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_trace_fact_wire_contract.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_trace_facts.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_trace_render.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_traces_read.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_tracked_gepa.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_truncation.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_upload_auth_headers.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_valsets_api.py +0 -0
- {cmpnd-0.57.0 → cmpnd-0.59.0}/tests/test_xxh64.py +0 -0
|
@@ -123,6 +123,40 @@ parameter, and a spec-less mock accepts one anyway.
|
|
|
123
123
|
with real LLMs (`CMPND_E2E_*` keys). It never uses a mock or an in-process
|
|
124
124
|
fake, and it runs only locally.
|
|
125
125
|
|
|
126
|
+
## DSPy versions under test
|
|
127
|
+
|
|
128
|
+
The SDK supports DSPy 3.3.x and 3.4.x, and `dependencies` floors DSPy at
|
|
129
|
+
3.3.0. `pyproject.toml` declares one dependency group per DSPy version the
|
|
130
|
+
`sdk-test` matrix tests, such as `dspy-3-4-1 = ["dspy==3.4.1"]`.
|
|
131
|
+
`[tool.uv] conflicts` makes the groups mutually exclusive, so `uv.lock` holds a
|
|
132
|
+
whole tree for each DSPy. Dependency groups never reach the wheel, so users
|
|
133
|
+
still install against the `dspy>=…` floor in `dependencies`.
|
|
134
|
+
|
|
135
|
+
- A plain `uv sync` or `uv run` installs the default group, the newest tested
|
|
136
|
+
DSPy.
|
|
137
|
+
- `scripts/install-test-env.sh <python> <dspy>` builds `.venv` from one group's
|
|
138
|
+
locked tree, as each `sdk-test` leg in `ci-sdk.yml` does. From the repo root,
|
|
139
|
+
run `just sdk-test-env 3.13 3.3.0`, then, from `python-sdk/`,
|
|
140
|
+
`.venv/bin/python -m pytest tests/ -k "not e2e"`. `uv sync --extra dev` in
|
|
141
|
+
`python-sdk/` returns to the default group.
|
|
142
|
+
- `--latest` resolves every package fresh, as a user's install today would.
|
|
143
|
+
With the DSPy version `newest` it pins no DSPy either. The daily
|
|
144
|
+
`sdk-latest-dependencies.yml` run uses both: every sdk-test leg, plus one
|
|
145
|
+
leg on the newest DSPy, which is the first to see a DSPy release no group
|
|
146
|
+
tests yet. A failure there alerts Slack.
|
|
147
|
+
- A DSPy release can pin some packages exactly, such as gepa (`dspy==3.3.0`
|
|
148
|
+
pins gepa 0.1.1). Its group locks those packages at the pinned versions. The
|
|
149
|
+
`mcp` extra floors DSPy at 3.3.1, so it conflicts with the `dspy-3-3-0`
|
|
150
|
+
group.
|
|
151
|
+
|
|
152
|
+
To test another DSPy version, add its group to `[dependency-groups]` and to
|
|
153
|
+
the first `conflicts` list, add a conflict with the `mcp` extra if it is below
|
|
154
|
+
3.3.1, add the leg to `sdk-test`'s matrix and to
|
|
155
|
+
`sdk-latest-dependencies.yml`, and run `uv lock`.
|
|
156
|
+
`.github/scripts/test_workflows.py` fails while the groups and the matrix
|
|
157
|
+
disagree. To move the default, change `default-groups` and refresh the span
|
|
158
|
+
reference fixture with `uv run python -m tests.span_reference_fixture`.
|
|
159
|
+
|
|
126
160
|
## Two packages, one published distribution
|
|
127
161
|
|
|
128
162
|
`cmpnd` (`cmpnd/`) is the SDK. `cmpnd_ingest` (`ingest/cmpnd_ingest/`) is the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cmpnd
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.59.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>
|
|
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.15
|
|
18
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
19
|
Requires-Python: >=3.11
|
|
20
|
-
Requires-Dist: dspy>=3.
|
|
20
|
+
Requires-Dist: dspy>=3.3.0
|
|
21
21
|
Requires-Dist: httpx>=0.27.0
|
|
22
22
|
Requires-Dist: ipython>=8.20
|
|
23
23
|
Requires-Dist: pydantic>=2.0
|
|
@@ -25,7 +25,7 @@ def _built_lms(root: Path):
|
|
|
25
25
|
|
|
26
26
|
The check reads objects the import built and parses no source. It wraps the
|
|
27
27
|
``__init__`` of ``dspy.BaseLM`` and of every LM class that defines its own, because
|
|
28
|
-
|
|
28
|
+
an LM subclass need not call ``BaseLM.__init__``. It names the innermost
|
|
29
29
|
module of the bundle on the stack, so an LM a helper module builds is named by that
|
|
30
30
|
module, and records each LM once, however many of the wrapped constructors it runs.
|
|
31
31
|
"""
|
|
@@ -172,9 +172,6 @@ def patch_gepa() -> None:
|
|
|
172
172
|
|
|
173
173
|
import dspy
|
|
174
174
|
|
|
175
|
-
if not hasattr(dspy, "GEPA"):
|
|
176
|
-
return
|
|
177
|
-
|
|
178
175
|
# Resolve callback support once, at patch time — not per compile — so the
|
|
179
176
|
# answer matches the installed gepa the patch bound to.
|
|
180
177
|
has_callbacks = _gepa_supports_callbacks()
|
|
@@ -223,10 +220,10 @@ def unpatch_gepa() -> None:
|
|
|
223
220
|
_gepa_patched = False
|
|
224
221
|
return
|
|
225
222
|
|
|
226
|
-
GEPA =
|
|
227
|
-
if
|
|
228
|
-
GEPA.__init__ = GEPA._cmpnd_original_init
|
|
229
|
-
GEPA.compile = GEPA._cmpnd_original_compile
|
|
230
|
-
del GEPA._cmpnd_original_init
|
|
231
|
-
del GEPA._cmpnd_original_compile
|
|
223
|
+
GEPA = dspy.GEPA
|
|
224
|
+
if hasattr(GEPA, "_cmpnd_original_init"):
|
|
225
|
+
GEPA.__init__ = GEPA._cmpnd_original_init # pyright: ignore[reportAttributeAccessIssue]
|
|
226
|
+
GEPA.compile = GEPA._cmpnd_original_compile # pyright: ignore[reportAttributeAccessIssue]
|
|
227
|
+
del GEPA._cmpnd_original_init # pyright: ignore[reportAttributeAccessIssue]
|
|
228
|
+
del GEPA._cmpnd_original_compile # pyright: ignore[reportAttributeAccessIssue]
|
|
232
229
|
_gepa_patched = False
|
|
@@ -1101,7 +1101,7 @@ class _ModuleMixin(_CallbackBase):
|
|
|
1101
1101
|
"""Map DSPy module instance to span type."""
|
|
1102
1102
|
if isinstance(instance, dspy.Retrieve):
|
|
1103
1103
|
return models.SpanType.RETRIEVE
|
|
1104
|
-
elif isinstance(instance,
|
|
1104
|
+
elif isinstance(instance, dspy.RLM):
|
|
1105
1105
|
return models.SpanType.RLM
|
|
1106
1106
|
elif isinstance(instance, dspy.ReAct):
|
|
1107
1107
|
return models.SpanType.REACT
|
|
@@ -890,6 +890,34 @@ def _program_args(args: Any, verb: str, *, needs_name: bool = True) -> str | Non
|
|
|
890
890
|
return name
|
|
891
891
|
|
|
892
892
|
|
|
893
|
+
class _CredentialLookup:
|
|
894
|
+
"""Turn a credential name a flag gave into the organization's ``cmpnd.Credential``.
|
|
895
|
+
|
|
896
|
+
Every command that takes a credential name looks it up here, before it stages or
|
|
897
|
+
sends anything. The lookup is ``cmpnd.credentials.get``, the line a Python caller
|
|
898
|
+
writes, so a mistyped name fails on the command line with the organization's names
|
|
899
|
+
and the nearest one. Serve checks the name again when the deploy or run names it,
|
|
900
|
+
which catches a credential revoked in between. Each distinct name is read once.
|
|
901
|
+
"""
|
|
902
|
+
|
|
903
|
+
def __init__(self, client: api_client.ApiClient) -> None:
|
|
904
|
+
self._client = client
|
|
905
|
+
self._found: dict[str, Any] = {}
|
|
906
|
+
|
|
907
|
+
def __call__(self, flag: str, name: str) -> Any:
|
|
908
|
+
"""The credential ``name``. ``flag`` is how the command line spelled it, for the error."""
|
|
909
|
+
from ... import credentials
|
|
910
|
+
|
|
911
|
+
if name not in self._found:
|
|
912
|
+
try:
|
|
913
|
+
self._found[name] = credentials.get(
|
|
914
|
+
name, api_key=self._client._api_key, endpoint=self._client._endpoint
|
|
915
|
+
)
|
|
916
|
+
except credentials.CredentialNotFound as exc:
|
|
917
|
+
raise api_client.CliError(f"{flag}: {exc}") from exc
|
|
918
|
+
return self._found[name]
|
|
919
|
+
|
|
920
|
+
|
|
893
921
|
def cmd_deploy(client: api_client.ApiClient, args: Any) -> None:
|
|
894
922
|
"""Stage authored Python and deploy the checked bundle.
|
|
895
923
|
|
|
@@ -904,12 +932,18 @@ def cmd_deploy(client: api_client.ApiClient, args: Any) -> None:
|
|
|
904
932
|
dspy.configure(lm=lm)
|
|
905
933
|
program = dspy.Predict("question -> answer")
|
|
906
934
|
PY
|
|
935
|
+
|
|
936
|
+
``--credential NAME`` names the credential the program's model spends, and is looked
|
|
937
|
+
up before the source is staged.
|
|
907
938
|
"""
|
|
908
939
|
from pathlib import Path
|
|
909
940
|
|
|
910
941
|
from ... import authored, deployment
|
|
911
942
|
|
|
912
943
|
program_name = _program_args(args, "deploy")
|
|
944
|
+
credential_name = getattr(args, "credential", None)
|
|
945
|
+
if credential_name is not None:
|
|
946
|
+
credential_name = _CredentialLookup(client)(f"--credential {credential_name}", credential_name).name
|
|
913
947
|
try:
|
|
914
948
|
if args.source == "-" or args.source is None:
|
|
915
949
|
prepared = authored.prepare_stdin(
|
|
@@ -939,7 +973,7 @@ def cmd_deploy(client: api_client.ApiClient, args: Any) -> None:
|
|
|
939
973
|
metadata=metadata,
|
|
940
974
|
program_name=program_name,
|
|
941
975
|
config=sdk_config(client),
|
|
942
|
-
credential_name=
|
|
976
|
+
credential_name=credential_name,
|
|
943
977
|
)
|
|
944
978
|
except ValueError as exc:
|
|
945
979
|
raise api_client.CliError(str(exc)) from exc
|
|
@@ -978,6 +1012,67 @@ def _optimizer_arg(value: str) -> str:
|
|
|
978
1012
|
return value
|
|
979
1013
|
|
|
980
1014
|
|
|
1015
|
+
def _lm_credential_arg(value: str) -> tuple[str, str]:
|
|
1016
|
+
"""Parse ``--lm-credential BINDING=NAME`` into the binding and the credential's name.
|
|
1017
|
+
|
|
1018
|
+
The program's own model is refused here, at parse time, because it spends the run's
|
|
1019
|
+
credential and ``--credential`` already names that.
|
|
1020
|
+
"""
|
|
1021
|
+
binding, sep, name = value.partition("=")
|
|
1022
|
+
if not sep or not binding or not name:
|
|
1023
|
+
raise ArgumentTypeError(
|
|
1024
|
+
f"{value!r} is not BINDING=NAME. Give the name of an LM the optimize file's `lms` binds, then a "
|
|
1025
|
+
"credential `cmpnd credentials list` shows, as in judge=typesafe"
|
|
1026
|
+
)
|
|
1027
|
+
from ... import optimization
|
|
1028
|
+
|
|
1029
|
+
if binding == optimization.DEFAULT_LM_BINDING:
|
|
1030
|
+
raise ArgumentTypeError(
|
|
1031
|
+
f"{binding} is the program's own model, which spends the run's credential. Name it with --credential"
|
|
1032
|
+
)
|
|
1033
|
+
return binding, name
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
def _optimize_credentials(
|
|
1037
|
+
client: api_client.ApiClient, args: Any
|
|
1038
|
+
) -> tuple[Any, str | Any | None, dict[str, Any] | None]:
|
|
1039
|
+
"""Look up every credential an optimize names, before the file is staged.
|
|
1040
|
+
|
|
1041
|
+
Returns the run's credential, the reflection LM to pass (the model name, wrapped in
|
|
1042
|
+
``cmpnd.HostedLM`` when ``--reflection-credential`` names its credential) and the
|
|
1043
|
+
credential per binding. :class:`_CredentialLookup` turns each name into a
|
|
1044
|
+
``cmpnd.Credential``, so a mistyped one fails before the import and the deploy a run
|
|
1045
|
+
makes ahead of its first step.
|
|
1046
|
+
"""
|
|
1047
|
+
from ...lm_configuration import HostedLM
|
|
1048
|
+
|
|
1049
|
+
pairs: list[tuple[str, str]] = list(getattr(args, "lm_credential", None) or [])
|
|
1050
|
+
seen: set[str] = set()
|
|
1051
|
+
for binding, _ in pairs:
|
|
1052
|
+
if binding in seen:
|
|
1053
|
+
raise api_client.CliError(
|
|
1054
|
+
f"--lm-credential names the binding {binding!r} more than once. Give each binding one credential."
|
|
1055
|
+
)
|
|
1056
|
+
seen.add(binding)
|
|
1057
|
+
reflection_model = getattr(args, "reflection_model", None)
|
|
1058
|
+
reflection_name = getattr(args, "reflection_credential", None)
|
|
1059
|
+
if reflection_name is not None and reflection_model is None:
|
|
1060
|
+
raise api_client.CliError(
|
|
1061
|
+
"--reflection-credential names the credential the reflection model spends, so it needs --reflection-model."
|
|
1062
|
+
)
|
|
1063
|
+
|
|
1064
|
+
lookup = _CredentialLookup(client)
|
|
1065
|
+
run_name = getattr(args, "credential", None)
|
|
1066
|
+
run = lookup(f"--credential {run_name}", run_name) if run_name is not None else None
|
|
1067
|
+
reflection_lm: str | HostedLM | None = reflection_model
|
|
1068
|
+
if reflection_name is not None and reflection_model is not None:
|
|
1069
|
+
reflection_lm = HostedLM(
|
|
1070
|
+
reflection_model, credential=lookup(f"--reflection-credential {reflection_name}", reflection_name)
|
|
1071
|
+
)
|
|
1072
|
+
lm_credentials = {binding: lookup(f"--lm-credential {binding}={name}", name) for binding, name in pairs}
|
|
1073
|
+
return run, reflection_lm, lm_credentials or None
|
|
1074
|
+
|
|
1075
|
+
|
|
981
1076
|
def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
|
|
982
1077
|
"""Optimize the program, metric and proposer one authored file binds.
|
|
983
1078
|
|
|
@@ -1001,6 +1096,11 @@ def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
1001
1096
|
return dspy.Prediction(score=float(ok),
|
|
1002
1097
|
feedback="" if ok else f"expected {gold.answer}")
|
|
1003
1098
|
PY
|
|
1099
|
+
|
|
1100
|
+
The file cannot name a credential, because the kernel that imports it reaches no
|
|
1101
|
+
platform. ``--credential`` names the run's, ``--reflection-credential`` the
|
|
1102
|
+
reflection model's and ``--lm-credential BINDING=NAME`` one of the file's ``lms``.
|
|
1103
|
+
Each name is looked up before the file is staged.
|
|
1004
1104
|
"""
|
|
1005
1105
|
from pathlib import Path
|
|
1006
1106
|
|
|
@@ -1008,6 +1108,7 @@ def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
1008
1108
|
|
|
1009
1109
|
deployment_id = getattr(args, "deployment_id", None)
|
|
1010
1110
|
program_name = _program_args(args, "optimize", needs_name=deployment_id is None)
|
|
1111
|
+
credential, reflection_lm, lm_credentials = _optimize_credentials(client, args)
|
|
1011
1112
|
try:
|
|
1012
1113
|
if args.source == "-" or args.source is None:
|
|
1013
1114
|
prepared = authored.prepare_optimize_stdin(
|
|
@@ -1041,7 +1142,9 @@ def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
1041
1142
|
# The flag is named for what a command line can carry — a model name,
|
|
1042
1143
|
# never an LM object — and the parameter for what the Python surface
|
|
1043
1144
|
# accepts. Each layer's name says what that layer takes.
|
|
1044
|
-
reflection_lm=
|
|
1145
|
+
reflection_lm=reflection_lm,
|
|
1146
|
+
credential=credential,
|
|
1147
|
+
lm_credentials=lm_credentials,
|
|
1045
1148
|
max_metric_calls=getattr(args, "max_metric_calls", None),
|
|
1046
1149
|
auto=getattr(args, "auto", None),
|
|
1047
1150
|
max_steps=getattr(args, "max_steps", 10),
|
|
@@ -1402,6 +1505,8 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
1402
1505
|
"--reflection-model openai/gpt-4o --auto light\n"
|
|
1403
1506
|
" cmpnd optimize gepa - capital-qa --reflection-model openai/gpt-4o "
|
|
1404
1507
|
"--max-metric-calls 60 < task.py\n"
|
|
1508
|
+
" cmpnd optimize gepa task.py capital-qa --reflection-model openai/gpt-4o --auto light "
|
|
1509
|
+
"--credential openai --lm-credential judge=typesafe\n"
|
|
1405
1510
|
" cmpnd optimize gepa task.py --reflection-model openai/gpt-4o --auto light --deployment-id d_a1b2c3d4\n"
|
|
1406
1511
|
),
|
|
1407
1512
|
formatter_class=raw,
|
|
@@ -1438,6 +1543,31 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
1438
1543
|
"instructions (required for gepa; the server will not substitute the "
|
|
1439
1544
|
"deployment's own model)",
|
|
1440
1545
|
)
|
|
1546
|
+
opt.add_argument(
|
|
1547
|
+
"--credential",
|
|
1548
|
+
metavar="NAME",
|
|
1549
|
+
default=None,
|
|
1550
|
+
help="The provider credential the run spends: the program's model, and every model that names no "
|
|
1551
|
+
"credential of its own (default: the organization's default credential). "
|
|
1552
|
+
"`cmpnd credentials list` shows the names",
|
|
1553
|
+
)
|
|
1554
|
+
opt.add_argument(
|
|
1555
|
+
"--reflection-credential",
|
|
1556
|
+
dest="reflection_credential",
|
|
1557
|
+
metavar="NAME",
|
|
1558
|
+
default=None,
|
|
1559
|
+
help="The credential the reflection model spends instead of the run's",
|
|
1560
|
+
)
|
|
1561
|
+
opt.add_argument(
|
|
1562
|
+
"--lm-credential",
|
|
1563
|
+
dest="lm_credential",
|
|
1564
|
+
metavar="BINDING=NAME",
|
|
1565
|
+
type=_lm_credential_arg,
|
|
1566
|
+
action="append",
|
|
1567
|
+
default=None,
|
|
1568
|
+
help="The credential one LM of the file's `lms` spends instead of the run's, as judge=typesafe. Repeat it "
|
|
1569
|
+
"for each binding",
|
|
1570
|
+
)
|
|
1441
1571
|
# A budget is mandatory and the optimizer will not choose one — see
|
|
1442
1572
|
# `cmpnd.optimization._apply_budget`. Two flags rather than one because a
|
|
1443
1573
|
# caller who does not yet know what a run costs reaches for `--auto`, and one
|
|
@@ -508,6 +508,62 @@ def _lms_config(config: dict[str, Any], lms: Any) -> None:
|
|
|
508
508
|
config["lms"] = declared
|
|
509
509
|
|
|
510
510
|
|
|
511
|
+
def _lm_credentials_config(config: dict[str, Any], lm_credentials: Any) -> None:
|
|
512
|
+
"""Name the credential each of the run's declared LM bindings spends.
|
|
513
|
+
|
|
514
|
+
An authored file declares its ``lms`` and cannot name a credential for one, because
|
|
515
|
+
the kernel that imports it again reaches no platform to look one up. So the call that
|
|
516
|
+
starts the run names it, by binding name, and the entry gains the ``credential`` field
|
|
517
|
+
that ``cmpnd.HostedLM(lm, credential=)`` and ``cmpnd.SystemOne(model, credential=)``
|
|
518
|
+
give it in a live-object call. The wire shape is the same either way.
|
|
519
|
+
|
|
520
|
+
Each binding must be one the run declares, and must not already name a credential:
|
|
521
|
+
a second credential for one binding is two answers to one question, the refusal
|
|
522
|
+
:func:`_reflection_config` makes of ``reflection_credential``. The program's own
|
|
523
|
+
model spends the run's ``credential=``, so ``default-lm`` is refused here.
|
|
524
|
+
|
|
525
|
+
``config["lms"]`` is replaced rather than edited, because it may be the dict the
|
|
526
|
+
import child reported, which the caller's prepared file still holds.
|
|
527
|
+
"""
|
|
528
|
+
if not lm_credentials:
|
|
529
|
+
return
|
|
530
|
+
if not isinstance(lm_credentials, dict):
|
|
531
|
+
raise ValueError(f"lm_credentials must map binding names to credentials (got {type(lm_credentials).__name__})")
|
|
532
|
+
declared: dict[str, Any] = config.get("lms") or {}
|
|
533
|
+
named: dict[str, str] = {}
|
|
534
|
+
for binding, credential in lm_credentials.items():
|
|
535
|
+
if binding == DEFAULT_LM_BINDING:
|
|
536
|
+
raise ValueError(
|
|
537
|
+
f"{DEFAULT_LM_BINDING!r} is the program's own model, and it spends the run's credential. "
|
|
538
|
+
"Name that credential for the run instead of for a binding."
|
|
539
|
+
)
|
|
540
|
+
name = credentials.name_of(credential, f"lm_credentials[{binding!r}]")
|
|
541
|
+
if name is None:
|
|
542
|
+
continue
|
|
543
|
+
if binding not in declared:
|
|
544
|
+
if not declared:
|
|
545
|
+
raise ValueError(
|
|
546
|
+
f"a credential is named for the LM binding {binding!r}, and the optimize file binds no "
|
|
547
|
+
"`lms`, so the run has no binding to spend it."
|
|
548
|
+
)
|
|
549
|
+
raise ValueError(
|
|
550
|
+
f"a credential is named for the LM binding {binding!r}, and the optimize file's `lms` binds no "
|
|
551
|
+
f"such name. It binds {', '.join(repr(each) for each in sorted(declared))}."
|
|
552
|
+
)
|
|
553
|
+
existing = declared[binding].get("credential")
|
|
554
|
+
if existing is not None:
|
|
555
|
+
raise ValueError(
|
|
556
|
+
f"the LM binding {binding!r} already names the credential {existing!r}, and a second one was "
|
|
557
|
+
f"named for it: these are two answers to which credential it spends. Keep one."
|
|
558
|
+
)
|
|
559
|
+
named[binding] = name
|
|
560
|
+
if named:
|
|
561
|
+
config["lms"] = {
|
|
562
|
+
binding: {**entry, "credential": named[binding]} if binding in named else entry
|
|
563
|
+
for binding, entry in declared.items()
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
|
|
511
567
|
def _check_metric_call(metric: Callable[..., Any], optimizer: str) -> None: # pyright: ignore[reportUnusedFunction] # called by the authored import child
|
|
512
568
|
"""Check the authored metric against the kernel's positional and named arguments."""
|
|
513
569
|
signature = inspect.signature(metric)
|
|
@@ -938,6 +994,7 @@ def optimize_authored(
|
|
|
938
994
|
config: dict[str, Any] | None = None,
|
|
939
995
|
reflection_lm: str | Any | None = None,
|
|
940
996
|
lms: dict[str, Any] | None = None,
|
|
997
|
+
lm_credentials: dict[str, credentials.Credential] | None = None,
|
|
941
998
|
credential: credentials.Credential | None = None,
|
|
942
999
|
max_metric_calls: int | None = None,
|
|
943
1000
|
auto: str | None = None,
|
|
@@ -972,6 +1029,9 @@ def optimize_authored(
|
|
|
972
1029
|
resolves, because that deployment already has its program.
|
|
973
1030
|
|
|
974
1031
|
``credential`` is the run's credential, as on :func:`optimize_deployment`.
|
|
1032
|
+
``lm_credentials`` maps a binding the file's ``lms`` declares to the credential it
|
|
1033
|
+
spends instead, which the file itself cannot name. A ``cmpnd.HostedLM`` or
|
|
1034
|
+
``cmpnd.SystemOne`` names one in a live-object call.
|
|
975
1035
|
"""
|
|
976
1036
|
check_optimizer(optimizer)
|
|
977
1037
|
credential_name = credentials.name_of(credential, "optimize_authored(credential=)")
|
|
@@ -985,6 +1045,7 @@ def optimize_authored(
|
|
|
985
1045
|
config["lms"] = prepared.facts.lms
|
|
986
1046
|
_reflection_config(config, reflection_lm)
|
|
987
1047
|
_lms_config(config, lms)
|
|
1048
|
+
_lm_credentials_config(config, lm_credentials)
|
|
988
1049
|
_apply_budget(config, max_metric_calls=max_metric_calls, auto=auto)
|
|
989
1050
|
|
|
990
1051
|
if prepared.program_bundle is None and deployment_id is not None and program_name is not None:
|
|
@@ -1332,15 +1332,13 @@ def code_component_types(student: Any) -> dict[str, str]:
|
|
|
1332
1332
|
serve cannot tell source from prose on its own, and without this a Flex's module
|
|
1333
1333
|
source renders as a word-diffed paragraph.
|
|
1334
1334
|
|
|
1335
|
-
Empty for a program with no Flex,
|
|
1336
|
-
not a dspy module.
|
|
1335
|
+
Empty for a program with no Flex, and for anything that is not a dspy module.
|
|
1337
1336
|
"""
|
|
1338
1337
|
import dspy
|
|
1339
1338
|
|
|
1340
|
-
|
|
1341
|
-
if flex_cls is None or not isinstance(student, dspy.Module):
|
|
1339
|
+
if not isinstance(student, dspy.Module):
|
|
1342
1340
|
return {}
|
|
1343
|
-
return {name: COMPONENT_KIND_CODE for name, sub in student.named_parameters() if isinstance(sub,
|
|
1341
|
+
return {name: COMPONENT_KIND_CODE for name, sub in student.named_parameters() if isinstance(sub, dspy.Flex)}
|
|
1344
1342
|
|
|
1345
1343
|
|
|
1346
1344
|
def enrich_from_program(
|
|
@@ -59,9 +59,6 @@ and sends the id.
|
|
|
59
59
|
`cmpnd programs purge --all` empties the Trash. Nothing restores a purged
|
|
60
60
|
program. Both ask for confirmation at a terminal and need `--force` anywhere
|
|
61
61
|
else. A live program is never purged.
|
|
62
|
-
A program created by name cannot be purged yet. It stays in the Trash, where
|
|
63
|
-
it no longer counts toward a plan's program cap, and `purge --all` answers
|
|
64
|
-
how many such programs it kept.
|
|
65
62
|
|
|
66
63
|
A program in the Trash or purged gives up its name. The next
|
|
67
64
|
`cmpnd programs create`, `cmpnd deploy` or SDK run under that name gets a new
|
|
@@ -197,9 +194,11 @@ model and the field, never the value. Seal the key with
|
|
|
197
194
|
`cmpnd credentials create` instead.
|
|
198
195
|
|
|
199
196
|
`--credential NAME` names the provider credential the program's model spends.
|
|
200
|
-
Without it, the deployment spends the organization's default. The CLI
|
|
201
|
-
name
|
|
202
|
-
|
|
197
|
+
Without it, the deployment spends the organization's default. The CLI looks
|
|
198
|
+
the name up before it stages the source. A name the organization does not hold
|
|
199
|
+
fails with the names it does hold and the nearest one, and nothing is sent.
|
|
200
|
+
The platform checks the name again at the deploy, which catches a credential
|
|
201
|
+
an owner revoked in between. The deploy prints the protocol, model and
|
|
203
202
|
credential of each binding, such as
|
|
204
203
|
`default-lm: chat_completions openai/deepseek-4.1-flash → luna-exec (https://gw.lunaroute.com)`.
|
|
205
204
|
|
|
@@ -621,6 +620,27 @@ organization must hold provider credentials for every model the run uses.
|
|
|
621
620
|
Named model declarations hold model names and sampling settings, and the CLI
|
|
622
621
|
refuses a provider key on them.
|
|
623
622
|
|
|
623
|
+
A run spends the organization's default credential unless the command names
|
|
624
|
+
another. `--credential NAME` names the run's credential. The program's model
|
|
625
|
+
spends it, and so does every model that names no credential of its own.
|
|
626
|
+
`--reflection-credential NAME` names the credential the reflection model
|
|
627
|
+
spends. `--lm-credential BINDING=NAME` names the credential one model in the
|
|
628
|
+
file's `lms` spends, and repeats once per binding:
|
|
629
|
+
|
|
630
|
+
```bash
|
|
631
|
+
cmpnd optimize gepa task.py capital-qa --reflection-model openai/gpt-5 --auto light \
|
|
632
|
+
--credential openai --lm-credential judge=typesafe
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
The file cannot name a credential itself, because the kernel imports it again
|
|
636
|
+
and reaches no platform there. These flags send what `credential=`,
|
|
637
|
+
`cmpnd.HostedLM(lm, credential=)` and `cmpnd.SystemOne(model, credential=)`
|
|
638
|
+
send from Python. The CLI looks up every name before it stages the file. A
|
|
639
|
+
name the organization does not hold fails with the names it does hold and the
|
|
640
|
+
nearest one. The CLI also refuses `--lm-credential` for a binding the file's
|
|
641
|
+
`lms` does not declare, for one binding twice and for `default-lm`. The
|
|
642
|
+
program's own model spends the run's credential, so `--credential` names it.
|
|
643
|
+
|
|
624
644
|
`cmpnd.Client.optimize_deployment(deployment, metric=fn)` in a notebook packages
|
|
625
645
|
live objects and takes named models through its `lms` argument. An authored file's `lms`
|
|
626
646
|
binding cannot be combined with `lms` in the parent call or `config["lms"]`.
|
|
@@ -5,7 +5,7 @@ name = "cmpnd"
|
|
|
5
5
|
# it's new) — no git tags. Bump with `uv version --bump {minor,patch}` or by
|
|
6
6
|
# hand. Runtime `cmpnd.__version__` reads installed metadata, so it tracks
|
|
7
7
|
# this automatically. See docs/plans/2026-07-02-sdk-pypi-tag-free-publish.md.
|
|
8
|
-
version = "0.
|
|
8
|
+
version = "0.59.0"
|
|
9
9
|
description = "DSPy observability and deployment SDK for cmpnd"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.11"
|
|
@@ -28,7 +28,7 @@ classifiers = [
|
|
|
28
28
|
]
|
|
29
29
|
|
|
30
30
|
dependencies = [
|
|
31
|
-
"dspy>=3.
|
|
31
|
+
"dspy>=3.3.0",
|
|
32
32
|
# Reached directly by the SDK (cmpnd/optimization.py's poll, cmpnd/datasets.py)
|
|
33
33
|
# and by the ingest transport this wheel bundles (ingest/cmpnd_ingest), whose
|
|
34
34
|
# only non-stdlib import it is.
|
|
@@ -129,6 +129,33 @@ fake-provider = [
|
|
|
129
129
|
[tool.uv.sources]
|
|
130
130
|
cmpnd-fake-provider = { path = "../fake-provider", editable = true }
|
|
131
131
|
|
|
132
|
+
# One group per DSPy version the test matrix runs, so uv.lock locks a whole tree for
|
|
133
|
+
# each and a test leg installs from the lock with only DSPy varying
|
|
134
|
+
# (`scripts/install-test-env.sh`; AGENTS.md, "DSPy versions under test"). Dependency
|
|
135
|
+
# groups never reach the wheel's metadata, so a user's install still takes
|
|
136
|
+
# `dspy>=3.3.0` above. Each group name is `dspy-` and the version with dots turned to
|
|
137
|
+
# dashes.
|
|
138
|
+
[dependency-groups]
|
|
139
|
+
dspy-3-4-1 = ["dspy==3.4.1"]
|
|
140
|
+
dspy-3-4-0 = ["dspy==3.4.0"]
|
|
141
|
+
dspy-3-3-1 = ["dspy==3.3.1"]
|
|
142
|
+
dspy-3-3-0 = ["dspy==3.3.0"]
|
|
143
|
+
|
|
144
|
+
[tool.uv]
|
|
145
|
+
# A plain `uv sync` or `uv run` installs the newest tested DSPy.
|
|
146
|
+
default-groups = ["dspy-3-4-1"]
|
|
147
|
+
# The groups pin different DSPy versions, so uv resolves a separate fork for each.
|
|
148
|
+
# The mcp extra floors dspy at 3.3.1, so it conflicts with the dspy-3-3-0 group.
|
|
149
|
+
conflicts = [
|
|
150
|
+
[
|
|
151
|
+
{ group = "dspy-3-4-1" },
|
|
152
|
+
{ group = "dspy-3-4-0" },
|
|
153
|
+
{ group = "dspy-3-3-1" },
|
|
154
|
+
{ group = "dspy-3-3-0" },
|
|
155
|
+
],
|
|
156
|
+
[{ extra = "mcp" }, { group = "dspy-3-3-0" }],
|
|
157
|
+
]
|
|
158
|
+
|
|
132
159
|
[project.scripts]
|
|
133
160
|
cmpnd = "cmpnd.cli:main"
|
|
134
161
|
|
|
@@ -141,8 +168,8 @@ Homepage = "https://cmpnd.ai"
|
|
|
141
168
|
# the two move together or a release fails at upload. hatchling 1.32.0 stamps
|
|
142
169
|
# 2.5, and the twine inside the pinned pypa/gh-action-pypi-publish accepts it;
|
|
143
170
|
# that pairing is what `twine check --strict` in sdk-lint proves on every PR.
|
|
144
|
-
# `==` so a backend bump
|
|
145
|
-
# than
|
|
171
|
+
# `==` so a backend bump is a reviewed edit here that runs that check, rather
|
|
172
|
+
# than a silent resolution on release day.
|
|
146
173
|
requires = ["hatchling==1.32.0"]
|
|
147
174
|
build-backend = "hatchling.build"
|
|
148
175
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Install cmpnd with pip's own resolver and check that the installed package
|
|
3
3
|
# imports and instruments DSPy. Called by .github/workflows/ci-sdk.yml (the
|
|
4
|
-
# sdk-test leg for Python 3.15).
|
|
4
|
+
# sdk-test-py315 job, the sdk-test leg for Python 3.15).
|
|
5
5
|
#
|
|
6
6
|
# `pip install cmpnd` is the install PyPI advertises. pip honours the
|
|
7
7
|
# requires-python upper bounds that uv ignores, so this check fails when no
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Build python-sdk/.venv for one sdk-test leg: the SDK and its dev extra, on one
|
|
3
|
+
# Python, at one DSPy version.
|
|
4
|
+
#
|
|
5
|
+
# CALLED BY: .github/workflows/ci-sdk.yml (sdk-test and sdk-test-py315),
|
|
6
|
+
# .github/workflows/sdk-latest-dependencies.yml (with --latest), `just
|
|
7
|
+
# sdk-test-env` at the repo root, and scripts/test-in-linux.sh.
|
|
8
|
+
#
|
|
9
|
+
# The install reads uv.lock. pyproject.toml declares one dependency group per
|
|
10
|
+
# tested DSPy, named `dspy-` and the version with dots turned to dashes, and the lock
|
|
11
|
+
# holds a whole tree for each. A leg syncs its group's tree exactly, so only DSPy,
|
|
12
|
+
# and whatever that DSPy itself pins, differs between legs. A red leg means the
|
|
13
|
+
# change broke something rather than that an upstream release did. The lock moves only
|
|
14
|
+
# by a reviewed commit: a security upgrade when bin/vulncheck fails on an advisory a
|
|
15
|
+
# bump would fix, and any other upgrade as a deliberate lock bump.
|
|
16
|
+
#
|
|
17
|
+
# --latest resolves every package fresh, with only the Python and DSPy versions
|
|
18
|
+
# pinned, as a user's install today would. With the DSPy version `newest`, it pins no
|
|
19
|
+
# DSPy either, so the install takes the newest release `dspy>=…` admits, as `pip
|
|
20
|
+
# install cmpnd` does. It prints the DSPy it installed. The daily run in
|
|
21
|
+
# sdk-latest-dependencies.yml uses it.
|
|
22
|
+
#
|
|
23
|
+
# Usage: scripts/install-test-env.sh [--latest] <python-version> <dspy-version>
|
|
24
|
+
# e.g. scripts/install-test-env.sh 3.13 3.3.0
|
|
25
|
+
# scripts/install-test-env.sh --latest 3.13 3.4.1
|
|
26
|
+
# scripts/install-test-env.sh --latest 3.14 newest
|
|
27
|
+
set -euo pipefail
|
|
28
|
+
|
|
29
|
+
usage() {
|
|
30
|
+
echo "usage: $0 [--latest] <python-version> <dspy-version>" >&2
|
|
31
|
+
exit 2
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
latest=false
|
|
35
|
+
if [ "${1:-}" = "--latest" ]; then
|
|
36
|
+
latest=true
|
|
37
|
+
shift
|
|
38
|
+
fi
|
|
39
|
+
[ "$#" -eq 2 ] || usage
|
|
40
|
+
python_version=$1
|
|
41
|
+
dspy_version=$2
|
|
42
|
+
|
|
43
|
+
cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
|
44
|
+
|
|
45
|
+
if [ "$latest" = true ]; then
|
|
46
|
+
uv venv --clear --python "$python_version" .venv
|
|
47
|
+
if [ "$dspy_version" = newest ]; then
|
|
48
|
+
uv pip install --python .venv/bin/python -e ".[dev]"
|
|
49
|
+
else
|
|
50
|
+
uv pip install --python .venv/bin/python -e ".[dev]" "dspy==$dspy_version"
|
|
51
|
+
fi
|
|
52
|
+
echo "install-test-env: installed $(uv pip freeze --python .venv/bin/python | grep -i '^dspy==')"
|
|
53
|
+
exit 0
|
|
54
|
+
fi
|
|
55
|
+
if [ "$dspy_version" = newest ]; then
|
|
56
|
+
echo "install-test-env: the DSPy version newest needs --latest. uv.lock locks one tree per tested DSPy, and a locked install names one." >&2
|
|
57
|
+
exit 2
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
group="dspy-${dspy_version//./-}"
|
|
61
|
+
if ! grep -qE "^$group = " pyproject.toml; then
|
|
62
|
+
echo "install-test-env: pyproject.toml declares no dependency group $group. To test dspy $dspy_version, add $group = [\"dspy==$dspy_version\"] to [dependency-groups] and to the conflicts list in [tool.uv], then run uv lock." >&2
|
|
63
|
+
exit 2
|
|
64
|
+
fi
|
|
65
|
+
# --frozen installs exactly what uv.lock holds and never re-locks. sdk-lint's
|
|
66
|
+
# `uv lock --check` fails when the lock is stale against pyproject.toml.
|
|
67
|
+
uv sync --frozen --extra dev --no-default-groups --group "$group" --python "$python_version"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Run the SDK suite (or a subset) on Linux, in a container that
|
|
2
|
+
# Run the SDK suite (or a subset) on Linux, in a container that installs its own
|
|
3
3
|
# dependencies.
|
|
4
4
|
#
|
|
5
5
|
# The lane exists because some of what this SDK has to get right is only
|
|
@@ -16,14 +16,16 @@
|
|
|
16
16
|
#
|
|
17
17
|
# It deliberately does NOT mount the host `.venv`: those wheels are macOS
|
|
18
18
|
# wheels, and a container reusing them fails in ways that teach you nothing about
|
|
19
|
-
# the code. uv
|
|
20
|
-
# to "would CI see this too".
|
|
21
|
-
#
|
|
19
|
+
# the code. uv installs from uv.lock inside, which is also what makes this a real
|
|
20
|
+
# answer to "would CI see this too". SDK_TEST_DSPY_VERSION picks a DSPy group the
|
|
21
|
+
# way an sdk-test leg does (scripts/install-test-env.sh). The copy also drops
|
|
22
|
+
# `.pyc` files because their embedded source paths may name a worktree that moved
|
|
23
|
+
# or no longer exists.
|
|
22
24
|
#
|
|
23
25
|
# Usage — everything, or a pytest selector:
|
|
24
26
|
# python-sdk/scripts/test-in-linux.sh
|
|
25
27
|
# python-sdk/scripts/test-in-linux.sh tests/test_exporter_queue.py -v
|
|
26
|
-
# SDK_TEST_DSPY_VERSION=3.
|
|
28
|
+
# SDK_TEST_DSPY_VERSION=3.3.0 python-sdk/scripts/test-in-linux.sh tests/test_run_attribution_paths.py -v
|
|
27
29
|
#
|
|
28
30
|
# Called by: `just sdk-test-linux` (repo root). No CI workflow — CI already runs
|
|
29
31
|
# on Linux; this is the local mirror of what it does.
|
|
@@ -31,7 +33,7 @@ set -euo pipefail
|
|
|
31
33
|
|
|
32
34
|
SDK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
33
35
|
REPO_DIR="$(cd "${SDK_DIR}/.." && pwd)"
|
|
34
|
-
IMAGE="${SDK_TEST_LINUX_IMAGE:-ghcr.io/astral-sh/uv:python3.12-
|
|
36
|
+
IMAGE="${SDK_TEST_LINUX_IMAGE:-ghcr.io/astral-sh/uv:python3.12-trixie-slim}"
|
|
35
37
|
ARGS=("$@")
|
|
36
38
|
if [ ${#ARGS[@]} -eq 0 ]; then
|
|
37
39
|
ARGS=(tests/ -k "not e2e")
|
|
@@ -53,8 +55,7 @@ cp -r /repo/python-sdk /repo/fake-provider /repo/docs /work/
|
|
|
53
55
|
rm -rf /work/python-sdk/.venv
|
|
54
56
|
find /work/python-sdk -name "*.pyc" -delete
|
|
55
57
|
if [ -n "$SDK_TEST_DSPY_VERSION" ]; then
|
|
56
|
-
|
|
57
|
-
uv pip install --quiet -e ".[dev]" "dspy==$SDK_TEST_DSPY_VERSION"
|
|
58
|
+
scripts/install-test-env.sh 3.12 "$SDK_TEST_DSPY_VERSION"
|
|
58
59
|
else
|
|
59
60
|
uv sync --extra dev --quiet
|
|
60
61
|
fi
|