cmpnd 0.55.0__tar.gz → 0.57.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.55.0 → cmpnd-0.57.0}/PKG-INFO +173 -11
- {cmpnd-0.55.0 → cmpnd-0.57.0}/README.md +172 -10
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/__init__.py +8 -2
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/_authored_import.py +113 -4
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/callback.py +3 -2
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/commands/programs.py +139 -0
- cmpnd-0.57.0/cmpnd/cli/commands/provider_credentials.py +532 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/commands/top.py +19 -1
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/verbs.py +5 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/client.py +54 -9
- cmpnd-0.57.0/cmpnd/credentials.py +199 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/deployment.py +105 -22
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/helpers.py +116 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/lm_configuration.py +71 -15
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/optimization.py +109 -53
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/programs.py +1 -1
- {cmpnd-0.55.0 → cmpnd-0.57.0}/docs/cli.md +122 -24
- {cmpnd-0.55.0 → cmpnd-0.57.0}/docs/sdk-instrumentation.md +16 -5
- {cmpnd-0.55.0 → cmpnd-0.57.0}/pyproject.toml +1 -1
- {cmpnd-0.55.0 → cmpnd-0.57.0}/scripts/capture_review_run.py +4 -1
- cmpnd-0.57.0/tests/_credential_route.py +96 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/committee_task.py +49 -54
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/helpers.py +93 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +3 -3
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/integration/hosted_optimize_probe.py +51 -26
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/integration/test_logs_renders_traces.py +2 -2
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/stress/_provider.py +31 -24
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/stress/test_deploy_programs.py +7 -4
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/stress/test_optimize_loop.py +19 -18
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/stress/test_optimize_loop_wasm.py +23 -50
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/stress/test_optimize_stress.py +16 -18
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/stress/test_provider_roles.py +65 -3
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/stress/test_real_provider_twins.py +11 -9
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/stress/test_rlm_wasm.py +8 -13
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_authored_named_models.py +19 -5
- cmpnd-0.57.0/tests/test_cli/test_programs.py +290 -0
- cmpnd-0.57.0/tests/test_cli/test_provider_credentials.py +660 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_top.py +31 -1
- cmpnd-0.57.0/tests/test_credential_kwargs.py +306 -0
- cmpnd-0.57.0/tests/test_credential_selection.py +320 -0
- cmpnd-0.57.0/tests/test_credential_values.py +193 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_deploy.py +55 -84
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_optimize_lms.py +29 -15
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_optimize_reflection_lm.py +18 -34
- cmpnd-0.57.0/tests/test_seal_lm_credential.py +153 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_system_one.py +31 -12
- {cmpnd-0.55.0 → cmpnd-0.57.0}/uv.lock +1 -1
- cmpnd-0.55.0/cmpnd/cli/commands/provider_credentials.py +0 -321
- cmpnd-0.55.0/tests/test_cli/test_programs.py +0 -143
- cmpnd-0.55.0/tests/test_cli/test_provider_credentials.py +0 -319
- {cmpnd-0.55.0 → cmpnd-0.57.0}/.gitignore +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/AGENTS.md +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/CLAUDE.md +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/_assessments.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/_deployments.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/_gepa_patch.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/_http.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/_runtime_tools.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/_traces.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/_valsets.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/authored.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/__init__.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/commands/admin.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/commands/assessments.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/commands/deployments.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/commands/evals.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/commands/org.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/commands/signatures.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/commands/traces.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/commands/valsets.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/shell.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/trace_render.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/cli/tree_help.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/configuration.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/context.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/dataset_sync.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/datasets.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/decorators.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/deployment_metadata.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/deployment_runs.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/execution.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/exporter.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/identity.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/imaging.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/ir/reflection.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/mcp_servers.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/models.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/named_programs.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/optimizers/gepa.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/optimizers/gepa_callback.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/optimizers/tracker.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/packaging.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/references.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/retry.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/cmpnd/traces.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/docs/2026-04-02-code-review.md +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/docs/client.md +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/docs/custom-proposer.md +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/docs/reading-traces.md +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/examples/local_ollama.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/ingest/README.md +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/ingest/cmpnd_ingest/bodies.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/ingest/cmpnd_ingest/client_floor.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/ingest/cmpnd_ingest/program_names.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/ingest/cmpnd_ingest/protocols.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/ingest/pyproject.toml +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/scripts/check-pip-install.sh +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/scripts/release-stress.sh +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/scripts/require_version_bump.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/scripts/seed_review_run.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/scripts/selfhost_compat.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/scripts/test-in-linux.sh +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/stubs/dspy/utils/dummies.pyi +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/__init__.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/_deployment_signature_helpers.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/_fake_clock.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/_resolve_refs.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/_traced_run.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/conftest.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/target_capabilities.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_assessments_cli.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_composite_module.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_deploy_execute.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_deploy_pandas.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_deploy_programs.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_optimize_failures.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_parallel_export.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_project_attribution.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_traces_read.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_user_attribution.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/e2e/test_valsets_cli.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/integration/__init__.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/integration/authored_optimize/judge.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/integration/conftest.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/integration/test_unhappy_path.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/load/__init__.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/load/__main__.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/load/drain.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/load/ingest.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/load/rlm_shape.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/load/rlm_size_report.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/load/sweep.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/load/test_rlm_size_report.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/parity/__init__.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/parity/conftest.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/parity/test_health.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/parity/test_trace_streaming.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/parity/test_traces.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/span_reference_fixture.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/stress/__init__.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/stress/_readiness.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/stress/_reliability.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/stress/_result.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/stress/mint_bedrock_token.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/stress/test_multi_client_stress.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/stress/test_optimize_client_committee.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/stress/test_readiness.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/stress/test_reliability.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/stress/test_reliability_classify.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/stress/test_trace_search.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_adapter_reentrancy.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_assessments_api.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_authored.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_authored_bundle_collisions.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_authored_metric_contract.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_authored_optimize.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_callback.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_admin.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_api_client.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_assessments.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_deployment_runs.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_deployments.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_evals.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_main.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_org.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_parity.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_schema_sample.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_shell.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_signatures.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_traces.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_traces_search_wire.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_tree_help.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cli/test_valsets.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_client.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_client_floor.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_client_program_names.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_configuration.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_context.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_context_propagation.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_dataset_sync.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_datasets.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_decision_client_spans.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_decorators.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_deploy_program_id.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_deployment_metadata.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_deployment_runs.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_deployments_read.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_eval.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_eval_example_attribution.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_eval_start.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_eval_trace_grouping.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_execute.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_execute_run_identity.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_exporter.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_exporter_program_names.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_exporter_queue.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_exporter_real.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_gepa_output_normalization.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_identity.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_imaging.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_import_weight.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_ingest_conformance.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_integration.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_interpreter_callback.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_lm15_native_calls.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_lm_configuration.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_lm_usage_shapes.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_mcp_tools.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_models.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_multi_client_stress_readiness.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_optimize.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_packaging.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_packaging_mcp_tools.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_packaging_program_source.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_poll_until.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_program_id_deprecation.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_program_identity.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_program_lineage_persist.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_program_names_in_deploy.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_programs_create.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_project_payload.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_real_provider_twins_readiness.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_reflector_properties.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_reflector_smoke.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_release_stress_script.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_replaced_names.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_repro_reactv2_bugs.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_retry.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_rlm_patch.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_run_attribution_paths.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_runtime_tools.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_schemas.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_signature_inputs.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_span_references.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_span_sent_once.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_trace_context_concurrency.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_trace_fact_wire_contract.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_trace_facts.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_trace_render.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_traces_read.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_tracked_gepa.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_truncation.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_upload_auth_headers.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.0}/tests/test_valsets_api.py +0 -0
- {cmpnd-0.55.0 → cmpnd-0.57.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.57.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>
|
|
@@ -205,6 +205,145 @@ The platform makes the program the first time your organization uses the
|
|
|
205
205
|
program name. Later deploys under the same program name go to the same
|
|
206
206
|
program, and so do the traces of a client that runs it.
|
|
207
207
|
|
|
208
|
+
### Credentials
|
|
209
|
+
|
|
210
|
+
A hosted program spends a credential your organization sealed, and the deploy
|
|
211
|
+
names it. The program's code stays the code you run on your laptop:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
lm = dspy.LM("openai/deepseek-4.1-flash", api_base="https://gw.lunaroute.com/v1") # unchanged
|
|
215
|
+
|
|
216
|
+
luna = cmpnd.credentials.get("luna-exec")
|
|
217
|
+
deployed = sdk.deploy_program(program, credential=luna)
|
|
218
|
+
# deploy: default-lm: chat_completions openai/deepseek-4.1-flash → luna-exec (https://gw.lunaroute.com)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
- `cmpnd.credentials.get(name)` returns a `cmpnd.Credential` holding the name,
|
|
222
|
+
the protocols its endpoint serves and its `api_base`. It holds no key. A name
|
|
223
|
+
the organization does not hold raises `cmpnd.CredentialNotFound`, which lists
|
|
224
|
+
the names it does hold and the nearest one. `cmpnd.credentials.list()`
|
|
225
|
+
returns them all.
|
|
226
|
+
- `credential=` takes a `cmpnd.Credential`, never a string. A string raises
|
|
227
|
+
and shows the `cmpnd.credentials.get(...)` line to write.
|
|
228
|
+
- Without `credential=`, the deployment spends the organization's default,
|
|
229
|
+
which an owner sets with `cmpnd credentials default NAME`.
|
|
230
|
+
- The deployment records the credential's name. Each call spends the newest
|
|
231
|
+
credential sealed under that name, so an owner who rotates a key needs no
|
|
232
|
+
redeploy.
|
|
233
|
+
- The deploy prints the protocol, model and credential of each binding, as
|
|
234
|
+
above. A server that prints none chose the credential from the model's
|
|
235
|
+
provider prefix, and the SDK warns.
|
|
236
|
+
- A hosted call goes to the credential's `api_base`. The LM's own `api_base`
|
|
237
|
+
serves your laptop run and is not sent.
|
|
238
|
+
|
|
239
|
+
Keys never leave your laptop. The SDK sends no key on the LM, and a key reaches
|
|
240
|
+
the platform only through `cmpnd credentials create`. `cmpnd deploy FILE`
|
|
241
|
+
refuses source whose import builds an LM carrying a key, and names the module,
|
|
242
|
+
the model and the field.
|
|
243
|
+
|
|
244
|
+
### The same source on your laptop and hosted
|
|
245
|
+
|
|
246
|
+
A program's logic runs unchanged on your laptop and hosted. On your laptop it
|
|
247
|
+
spends the key in your environment. Hosted, it spends a credential your
|
|
248
|
+
organization sealed. Keep the logic in one file and let a small entry point
|
|
249
|
+
choose where it runs.
|
|
250
|
+
|
|
251
|
+
The program file holds the logic and names its model. It holds no key:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
# qa.py
|
|
255
|
+
import dspy
|
|
256
|
+
|
|
257
|
+
lm = dspy.LM("openai/gpt-5-mini") # no api_key: litellm reads OPENAI_API_KEY on your laptop
|
|
258
|
+
program = dspy.Predict("question -> answer")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def metric(gold, pred, trace=None, pred_name=None, pred_trace=None, lms=None):
|
|
262
|
+
verdict = dspy.Predict("answer, gold -> correct: bool")(answer=pred.answer, gold=gold.answer, lm=lms["critic"])
|
|
263
|
+
return dspy.Prediction(score=float(verdict.correct), feedback="correct" if verdict.correct else "wrong")
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Seal the key once for the organization and make it the default:
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
printf '%s' "$OPENAI_API_KEY" | cmpnd credentials create --name openai --preset openai
|
|
270
|
+
cmpnd credentials default openai
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
The entry point is the only code that knows where the run happens:
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
# main.py
|
|
277
|
+
import functools
|
|
278
|
+
import sys
|
|
279
|
+
|
|
280
|
+
import cmpnd
|
|
281
|
+
import dspy
|
|
282
|
+
|
|
283
|
+
from qa import lm, metric, program
|
|
284
|
+
|
|
285
|
+
HOSTED = "--hosted" in sys.argv
|
|
286
|
+
|
|
287
|
+
sdk = cmpnd.Client()
|
|
288
|
+
qa = cmpnd.Program("qa", module=program, lm=lm)
|
|
289
|
+
reflection = dspy.LM("openai/gpt-5", max_tokens=32000)
|
|
290
|
+
lms = {"critic": dspy.LM("openai/gpt-5-mini")}
|
|
291
|
+
train, val = load_examples() # your dspy.Example lists
|
|
292
|
+
|
|
293
|
+
if HOSTED:
|
|
294
|
+
deployed = sdk.deploy_program(qa) # spends the default credential, openai
|
|
295
|
+
print(sdk.run_deployment(deployed, question="What is DSPy?"))
|
|
296
|
+
best = sdk.optimize_deployment(
|
|
297
|
+
deployed,
|
|
298
|
+
trainset=train,
|
|
299
|
+
valset=val,
|
|
300
|
+
metric=metric, # the platform hands it lms
|
|
301
|
+
reflection_lm=cmpnd.HostedLM(reflection),
|
|
302
|
+
lms={name: cmpnd.HostedLM(model) for name, model in lms.items()},
|
|
303
|
+
auto="light",
|
|
304
|
+
)
|
|
305
|
+
else:
|
|
306
|
+
print(sdk.run_program(qa, question="What is DSPy?")) # spends $OPENAI_API_KEY
|
|
307
|
+
gepa = dspy.GEPA(metric=functools.partial(metric, lms=lms), reflection_lm=reflection, auto="light")
|
|
308
|
+
best = sdk.optimize_program(qa, gepa, trainset=train, valset=val)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
`python main.py` runs everything on your laptop, and `python main.py --hosted`
|
|
312
|
+
runs it on the platform. `cmpnd deploy qa.py qa` deploys the same file from the
|
|
313
|
+
terminal.
|
|
314
|
+
|
|
315
|
+
| | On your laptop | Hosted |
|
|
316
|
+
|---|---|---|
|
|
317
|
+
| Key | the provider's variable in your environment, such as `OPENAI_API_KEY` | the credential the call names, else the organization's default |
|
|
318
|
+
| Address | the LM's `api_base`, else the provider's endpoint | the credential's `api_base` |
|
|
319
|
+
| Model | `gpt-5-mini`, the model after its first `/` | `gpt-5-mini` |
|
|
320
|
+
| The metric's `lms` | the dict the entry point binds | the run's `lms=`, which the platform hands it |
|
|
321
|
+
|
|
322
|
+
These rules keep the program file portable:
|
|
323
|
+
|
|
324
|
+
- **Leave the key off the program's LM** and let litellm read the provider's
|
|
325
|
+
own variable. For a key in a variable litellm does not read, write
|
|
326
|
+
`api_key=os.environ.get("NAME")`. cmpnd imports a deploy file with an empty
|
|
327
|
+
environment, so the key is `None` there and present on your laptop.
|
|
328
|
+
- **Never write a key into the file, and never read it as
|
|
329
|
+
`os.environ["NAME"]` at module scope.** The deploy refuses the first. The
|
|
330
|
+
second raises `KeyError` hosted, where the environment holds no key.
|
|
331
|
+
- **Seal the credential at the address your laptop LM reaches.** A program
|
|
332
|
+
that sets `api_base="https://gw.lunaroute.com/v1"` needs a credential sealed
|
|
333
|
+
there, with `--preset lunaroute` or `--api-base`. Hosted calls go to the
|
|
334
|
+
credential's address and ignore the LM's `api_base`. The deploy prints the
|
|
335
|
+
address it chose, so you can compare the two.
|
|
336
|
+
- **Wrap a model you hand the hosted optimization in `cmpnd.HostedLM`.** The
|
|
337
|
+
entry point builds `reflection_lm=` and each `lms=` entry, so those LMs may
|
|
338
|
+
hold a key for the laptop run. The wrapper leaves that key on your machine.
|
|
339
|
+
`optimize_deployment` refuses a key on an unwrapped LM, because there it would
|
|
340
|
+
look spent. `cmpnd.HostedLM(model, credential=c)` makes a model spend a
|
|
341
|
+
credential other than the run's.
|
|
342
|
+
|
|
343
|
+
A judge built on `dspy.experimental.TypeSafe` also runs unchanged in both
|
|
344
|
+
places. [`docs/sdk-instrumentation.md`](docs/sdk-instrumentation.md), under
|
|
345
|
+
"System One judges", describes the hosted side.
|
|
346
|
+
|
|
208
347
|
### Options
|
|
209
348
|
|
|
210
349
|
```python
|
|
@@ -213,6 +352,7 @@ sdk.deploy_program(
|
|
|
213
352
|
metric=None, # optional: a metric callable, packaged for optimization
|
|
214
353
|
timeout=300, # optional: seconds to wait for the deployment to be ready
|
|
215
354
|
metadata=None, # optional: a JSON-serializable dict, at most 64 KiB, stored with the deployment
|
|
355
|
+
credential=None, # optional: a cmpnd.Credential the program's model spends
|
|
216
356
|
)
|
|
217
357
|
```
|
|
218
358
|
|
|
@@ -270,11 +410,32 @@ from the deployment.
|
|
|
270
410
|
`reflection_lm` is the model that proposes each new instruction. The search is
|
|
271
411
|
only as good as this model, so it is usually a strong model and not the one
|
|
272
412
|
being optimized. A `dspy.LM` passes its sampling settings to the provider. A
|
|
273
|
-
bare model name gives the same model with no sampling settings.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
413
|
+
bare model name gives the same model with no sampling settings. A run spends
|
|
414
|
+
sealed credentials only, so the SDK refuses a key on the LM before any network
|
|
415
|
+
call. `cmpnd.HostedLM(lm)` keeps a key your laptop runs spend on this machine
|
|
416
|
+
and passes the LM's model and sampling.
|
|
417
|
+
|
|
418
|
+
`credential=` names the run's credential, which every binding that names none
|
|
419
|
+
spends: the program's model, the reflection model and each entry of `lms`. A
|
|
420
|
+
binding names its own credential through a wrapper for its protocol:
|
|
421
|
+
|
|
422
|
+
```python
|
|
423
|
+
luna = cmpnd.credentials.get("luna-exec") # serves chat_completions and system_one
|
|
424
|
+
openai = cmpnd.credentials.get("openai")
|
|
425
|
+
best = sdk.optimize_deployment(
|
|
426
|
+
deployed, trainset=train, valset=val, metric=metric,
|
|
427
|
+
credential=luna,
|
|
428
|
+
reflection_lm=cmpnd.HostedLM(dspy.LM("openai/gpt-5"), credential=openai),
|
|
429
|
+
lms={"judge": cmpnd.SystemOne("lunaroute/djev", credential=luna)},
|
|
430
|
+
auto="light",
|
|
431
|
+
)
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
`cmpnd.HostedLM(lm, credential=c)` wraps a chat LM, and
|
|
435
|
+
`cmpnd.SystemOne(model, credential=c)` declares a System One model. A binding
|
|
436
|
+
without a credential spends the run's, or the organization's default. The
|
|
437
|
+
platform refuses a credential whose endpoint does not serve a binding's
|
|
438
|
+
protocol, and names the `credential=` to add.
|
|
278
439
|
|
|
279
440
|
The SDK checks the metric before any network call. GEPA writes each new
|
|
280
441
|
instruction from the textual feedback the metric returns, so it needs a
|
|
@@ -322,9 +483,9 @@ rules:
|
|
|
322
483
|
names.
|
|
323
484
|
- `"default-lm"` is reserved for the program's own model, which comes from the
|
|
324
485
|
deployment. Declaring it raises an error that names it.
|
|
325
|
-
- The platform refuses a
|
|
326
|
-
the run, before the run starts. These LMs, like `reflection_lm`, must not
|
|
327
|
-
|
|
486
|
+
- The platform refuses a binding it finds no credential for when it dispatches
|
|
487
|
+
the run, before the run starts. These LMs, like `reflection_lm`, must not
|
|
488
|
+
carry a key.
|
|
328
489
|
|
|
329
490
|
The `lms` parameter does not change the arity check. Five or more positional
|
|
330
491
|
arguments make a feedback metric, and a metric without `lms` never receives
|
|
@@ -340,8 +501,9 @@ sdk.optimize_deployment(
|
|
|
340
501
|
valset=val, # required: validation examples
|
|
341
502
|
metric=metric, # required: the metric, arity-checked
|
|
342
503
|
config=None, # optional: optimizer config, allow-listed by the platform
|
|
343
|
-
reflection_lm=None, # required: a dspy.LM
|
|
344
|
-
lms=None, # optional: name -> dspy.LM,
|
|
504
|
+
reflection_lm=None, # required: a dspy.LM, a bare model name or a cmpnd.HostedLM
|
|
505
|
+
lms=None, # optional: name -> dspy.LM, cmpnd.HostedLM or cmpnd.SystemOne
|
|
506
|
+
credential=None, # optional: the cmpnd.Credential every other binding spends
|
|
345
507
|
max_metric_calls=None, # budget: the metric-call ceiling for one step
|
|
346
508
|
auto=None, # budget: "light", "medium" or "heavy"
|
|
347
509
|
may_author_code=None, # optional: False keeps each dspy.Flex at its deployed source
|
|
@@ -154,6 +154,145 @@ The platform makes the program the first time your organization uses the
|
|
|
154
154
|
program name. Later deploys under the same program name go to the same
|
|
155
155
|
program, and so do the traces of a client that runs it.
|
|
156
156
|
|
|
157
|
+
### Credentials
|
|
158
|
+
|
|
159
|
+
A hosted program spends a credential your organization sealed, and the deploy
|
|
160
|
+
names it. The program's code stays the code you run on your laptop:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
lm = dspy.LM("openai/deepseek-4.1-flash", api_base="https://gw.lunaroute.com/v1") # unchanged
|
|
164
|
+
|
|
165
|
+
luna = cmpnd.credentials.get("luna-exec")
|
|
166
|
+
deployed = sdk.deploy_program(program, credential=luna)
|
|
167
|
+
# deploy: default-lm: chat_completions openai/deepseek-4.1-flash → luna-exec (https://gw.lunaroute.com)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
- `cmpnd.credentials.get(name)` returns a `cmpnd.Credential` holding the name,
|
|
171
|
+
the protocols its endpoint serves and its `api_base`. It holds no key. A name
|
|
172
|
+
the organization does not hold raises `cmpnd.CredentialNotFound`, which lists
|
|
173
|
+
the names it does hold and the nearest one. `cmpnd.credentials.list()`
|
|
174
|
+
returns them all.
|
|
175
|
+
- `credential=` takes a `cmpnd.Credential`, never a string. A string raises
|
|
176
|
+
and shows the `cmpnd.credentials.get(...)` line to write.
|
|
177
|
+
- Without `credential=`, the deployment spends the organization's default,
|
|
178
|
+
which an owner sets with `cmpnd credentials default NAME`.
|
|
179
|
+
- The deployment records the credential's name. Each call spends the newest
|
|
180
|
+
credential sealed under that name, so an owner who rotates a key needs no
|
|
181
|
+
redeploy.
|
|
182
|
+
- The deploy prints the protocol, model and credential of each binding, as
|
|
183
|
+
above. A server that prints none chose the credential from the model's
|
|
184
|
+
provider prefix, and the SDK warns.
|
|
185
|
+
- A hosted call goes to the credential's `api_base`. The LM's own `api_base`
|
|
186
|
+
serves your laptop run and is not sent.
|
|
187
|
+
|
|
188
|
+
Keys never leave your laptop. The SDK sends no key on the LM, and a key reaches
|
|
189
|
+
the platform only through `cmpnd credentials create`. `cmpnd deploy FILE`
|
|
190
|
+
refuses source whose import builds an LM carrying a key, and names the module,
|
|
191
|
+
the model and the field.
|
|
192
|
+
|
|
193
|
+
### The same source on your laptop and hosted
|
|
194
|
+
|
|
195
|
+
A program's logic runs unchanged on your laptop and hosted. On your laptop it
|
|
196
|
+
spends the key in your environment. Hosted, it spends a credential your
|
|
197
|
+
organization sealed. Keep the logic in one file and let a small entry point
|
|
198
|
+
choose where it runs.
|
|
199
|
+
|
|
200
|
+
The program file holds the logic and names its model. It holds no key:
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
# qa.py
|
|
204
|
+
import dspy
|
|
205
|
+
|
|
206
|
+
lm = dspy.LM("openai/gpt-5-mini") # no api_key: litellm reads OPENAI_API_KEY on your laptop
|
|
207
|
+
program = dspy.Predict("question -> answer")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def metric(gold, pred, trace=None, pred_name=None, pred_trace=None, lms=None):
|
|
211
|
+
verdict = dspy.Predict("answer, gold -> correct: bool")(answer=pred.answer, gold=gold.answer, lm=lms["critic"])
|
|
212
|
+
return dspy.Prediction(score=float(verdict.correct), feedback="correct" if verdict.correct else "wrong")
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Seal the key once for the organization and make it the default:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
printf '%s' "$OPENAI_API_KEY" | cmpnd credentials create --name openai --preset openai
|
|
219
|
+
cmpnd credentials default openai
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The entry point is the only code that knows where the run happens:
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
# main.py
|
|
226
|
+
import functools
|
|
227
|
+
import sys
|
|
228
|
+
|
|
229
|
+
import cmpnd
|
|
230
|
+
import dspy
|
|
231
|
+
|
|
232
|
+
from qa import lm, metric, program
|
|
233
|
+
|
|
234
|
+
HOSTED = "--hosted" in sys.argv
|
|
235
|
+
|
|
236
|
+
sdk = cmpnd.Client()
|
|
237
|
+
qa = cmpnd.Program("qa", module=program, lm=lm)
|
|
238
|
+
reflection = dspy.LM("openai/gpt-5", max_tokens=32000)
|
|
239
|
+
lms = {"critic": dspy.LM("openai/gpt-5-mini")}
|
|
240
|
+
train, val = load_examples() # your dspy.Example lists
|
|
241
|
+
|
|
242
|
+
if HOSTED:
|
|
243
|
+
deployed = sdk.deploy_program(qa) # spends the default credential, openai
|
|
244
|
+
print(sdk.run_deployment(deployed, question="What is DSPy?"))
|
|
245
|
+
best = sdk.optimize_deployment(
|
|
246
|
+
deployed,
|
|
247
|
+
trainset=train,
|
|
248
|
+
valset=val,
|
|
249
|
+
metric=metric, # the platform hands it lms
|
|
250
|
+
reflection_lm=cmpnd.HostedLM(reflection),
|
|
251
|
+
lms={name: cmpnd.HostedLM(model) for name, model in lms.items()},
|
|
252
|
+
auto="light",
|
|
253
|
+
)
|
|
254
|
+
else:
|
|
255
|
+
print(sdk.run_program(qa, question="What is DSPy?")) # spends $OPENAI_API_KEY
|
|
256
|
+
gepa = dspy.GEPA(metric=functools.partial(metric, lms=lms), reflection_lm=reflection, auto="light")
|
|
257
|
+
best = sdk.optimize_program(qa, gepa, trainset=train, valset=val)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
`python main.py` runs everything on your laptop, and `python main.py --hosted`
|
|
261
|
+
runs it on the platform. `cmpnd deploy qa.py qa` deploys the same file from the
|
|
262
|
+
terminal.
|
|
263
|
+
|
|
264
|
+
| | On your laptop | Hosted |
|
|
265
|
+
|---|---|---|
|
|
266
|
+
| Key | the provider's variable in your environment, such as `OPENAI_API_KEY` | the credential the call names, else the organization's default |
|
|
267
|
+
| Address | the LM's `api_base`, else the provider's endpoint | the credential's `api_base` |
|
|
268
|
+
| Model | `gpt-5-mini`, the model after its first `/` | `gpt-5-mini` |
|
|
269
|
+
| The metric's `lms` | the dict the entry point binds | the run's `lms=`, which the platform hands it |
|
|
270
|
+
|
|
271
|
+
These rules keep the program file portable:
|
|
272
|
+
|
|
273
|
+
- **Leave the key off the program's LM** and let litellm read the provider's
|
|
274
|
+
own variable. For a key in a variable litellm does not read, write
|
|
275
|
+
`api_key=os.environ.get("NAME")`. cmpnd imports a deploy file with an empty
|
|
276
|
+
environment, so the key is `None` there and present on your laptop.
|
|
277
|
+
- **Never write a key into the file, and never read it as
|
|
278
|
+
`os.environ["NAME"]` at module scope.** The deploy refuses the first. The
|
|
279
|
+
second raises `KeyError` hosted, where the environment holds no key.
|
|
280
|
+
- **Seal the credential at the address your laptop LM reaches.** A program
|
|
281
|
+
that sets `api_base="https://gw.lunaroute.com/v1"` needs a credential sealed
|
|
282
|
+
there, with `--preset lunaroute` or `--api-base`. Hosted calls go to the
|
|
283
|
+
credential's address and ignore the LM's `api_base`. The deploy prints the
|
|
284
|
+
address it chose, so you can compare the two.
|
|
285
|
+
- **Wrap a model you hand the hosted optimization in `cmpnd.HostedLM`.** The
|
|
286
|
+
entry point builds `reflection_lm=` and each `lms=` entry, so those LMs may
|
|
287
|
+
hold a key for the laptop run. The wrapper leaves that key on your machine.
|
|
288
|
+
`optimize_deployment` refuses a key on an unwrapped LM, because there it would
|
|
289
|
+
look spent. `cmpnd.HostedLM(model, credential=c)` makes a model spend a
|
|
290
|
+
credential other than the run's.
|
|
291
|
+
|
|
292
|
+
A judge built on `dspy.experimental.TypeSafe` also runs unchanged in both
|
|
293
|
+
places. [`docs/sdk-instrumentation.md`](docs/sdk-instrumentation.md), under
|
|
294
|
+
"System One judges", describes the hosted side.
|
|
295
|
+
|
|
157
296
|
### Options
|
|
158
297
|
|
|
159
298
|
```python
|
|
@@ -162,6 +301,7 @@ sdk.deploy_program(
|
|
|
162
301
|
metric=None, # optional: a metric callable, packaged for optimization
|
|
163
302
|
timeout=300, # optional: seconds to wait for the deployment to be ready
|
|
164
303
|
metadata=None, # optional: a JSON-serializable dict, at most 64 KiB, stored with the deployment
|
|
304
|
+
credential=None, # optional: a cmpnd.Credential the program's model spends
|
|
165
305
|
)
|
|
166
306
|
```
|
|
167
307
|
|
|
@@ -219,11 +359,32 @@ from the deployment.
|
|
|
219
359
|
`reflection_lm` is the model that proposes each new instruction. The search is
|
|
220
360
|
only as good as this model, so it is usually a strong model and not the one
|
|
221
361
|
being optimized. A `dspy.LM` passes its sampling settings to the provider. A
|
|
222
|
-
bare model name gives the same model with no sampling settings.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
362
|
+
bare model name gives the same model with no sampling settings. A run spends
|
|
363
|
+
sealed credentials only, so the SDK refuses a key on the LM before any network
|
|
364
|
+
call. `cmpnd.HostedLM(lm)` keeps a key your laptop runs spend on this machine
|
|
365
|
+
and passes the LM's model and sampling.
|
|
366
|
+
|
|
367
|
+
`credential=` names the run's credential, which every binding that names none
|
|
368
|
+
spends: the program's model, the reflection model and each entry of `lms`. A
|
|
369
|
+
binding names its own credential through a wrapper for its protocol:
|
|
370
|
+
|
|
371
|
+
```python
|
|
372
|
+
luna = cmpnd.credentials.get("luna-exec") # serves chat_completions and system_one
|
|
373
|
+
openai = cmpnd.credentials.get("openai")
|
|
374
|
+
best = sdk.optimize_deployment(
|
|
375
|
+
deployed, trainset=train, valset=val, metric=metric,
|
|
376
|
+
credential=luna,
|
|
377
|
+
reflection_lm=cmpnd.HostedLM(dspy.LM("openai/gpt-5"), credential=openai),
|
|
378
|
+
lms={"judge": cmpnd.SystemOne("lunaroute/djev", credential=luna)},
|
|
379
|
+
auto="light",
|
|
380
|
+
)
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
`cmpnd.HostedLM(lm, credential=c)` wraps a chat LM, and
|
|
384
|
+
`cmpnd.SystemOne(model, credential=c)` declares a System One model. A binding
|
|
385
|
+
without a credential spends the run's, or the organization's default. The
|
|
386
|
+
platform refuses a credential whose endpoint does not serve a binding's
|
|
387
|
+
protocol, and names the `credential=` to add.
|
|
227
388
|
|
|
228
389
|
The SDK checks the metric before any network call. GEPA writes each new
|
|
229
390
|
instruction from the textual feedback the metric returns, so it needs a
|
|
@@ -271,9 +432,9 @@ rules:
|
|
|
271
432
|
names.
|
|
272
433
|
- `"default-lm"` is reserved for the program's own model, which comes from the
|
|
273
434
|
deployment. Declaring it raises an error that names it.
|
|
274
|
-
- The platform refuses a
|
|
275
|
-
the run, before the run starts. These LMs, like `reflection_lm`, must not
|
|
276
|
-
|
|
435
|
+
- The platform refuses a binding it finds no credential for when it dispatches
|
|
436
|
+
the run, before the run starts. These LMs, like `reflection_lm`, must not
|
|
437
|
+
carry a key.
|
|
277
438
|
|
|
278
439
|
The `lms` parameter does not change the arity check. Five or more positional
|
|
279
440
|
arguments make a feedback metric, and a metric without `lms` never receives
|
|
@@ -289,8 +450,9 @@ sdk.optimize_deployment(
|
|
|
289
450
|
valset=val, # required: validation examples
|
|
290
451
|
metric=metric, # required: the metric, arity-checked
|
|
291
452
|
config=None, # optional: optimizer config, allow-listed by the platform
|
|
292
|
-
reflection_lm=None, # required: a dspy.LM
|
|
293
|
-
lms=None, # optional: name -> dspy.LM,
|
|
453
|
+
reflection_lm=None, # required: a dspy.LM, a bare model name or a cmpnd.HostedLM
|
|
454
|
+
lms=None, # optional: name -> dspy.LM, cmpnd.HostedLM or cmpnd.SystemOne
|
|
455
|
+
credential=None, # optional: the cmpnd.Credential every other binding spends
|
|
294
456
|
max_metric_calls=None, # budget: the metric-call ceiling for one step
|
|
295
457
|
auto=None, # budget: "light", "medium" or "heavy"
|
|
296
458
|
may_author_code=None, # optional: False keeps each dspy.Flex at its deployed source
|
|
@@ -17,7 +17,7 @@ from typing import TYPE_CHECKING
|
|
|
17
17
|
|
|
18
18
|
from cmpnd_ingest.client_floor import UpgradeRequiredWarning
|
|
19
19
|
|
|
20
|
-
from . import authored, programs, traces
|
|
20
|
+
from . import authored, credentials, programs, traces
|
|
21
21
|
from ._http import ApiError
|
|
22
22
|
from .callback import CmpndCallback, trace_id
|
|
23
23
|
from .client import Client
|
|
@@ -27,6 +27,7 @@ from .context import (
|
|
|
27
27
|
get_current_span,
|
|
28
28
|
get_current_trace,
|
|
29
29
|
)
|
|
30
|
+
from .credentials import Credential, CredentialNotFound
|
|
30
31
|
from .datasets import DatasetNotFoundError, VersionNotFoundError, load_dataset
|
|
31
32
|
from .decorators import start_span, trace
|
|
32
33
|
from .deployment import DeployFailed
|
|
@@ -39,7 +40,7 @@ from .deployment_metadata import (
|
|
|
39
40
|
from .deployment_runs import RunResultExpired, RunSubmissionUncertain, RunWaitTimeout
|
|
40
41
|
from .execution import DeployedProgram, RetryableProviderError
|
|
41
42
|
from .exporter import flush_exporter, shutdown_exporter
|
|
42
|
-
from .lm_configuration import SystemOne, configure_lm
|
|
43
|
+
from .lm_configuration import HostedLM, SystemOne, configure_lm
|
|
43
44
|
from .mcp_servers import (
|
|
44
45
|
Bearer,
|
|
45
46
|
McpToolReference,
|
|
@@ -70,6 +71,7 @@ __all__ = [
|
|
|
70
71
|
"configure",
|
|
71
72
|
"configure_lm",
|
|
72
73
|
"SystemOne",
|
|
74
|
+
"HostedLM",
|
|
73
75
|
"get_config",
|
|
74
76
|
"require_config",
|
|
75
77
|
# Main callback
|
|
@@ -95,6 +97,10 @@ __all__ = [
|
|
|
95
97
|
"traces",
|
|
96
98
|
# Program name checks (cmpnd.programs.validate_program_name)
|
|
97
99
|
"programs",
|
|
100
|
+
# The organization's provider credentials (cmpnd.credentials.get / list)
|
|
101
|
+
"credentials",
|
|
102
|
+
"Credential",
|
|
103
|
+
"CredentialNotFound",
|
|
98
104
|
# A program declared by name
|
|
99
105
|
"Program",
|
|
100
106
|
"program",
|
|
@@ -15,6 +15,103 @@ from pathlib import Path
|
|
|
15
15
|
from typing import Any
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
class BundleCarriesCredential(ValueError):
|
|
19
|
+
"""The bundle's import built an LM that holds a provider credential."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@contextlib.contextmanager
|
|
23
|
+
def _built_lms(root: Path):
|
|
24
|
+
"""Record every dspy LM built inside the block, with the bundle module that built it.
|
|
25
|
+
|
|
26
|
+
The check reads objects the import built and parses no source. It wraps the
|
|
27
|
+
``__init__`` of ``dspy.BaseLM`` and of every LM class that defines its own, because
|
|
28
|
+
``dspy.LM`` before dspy 3.3 never calls ``BaseLM.__init__``. It names the innermost
|
|
29
|
+
module of the bundle on the stack, so an LM a helper module builds is named by that
|
|
30
|
+
module, and records each LM once, however many of the wrapped constructors it runs.
|
|
31
|
+
"""
|
|
32
|
+
from dspy.clients.base_lm import BaseLM # pyright: ignore[reportMissingTypeStubs] # dspy re-exports BaseLM untyped
|
|
33
|
+
|
|
34
|
+
built: list[tuple[Any, str | None]] = []
|
|
35
|
+
seen: set[int] = set()
|
|
36
|
+
|
|
37
|
+
def lm_classes(cls: type) -> list[type]:
|
|
38
|
+
found = [cls]
|
|
39
|
+
for sub in cls.__subclasses__():
|
|
40
|
+
found.extend(lm_classes(sub))
|
|
41
|
+
return found
|
|
42
|
+
|
|
43
|
+
def recording(original: Any) -> Any:
|
|
44
|
+
def init(self: Any, *args: Any, **kwargs: Any) -> None:
|
|
45
|
+
original(self, *args, **kwargs)
|
|
46
|
+
if id(self) not in seen:
|
|
47
|
+
seen.add(id(self))
|
|
48
|
+
built.append((self, _bundle_module(root)))
|
|
49
|
+
|
|
50
|
+
return init
|
|
51
|
+
|
|
52
|
+
originals = {cls: cls.__dict__["__init__"] for cls in lm_classes(BaseLM) if "__init__" in cls.__dict__}
|
|
53
|
+
for cls, original in originals.items():
|
|
54
|
+
cls.__init__ = recording(original)
|
|
55
|
+
try:
|
|
56
|
+
yield built
|
|
57
|
+
finally:
|
|
58
|
+
for cls, original in originals.items():
|
|
59
|
+
cls.__init__ = original
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _bundle_module(root: Path) -> str | None:
|
|
63
|
+
"""The name of the innermost module on the stack whose file sits in the staged bundle."""
|
|
64
|
+
frame = sys._getframe(2) # the caller of the LM's __init__
|
|
65
|
+
prefix = str(root.resolve()) + "/"
|
|
66
|
+
while frame is not None:
|
|
67
|
+
if str(Path(frame.f_code.co_filename).resolve()).startswith(prefix):
|
|
68
|
+
name = frame.f_globals.get("__name__")
|
|
69
|
+
return name if isinstance(name, str) else None
|
|
70
|
+
frame = frame.f_back
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _refuse_bundled_credentials(built: list[tuple[Any, str | None]], entry: str) -> None:
|
|
75
|
+
"""Refuse a bundle whose import built an LM that holds a provider credential.
|
|
76
|
+
|
|
77
|
+
The import ran with no environment, so a field set here came from the bundle: a
|
|
78
|
+
literal, or a file it ships, such as a ``.env`` it loads. The platform stores the
|
|
79
|
+
source it deploys, and a key belongs only in a sealed credential. The refusal names
|
|
80
|
+
the module, the model and the field, and never the value.
|
|
81
|
+
"""
|
|
82
|
+
from . import helpers
|
|
83
|
+
|
|
84
|
+
findings: list[tuple[str, str, str]] = []
|
|
85
|
+
for lm, module in built:
|
|
86
|
+
model = str(getattr(lm, "model", None) or "?")
|
|
87
|
+
for field in helpers.credential_fields(getattr(lm, "kwargs", None)):
|
|
88
|
+
findings.append((module or entry, model, field))
|
|
89
|
+
if not findings:
|
|
90
|
+
return
|
|
91
|
+
lines = [f" module {module!r} builds an LM for {model!r} with {field} set" for module, model, field in findings]
|
|
92
|
+
module, model, field = findings[0]
|
|
93
|
+
holder, bracket, header = field.partition("[")
|
|
94
|
+
prefix = model.split("/", 1)[0].upper()
|
|
95
|
+
variable = f"{prefix}_API_KEY" if bracket or field == "api_key" else field.upper()
|
|
96
|
+
variable = "".join(char if char.isalnum() else "_" for char in variable)
|
|
97
|
+
if bracket:
|
|
98
|
+
header = header.removesuffix("]")
|
|
99
|
+
value = '"Bearer " + key' if "authorization" in header.lower() else "key"
|
|
100
|
+
advice = (
|
|
101
|
+
"Read the key from the environment instead, and send the header only where it is set, as "
|
|
102
|
+
f'{holder}={{{header}: {value}}} if (key := os.environ.get("{variable}")) else None.'
|
|
103
|
+
)
|
|
104
|
+
else:
|
|
105
|
+
advice = f'Read the key from the environment instead, as {field}=os.environ.get("{variable}").'
|
|
106
|
+
raise BundleCarriesCredential(
|
|
107
|
+
"the source carries a provider credential, and the platform stores the source it deploys:\n"
|
|
108
|
+
+ "\n".join(lines)
|
|
109
|
+
+ f"\n{advice} This check and the hosted kernel run with no "
|
|
110
|
+
"environment, so it yields None there, and your own runs keep their key. Seal the key for "
|
|
111
|
+
"hosted runs with `cmpnd credentials create --name NAME --preset PROVIDER`."
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
18
115
|
def _program(module: Any, entrypoint: str) -> tuple[Any, str]:
|
|
19
116
|
import dspy
|
|
20
117
|
|
|
@@ -188,10 +285,11 @@ def _check(request: dict[str, Any]) -> dict[str, Any]:
|
|
|
188
285
|
from . import lm_configuration, packaging
|
|
189
286
|
|
|
190
287
|
authored_limits = _configured(request)
|
|
191
|
-
with _staged(request) as root:
|
|
288
|
+
with _staged(request) as root, _built_lms(root) as built:
|
|
192
289
|
manifest = json.loads((root / "manifest.json").read_text())
|
|
193
290
|
module = _import_member(str(manifest["program"]))
|
|
194
291
|
program, kind = _program(module, str(manifest["entrypoint"]))
|
|
292
|
+
_refuse_bundled_credentials(built, module.__name__)
|
|
195
293
|
facts = _program_facts(program, kind, authored_limits)
|
|
196
294
|
bindings = dict(getattr(module, "lms", None) or {})
|
|
197
295
|
default_lm = packaging._extract_lm_object(program)
|
|
@@ -212,7 +310,7 @@ def _check_optimize(request: dict[str, Any]) -> dict[str, Any]:
|
|
|
212
310
|
|
|
213
311
|
ask = request["optimize"]
|
|
214
312
|
authored_limits = _configured(request)
|
|
215
|
-
with _staged(request):
|
|
313
|
+
with _staged(request) as root, _built_lms(root) as built:
|
|
216
314
|
module = _import_member(str(ask["entry"]))
|
|
217
315
|
declared_lms: dict[str, Any] | None = None
|
|
218
316
|
if hasattr(module, "lms"):
|
|
@@ -256,6 +354,7 @@ def _check_optimize(request: dict[str, Any]) -> dict[str, Any]:
|
|
|
256
354
|
program, kind = _program(module, program_entrypoint)
|
|
257
355
|
facts = _program_facts(program, kind, authored_limits)
|
|
258
356
|
found_program = program_entrypoint
|
|
357
|
+
_refuse_bundled_credentials(built, module.__name__)
|
|
259
358
|
|
|
260
359
|
proposer_entrypoint = str(ask["proposer_entrypoint"])
|
|
261
360
|
declared_proposer = getattr(module, proposer_entrypoint, None)
|
|
@@ -331,6 +430,8 @@ def main() -> int:
|
|
|
331
430
|
answer = _check_optimize(request) if request.get("optimize") else _check(request)
|
|
332
431
|
print(json.dumps(answer))
|
|
333
432
|
return 0
|
|
433
|
+
except BundleCarriesCredential as exc:
|
|
434
|
+
print(json.dumps({"error": str(exc)}))
|
|
334
435
|
except ModuleNotFoundError as exc:
|
|
335
436
|
print(
|
|
336
437
|
json.dumps(
|
|
@@ -344,14 +445,22 @@ def main() -> int:
|
|
|
344
445
|
)
|
|
345
446
|
)
|
|
346
447
|
except KeyError as exc:
|
|
448
|
+
from . import helpers
|
|
449
|
+
|
|
450
|
+
variable = helpers.environment_variable_missing(exc)
|
|
451
|
+
advice = (
|
|
452
|
+
helpers.environment_read_advice(variable)
|
|
453
|
+
if variable is not None
|
|
454
|
+
else 'Read a key with os.environ.get("VARIABLE"), which yields None here.'
|
|
455
|
+
)
|
|
347
456
|
print(
|
|
348
457
|
json.dumps(
|
|
349
458
|
{
|
|
350
459
|
"error": (
|
|
351
460
|
f"authored source import failed with KeyError: {exc}. The check runs without your "
|
|
352
461
|
"environment because the worker has none. Do not read provider or MCP credentials at "
|
|
353
|
-
"module scope.
|
|
354
|
-
|
|
462
|
+
f"module scope. {advice} Use cmpnd.mcp_tools(url) for a registered server.\n"
|
|
463
|
+
+ _concise_traceback()
|
|
355
464
|
)
|
|
356
465
|
}
|
|
357
466
|
)
|
|
@@ -1403,9 +1403,10 @@ class _LMMixin(_CallbackBase):
|
|
|
1403
1403
|
if parent_span and getattr(parent_span, "span_type", None) == models.SpanType.RLM:
|
|
1404
1404
|
span.attributes["rlm.subcall"] = "true"
|
|
1405
1405
|
|
|
1406
|
-
# Capture prompt/messages if enabled
|
|
1406
|
+
# Capture prompt/messages if enabled. A credential passed to the call itself, as in
|
|
1407
|
+
# `lm(prompt, api_key=...)`, is stripped first: every member of the org reads a span.
|
|
1407
1408
|
if self._capture_prompts:
|
|
1408
|
-
cleaned_inputs = helpers.clean_inputs(inputs, dspy, self._max_input_field_size)
|
|
1409
|
+
cleaned_inputs = helpers.clean_inputs(helpers.without_credentials(inputs), dspy, self._max_input_field_size)
|
|
1409
1410
|
self._reference_rendered_messages(parent_span, inputs, cleaned_inputs)
|
|
1410
1411
|
span.set_inputs(cleaned_inputs)
|
|
1411
1412
|
|