cmpnd 0.43.0__tar.gz → 0.44.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.0 → cmpnd-0.44.0}/PKG-INFO +2 -2
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/__init__.py +18 -1
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/client.py +48 -17
- cmpnd-0.44.0/cmpnd/named_programs.py +274 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/packaging.py +61 -1
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/programs.py +0 -43
- {cmpnd-0.43.0 → cmpnd-0.44.0}/docs/client.md +47 -12
- {cmpnd-0.43.0 → cmpnd-0.44.0}/pyproject.toml +6 -11
- cmpnd-0.44.0/tests/test_client_program_names.py +817 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/uv.lock +36 -23
- cmpnd-0.43.0/tests/test_client_program_names.py +0 -339
- {cmpnd-0.43.0 → cmpnd-0.44.0}/.gitignore +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/AGENTS.md +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/CLAUDE.md +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/README.md +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/_assessments.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/_authored_import.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/_deployments.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/_gepa_patch.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/_http.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/_runtime_tools.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/_traces.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/_valsets.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/authored.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/callback.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/__init__.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/commands/admin.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/commands/assessments.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/commands/deployments.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/commands/evals.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/commands/org.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/commands/programs.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/commands/signatures.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/commands/top.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/commands/traces.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/commands/valsets.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/shell.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/trace_render.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/tree_help.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/cli/verbs.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/configuration.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/context.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/dataset_sync.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/datasets.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/decorators.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/deployment.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/deployment_metadata.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/deployment_runs.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/execution.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/exporter.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/helpers.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/identity.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/imaging.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/ir/reflection.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/lm_configuration.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/mcp_servers.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/models.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/optimization.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/optimizers/gepa.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/optimizers/gepa_callback.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/optimizers/tracker.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/references.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/retry.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/cmpnd/traces.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/docs/2026-04-02-code-review.md +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/docs/cli.md +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/docs/custom-proposer.md +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/docs/reading-traces.md +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/docs/sdk-instrumentation.md +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/examples/local_ollama.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/ingest/README.md +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/ingest/cmpnd_ingest/bodies.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/ingest/cmpnd_ingest/program_names.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/ingest/cmpnd_ingest/protocols.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/ingest/pyproject.toml +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/scripts/capture_review_run.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/scripts/check-pip-install.sh +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/scripts/release-stress.sh +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/scripts/require_version_bump.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/scripts/seed_review_run.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/scripts/test-in-linux.sh +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/stubs/dspy/utils/dummies.pyi +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/__init__.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/_deployment_signature_helpers.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/_fake_clock.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/_resolve_refs.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/_traced_run.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/conftest.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/committee_task.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/helpers.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/target_capabilities.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_assessments_cli.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_composite_module.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_deploy_execute.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_deploy_pandas.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_deploy_programs.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_optimize_failures.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_parallel_export.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_project_attribution.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_traces_read.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_user_attribution.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/e2e/test_valsets_cli.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/integration/__init__.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/integration/authored_optimize/judge.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/integration/conftest.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/integration/hosted_optimize_probe.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/integration/test_logs_renders_traces.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/integration/test_unhappy_path.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/load/__init__.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/load/__main__.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/load/drain.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/load/ingest.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/load/rlm_shape.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/load/rlm_size_report.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/load/sweep.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/load/test_rlm_size_report.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/parity/__init__.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/parity/conftest.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/parity/test_health.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/parity/test_trace_streaming.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/parity/test_traces.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/span_reference_fixture.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/stress/__init__.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/stress/_provider.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/stress/_readiness.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/stress/_reliability.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/stress/_result.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/stress/mint_bedrock_token.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/stress/test_deploy_programs.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/stress/test_multi_client_stress.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/stress/test_optimize_client_committee.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/stress/test_optimize_loop.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/stress/test_optimize_stress.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/stress/test_provider_roles.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/stress/test_readiness.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/stress/test_real_provider_twins.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/stress/test_reliability.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/stress/test_reliability_classify.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/stress/test_rlm_wasm.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/stress/test_trace_search.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_adapter_reentrancy.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_assessments_api.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_authored.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_authored_bundle_collisions.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_authored_metric_contract.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_authored_named_models.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_authored_optimize.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_callback.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_admin.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_api_client.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_assessments.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_deployment_runs.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_deployments.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_evals.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_main.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_org.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_parity.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_programs.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_provider_credentials.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_schema_sample.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_shell.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_signatures.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_top.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_traces.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_traces_search_wire.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_tree_help.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cli/test_valsets.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_client.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_configuration.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_context.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_context_propagation.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_dataset_sync.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_datasets.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_decorators.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_deploy.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_deploy_program_id.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_deployment_metadata.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_deployment_runs.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_deployments_read.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_eval.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_eval_example_attribution.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_eval_start.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_eval_trace_grouping.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_execute.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_execute_run_identity.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_exporter.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_exporter_program_names.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_exporter_queue.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_exporter_real.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_gepa_output_normalization.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_identity.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_imaging.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_import_weight.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_ingest_conformance.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_integration.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_interpreter_callback.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_lm15_native_calls.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_lm_configuration.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_lm_usage_shapes.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_mcp_tools.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_models.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_multi_client_stress_readiness.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_optimize.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_optimize_lms.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_optimize_reflection_lm.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_packaging.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_packaging_mcp_tools.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_packaging_program_source.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_poll_until.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_program_identity.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_program_lineage_persist.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_programs_create.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_project_payload.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_real_provider_twins_readiness.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_reflector_properties.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_reflector_smoke.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_release_stress_script.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_repro_reactv2_bugs.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_retry.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_rlm_patch.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_run_attribution_paths.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_runtime_tools.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_schemas.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_signature_inputs.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_span_references.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_span_sent_once.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_trace_context_concurrency.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_trace_fact_wire_contract.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_trace_facts.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_trace_render.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_traces_read.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_tracked_gepa.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_truncation.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_valsets_api.py +0 -0
- {cmpnd-0.43.0 → cmpnd-0.44.0}/tests/test_xxh64.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cmpnd
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.44.0
|
|
4
4
|
Summary: DSPy observability and deployment SDK for cmpnd
|
|
5
5
|
Project-URL: Homepage, https://cmpnd.ai
|
|
6
6
|
Author-email: cmpnd <hello@cmpnd.ai>
|
|
@@ -20,7 +20,6 @@ Requires-Python: >=3.11
|
|
|
20
20
|
Requires-Dist: dspy>=3.1.3
|
|
21
21
|
Requires-Dist: httpx>=0.27.0
|
|
22
22
|
Requires-Dist: ipython>=8.20
|
|
23
|
-
Requires-Dist: openai<2
|
|
24
23
|
Requires-Dist: pydantic>=2.0
|
|
25
24
|
Requires-Dist: typing-extensions>=4.1.0
|
|
26
25
|
Requires-Dist: xxhash>=3.0.0
|
|
@@ -31,6 +30,7 @@ Requires-Dist: gepa; extra == 'dev'
|
|
|
31
30
|
Requires-Dist: hypothesis>=6.100; extra == 'dev'
|
|
32
31
|
Requires-Dist: jsonschema>=4.0; extra == 'dev'
|
|
33
32
|
Requires-Dist: litellm; extra == 'dev'
|
|
33
|
+
Requires-Dist: litellm<1.102; (python_version >= '3.15') and extra == 'dev'
|
|
34
34
|
Requires-Dist: numpy>=2.3; extra == 'dev'
|
|
35
35
|
Requires-Dist: pandas>=2.2; extra == 'dev'
|
|
36
36
|
Requires-Dist: pillow>=10.0; extra == 'dev'
|
|
@@ -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
|
|
@@ -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")
|
|
@@ -103,6 +107,7 @@ __all__ = [
|
|
|
103
107
|
"programs",
|
|
104
108
|
# A program declared by name
|
|
105
109
|
"Program",
|
|
110
|
+
"program",
|
|
106
111
|
"authored",
|
|
107
112
|
"ApiError",
|
|
108
113
|
# Types
|
|
@@ -253,3 +258,15 @@ def de_instrument() -> None:
|
|
|
253
258
|
def _mcp_tool(declaration): # pyright: ignore[reportUnusedFunction] -- generated bundles call this hook
|
|
254
259
|
"""Keep generated declarations importable outside a worker without granting access."""
|
|
255
260
|
return declaration
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def __getattr__(name: str): # noqa: ANN202 — PEP 562 module attribute hook
|
|
264
|
+
"""Load ``Program`` and ``program`` on first use, because their module imports dspy.
|
|
265
|
+
|
|
266
|
+
``import cmpnd`` stays free of dspy until a caller asks for one of them.
|
|
267
|
+
"""
|
|
268
|
+
if name in ("Program", "program"):
|
|
269
|
+
from . import named_programs
|
|
270
|
+
|
|
271
|
+
return getattr(named_programs, name)
|
|
272
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -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
|
#
|
|
@@ -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)
|
|
@@ -826,6 +826,54 @@ def declared_module_tools(module: dspy.Module) -> dict[str, typing.Any]:
|
|
|
826
826
|
return collected
|
|
827
827
|
|
|
828
828
|
|
|
829
|
+
def _resolve_decorator(target: ast.expr, scope: Mapping[str, typing.Any]) -> typing.Any:
|
|
830
|
+
"""The object a decorator's name or dotted name refers to in ``scope``, or None."""
|
|
831
|
+
if isinstance(target, ast.Name):
|
|
832
|
+
return scope.get(target.id, getattr(builtins, target.id, None))
|
|
833
|
+
if isinstance(target, ast.Attribute):
|
|
834
|
+
owner = _resolve_decorator(target.value, scope)
|
|
835
|
+
return getattr(owner, target.attr, None) if owner is not None else None
|
|
836
|
+
return None
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
def _without_program_decorator(class_source: str, scope: Mapping[str, typing.Any], *, decorated: bool = False) -> str:
|
|
840
|
+
"""Drop the ``@cmpnd.program(...)`` lines above a class's source.
|
|
841
|
+
|
|
842
|
+
A worker runs deployed source without ``cmpnd.program``, and the deployment
|
|
843
|
+
already names its program, so the decorator has nothing to do there. A
|
|
844
|
+
decorator is dropped when its callee, resolved in ``scope`` (the class's
|
|
845
|
+
module globals), is ``cmpnd.program`` under any name, such as an alias.
|
|
846
|
+
|
|
847
|
+
A class defined inside a function can bind the decorator locally, where module
|
|
848
|
+
globals cannot see it. So for a class ``@cmpnd.program`` is known to have
|
|
849
|
+
decorated (``decorated``), a decorator that does not resolve is also dropped
|
|
850
|
+
when it is spelled ``program`` or ``<anything>.program``. Other decorators stay.
|
|
851
|
+
Source that does not parse is returned unchanged.
|
|
852
|
+
"""
|
|
853
|
+
try:
|
|
854
|
+
tree = ast.parse(class_source)
|
|
855
|
+
except SyntaxError:
|
|
856
|
+
return class_source
|
|
857
|
+
node = tree.body[0] if tree.body else None
|
|
858
|
+
if not isinstance(node, ast.ClassDef):
|
|
859
|
+
return class_source
|
|
860
|
+
from . import named_programs
|
|
861
|
+
|
|
862
|
+
drop: set[int] = set()
|
|
863
|
+
for decorator in node.decorator_list:
|
|
864
|
+
target = decorator.func if isinstance(decorator, ast.Call) else decorator
|
|
865
|
+
resolved = _resolve_decorator(target, scope)
|
|
866
|
+
spelled = (isinstance(target, ast.Attribute) and target.attr == "program") or (
|
|
867
|
+
isinstance(target, ast.Name) and target.id == "program"
|
|
868
|
+
)
|
|
869
|
+
if resolved is named_programs.program or (decorated and resolved is None and spelled):
|
|
870
|
+
drop.update(range(decorator.lineno, (decorator.end_lineno or decorator.lineno) + 1))
|
|
871
|
+
if not drop:
|
|
872
|
+
return class_source
|
|
873
|
+
kept = [line for number, line in enumerate(class_source.splitlines(), start=1) if number not in drop]
|
|
874
|
+
return "\n".join(kept)
|
|
875
|
+
|
|
876
|
+
|
|
829
877
|
def generate_program_source(
|
|
830
878
|
module: dspy.Module,
|
|
831
879
|
mcp_tools: Mapping[tuple[str, str], typing.Any] | None = None,
|
|
@@ -865,6 +913,13 @@ def generate_program_source(
|
|
|
865
913
|
lines.append(f'lm = dspy.LM("{model}")')
|
|
866
914
|
lines.append("dspy.configure(lm=lm)")
|
|
867
915
|
|
|
916
|
+
from . import named_programs
|
|
917
|
+
|
|
918
|
+
if isinstance(module, named_programs.Program):
|
|
919
|
+
raise ValueError(
|
|
920
|
+
f"{type(module).__name__} is a cmpnd.Program, and deploying a cmpnd.Program is not supported "
|
|
921
|
+
"yet. Deploy its module instead: cmpnd.deploy(program.module)."
|
|
922
|
+
)
|
|
868
923
|
cls = type(module)
|
|
869
924
|
is_user_module = cls.__module__ == "__main__" or not cls.__module__.startswith("dspy")
|
|
870
925
|
|
|
@@ -920,7 +975,12 @@ def generate_program_source(
|
|
|
920
975
|
|
|
921
976
|
if is_user_module:
|
|
922
977
|
try:
|
|
923
|
-
|
|
978
|
+
scope = vars(sys.modules[cls.__module__]) if cls.__module__ in sys.modules else {}
|
|
979
|
+
# A class @cmpnd.program decorated was renamed then, so its source was read
|
|
980
|
+
# first. It still carries the decorator line, which the worker cannot run.
|
|
981
|
+
declared = named_programs.declared_source(cls)
|
|
982
|
+
source = textwrap.dedent(declared if declared is not None else inspect.getsource(cls)).rstrip()
|
|
983
|
+
lines.append(_without_program_decorator(source, scope, decorated=declared is not None))
|
|
924
984
|
except (OSError, TypeError):
|
|
925
985
|
pass
|
|
926
986
|
|
|
@@ -108,46 +108,3 @@ def validate_program_name(name: object) -> str:
|
|
|
108
108
|
"letters, digits, '.', '_' or '-', at most 64 characters, and does not start with p_."
|
|
109
109
|
)
|
|
110
110
|
return name
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
class Program:
|
|
114
|
-
"""A DSPy module, the LM it runs on, and the name of the program it is.
|
|
115
|
-
|
|
116
|
-
The name is the program's handle: traces from a ``cmpnd.Client`` run of this
|
|
117
|
-
program go to the program with this name in the caller's organization, which the
|
|
118
|
-
platform creates on first use. Programs in one organization that share a name
|
|
119
|
-
share a history.
|
|
120
|
-
|
|
121
|
-
Declaring a program makes no network call. The SDK asks the platform for the
|
|
122
|
-
program behind the name the first time a client sends one of its traces.
|
|
123
|
-
|
|
124
|
-
>>> qa = cmpnd.Program("capital-qa", module=dspy.Predict("question -> answer"), lm=dspy.LM("openai/gpt-5-mini"))
|
|
125
|
-
>>> with cmpnd.Client() as client:
|
|
126
|
-
... client.run_program(qa, question="What is the capital of France?")
|
|
127
|
-
"""
|
|
128
|
-
|
|
129
|
-
def __init__(self, name: str, *, module: Any, lm: Any) -> None:
|
|
130
|
-
import dspy
|
|
131
|
-
from dspy.clients import base_lm # pyright: ignore[reportMissingTypeStubs] # dspy re-exports BaseLM untyped
|
|
132
|
-
|
|
133
|
-
self.name = validate_program_name(name)
|
|
134
|
-
if not isinstance(module, dspy.Module):
|
|
135
|
-
raise TypeError(f"A Program needs a DSPy module, not {type(module).__name__}")
|
|
136
|
-
if not isinstance(lm, base_lm.BaseLM):
|
|
137
|
-
raise TypeError(f"A Program needs an LM such as dspy.LM(...), not {type(lm).__name__}")
|
|
138
|
-
self.module = module
|
|
139
|
-
self.lm = lm
|
|
140
|
-
|
|
141
|
-
def __call__(self, **inputs: Any) -> Any:
|
|
142
|
-
"""Run the module on this program's LM, as plain DSPy.
|
|
143
|
-
|
|
144
|
-
A direct call records nothing under the program's name. Run the program
|
|
145
|
-
through ``cmpnd.Client.run_program`` to trace it under its name.
|
|
146
|
-
"""
|
|
147
|
-
import dspy
|
|
148
|
-
|
|
149
|
-
with dspy.context(lm=self.lm):
|
|
150
|
-
return self.module(**inputs)
|
|
151
|
-
|
|
152
|
-
def __repr__(self) -> str:
|
|
153
|
-
return f"Program({self.name!r}, lm={getattr(self.lm, 'model', self.lm)!r})"
|