cmpnd 0.43.1__tar.gz → 0.45.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.43.1 → cmpnd-0.45.0}/PKG-INFO +1 -1
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/__init__.py +20 -2
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/callback.py +6 -1
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/client.py +48 -17
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/lm_configuration.py +68 -4
- cmpnd-0.45.0/cmpnd/named_programs.py +274 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/optimization.py +17 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/packaging.py +61 -1
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/programs.py +0 -43
- {cmpnd-0.43.1 → cmpnd-0.45.0}/docs/client.md +47 -12
- {cmpnd-0.43.1 → cmpnd-0.45.0}/docs/sdk-instrumentation.md +21 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/pyproject.toml +1 -1
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/hosted_optimize_probe.py +58 -0
- cmpnd-0.45.0/tests/test_client_program_names.py +817 -0
- cmpnd-0.45.0/tests/test_decision_client_spans.py +144 -0
- cmpnd-0.45.0/tests/test_system_one.py +106 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/uv.lock +1 -1
- cmpnd-0.43.1/tests/test_client_program_names.py +0 -339
- {cmpnd-0.43.1 → cmpnd-0.45.0}/.gitignore +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/AGENTS.md +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/CLAUDE.md +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/README.md +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_assessments.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_authored_import.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_deployments.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_gepa_patch.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_http.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_runtime_tools.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_traces.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_valsets.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/authored.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/__init__.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/admin.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/assessments.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/deployments.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/evals.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/org.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/programs.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/signatures.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/top.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/traces.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/valsets.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/shell.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/trace_render.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/tree_help.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/verbs.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/configuration.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/context.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/dataset_sync.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/datasets.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/decorators.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/deployment.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/deployment_metadata.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/deployment_runs.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/execution.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/exporter.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/helpers.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/identity.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/imaging.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/ir/reflection.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/mcp_servers.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/models.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/optimizers/gepa.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/optimizers/gepa_callback.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/optimizers/tracker.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/references.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/retry.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/traces.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/docs/2026-04-02-code-review.md +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/docs/cli.md +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/docs/custom-proposer.md +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/docs/reading-traces.md +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/examples/local_ollama.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/README.md +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/cmpnd_ingest/bodies.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/cmpnd_ingest/program_names.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/cmpnd_ingest/protocols.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/pyproject.toml +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/capture_review_run.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/check-pip-install.sh +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/release-stress.sh +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/require_version_bump.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/seed_review_run.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/test-in-linux.sh +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/stubs/dspy/utils/dummies.pyi +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/__init__.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/_deployment_signature_helpers.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/_fake_clock.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/_resolve_refs.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/_traced_run.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/conftest.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/committee_task.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/helpers.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/target_capabilities.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_assessments_cli.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_composite_module.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_deploy_execute.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_deploy_pandas.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_deploy_programs.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_optimize_failures.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_parallel_export.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_project_attribution.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_traces_read.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_user_attribution.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_valsets_cli.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/__init__.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/authored_optimize/judge.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/conftest.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/test_logs_renders_traces.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/test_unhappy_path.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/__init__.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/__main__.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/drain.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/ingest.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/rlm_shape.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/rlm_size_report.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/sweep.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/test_rlm_size_report.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/__init__.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/conftest.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_health.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_trace_streaming.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_traces.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/span_reference_fixture.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/__init__.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/_provider.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/_readiness.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/_reliability.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/_result.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/mint_bedrock_token.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_deploy_programs.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_multi_client_stress.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_optimize_client_committee.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_optimize_loop.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_optimize_stress.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_provider_roles.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_readiness.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_real_provider_twins.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_reliability.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_reliability_classify.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_rlm_wasm.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_trace_search.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_adapter_reentrancy.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_assessments_api.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_authored.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_authored_bundle_collisions.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_authored_metric_contract.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_authored_named_models.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_authored_optimize.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_callback.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_admin.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_api_client.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_assessments.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_deployment_runs.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_deployments.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_evals.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_main.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_org.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_parity.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_programs.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_provider_credentials.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_schema_sample.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_shell.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_signatures.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_top.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_traces.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_traces_search_wire.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_tree_help.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_valsets.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_client.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_configuration.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_context.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_context_propagation.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_dataset_sync.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_datasets.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_decorators.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_deploy.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_deploy_program_id.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_deployment_metadata.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_deployment_runs.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_deployments_read.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_eval.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_eval_example_attribution.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_eval_start.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_eval_trace_grouping.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_execute.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_execute_run_identity.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_exporter.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_exporter_program_names.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_exporter_queue.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_exporter_real.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_gepa_output_normalization.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_identity.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_imaging.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_import_weight.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_ingest_conformance.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_integration.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_interpreter_callback.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_lm15_native_calls.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_lm_configuration.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_lm_usage_shapes.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_mcp_tools.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_models.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_multi_client_stress_readiness.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_optimize.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_optimize_lms.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_optimize_reflection_lm.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_packaging.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_packaging_mcp_tools.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_packaging_program_source.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_poll_until.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_program_identity.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_program_lineage_persist.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_programs_create.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_project_payload.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_real_provider_twins_readiness.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_reflector_properties.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_reflector_smoke.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_release_stress_script.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_repro_reactv2_bugs.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_retry.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_rlm_patch.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_run_attribution_paths.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_runtime_tools.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_schemas.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_signature_inputs.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_span_references.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_span_sent_once.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_trace_context_concurrency.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_trace_fact_wire_contract.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_trace_facts.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_trace_render.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_traces_read.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_tracked_gepa.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_truncation.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_valsets_api.py +0 -0
- {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_xxh64.py +0 -0
|
@@ -13,6 +13,7 @@ Usage:
|
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
import importlib.metadata
|
|
16
|
+
from typing import TYPE_CHECKING
|
|
16
17
|
|
|
17
18
|
from . import authored, programs, traces
|
|
18
19
|
from ._http import ApiError
|
|
@@ -45,7 +46,7 @@ from .deployment_runs import (
|
|
|
45
46
|
)
|
|
46
47
|
from .execution import DeployedProgram, RetryableProviderError, deployment_run_id, execute
|
|
47
48
|
from .exporter import flush_exporter, shutdown_exporter
|
|
48
|
-
from .lm_configuration import configure_lm
|
|
49
|
+
from .lm_configuration import SystemOne, configure_lm
|
|
49
50
|
from .mcp_servers import (
|
|
50
51
|
Bearer,
|
|
51
52
|
McpToolReference,
|
|
@@ -55,12 +56,15 @@ from .mcp_servers import (
|
|
|
55
56
|
mcp_tools,
|
|
56
57
|
)
|
|
57
58
|
from .models import SpanBuilder, SpanType, TraceBuilder, TraceStatus
|
|
59
|
+
|
|
60
|
+
if TYPE_CHECKING:
|
|
61
|
+
# Loaded on first use by __getattr__ below, because their module imports dspy.
|
|
62
|
+
from .named_programs import Program, program
|
|
58
63
|
from .optimization import (
|
|
59
64
|
OptimizeError,
|
|
60
65
|
OptimizeFailed,
|
|
61
66
|
optimize,
|
|
62
67
|
)
|
|
63
|
-
from .programs import Program
|
|
64
68
|
|
|
65
69
|
try:
|
|
66
70
|
__version__ = importlib.metadata.version("cmpnd")
|
|
@@ -76,6 +80,7 @@ __all__ = [
|
|
|
76
80
|
# Configuration
|
|
77
81
|
"configure",
|
|
78
82
|
"configure_lm",
|
|
83
|
+
"SystemOne",
|
|
79
84
|
"get_config",
|
|
80
85
|
"require_config",
|
|
81
86
|
# Main callback
|
|
@@ -103,6 +108,7 @@ __all__ = [
|
|
|
103
108
|
"programs",
|
|
104
109
|
# A program declared by name
|
|
105
110
|
"Program",
|
|
111
|
+
"program",
|
|
106
112
|
"authored",
|
|
107
113
|
"ApiError",
|
|
108
114
|
# Types
|
|
@@ -253,3 +259,15 @@ def de_instrument() -> None:
|
|
|
253
259
|
def _mcp_tool(declaration): # pyright: ignore[reportUnusedFunction] -- generated bundles call this hook
|
|
254
260
|
"""Keep generated declarations importable outside a worker without granting access."""
|
|
255
261
|
return declaration
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def __getattr__(name: str): # noqa: ANN202 — PEP 562 module attribute hook
|
|
265
|
+
"""Load ``Program`` and ``program`` on first use, because their module imports dspy.
|
|
266
|
+
|
|
267
|
+
``import cmpnd`` stays free of dspy until a caller asks for one of them.
|
|
268
|
+
"""
|
|
269
|
+
if name in ("Program", "program"):
|
|
270
|
+
from . import named_programs
|
|
271
|
+
|
|
272
|
+
return getattr(named_programs, name)
|
|
273
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -383,6 +383,10 @@ def _lm15_cache_hit(entry: Any, response: Any) -> bool | None:
|
|
|
383
383
|
|
|
384
384
|
def _usage_dialect(instance: Any, usage: Any) -> str | None:
|
|
385
385
|
"""Name the response surface only when its shape establishes one."""
|
|
386
|
+
# A decision client (DSPy's `TypeSafe`) speaks System One. Its usage names chat's token
|
|
387
|
+
# fields, so the shape alone would misname it. DSPy dispatches on the same flag.
|
|
388
|
+
if getattr(instance, "supports_decision_requests", False) is True:
|
|
389
|
+
return "system_one"
|
|
386
390
|
model_type = _text_field(instance, "model_type")
|
|
387
391
|
if model_type == "responses":
|
|
388
392
|
return "responses"
|
|
@@ -1489,7 +1493,8 @@ class _LMMixin(_CallbackBase):
|
|
|
1489
1493
|
observation.usage_dialect = _usage_dialect(instance, usage)
|
|
1490
1494
|
if observation.usage_dialect in {"chat_completions", "responses"}:
|
|
1491
1495
|
observation.prompt_inclusion = "includes_cache_reads"
|
|
1492
|
-
elif observation.usage_dialect
|
|
1496
|
+
elif observation.usage_dialect in {"converse", "system_one"}:
|
|
1497
|
+
# System One serves nothing from a cache, so its prompt is all fresh input.
|
|
1493
1498
|
observation.prompt_inclusion = "excludes_cache_reads"
|
|
1494
1499
|
|
|
1495
1500
|
if usage_reported and usage is not None:
|
|
@@ -29,7 +29,7 @@ import dataclasses
|
|
|
29
29
|
import os
|
|
30
30
|
from typing import Any, Literal
|
|
31
31
|
|
|
32
|
-
from . import _assessments, _deployments, _exporter_pool, _traces, _valsets, callback, configuration
|
|
32
|
+
from . import _assessments, _deployments, _exporter_pool, _traces, _valsets, callback, configuration
|
|
33
33
|
from .deployment_metadata import DeploymentSignatureResponse
|
|
34
34
|
|
|
35
35
|
|
|
@@ -106,28 +106,44 @@ class Client:
|
|
|
106
106
|
|
|
107
107
|
# -- scoped runs ------------------------------------------------------
|
|
108
108
|
|
|
109
|
+
@staticmethod
|
|
110
|
+
def _as_program(obj: Any) -> Any:
|
|
111
|
+
"""``obj`` when it is a ``cmpnd.Program``, else None."""
|
|
112
|
+
from . import named_programs
|
|
113
|
+
|
|
114
|
+
return obj if isinstance(obj, named_programs.Program) else None
|
|
115
|
+
|
|
109
116
|
def _declared(self, program: Any) -> dict[str, Any]:
|
|
110
117
|
"""The ``dspy.context`` settings that run ``program`` under its declared name.
|
|
111
118
|
|
|
112
|
-
A ``cmpnd.Program`` runs on its own LM
|
|
113
|
-
declares
|
|
119
|
+
A ``cmpnd.Program`` runs on its own LM, which an enclosing program's LM would
|
|
120
|
+
override, and declares its name. Any other module declares no name and no
|
|
121
|
+
program LM, which also clears the ones an enclosing call declared.
|
|
114
122
|
"""
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
123
|
+
from . import named_programs
|
|
124
|
+
|
|
125
|
+
named = self._as_program(program)
|
|
126
|
+
if named is not None:
|
|
127
|
+
return {
|
|
128
|
+
"lm": named.lm,
|
|
129
|
+
named_programs.PROGRAM_LM_SETTING: named.lm,
|
|
130
|
+
callback.DECLARED_PROGRAM_SETTING: named.name,
|
|
131
|
+
}
|
|
132
|
+
return {named_programs.PROGRAM_LM_SETTING: None, callback.DECLARED_PROGRAM_SETTING: None}
|
|
118
133
|
|
|
119
134
|
def run_program(self, program: Any, **inputs: Any) -> Any:
|
|
120
135
|
"""Run ``program(**inputs)`` traced on this client's account.
|
|
121
136
|
|
|
122
137
|
``program`` is a ``cmpnd.Program`` or a plain DSPy module. A ``Program`` runs
|
|
123
|
-
on its own LM, and its traces go to the program with its name.
|
|
138
|
+
on its own LM, and its traces go to the program with its name. A program adds
|
|
139
|
+
no span of its own, so a program and its module run plain record the same
|
|
140
|
+
structure.
|
|
124
141
|
"""
|
|
125
142
|
self._check_active()
|
|
126
143
|
import dspy
|
|
127
144
|
|
|
128
|
-
module = program.module if isinstance(program, programs.Program) else program
|
|
129
145
|
with dspy.context(callbacks=self._scoped_callbacks(), **self._declared(program)):
|
|
130
|
-
return
|
|
146
|
+
return program(**inputs)
|
|
131
147
|
|
|
132
148
|
def run_optimization(
|
|
133
149
|
self,
|
|
@@ -150,14 +166,22 @@ class Client:
|
|
|
150
166
|
suppresses the global class patch so the run is recorded once, on the
|
|
151
167
|
client's account, not the global one.
|
|
152
168
|
|
|
153
|
-
``student`` is a ``cmpnd.Program`` or a plain DSPy module.
|
|
154
|
-
|
|
155
|
-
its name
|
|
169
|
+
``student`` is a ``cmpnd.Program`` or a plain DSPy module. The optimizer
|
|
170
|
+
works on a ``Program``'s module, on the program's LM, and its rollouts and run
|
|
171
|
+
record go to the program with its name. The result is a copy of the student,
|
|
172
|
+
of the same class and with the same name and LM, holding the optimized module.
|
|
156
173
|
"""
|
|
157
174
|
self._check_active()
|
|
175
|
+
import copy
|
|
176
|
+
|
|
158
177
|
import dspy
|
|
159
178
|
|
|
160
|
-
|
|
179
|
+
from . import named_programs
|
|
180
|
+
|
|
181
|
+
named = self._as_program(student)
|
|
182
|
+
whole = named is not None and named_programs.runs_itself(named)
|
|
183
|
+
module = named.module if named is not None and not whole else student
|
|
184
|
+
result: Any = None
|
|
161
185
|
with dspy.context(callbacks=self._scoped_callbacks(), **self._declared(student)):
|
|
162
186
|
try:
|
|
163
187
|
if isinstance(optimizer, dspy.GEPA):
|
|
@@ -167,15 +191,22 @@ class Client:
|
|
|
167
191
|
trainset=trainset,
|
|
168
192
|
valset=valset,
|
|
169
193
|
kwargs=kwargs,
|
|
170
|
-
declared_name=
|
|
194
|
+
declared_name=named.name if named is not None else None,
|
|
171
195
|
)
|
|
172
196
|
else:
|
|
173
197
|
result = optimizer.compile(module, trainset=trainset, valset=valset, **kwargs)
|
|
174
198
|
finally:
|
|
175
199
|
self._callback._clear_optimization_trace_state()
|
|
176
|
-
if
|
|
177
|
-
return
|
|
178
|
-
|
|
200
|
+
if named is None:
|
|
201
|
+
return result
|
|
202
|
+
if whole and isinstance(result, type(named)):
|
|
203
|
+
# The optimizer worked on the program itself and returned a copy of it, which
|
|
204
|
+
# deep-copied the LM; the copy gets the student's back.
|
|
205
|
+
result.lm = named.lm
|
|
206
|
+
return result
|
|
207
|
+
optimized = copy.copy(named)
|
|
208
|
+
optimized.module = result
|
|
209
|
+
return optimized
|
|
179
210
|
|
|
180
211
|
# -- data surface (flat verb-object over the client's credentials) -----
|
|
181
212
|
#
|
|
@@ -9,11 +9,25 @@ class LMConfigurationError(ValueError):
|
|
|
9
9
|
"""A hosted binding declaration cannot be resolved consistently."""
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class LMCapabilities(TypedDict):
|
|
13
|
-
"""Explicit adapter hints for a hosted model, independent of its provider.
|
|
12
|
+
class LMCapabilities(TypedDict, total=False):
|
|
13
|
+
"""Explicit adapter hints for a hosted model, independent of its provider.
|
|
14
|
+
|
|
15
|
+
``dialect`` is present only for a System One binding, which :class:`SystemOne`
|
|
16
|
+
writes. Absent, the binding is a chat model.
|
|
17
|
+
"""
|
|
14
18
|
|
|
15
19
|
supported_params: list[str]
|
|
16
20
|
supports_response_schema: bool
|
|
21
|
+
dialect: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# The wire protocol a binding's model speaks, named for the protocol and never for a
|
|
25
|
+
# provider or a client library: a chat model answers chat completions, and a System One
|
|
26
|
+
# model (TypeSafe's System One protocol, which other providers serve too) answers a set
|
|
27
|
+
# of questions about a state.
|
|
28
|
+
CHAT_COMPLETIONS = "chat_completions"
|
|
29
|
+
SYSTEM_ONE = "system_one"
|
|
30
|
+
_DIALECTS = frozenset({CHAT_COMPLETIONS, SYSTEM_ONE})
|
|
17
31
|
|
|
18
32
|
|
|
19
33
|
def _validate_lm_capabilities(value: object) -> dict[str, LMCapabilities]:
|
|
@@ -27,7 +41,7 @@ def _validate_lm_capabilities(value: object) -> dict[str, LMCapabilities]:
|
|
|
27
41
|
)
|
|
28
42
|
if not isinstance(declaration, dict):
|
|
29
43
|
raise LMConfigurationError(f"lm_capabilities[{binding!r}] must be a capability declaration")
|
|
30
|
-
unknown = declaration.keys() - {"supported_params", "supports_response_schema"}
|
|
44
|
+
unknown = declaration.keys() - {"supported_params", "supports_response_schema", "dialect"}
|
|
31
45
|
if unknown:
|
|
32
46
|
raise LMConfigurationError(f"lm_capabilities[{binding!r}] has unknown fields: {sorted(unknown, key=str)}")
|
|
33
47
|
params = declaration.get("supported_params", [])
|
|
@@ -38,11 +52,24 @@ def _validate_lm_capabilities(value: object) -> dict[str, LMCapabilities]:
|
|
|
38
52
|
schema = declaration.get("supports_response_schema", False)
|
|
39
53
|
if not isinstance(schema, bool):
|
|
40
54
|
raise LMConfigurationError(f"lm_capabilities[{binding!r}].supports_response_schema must be a boolean")
|
|
55
|
+
dialect = declaration.get("dialect", CHAT_COMPLETIONS)
|
|
56
|
+
if dialect not in _DIALECTS:
|
|
57
|
+
raise LMConfigurationError(
|
|
58
|
+
f"lm_capabilities[{binding!r}].dialect must be one of {sorted(_DIALECTS)} (got {dialect!r})"
|
|
59
|
+
)
|
|
60
|
+
if dialect == SYSTEM_ONE and (params or schema):
|
|
61
|
+
raise LMConfigurationError(
|
|
62
|
+
f"lm_capabilities[{binding!r}] is a System One binding, which takes no sampling "
|
|
63
|
+
"parameters and no response schema"
|
|
64
|
+
)
|
|
41
65
|
if schema and "response_format" not in params:
|
|
42
66
|
raise LMConfigurationError(
|
|
43
67
|
f"lm_capabilities[{binding!r}] requires response_format in supported_params for schema output"
|
|
44
68
|
)
|
|
45
|
-
|
|
69
|
+
hints: LMCapabilities = {"supported_params": list(dict.fromkeys(params)), "supports_response_schema": schema}
|
|
70
|
+
if dialect == SYSTEM_ONE:
|
|
71
|
+
hints["dialect"] = SYSTEM_ONE
|
|
72
|
+
result[binding] = hints
|
|
46
73
|
return result
|
|
47
74
|
|
|
48
75
|
|
|
@@ -122,6 +149,11 @@ def configure_lm(
|
|
|
122
149
|
)
|
|
123
150
|
_merge(_names, _name(lm), hints)
|
|
124
151
|
return
|
|
152
|
+
_declare(lm, hints)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _declare(lm: Any, hints: dict[str, Any]) -> None:
|
|
156
|
+
"""Attach a declaration to one LM object, shared by every binding made from it."""
|
|
125
157
|
model = _model(lm)
|
|
126
158
|
declared = _object_declaration(lm)
|
|
127
159
|
if declared is not None and declared != (model, hints):
|
|
@@ -131,6 +163,38 @@ def configure_lm(
|
|
|
131
163
|
_objects.append(lm)
|
|
132
164
|
|
|
133
165
|
|
|
166
|
+
class SystemOne:
|
|
167
|
+
"""A System One decision model, for a run's ``lms``.
|
|
168
|
+
|
|
169
|
+
A System One model answers a set of questions about a state instead of continuing a
|
|
170
|
+
conversation: TypeSafe's Jev, or another endpoint serving the same schema. A hosted
|
|
171
|
+
judge built on ``dspy.experimental.TypeSafe`` runs against the run's System One
|
|
172
|
+
binding without naming a key or a URL::
|
|
173
|
+
|
|
174
|
+
cmpnd.optimize(..., lms={"judge": cmpnd.SystemOne("typesafe/jev-latest")})
|
|
175
|
+
|
|
176
|
+
``model`` is a platform model id: the prefix names the organization's sealed
|
|
177
|
+
credential, and the rest is the model at that credential's endpoint. Writing the
|
|
178
|
+
binding this way is the only thing that makes it a System One binding; a model name
|
|
179
|
+
on its own never does.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
def __init__(self, model: object):
|
|
183
|
+
if not isinstance(model, str) or not model.strip():
|
|
184
|
+
raise ValueError(f"cmpnd.SystemOne needs a model id such as 'typesafe/jev-latest' (got {model!r})")
|
|
185
|
+
credential, _, name = model.strip().partition("/")
|
|
186
|
+
if not credential or not name:
|
|
187
|
+
raise ValueError(
|
|
188
|
+
f"cmpnd.SystemOne({model!r}) names no credential: write the model as "
|
|
189
|
+
"'<credential>/<model>', for example 'typesafe/jev-latest'"
|
|
190
|
+
)
|
|
191
|
+
self.model = model.strip()
|
|
192
|
+
_declare(self, {"supported_params": [], "supports_response_schema": False, "dialect": SYSTEM_ONE})
|
|
193
|
+
|
|
194
|
+
def __repr__(self) -> str:
|
|
195
|
+
return f"cmpnd.SystemOne({self.model!r})"
|
|
196
|
+
|
|
197
|
+
|
|
134
198
|
def restore(declarations: object) -> None:
|
|
135
199
|
"""Load model-pinned declarations from a packaged artifact."""
|
|
136
200
|
if not isinstance(declarations, dict):
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""Programs declared by name: ``cmpnd.Program`` and the ``@cmpnd.program`` decorator.
|
|
2
|
+
|
|
3
|
+
A ``cmpnd.Program`` is a DSPy module with two more things: the name of the program it
|
|
4
|
+
is, and the LM it runs on. It holds the module that does the work as a submodule, so
|
|
5
|
+
it nests inside a larger DSPy program like any other module. The name is the
|
|
6
|
+
program's handle: traces from a ``cmpnd.Client`` run of it go to the program with that
|
|
7
|
+
name in the caller's organization, which the platform creates on first use.
|
|
8
|
+
|
|
9
|
+
``@cmpnd.program(name)`` is a shorter way to define one. Above a ``dspy.Module`` class,
|
|
10
|
+
it returns a ``cmpnd.Program`` subclass of the same name, so that
|
|
11
|
+
|
|
12
|
+
@cmpnd.program("capital-qa")
|
|
13
|
+
class CapitalQA(dspy.Module): ...
|
|
14
|
+
|
|
15
|
+
qa = CapitalQA(lm=lm)
|
|
16
|
+
|
|
17
|
+
builds the same program as ``cmpnd.Program("capital-qa", module=<the class as
|
|
18
|
+
written>(), lm=lm)``.
|
|
19
|
+
|
|
20
|
+
When programs nest, the outermost program's LM wins, so a caller can always choose the
|
|
21
|
+
model from outside. Each program records the LM it applied in a DSPy settings key, and
|
|
22
|
+
a program inside applies its own LM only when the key is unset.
|
|
23
|
+
|
|
24
|
+
A program adds no span to a trace. Calling one calls its module directly, past the
|
|
25
|
+
``dspy.Module.__call__`` that traces a module, so a program, a program that wraps
|
|
26
|
+
another, and a program nested in a larger module all trace as the module they hold.
|
|
27
|
+
|
|
28
|
+
This module imports dspy, so ``cmpnd`` loads it only when a caller first uses
|
|
29
|
+
``cmpnd.Program`` or ``cmpnd.program``.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import contextlib
|
|
35
|
+
import inspect
|
|
36
|
+
import weakref
|
|
37
|
+
from collections.abc import Callable
|
|
38
|
+
from typing import Any
|
|
39
|
+
|
|
40
|
+
import dspy
|
|
41
|
+
from dspy.clients import base_lm # pyright: ignore[reportMissingTypeStubs] # dspy re-exports BaseLM untyped
|
|
42
|
+
|
|
43
|
+
from . import programs
|
|
44
|
+
|
|
45
|
+
# The DSPy settings key that holds the LM the outermost running program applied.
|
|
46
|
+
PROGRAM_LM_SETTING = "cmpnd_program_lm"
|
|
47
|
+
|
|
48
|
+
# The source of each class @cmpnd.program decorated, read when it was decorated. The
|
|
49
|
+
# class's qualified name changes then (see `program`), and before Python 3.13
|
|
50
|
+
# `inspect.getsource` finds a class by that name.
|
|
51
|
+
_DECLARED_SOURCE: weakref.WeakKeyDictionary[type, str] = weakref.WeakKeyDictionary()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _check_lm(lm: Any, usage: str) -> None:
|
|
55
|
+
if not isinstance(lm, base_lm.BaseLM):
|
|
56
|
+
raise TypeError(f"A program needs an LM such as dspy.LM(...), not {type(lm).__name__}: {usage}")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _check_module(module: object) -> None:
|
|
60
|
+
if not isinstance(module, dspy.Module):
|
|
61
|
+
raise TypeError(f"A Program needs a DSPy module, not {type(module).__name__}")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _without_self(signature: inspect.Signature) -> inspect.Signature:
|
|
65
|
+
return signature.replace(
|
|
66
|
+
parameters=list(signature.parameters.values())[1:], return_annotation=inspect.Signature.empty
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
_ModuleMeta: Any = type(dspy.Module) # DSPy's metaclass, which every dspy.Module uses
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class _ProgramMeta(_ModuleMeta):
|
|
74
|
+
"""DSPy's metaclass, with each program class's constructor arguments for help().
|
|
75
|
+
|
|
76
|
+
DSPy's metaclass defines ``__call__``, which hides a class's ``__init__`` from
|
|
77
|
+
``inspect.signature``. A property here is read by ``inspect`` and not listed by
|
|
78
|
+
``help()``.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def __signature__(cls) -> inspect.Signature:
|
|
83
|
+
return _without_self(inspect.signature(cls.__init__))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class Program(dspy.Module, metaclass=_ProgramMeta):
|
|
87
|
+
"""A DSPy module with a program name and the LM it runs on.
|
|
88
|
+
|
|
89
|
+
A program holds the DSPy module that does the work. Run it through a
|
|
90
|
+
``cmpnd.Client``, and its traces go to the program with its name in your
|
|
91
|
+
organization:
|
|
92
|
+
|
|
93
|
+
>>> qa = cmpnd.Program("capital-qa", module=dspy.Predict("question -> answer"), lm=dspy.LM("openai/gpt-5-mini"))
|
|
94
|
+
>>> with cmpnd.Client() as client:
|
|
95
|
+
... client.run_program(qa, question="What is the capital of France?")
|
|
96
|
+
|
|
97
|
+
``@cmpnd.program(name)`` is a shorter way to define a program on a class you
|
|
98
|
+
write. Use this constructor when one class serves several programs, when you
|
|
99
|
+
cannot edit the class, or when its constructor already takes ``lm``.
|
|
100
|
+
|
|
101
|
+
Attributes:
|
|
102
|
+
name: The program's name: 1 to 64 letters, digits, ".", "_" or "-", starting
|
|
103
|
+
with a letter or digit and not with "p_". Programs in one organization
|
|
104
|
+
that share a name share a history.
|
|
105
|
+
module: The DSPy module that does the work. It is a submodule of the program,
|
|
106
|
+
so ``named_predictors()`` and optimizers find its predictors, also when
|
|
107
|
+
the program is inside a larger DSPy module.
|
|
108
|
+
lm: The LM the program runs on. When programs nest, the outermost program's
|
|
109
|
+
LM wins, so a caller can choose the model from outside.
|
|
110
|
+
|
|
111
|
+
Declaring a program makes no network call. The platform creates the program
|
|
112
|
+
the first time a client sends one of its traces. Calling a program directly
|
|
113
|
+
runs it on its LM and records nothing under its name.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def __init__(
|
|
117
|
+
self,
|
|
118
|
+
name: str,
|
|
119
|
+
*,
|
|
120
|
+
module: dspy.Module,
|
|
121
|
+
lm: dspy.BaseLM, # pyright: ignore[reportAttributeAccessIssue] # dspy re-exports BaseLM untyped
|
|
122
|
+
) -> None:
|
|
123
|
+
"""Declare a program named ``name`` that runs ``module`` on ``lm``."""
|
|
124
|
+
super().__init__()
|
|
125
|
+
self.name = programs.validate_program_name(name)
|
|
126
|
+
_check_module(module)
|
|
127
|
+
_check_lm(lm, "cmpnd.Program(name, module=..., lm=dspy.LM(...))")
|
|
128
|
+
self.module: Any = module
|
|
129
|
+
self.lm = lm
|
|
130
|
+
|
|
131
|
+
def _lm_context(self) -> Any:
|
|
132
|
+
"""A context that applies this program's LM, unless an enclosing program applied one."""
|
|
133
|
+
if dspy.settings.get(PROGRAM_LM_SETTING) is not None:
|
|
134
|
+
return contextlib.nullcontext()
|
|
135
|
+
return dspy.context(lm=self.lm, **{PROGRAM_LM_SETTING: self.lm})
|
|
136
|
+
|
|
137
|
+
def __call__(self, *args: Any, **inputs: Any) -> dspy.Prediction:
|
|
138
|
+
"""Run the module on this program's LM, and return its prediction.
|
|
139
|
+
|
|
140
|
+
The program makes no span of its own. ``dspy.Module.__call__`` makes a span for
|
|
141
|
+
each module it runs, and this method calls the module directly instead, so
|
|
142
|
+
wrapped and nested programs add no layers to a trace. A subclass that overrides
|
|
143
|
+
``forward`` goes through ``dspy.Module.__call__`` and traces as itself.
|
|
144
|
+
"""
|
|
145
|
+
with self._lm_context():
|
|
146
|
+
if runs_itself(self):
|
|
147
|
+
return super().__call__(*args, **inputs)
|
|
148
|
+
return self.module(*args, **inputs)
|
|
149
|
+
|
|
150
|
+
async def acall(self, *args: Any, **inputs: Any) -> dspy.Prediction:
|
|
151
|
+
"""Run the module asynchronously, with no span for the program itself, as ``__call__`` does."""
|
|
152
|
+
with self._lm_context():
|
|
153
|
+
if runs_itself(self):
|
|
154
|
+
return await getattr(super(), "acall")(*args, **inputs) # dspy's Module.acall is untyped
|
|
155
|
+
return await self.module.acall(*args, **inputs)
|
|
156
|
+
|
|
157
|
+
def forward(self, *args: Any, **inputs: Any) -> dspy.Prediction:
|
|
158
|
+
"""Run the module on this program's LM. A subclass may override this to add behavior."""
|
|
159
|
+
with self._lm_context():
|
|
160
|
+
return self.module(*args, **inputs)
|
|
161
|
+
|
|
162
|
+
async def aforward(self, *args: Any, **inputs: Any) -> dspy.Prediction:
|
|
163
|
+
"""Run the module asynchronously on this program's LM, as ``forward`` does."""
|
|
164
|
+
with self._lm_context():
|
|
165
|
+
return await self.module.acall(*args, **inputs)
|
|
166
|
+
|
|
167
|
+
def __repr__(self) -> str:
|
|
168
|
+
return f"{type(self).__name__}({self.name!r}, lm={getattr(self.lm, 'model', self.lm)!r})"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _with_lm(signature: inspect.Signature) -> inspect.Signature:
|
|
172
|
+
"""``signature`` with a required keyword ``lm`` added before any ``**kwargs``."""
|
|
173
|
+
parameters = list(signature.parameters.values())
|
|
174
|
+
at = next((i for i, p in enumerate(parameters) if p.kind is inspect.Parameter.VAR_KEYWORD), len(parameters))
|
|
175
|
+
parameters.insert(at, inspect.Parameter("lm", inspect.Parameter.KEYWORD_ONLY, annotation="dspy.BaseLM"))
|
|
176
|
+
return signature.replace(parameters=parameters)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def runs_itself(program: Program) -> bool:
|
|
180
|
+
"""Whether running ``program`` means running its own code, not only its module.
|
|
181
|
+
|
|
182
|
+
A subclass that overrides ``forward`` or ``aforward`` adds behavior around its
|
|
183
|
+
module, so calling it runs that code as a traced module, and a client optimizes the
|
|
184
|
+
program itself. Any other program only runs its module, so its trace's root and
|
|
185
|
+
structure match the module run plain.
|
|
186
|
+
"""
|
|
187
|
+
cls = type(program)
|
|
188
|
+
return cls.forward is not Program.forward or cls.aforward is not Program.aforward
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def program(name: str) -> Callable[[type[dspy.Module]], type[Program]]:
|
|
192
|
+
"""Declare a program on a DSPy module class, where the class is defined.
|
|
193
|
+
|
|
194
|
+
``@cmpnd.program(name)`` is a shorter way to write the ``cmpnd.Program``
|
|
195
|
+
constructor. Above a ``dspy.Module`` class, it returns a ``cmpnd.Program``
|
|
196
|
+
subclass of the same name. That subclass takes the class's own constructor
|
|
197
|
+
arguments and a required keyword ``lm``, and builds the class as written as the
|
|
198
|
+
program's ``module``:
|
|
199
|
+
|
|
200
|
+
>>> @cmpnd.program("capital-qa")
|
|
201
|
+
... class CapitalQA(dspy.Module):
|
|
202
|
+
... def __init__(self):
|
|
203
|
+
... super().__init__()
|
|
204
|
+
... self.answer = dspy.Predict("question -> answer")
|
|
205
|
+
...
|
|
206
|
+
... def forward(self, question):
|
|
207
|
+
... return self.answer(question=question)
|
|
208
|
+
>>> qa = CapitalQA(lm=dspy.LM("openai/gpt-5-mini"))
|
|
209
|
+
>>> with cmpnd.Client() as client:
|
|
210
|
+
... client.run_program(qa, question="What is the capital of France?")
|
|
211
|
+
|
|
212
|
+
``qa`` is the same program as ``cmpnd.Program("capital-qa", module=<CapitalQA as
|
|
213
|
+
written>(), lm=...)``, and the class's attributes are on ``qa.module``.
|
|
214
|
+
|
|
215
|
+
A decorated class has one name. Use the ``cmpnd.Program`` constructor when one
|
|
216
|
+
class serves several programs, when you cannot edit the class, or when its
|
|
217
|
+
constructor already takes ``lm``. ``help(cmpnd.Program)`` gives the name rules
|
|
218
|
+
and how a program runs.
|
|
219
|
+
"""
|
|
220
|
+
programs.validate_program_name(name)
|
|
221
|
+
|
|
222
|
+
def decorate(module_class: Any) -> Any:
|
|
223
|
+
if not (isinstance(module_class, type) and issubclass(module_class, dspy.Module)):
|
|
224
|
+
raise TypeError("@cmpnd.program() decorates a dspy.Module class")
|
|
225
|
+
if issubclass(module_class, Program):
|
|
226
|
+
raise TypeError(f"{module_class.__name__} is already a program; subclass it instead of decorating it")
|
|
227
|
+
if "lm" in inspect.signature(module_class.__init__).parameters:
|
|
228
|
+
raise TypeError(
|
|
229
|
+
f"{module_class.__name__}.__init__ already takes lm, so it cannot be decorated. "
|
|
230
|
+
"Wrap an instance instead: cmpnd.Program(name, module=..., lm=...)"
|
|
231
|
+
)
|
|
232
|
+
try:
|
|
233
|
+
_DECLARED_SOURCE[module_class] = inspect.getsource(module_class)
|
|
234
|
+
except (OSError, TypeError):
|
|
235
|
+
pass # no source to read, as in a bare REPL; a deploy then fails as it would undecorated
|
|
236
|
+
|
|
237
|
+
def __init__(self: Any, *args: Any, lm: Any = None, **kwargs: Any) -> None:
|
|
238
|
+
# lm has a default only so that a missing LM gets the message below.
|
|
239
|
+
_check_lm(lm, f"{module_class.__name__}(..., lm=dspy.LM(...))")
|
|
240
|
+
Program.__init__(self, name, module=module_class(*args, **kwargs), lm=lm)
|
|
241
|
+
|
|
242
|
+
qualname = module_class.__qualname__
|
|
243
|
+
signature = _with_lm(inspect.signature(module_class.__init__))
|
|
244
|
+
__init__.__qualname__ = f"{qualname}.__init__"
|
|
245
|
+
__init__.__module__ = module_class.__module__
|
|
246
|
+
__init__.__doc__ = f"Build a {module_class.__name__} as the module of a program named {name!r}."
|
|
247
|
+
setattr(__init__, "__signature__", signature)
|
|
248
|
+
doc = f"A cmpnd.Program named {name!r}, which runs {module_class.__name__} as written."
|
|
249
|
+
if module_class.__doc__:
|
|
250
|
+
doc += "\n\n" + inspect.cleandoc(module_class.__doc__)
|
|
251
|
+
decorated = _ProgramMeta(
|
|
252
|
+
module_class.__name__,
|
|
253
|
+
(Program,),
|
|
254
|
+
{
|
|
255
|
+
"__init__": __init__,
|
|
256
|
+
"__module__": module_class.__module__,
|
|
257
|
+
"__qualname__": qualname,
|
|
258
|
+
"__doc__": doc,
|
|
259
|
+
"_cmpnd_module": module_class,
|
|
260
|
+
},
|
|
261
|
+
)
|
|
262
|
+
# The decorated class takes the class's name in its module, so pickle would find
|
|
263
|
+
# it, not the class as written, when it looks a module instance's class up by
|
|
264
|
+
# name. The class as written moves under the decorated one, where pickle finds it.
|
|
265
|
+
# The attribute's single leading underscore keeps it out of help().
|
|
266
|
+
module_class.__qualname__ = f"{qualname}._cmpnd_module"
|
|
267
|
+
return decorated
|
|
268
|
+
|
|
269
|
+
return decorate
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def declared_source(cls: type) -> str | None:
|
|
273
|
+
"""The source of a class ``@cmpnd.program`` decorated, read before it was renamed."""
|
|
274
|
+
return _DECLARED_SOURCE.get(cls)
|
|
@@ -462,6 +462,23 @@ def _lms_config(config: dict[str, Any], lms: Any) -> None:
|
|
|
462
462
|
if isinstance(lm, str):
|
|
463
463
|
declared[name] = {"model": lm}
|
|
464
464
|
continue
|
|
465
|
+
from .lm_configuration import SystemOne
|
|
466
|
+
|
|
467
|
+
if isinstance(lm, SystemOne):
|
|
468
|
+
# A System One model takes no sampling, and its declaration (which says it
|
|
469
|
+
# is one) travels in the bundle rather than on the wire.
|
|
470
|
+
declared[name] = {"model": lm.model}
|
|
471
|
+
continue
|
|
472
|
+
if getattr(lm, "supports_decision_requests", False):
|
|
473
|
+
# dspy.experimental.TypeSafe, or a client like it: it carries a key and a URL
|
|
474
|
+
# rather than the name of a sealed credential, so the platform has nothing to
|
|
475
|
+
# resolve. Duck-typed so this module never imports dspy at scope.
|
|
476
|
+
raise ValueError(
|
|
477
|
+
f"{subject} is a {type(lm).__name__} client, which names a key and a URL rather "
|
|
478
|
+
f"than a credential. Bind the model with cmpnd.SystemOne, for example "
|
|
479
|
+
f"lms={{{name!r}: cmpnd.SystemOne('typesafe/jev-latest')}}; a judge that builds "
|
|
480
|
+
f"its own {type(lm).__name__} runs against that binding when hosted"
|
|
481
|
+
)
|
|
465
482
|
model = _lm_model(lm, subject)
|
|
466
483
|
_refuse_credential_on(lm, model, subject)
|
|
467
484
|
entry: dict[str, Any] = {"model": model}
|