cmpnd 0.27.1__tar.gz → 0.27.2__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.27.1 → cmpnd-0.27.2}/AGENTS.md +2 -2
- {cmpnd-0.27.1 → cmpnd-0.27.2}/PKG-INFO +1 -1
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/__init__.py +2 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/_authored_import.py +26 -5
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/authored.py +6 -0
- cmpnd-0.27.2/cmpnd/lm_configuration.py +203 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/optimization.py +52 -3
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/packaging.py +15 -8
- {cmpnd-0.27.1 → cmpnd-0.27.2}/docs/sdk-instrumentation.md +44 -1
- {cmpnd-0.27.1 → cmpnd-0.27.2}/pyproject.toml +1 -1
- cmpnd-0.27.2/tests/test_lm_configuration.py +286 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_reflector_properties.py +1 -1
- {cmpnd-0.27.1 → cmpnd-0.27.2}/uv.lock +1 -1
- {cmpnd-0.27.1 → cmpnd-0.27.2}/.gitignore +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/CLAUDE.md +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/README.md +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/_assessments.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/_gepa_patch.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/_http.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/_traces.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/_valsets.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/callback.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/__init__.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/commands/admin.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/commands/assessments.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/commands/deployments.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/commands/evals.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/commands/org.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/commands/programs.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/commands/provider_credentials.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/commands/signatures.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/commands/top.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/commands/traces.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/commands/valsets.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/shell.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/trace_render.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/tree_help.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/cli/verbs.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/client.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/configuration.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/context.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/dataset_sync.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/datasets.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/decorators.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/deployment.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/execution.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/exporter.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/helpers.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/identity.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/imaging.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/ir/reflection.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/mcp_servers.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/models.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/optimizers/gepa.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/optimizers/gepa_callback.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/optimizers/tracker.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/retry.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/cmpnd/traces.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/docs/2026-04-02-code-review.md +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/docs/cli.md +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/docs/client.md +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/docs/custom-proposer.md +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/docs/reading-traces.md +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/examples/local_ollama.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/ingest/README.md +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/ingest/cmpnd_ingest/bodies.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/ingest/cmpnd_ingest/exporter_http.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/ingest/cmpnd_ingest/protocols.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/ingest/pyproject.toml +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/scripts/capture_review_run.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/scripts/release-stress.sh +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/scripts/require_version_bump.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/scripts/seed_review_run.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/scripts/test-in-linux.sh +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/__init__.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/_fake_clock.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/conftest.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/committee_task.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/helpers.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/target_capabilities.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_assessments_cli.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_composite_module.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_deploy_execute.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_deploy_programs.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_optimize_failures.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_parallel_export.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_project_attribution.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_traces_read.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_user_attribution.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/e2e/test_valsets_cli.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/integration/__init__.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/integration/authored_optimize/judge.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/integration/conftest.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/integration/hosted_optimize_probe.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/integration/test_logs_renders_traces.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/integration/test_unhappy_path.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/load/__init__.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/load/__main__.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/load/drain.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/load/ingest.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/load/rlm_shape.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/load/sweep.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/parity/__init__.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/parity/conftest.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/parity/test_health.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/parity/test_trace_streaming.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/parity/test_traces.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/stress/__init__.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/stress/_provider.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/stress/_reliability.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/stress/_result.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/stress/mint_bedrock_token.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/stress/test_deploy_programs.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/stress/test_multi_client_stress.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/stress/test_optimize_client_committee.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/stress/test_optimize_loop.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/stress/test_optimize_loop_wasm.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/stress/test_optimize_stress.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/stress/test_provider_roles.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/stress/test_real_provider_twins.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/stress/test_reliability.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/stress/test_reliability_classify.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/stress/test_rlm_wasm.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/stress/test_trace_search.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_assessments_api.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_authored.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_authored_bundle_collisions.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_authored_metric_contract.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_authored_named_models.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_authored_optimize.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_callback.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_admin.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_api_client.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_assessments.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_deployments.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_evals.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_main.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_org.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_parity.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_programs.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_provider_credentials.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_schema_sample.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_shell.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_signatures.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_top.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_traces.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_traces_search_wire.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_tree_help.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cli/test_valsets.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_client.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_configuration.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_context.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_context_propagation.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_dataset_sync.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_datasets.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_decorators.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_deploy.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_eval.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_eval_example_attribution.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_eval_start.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_eval_trace_grouping.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_execute.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_exporter.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_exporter_queue.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_exporter_real.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_identity.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_imaging.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_import_weight.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_ingest_conformance.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_integration.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_interpreter_callback.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_lm_usage_shapes.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_mcp_tools.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_models.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_optimize.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_optimize_lms.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_optimize_reflection_lm.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_packaging.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_packaging_mcp_tools.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_packaging_program_source.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_poll_until.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_program_lineage_persist.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_project_payload.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_reflector_smoke.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_release_stress_script.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_repro_reactv2_bugs.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_retry.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_rlm_patch.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_schemas.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_span_sent_once.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_trace_render.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_traces_read.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_tracked_gepa.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_truncation.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_valsets_api.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/tests/test_xxh64.py +0 -0
- {cmpnd-0.27.1 → cmpnd-0.27.2}/todo.md +0 -0
|
@@ -130,7 +130,7 @@ not a copy — see the reasoning at the top of `cmpnd/exporter_http.py`).
|
|
|
130
130
|
|
|
131
131
|
The transport sits under its own directory, with its own `pyproject.toml`
|
|
132
132
|
(`cmpnd-ingest`), so a producer that is not a DSPy program can install it without
|
|
133
|
-
dspy: its entire dependency set is httpx. The
|
|
133
|
+
dspy: its entire dependency set is httpx. The optimizer's search process
|
|
134
134
|
(`search-process/`) is that producer, and a venv unable to supply dspy is
|
|
135
135
|
what enforces its boundary. `ingest/README.md` has the full argument.
|
|
136
136
|
|
|
@@ -158,7 +158,7 @@ What that costs you, before touching either pyproject:
|
|
|
158
158
|
- `cmpnd/callback.py` - DSPy instrumentation, signature extraction
|
|
159
159
|
- `cmpnd/optimizers/tracker.py` - Optimization tracking
|
|
160
160
|
- `cmpnd/packaging.py` - Source ZIP generation for deploy (ported from the retired `deploy/local_cmpnd.py`)
|
|
161
|
-
- `cmpnd/deployment.py` - `deploy()` function:
|
|
161
|
+
- `cmpnd/deployment.py` - `deploy()` function: package the module, create the deployment on serve, PUT the bundle to the upload slot serve provisioned (a presigned S3 PUT when hosted, the worker's own slot on-prem), poll the parser
|
|
162
162
|
|
|
163
163
|
## Reflector ↔ DSPy compatibility
|
|
164
164
|
|
|
@@ -29,6 +29,7 @@ from .decorators import start_span, trace
|
|
|
29
29
|
from .deployment import DeployFailed, deploy, deploy_bundle
|
|
30
30
|
from .execution import DeployedProgram, RetryableProviderError, execute
|
|
31
31
|
from .exporter import flush_exporter, shutdown_exporter
|
|
32
|
+
from .lm_configuration import configure_lm
|
|
32
33
|
from .mcp_servers import (
|
|
33
34
|
Bearer,
|
|
34
35
|
McpToolReference,
|
|
@@ -57,6 +58,7 @@ _auto_instrument_callback: CmpndCallback | None = None
|
|
|
57
58
|
__all__ = [
|
|
58
59
|
# Configuration
|
|
59
60
|
"configure",
|
|
61
|
+
"configure_lm",
|
|
60
62
|
"get_config",
|
|
61
63
|
"require_config",
|
|
62
64
|
# Main callback
|
|
@@ -56,7 +56,9 @@ def _configured(request: dict[str, Any]) -> dict[str, int | None]:
|
|
|
56
56
|
transport while letting the file change the settings it owns, and records the two
|
|
57
57
|
trace limits the create call carries.
|
|
58
58
|
"""
|
|
59
|
-
from . import configuration
|
|
59
|
+
from . import configuration, lm_configuration
|
|
60
|
+
|
|
61
|
+
lm_configuration.begin_authored()
|
|
60
62
|
|
|
61
63
|
original_configure = configuration.configure
|
|
62
64
|
authored_limits: dict[str, int | None] = {"max_input_size": None, "max_output_size": None}
|
|
@@ -115,7 +117,6 @@ def _program_facts(
|
|
|
115
117
|
authored_limits: dict[str, int | None],
|
|
116
118
|
) -> dict[str, Any]:
|
|
117
119
|
"""Everything the deployment create call reads off the imported program."""
|
|
118
|
-
import dspy
|
|
119
120
|
|
|
120
121
|
from . import configuration, deployment, identity, mcp_servers, packaging
|
|
121
122
|
from .authored import ProgramFacts
|
|
@@ -163,7 +164,9 @@ def _program_facts(
|
|
|
163
164
|
"input_field_types": [identity.get_type_string(f.annotation) for f in signature.input_fields.values()],
|
|
164
165
|
"output_field_types": [identity.get_type_string(f.annotation) for f in signature.output_fields.values()],
|
|
165
166
|
}
|
|
166
|
-
|
|
167
|
+
from . import lm_configuration
|
|
168
|
+
|
|
169
|
+
lm = packaging._extract_lm_object(program)
|
|
167
170
|
lm_kwargs = getattr(lm, "kwargs", {}) if lm is not None else {}
|
|
168
171
|
facts = ProgramFacts(
|
|
169
172
|
program_signature_hash=identity.compute_program_hash(program),
|
|
@@ -177,6 +180,7 @@ def _program_facts(
|
|
|
177
180
|
max_output_size=authored_limits["max_output_size"],
|
|
178
181
|
tool_references=tool_references,
|
|
179
182
|
tool_declarations=tool_declarations,
|
|
183
|
+
lm_capabilities=lm_configuration.resolve({"default-lm": lm} if lm is not None else {}, require_all=False),
|
|
180
184
|
**fields,
|
|
181
185
|
)
|
|
182
186
|
return asdict(facts)
|
|
@@ -184,12 +188,20 @@ def _program_facts(
|
|
|
184
188
|
|
|
185
189
|
def _check(request: dict[str, Any]) -> dict[str, Any]:
|
|
186
190
|
"""The program bundle's answer: one import, the entrypoint's facts."""
|
|
191
|
+
from . import lm_configuration, packaging
|
|
192
|
+
|
|
187
193
|
authored_limits = _configured(request)
|
|
188
194
|
with _staged(request) as root:
|
|
189
195
|
manifest = json.loads((root / "manifest.json").read_text())
|
|
190
196
|
module = _import_member(str(manifest["program"]))
|
|
191
197
|
program, kind = _program(module, str(manifest["entrypoint"]))
|
|
192
|
-
|
|
198
|
+
facts = _program_facts(program, kind, authored_limits)
|
|
199
|
+
bindings = dict(getattr(module, "lms", None) or {})
|
|
200
|
+
default_lm = packaging._extract_lm_object(program)
|
|
201
|
+
if default_lm is not None:
|
|
202
|
+
bindings["default-lm"] = default_lm
|
|
203
|
+
facts["lm_capabilities"] = lm_configuration.resolve(bindings)
|
|
204
|
+
return {"facts": facts}
|
|
193
205
|
|
|
194
206
|
|
|
195
207
|
def _check_optimize(request: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -199,7 +211,7 @@ def _check_optimize(request: dict[str, Any]) -> dict[str, Any]:
|
|
|
199
211
|
importing it three times would run its module scope three times — which is neither
|
|
200
212
|
what the author's own script does nor what the kernel does.
|
|
201
213
|
"""
|
|
202
|
-
from . import optimization
|
|
214
|
+
from . import lm_configuration, optimization, packaging
|
|
203
215
|
|
|
204
216
|
ask = request["optimize"]
|
|
205
217
|
authored_limits = _configured(request)
|
|
@@ -242,6 +254,7 @@ def _check_optimize(request: dict[str, Any]) -> dict[str, Any]:
|
|
|
242
254
|
f"Bind `{program_entrypoint} = ...` to optimize the file's own program, or pass an "
|
|
243
255
|
"existing deployment id to optimize that."
|
|
244
256
|
)
|
|
257
|
+
program = None
|
|
245
258
|
if declared_program is not None:
|
|
246
259
|
program, kind = _program(module, program_entrypoint)
|
|
247
260
|
facts = _program_facts(program, kind, authored_limits)
|
|
@@ -254,6 +267,13 @@ def _check_optimize(request: dict[str, Any]) -> dict[str, Any]:
|
|
|
254
267
|
optimization._check_proposer_call(declared_proposer)
|
|
255
268
|
found_proposer = proposer_entrypoint
|
|
256
269
|
|
|
270
|
+
bindings = dict(getattr(module, "lms", None) or {})
|
|
271
|
+
if program is not None:
|
|
272
|
+
default_lm = packaging._extract_lm_object(program)
|
|
273
|
+
if default_lm is not None:
|
|
274
|
+
bindings["default-lm"] = default_lm
|
|
275
|
+
declarations = lm_configuration.resolve(bindings)
|
|
276
|
+
|
|
257
277
|
work = Path(request["work_dir"])
|
|
258
278
|
trainset = _rows(module, "trainset")
|
|
259
279
|
valset = _rows(module, "valset")
|
|
@@ -266,6 +286,7 @@ def _check_optimize(request: dict[str, Any]) -> dict[str, Any]:
|
|
|
266
286
|
"program_entrypoint": found_program,
|
|
267
287
|
"proposer_entrypoint": found_proposer,
|
|
268
288
|
"lms": declared_lms,
|
|
289
|
+
"lm_capabilities": declarations,
|
|
269
290
|
"trainset_rows": len(trainset),
|
|
270
291
|
"valset_rows": len(valset),
|
|
271
292
|
}
|
|
@@ -59,6 +59,7 @@ class ProgramFacts:
|
|
|
59
59
|
max_output_size: int | None = None
|
|
60
60
|
tool_references: list[dict[str, Any]] = field(default_factory=list)
|
|
61
61
|
tool_declarations: list[dict[str, Any]] = field(default_factory=list)
|
|
62
|
+
lm_capabilities: dict[str, Any] = field(default_factory=dict)
|
|
62
63
|
|
|
63
64
|
@classmethod
|
|
64
65
|
def minimal(cls) -> ProgramFacts:
|
|
@@ -177,6 +178,9 @@ def _prepare(
|
|
|
177
178
|
}
|
|
178
179
|
bundle = _zip(manifest, members)
|
|
179
180
|
facts = _check_bundle(bundle, api_key=api_key, endpoint=endpoint)
|
|
181
|
+
if facts.lm_capabilities:
|
|
182
|
+
manifest["lm_capabilities"] = facts.lm_capabilities
|
|
183
|
+
bundle = _zip(manifest, members)
|
|
180
184
|
if facts.tool_declarations:
|
|
181
185
|
# The child pinned these declarations while importing this exact source.
|
|
182
186
|
# Serve checks each schema hash against the registry again on create, so
|
|
@@ -350,6 +354,8 @@ def _prepare_optimize(
|
|
|
350
354
|
manifest["proposer_entrypoint"] = facts.proposer_entrypoint
|
|
351
355
|
if program_facts is not None and program_facts.tool_declarations:
|
|
352
356
|
manifest["tools"] = program_facts.tool_declarations
|
|
357
|
+
if found.get("lm_capabilities"):
|
|
358
|
+
manifest["lm_capabilities"] = found["lm_capabilities"]
|
|
353
359
|
program_bundle = _zip(manifest, members) if facts.program_entrypoint else None
|
|
354
360
|
optimization_bundle = _zip(manifest, {**members, **produced})
|
|
355
361
|
return PreparedOptimize(
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Hosted LM declarations, resolved by binding name and object identity."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, TypedDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LMConfigurationError(ValueError):
|
|
9
|
+
"""A hosted binding declaration cannot be resolved consistently."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LMCapabilities(TypedDict):
|
|
13
|
+
"""Explicit adapter hints for a hosted model, independent of its provider."""
|
|
14
|
+
|
|
15
|
+
supported_params: list[str]
|
|
16
|
+
supports_response_schema: bool
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _validate_lm_capabilities(value: object) -> dict[str, LMCapabilities]:
|
|
20
|
+
if not isinstance(value, dict):
|
|
21
|
+
raise LMConfigurationError("lm_capabilities must map exact binding names to capability declarations")
|
|
22
|
+
result: dict[str, LMCapabilities] = {}
|
|
23
|
+
for binding, declaration in value.items():
|
|
24
|
+
if not isinstance(binding, str) or not binding or binding != binding.strip():
|
|
25
|
+
raise LMConfigurationError(
|
|
26
|
+
"lm_capabilities binding names must be nonempty strings without surrounding whitespace"
|
|
27
|
+
)
|
|
28
|
+
if not isinstance(declaration, dict):
|
|
29
|
+
raise LMConfigurationError(f"lm_capabilities[{binding!r}] must be a capability declaration")
|
|
30
|
+
unknown = declaration.keys() - {"supported_params", "supports_response_schema"}
|
|
31
|
+
if unknown:
|
|
32
|
+
raise LMConfigurationError(f"lm_capabilities[{binding!r}] has unknown fields: {sorted(unknown, key=str)}")
|
|
33
|
+
params = declaration.get("supported_params", [])
|
|
34
|
+
if not isinstance(params, list) or any(not isinstance(param, str) or not param for param in params):
|
|
35
|
+
raise LMConfigurationError(
|
|
36
|
+
f"lm_capabilities[{binding!r}].supported_params must be a list of nonempty strings"
|
|
37
|
+
)
|
|
38
|
+
schema = declaration.get("supports_response_schema", False)
|
|
39
|
+
if not isinstance(schema, bool):
|
|
40
|
+
raise LMConfigurationError(f"lm_capabilities[{binding!r}].supports_response_schema must be a boolean")
|
|
41
|
+
if schema and "response_format" not in params:
|
|
42
|
+
raise LMConfigurationError(
|
|
43
|
+
f"lm_capabilities[{binding!r}] requires response_format in supported_params for schema output"
|
|
44
|
+
)
|
|
45
|
+
result[binding] = {"supported_params": list(dict.fromkeys(params)), "supports_response_schema": schema}
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
_objects: list[Any] = []
|
|
50
|
+
_authored = False
|
|
51
|
+
_names: dict[str, dict[str, Any]] = {}
|
|
52
|
+
_restored: dict[str, dict[str, Any]] = {}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def reset() -> None:
|
|
56
|
+
global _authored
|
|
57
|
+
_authored = False
|
|
58
|
+
_objects.clear()
|
|
59
|
+
_names.clear()
|
|
60
|
+
_restored.clear()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def begin_authored() -> None:
|
|
64
|
+
"""Start the isolated importer or kernel scope before loading authored code."""
|
|
65
|
+
global _authored
|
|
66
|
+
reset()
|
|
67
|
+
_authored = True
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _object_declaration(lm: Any) -> Any:
|
|
71
|
+
declaration = getattr(lm, "_cmpnd_lm_capabilities", None)
|
|
72
|
+
if declaration is not None and declaration[0] == id(lm):
|
|
73
|
+
return declaration[1:]
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _name(value: object) -> str:
|
|
78
|
+
if not isinstance(value, str) or not value or value != value.strip():
|
|
79
|
+
raise LMConfigurationError("an LM binding name must be a nonempty string without surrounding whitespace")
|
|
80
|
+
return value
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _model(lm: Any) -> str:
|
|
84
|
+
model = lm if isinstance(lm, str) else getattr(lm, "model", None)
|
|
85
|
+
if not isinstance(model, str) or not model:
|
|
86
|
+
raise LMConfigurationError("configure_lm needs an LM object with a nonempty model name")
|
|
87
|
+
return model
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _merge(target: dict[str, Any], name: str, declaration: dict[str, Any]) -> None:
|
|
91
|
+
if name in target and target[name] != declaration:
|
|
92
|
+
raise LMConfigurationError(f"conflicting LM capability declarations for binding {name!r}")
|
|
93
|
+
target[name] = declaration
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def configure_lm(
|
|
97
|
+
lm: Any,
|
|
98
|
+
*,
|
|
99
|
+
supported_params: list[str] | None = None,
|
|
100
|
+
supports_response_schema: bool = False,
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Declare capabilities once for an LM object or explicit hosted binding name.
|
|
103
|
+
|
|
104
|
+
Every binding created from the same object shares its declaration. Separate
|
|
105
|
+
objects remain independent, even when their settings match. Packaged
|
|
106
|
+
declarations retain their resolved model and cannot follow a model change.
|
|
107
|
+
"""
|
|
108
|
+
hints = dict(
|
|
109
|
+
_validate_lm_capabilities(
|
|
110
|
+
{
|
|
111
|
+
"binding": {
|
|
112
|
+
"supported_params": supported_params if supported_params is not None else [],
|
|
113
|
+
"supports_response_schema": supports_response_schema,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
)["binding"]
|
|
117
|
+
)
|
|
118
|
+
if isinstance(lm, str):
|
|
119
|
+
if not _authored:
|
|
120
|
+
raise LMConfigurationError(
|
|
121
|
+
"named LM declarations are supported only inside authored programs; pass an LM object"
|
|
122
|
+
)
|
|
123
|
+
_merge(_names, _name(lm), hints)
|
|
124
|
+
return
|
|
125
|
+
model = _model(lm)
|
|
126
|
+
declared = _object_declaration(lm)
|
|
127
|
+
if declared is not None and declared != (model, hints):
|
|
128
|
+
raise LMConfigurationError("conflicting LM capability declarations for the same LM object")
|
|
129
|
+
lm._cmpnd_lm_capabilities = (id(lm), model, hints)
|
|
130
|
+
if _authored and not any(obj is lm for obj in _objects):
|
|
131
|
+
_objects.append(lm)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def restore(declarations: object) -> None:
|
|
135
|
+
"""Load model-pinned declarations from a packaged artifact."""
|
|
136
|
+
if not isinstance(declarations, dict):
|
|
137
|
+
raise LMConfigurationError("lm_capabilities must map binding names to declarations")
|
|
138
|
+
for name, declaration in declarations.items():
|
|
139
|
+
_name(name)
|
|
140
|
+
if not isinstance(declaration, dict):
|
|
141
|
+
raise LMConfigurationError(f"lm_capabilities[{name!r}] must be a capability declaration")
|
|
142
|
+
model = _model(declaration.get("model"))
|
|
143
|
+
hints = _validate_lm_capabilities({name: {k: v for k, v in declaration.items() if k != "model"}})[name]
|
|
144
|
+
_merge(_restored, name, {"model": model, **hints})
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def resolve(bindings: dict[str, Any], *, require_all: bool = True) -> dict[str, Any]:
|
|
148
|
+
"""Resolve aliases and names, rejecting conflicts before any LM call.
|
|
149
|
+
|
|
150
|
+
Packaging a program alone may defer named optimization declarations. The
|
|
151
|
+
optimization package resolves the full set and rejects names it cannot bind.
|
|
152
|
+
"""
|
|
153
|
+
result: dict[str, Any] = {}
|
|
154
|
+
for name, lm in bindings.items():
|
|
155
|
+
_name(name)
|
|
156
|
+
if name not in _restored and name not in _names and _object_declaration(lm) is None:
|
|
157
|
+
continue
|
|
158
|
+
model = _model(lm)
|
|
159
|
+
if name in _restored:
|
|
160
|
+
stored = _restored[name]
|
|
161
|
+
if stored["model"] != model:
|
|
162
|
+
raise LMConfigurationError(
|
|
163
|
+
f"model changed for binding {name!r}; configure the replacement with a new declaration"
|
|
164
|
+
)
|
|
165
|
+
_merge(result, name, dict(stored))
|
|
166
|
+
if name in _names:
|
|
167
|
+
_merge(result, name, {"model": model, **_names[name]})
|
|
168
|
+
declaration = _object_declaration(lm)
|
|
169
|
+
if declaration is not None:
|
|
170
|
+
declared_model, hints = declaration
|
|
171
|
+
if declared_model != model:
|
|
172
|
+
raise LMConfigurationError(
|
|
173
|
+
f"model changed for binding {name!r}; configure the replacement with a new declaration"
|
|
174
|
+
)
|
|
175
|
+
_merge(result, name, {"model": model, **hints})
|
|
176
|
+
if require_all:
|
|
177
|
+
unresolved = sorted((_names.keys() | _restored.keys()) - bindings.keys())
|
|
178
|
+
if unresolved:
|
|
179
|
+
raise LMConfigurationError(f"unresolved LM capability binding declarations: {', '.join(unresolved)}")
|
|
180
|
+
if any(not any(obj is lm for lm in bindings.values()) for obj in _objects):
|
|
181
|
+
raise LMConfigurationError(
|
|
182
|
+
"unresolved LM object capability declaration; pass that object as a hosted binding"
|
|
183
|
+
)
|
|
184
|
+
return result
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def resolve_call(identities: dict[str, Any], models: dict[str, Any]) -> dict[str, Any]:
|
|
188
|
+
"""Resolve the objects loaded for one call against its granted model names."""
|
|
189
|
+
declarations = resolve(identities, require_all=False)
|
|
190
|
+
unresolved = _names.keys() - identities.keys() - _restored.keys()
|
|
191
|
+
if unresolved:
|
|
192
|
+
raise LMConfigurationError(f"unresolved LM capability binding declarations: {', '.join(sorted(unresolved))}")
|
|
193
|
+
for obj in _objects:
|
|
194
|
+
if not any(obj is value for value in identities.values()):
|
|
195
|
+
raise LMConfigurationError(
|
|
196
|
+
"unresolved LM object capability declaration; pass that object as a hosted binding"
|
|
197
|
+
)
|
|
198
|
+
for name, model in models.items():
|
|
199
|
+
if name in declarations and declarations[name]["model"] != model:
|
|
200
|
+
raise LMConfigurationError(
|
|
201
|
+
f"model changed for binding {name!r}; configure the replacement with a new declaration"
|
|
202
|
+
)
|
|
203
|
+
return {name: declaration for name, declaration in declarations.items() if name in models}
|
|
@@ -658,6 +658,7 @@ def _build_optimization_bundle(
|
|
|
658
658
|
valset: list[Any],
|
|
659
659
|
metric: Callable[..., Any],
|
|
660
660
|
proposer: Callable[..., Any] | None = None,
|
|
661
|
+
lm_capabilities: dict[str, Any] | None = None,
|
|
661
662
|
) -> bytes:
|
|
662
663
|
"""Build the optimization-bundle ZIP: ``trainset.ndjson`` +
|
|
663
664
|
``valset.ndjson`` (one flattened example per line) + ``metric.py`` (the
|
|
@@ -679,6 +680,13 @@ def _build_optimization_bundle(
|
|
|
679
680
|
"runs in a sandbox from its own source, so there is nothing else to send."
|
|
680
681
|
)
|
|
681
682
|
|
|
683
|
+
if lm_capabilities:
|
|
684
|
+
preamble = "from cmpnd.lm_configuration import restore as _restore_lms\n"
|
|
685
|
+
preamble += f"_restore_lms({lm_capabilities!r})\n"
|
|
686
|
+
metric_source += "\n" + preamble
|
|
687
|
+
if proposer_source is not None:
|
|
688
|
+
proposer_source += "\n" + preamble
|
|
689
|
+
|
|
682
690
|
def _ndjson(rows: list[Any]) -> str:
|
|
683
691
|
return "\n".join(json.dumps(_example_to_dict(r)) for r in rows) + "\n"
|
|
684
692
|
|
|
@@ -919,7 +927,15 @@ def optimize(
|
|
|
919
927
|
|
|
920
928
|
cfg = configuration.require_config()
|
|
921
929
|
|
|
922
|
-
|
|
930
|
+
from . import lm_configuration
|
|
931
|
+
|
|
932
|
+
bindings = {name: entry["model"] for name, entry in (config.get("lms") or {}).items()}
|
|
933
|
+
bindings.update(lms or {})
|
|
934
|
+
default_lm = packaging._extract_lm_object(program)
|
|
935
|
+
if default_lm is not None:
|
|
936
|
+
bindings["default-lm"] = default_lm
|
|
937
|
+
declarations = lm_configuration.resolve(bindings)
|
|
938
|
+
bundle = _build_optimization_bundle(trainset, valset, metric, proposer, declarations)
|
|
923
939
|
|
|
924
940
|
if deployment_id is None:
|
|
925
941
|
deployment_id = str(deployment.deploy(program, metric=metric))
|
|
@@ -1083,9 +1099,18 @@ def optimize_authored(
|
|
|
1083
1099
|
_refuse_conflicting_members(cfg, deployment_id, prepared.members)
|
|
1084
1100
|
facts = _deployment_signature_facts(cfg, deployment_id)
|
|
1085
1101
|
|
|
1102
|
+
from . import lm_configuration
|
|
1103
|
+
|
|
1104
|
+
bindings = {name: entry["model"] for name, entry in (config.get("lms") or {}).items()}
|
|
1105
|
+
bindings.update(lms or {})
|
|
1106
|
+
if facts is not None and facts.litellm_model:
|
|
1107
|
+
bindings["default-lm"] = facts.litellm_model
|
|
1108
|
+
declarations = lm_configuration.resolve(bindings)
|
|
1109
|
+
bundle = _authored_capabilities_bundle(prepared.optimization_bundle, declarations)
|
|
1110
|
+
|
|
1086
1111
|
best_deployment_id, best_score = _run_steps(
|
|
1087
1112
|
cfg,
|
|
1088
|
-
|
|
1113
|
+
bundle,
|
|
1089
1114
|
deployment_id,
|
|
1090
1115
|
optimizer=optimizer,
|
|
1091
1116
|
config=config,
|
|
@@ -1110,6 +1135,28 @@ def optimize_authored(
|
|
|
1110
1135
|
)
|
|
1111
1136
|
|
|
1112
1137
|
|
|
1138
|
+
def _authored_capabilities_bundle(bundle: bytes, declarations: dict[str, Any]) -> bytes:
|
|
1139
|
+
"""Add caller-resolved bindings without changing any authored source bytes."""
|
|
1140
|
+
if not declarations:
|
|
1141
|
+
return bundle
|
|
1142
|
+
from .lm_configuration import LMConfigurationError
|
|
1143
|
+
|
|
1144
|
+
output = io.BytesIO()
|
|
1145
|
+
with zipfile.ZipFile(io.BytesIO(bundle)) as archive:
|
|
1146
|
+
manifest = json.loads(archive.read("manifest.json"))
|
|
1147
|
+
merged = dict(manifest.get("lm_capabilities") or {})
|
|
1148
|
+
for name, declaration in declarations.items():
|
|
1149
|
+
if name in merged and merged[name] != declaration:
|
|
1150
|
+
raise LMConfigurationError(f"conflicting LM capability declarations for binding {name!r}")
|
|
1151
|
+
merged[name] = declaration
|
|
1152
|
+
manifest["lm_capabilities"] = merged
|
|
1153
|
+
with zipfile.ZipFile(output, "w", zipfile.ZIP_DEFLATED) as updated:
|
|
1154
|
+
for member in archive.infolist():
|
|
1155
|
+
body = json.dumps(manifest).encode() if member.filename == "manifest.json" else archive.read(member)
|
|
1156
|
+
updated.writestr(member, body)
|
|
1157
|
+
return output.getvalue()
|
|
1158
|
+
|
|
1159
|
+
|
|
1113
1160
|
def _deployment_signature_facts(cfg: configuration.Config, deployment_id: str) -> authored.ProgramFacts | None:
|
|
1114
1161
|
"""Read an existing deployment's signature without making metadata a run prerequisite."""
|
|
1115
1162
|
from . import identity
|
|
@@ -1120,7 +1167,8 @@ def _deployment_signature_facts(cfg: configuration.Config, deployment_id: str) -
|
|
|
1120
1167
|
response = client.get(f"{cfg.endpoint}/api/v1/deployments/{deployment_id}", headers=headers)
|
|
1121
1168
|
if not response.is_success:
|
|
1122
1169
|
return None
|
|
1123
|
-
|
|
1170
|
+
deployment_row = response.json()
|
|
1171
|
+
value = deployment_row.get("program_signature_hash")
|
|
1124
1172
|
if value is None or value == "":
|
|
1125
1173
|
return None
|
|
1126
1174
|
# Deployment responses use the 16-digit hex display form. Signature routes
|
|
@@ -1133,6 +1181,7 @@ def _deployment_signature_facts(cfg: configuration.Config, deployment_id: str) -
|
|
|
1133
1181
|
row = response.json()
|
|
1134
1182
|
return authored.ProgramFacts(
|
|
1135
1183
|
program_signature_hash=signature_hash,
|
|
1184
|
+
litellm_model=deployment_row.get("litellm_model"),
|
|
1136
1185
|
signature_name=row.get("signature_name"),
|
|
1137
1186
|
input_fields=row.get("input_fields") or [],
|
|
1138
1187
|
output_fields=row.get("output_fields") or [],
|
|
@@ -34,12 +34,11 @@ if TYPE_CHECKING:
|
|
|
34
34
|
import dspy
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def
|
|
38
|
-
"""
|
|
37
|
+
def _extract_lm_object(module: dspy.Module) -> dspy.LM | None:
|
|
38
|
+
"""Find the program's LM object without losing its identity.
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
to ``dspy.settings.lm`` if no module-level LM exists.
|
|
40
|
+
Walk the module tree for a non-None ``.lm`` attribute, then fall back to
|
|
41
|
+
``dspy.settings.lm`` if no module-level LM exists.
|
|
43
42
|
"""
|
|
44
43
|
seen: set[int] = set()
|
|
45
44
|
|
|
@@ -62,9 +61,12 @@ def _extract_lm_state(module: dspy.Module) -> dict[str, typing.Any] | None:
|
|
|
62
61
|
lm = _find_lm(module)
|
|
63
62
|
if lm is None:
|
|
64
63
|
lm = dspy.settings.lm
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
return lm
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _extract_lm_state(module: dspy.Module) -> dict[str, typing.Any] | None:
|
|
68
|
+
lm = _extract_lm_object(module)
|
|
69
|
+
return lm.dump_state() if lm is not None and hasattr(lm, "dump_state") else None
|
|
68
70
|
|
|
69
71
|
|
|
70
72
|
class ImportEntry(typing.NamedTuple):
|
|
@@ -1248,7 +1250,12 @@ def generate_source_zip(
|
|
|
1248
1250
|
if metric_source is not None:
|
|
1249
1251
|
metric_filename = "metric_source.py"
|
|
1250
1252
|
|
|
1253
|
+
from . import lm_configuration
|
|
1254
|
+
|
|
1255
|
+
lm = _extract_lm_object(module)
|
|
1256
|
+
declarations = lm_configuration.resolve({"default-lm": lm} if lm is not None else {}, require_all=False)
|
|
1251
1257
|
manifest = {
|
|
1258
|
+
"lm_capabilities": declarations,
|
|
1252
1259
|
"dependency_versions": {
|
|
1253
1260
|
"python": platform.python_version(),
|
|
1254
1261
|
"dspy": dspy.__version__,
|
|
@@ -19,7 +19,7 @@ cmpnd.configure(api_key="ck_...", project="my-app")
|
|
|
19
19
|
cmpnd.auto_instrument()
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
- **`cmpnd.configure()`** — `
|
|
22
|
+
- **`cmpnd.configure()`** — `cmpnd/configuration.py`. Reads `api_key` and `endpoint` from args or `CMPND_API_KEY` / `CMPND_ENDPOINT` env vars, builds a `Config` dataclass (`configuration.py`), and stores it under a lock. This call does not start any threads. It also does not touch DSPy. You can call `configure()` in a library and let the user decide whether to instrument.
|
|
23
23
|
- **`cmpnd.auto_instrument()`** — `python-sdk/cmpnd/__init__.py:272`. Requires `configure()` to have been called. It instantiates a `CmpndCallback`, appends it to `dspy.settings.callbacks` (deduplicating if already installed), and patches `dspy.GEPA.__init__` / `GEPA.compile` so optimization runs are tracked automatically. See [`./optimization-tracking.md`](../../docs/optimization-tracking.md) for what that patch does.
|
|
24
24
|
|
|
25
25
|
The exporter thread isn't started here directly. It is lazily started on first export via `ensure_exporter_started()` in `python-sdk/cmpnd/exporter.py:41`.
|
|
@@ -30,6 +30,49 @@ For an explicit, scoped alternative that traces only the programs you run throug
|
|
|
30
30
|
|
|
31
31
|
---
|
|
32
32
|
|
|
33
|
+
## Hosted LM capabilities
|
|
34
|
+
|
|
35
|
+
Declare hosted capabilities once for an LM object. Inside an authored program,
|
|
36
|
+
you can also name an explicit binding.
|
|
37
|
+
Every binding created from the same object shares its declaration. Separate LM
|
|
38
|
+
objects remain independent, even when their model names and sampling match.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import cmpnd
|
|
42
|
+
import dspy
|
|
43
|
+
|
|
44
|
+
lm = dspy.LM("provider/model", reasoning_effort="low")
|
|
45
|
+
cmpnd.configure_lm(
|
|
46
|
+
lm,
|
|
47
|
+
supported_params=["response_format"],
|
|
48
|
+
supports_response_schema=True,
|
|
49
|
+
)
|
|
50
|
+
dspy.configure(lm=lm, adapter=dspy.JSONAdapter())
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The object form follows the object into `default-lm` or the named `lms` passed to
|
|
54
|
+
optimization. The string form names a binding such as `default-lm`, `judge`, or
|
|
55
|
+
an authored proposer's LM. Call that form inside the authored source file, for
|
|
56
|
+
example `cmpnd.configure_lm("judge", supported_params=["response_format"])`.
|
|
57
|
+
Notebook and other live SDK callers pass LM objects. Object declarations belong
|
|
58
|
+
to those objects and apply only to workflows that use them, so independent
|
|
59
|
+
optimizations can run sequentially in one process.
|
|
60
|
+
Each binding has one model within a program or run.
|
|
61
|
+
Identical declarations are idempotent. Conflicting declarations and names that
|
|
62
|
+
an optimization cannot resolve raise an error before inference.
|
|
63
|
+
|
|
64
|
+
The SDK records resolved declarations in the program and optimization bundles.
|
|
65
|
+
Execute, metric, and authored proposer turns use those snapshots. A declaration
|
|
66
|
+
retains its resolved model, so replacing that model requires a new declaration.
|
|
67
|
+
An LM object's identity stays local to Python. The bundle carries its resolved
|
|
68
|
+
binding names and capabilities.
|
|
69
|
+
|
|
70
|
+
List `response_format` to request JSON-object mode. Add
|
|
71
|
+
`supports_response_schema=True` when the model and its configured provider
|
|
72
|
+
endpoint accept JSON schemas. Undeclared bindings keep prompt-only JSON. These
|
|
73
|
+
hints do not add a format the provider cannot accept or change local DSPy's
|
|
74
|
+
capability lookup.
|
|
75
|
+
|
|
33
76
|
## The callback
|
|
34
77
|
|
|
35
78
|
`python-sdk/cmpnd/callback.py` implements `CmpndCallback`, which satisfies `dspy.utils.callback.BaseCallback` without inheriting it — `callback.py` must import on the standard library alone (`tests/test_import_weight.py`), so it cannot import dspy to get a base class. DSPy calls its hook methods synchronously on whatever thread is executing the module. The callback owns no long-lived state beyond a per-call span builder map; everything trace/span related flows through context vars (see next section).
|
|
@@ -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.27.
|
|
8
|
+
version = "0.27.2"
|
|
9
9
|
description = "DSPy observability and deployment SDK for cmpnd"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.10"
|