cmpnd 0.47.0__tar.gz → 0.49.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.
Files changed (379) hide show
  1. {cmpnd-0.47.0 → cmpnd-0.49.0}/PKG-INFO +1 -1
  2. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/callback.py +5 -5
  3. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/commands/deployments.py +16 -0
  4. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/verbs.py +1 -0
  5. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/deployment_runs.py +40 -4
  6. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/helpers.py +52 -0
  7. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/optimization.py +6 -1
  8. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/packaging.py +7 -23
  9. {cmpnd-0.47.0 → cmpnd-0.49.0}/docs/cli.md +10 -1
  10. {cmpnd-0.47.0 → cmpnd-0.49.0}/docs/sdk-instrumentation.md +5 -1
  11. {cmpnd-0.47.0 → cmpnd-0.49.0}/pyproject.toml +1 -1
  12. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/stress/_provider.py +2 -2
  13. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_callback.py +45 -0
  14. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_deployment_runs.py +45 -0
  15. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_deploy.py +2 -2
  16. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_optimize_reflection_lm.py +12 -0
  17. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_packaging_program_source.py +35 -0
  18. {cmpnd-0.47.0 → cmpnd-0.49.0}/uv.lock +1 -1
  19. {cmpnd-0.47.0 → cmpnd-0.49.0}/.gitignore +0 -0
  20. {cmpnd-0.47.0 → cmpnd-0.49.0}/AGENTS.md +0 -0
  21. {cmpnd-0.47.0 → cmpnd-0.49.0}/CLAUDE.md +0 -0
  22. {cmpnd-0.47.0 → cmpnd-0.49.0}/README.md +0 -0
  23. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/__init__.py +0 -0
  24. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/_assessments.py +0 -0
  25. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/_authored_import.py +0 -0
  26. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/_deployments.py +0 -0
  27. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/_exporter_pool.py +0 -0
  28. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/_gepa_patch.py +0 -0
  29. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/_http.py +0 -0
  30. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/_program_patch.py +0 -0
  31. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/_rlm_patch.py +0 -0
  32. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/_runtime_tools.py +0 -0
  33. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/_timespec.py +0 -0
  34. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/_traces.py +0 -0
  35. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/_valsets.py +0 -0
  36. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/authored.py +0 -0
  37. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/__init__.py +0 -0
  38. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/api_client.py +0 -0
  39. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/commands/__init__.py +0 -0
  40. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/commands/admin.py +0 -0
  41. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/commands/assessments.py +0 -0
  42. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/commands/auth.py +0 -0
  43. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/commands/datasets.py +0 -0
  44. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/commands/evals.py +0 -0
  45. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/commands/login.py +0 -0
  46. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/commands/optimizations.py +0 -0
  47. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/commands/org.py +0 -0
  48. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/commands/programs.py +0 -0
  49. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
  50. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/commands/signatures.py +0 -0
  51. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/commands/tool_servers.py +0 -0
  52. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/commands/top.py +0 -0
  53. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/commands/traces.py +0 -0
  54. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/commands/valsets.py +0 -0
  55. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/credentials.py +0 -0
  56. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/deprecation.py +0 -0
  57. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/pagination.py +0 -0
  58. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/shell.py +0 -0
  59. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/trace_render.py +0 -0
  60. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/cli/tree_help.py +0 -0
  61. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/client.py +0 -0
  62. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/configuration.py +0 -0
  63. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/context.py +0 -0
  64. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/dataset_sync.py +0 -0
  65. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/datasets.py +0 -0
  66. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/decorators.py +0 -0
  67. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/deployment.py +0 -0
  68. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/deployment_metadata.py +0 -0
  69. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/eval_handler.py +0 -0
  70. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/execution.py +0 -0
  71. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/exporter.py +0 -0
  72. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/exporter_http.py +0 -0
  73. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/identity.py +0 -0
  74. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/imaging.py +0 -0
  75. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/ir/__init__.py +0 -0
  76. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/ir/decode.py +0 -0
  77. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/ir/encode.py +0 -0
  78. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/ir/hash.py +0 -0
  79. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/ir/program.py +0 -0
  80. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/ir/reflection.py +0 -0
  81. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/ir/types.py +0 -0
  82. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/ir/xxh64.py +0 -0
  83. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/lm_configuration.py +0 -0
  84. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/mcp_servers.py +0 -0
  85. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/models.py +0 -0
  86. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/named_programs.py +0 -0
  87. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/optimizers/__init__.py +0 -0
  88. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/optimizers/gepa.py +0 -0
  89. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/optimizers/gepa_callback.py +0 -0
  90. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/optimizers/resume.py +0 -0
  91. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/optimizers/tracker.py +0 -0
  92. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/programs.py +0 -0
  93. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/references.py +0 -0
  94. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/retry.py +0 -0
  95. {cmpnd-0.47.0 → cmpnd-0.49.0}/cmpnd/traces.py +0 -0
  96. {cmpnd-0.47.0 → cmpnd-0.49.0}/docs/2026-04-02-code-review.md +0 -0
  97. {cmpnd-0.47.0 → cmpnd-0.49.0}/docs/client.md +0 -0
  98. {cmpnd-0.47.0 → cmpnd-0.49.0}/docs/custom-proposer.md +0 -0
  99. {cmpnd-0.47.0 → cmpnd-0.49.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
  100. {cmpnd-0.47.0 → cmpnd-0.49.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
  101. {cmpnd-0.47.0 → cmpnd-0.49.0}/docs/reading-traces.md +0 -0
  102. {cmpnd-0.47.0 → cmpnd-0.49.0}/examples/generalizing_proposer.py +0 -0
  103. {cmpnd-0.47.0 → cmpnd-0.49.0}/examples/local_ollama.py +0 -0
  104. {cmpnd-0.47.0 → cmpnd-0.49.0}/ingest/LINEAGE.md +0 -0
  105. {cmpnd-0.47.0 → cmpnd-0.49.0}/ingest/README.md +0 -0
  106. {cmpnd-0.47.0 → cmpnd-0.49.0}/ingest/cmpnd_ingest/__init__.py +0 -0
  107. {cmpnd-0.47.0 → cmpnd-0.49.0}/ingest/cmpnd_ingest/bodies.py +0 -0
  108. {cmpnd-0.47.0 → cmpnd-0.49.0}/ingest/cmpnd_ingest/client_floor.py +0 -0
  109. {cmpnd-0.47.0 → cmpnd-0.49.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
  110. {cmpnd-0.47.0 → cmpnd-0.49.0}/ingest/cmpnd_ingest/program_names.py +0 -0
  111. {cmpnd-0.47.0 → cmpnd-0.49.0}/ingest/cmpnd_ingest/protocols.py +0 -0
  112. {cmpnd-0.47.0 → cmpnd-0.49.0}/ingest/cmpnd_ingest/py.typed +0 -0
  113. {cmpnd-0.47.0 → cmpnd-0.49.0}/ingest/cmpnd_ingest/request_log.py +0 -0
  114. {cmpnd-0.47.0 → cmpnd-0.49.0}/ingest/cmpnd_ingest/retry.py +0 -0
  115. {cmpnd-0.47.0 → cmpnd-0.49.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
  116. {cmpnd-0.47.0 → cmpnd-0.49.0}/ingest/pyproject.toml +0 -0
  117. {cmpnd-0.47.0 → cmpnd-0.49.0}/scripts/audit-sdist.sh +0 -0
  118. {cmpnd-0.47.0 → cmpnd-0.49.0}/scripts/capture_review_run.py +0 -0
  119. {cmpnd-0.47.0 → cmpnd-0.49.0}/scripts/check-pip-install.sh +0 -0
  120. {cmpnd-0.47.0 → cmpnd-0.49.0}/scripts/check_release_artifacts.py +0 -0
  121. {cmpnd-0.47.0 → cmpnd-0.49.0}/scripts/fixtures/review_run.json.gz +0 -0
  122. {cmpnd-0.47.0 → cmpnd-0.49.0}/scripts/gepa_resume_bug.py +0 -0
  123. {cmpnd-0.47.0 → cmpnd-0.49.0}/scripts/release-stress.sh +0 -0
  124. {cmpnd-0.47.0 → cmpnd-0.49.0}/scripts/require_version_bump.py +0 -0
  125. {cmpnd-0.47.0 → cmpnd-0.49.0}/scripts/seed_review_run.py +0 -0
  126. {cmpnd-0.47.0 → cmpnd-0.49.0}/scripts/test-in-linux.sh +0 -0
  127. {cmpnd-0.47.0 → cmpnd-0.49.0}/stubs/dspy/__init__.pyi +0 -0
  128. {cmpnd-0.47.0 → cmpnd-0.49.0}/stubs/dspy/primitives/__init__.pyi +0 -0
  129. {cmpnd-0.47.0 → cmpnd-0.49.0}/stubs/dspy/primitives/prediction.pyi +0 -0
  130. {cmpnd-0.47.0 → cmpnd-0.49.0}/stubs/dspy/utils/__init__.pyi +0 -0
  131. {cmpnd-0.47.0 → cmpnd-0.49.0}/stubs/dspy/utils/callback.pyi +0 -0
  132. {cmpnd-0.47.0 → cmpnd-0.49.0}/stubs/dspy/utils/dummies.pyi +0 -0
  133. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/__init__.py +0 -0
  134. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/_deployment_signature_helpers.py +0 -0
  135. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/_exporter_helpers.py +0 -0
  136. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/_fake_clock.py +0 -0
  137. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/_identity_helpers.py +0 -0
  138. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/_resolve_refs.py +0 -0
  139. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/_traced_run.py +0 -0
  140. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/conftest.py +0 -0
  141. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/__init__.py +0 -0
  142. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/committee_task.py +0 -0
  143. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/conftest.py +0 -0
  144. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
  145. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
  146. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/fake_lm.py +0 -0
  147. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/helpers.py +0 -0
  148. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/target_capabilities.py +0 -0
  149. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_assessments_cli.py +0 -0
  150. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_capture_prompts.py +0 -0
  151. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_composite_module.py +0 -0
  152. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
  153. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_deploy_execute.py +0 -0
  154. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_deploy_pandas.py +0 -0
  155. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_deploy_programs.py +0 -0
  156. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_edge_cases.py +0 -0
  157. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_example_hash.py +0 -0
  158. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_gepa_optimization.py +0 -0
  159. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_interpreter_spans.py +0 -0
  160. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_lm_accuracy.py +0 -0
  161. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_module_tracing.py +0 -0
  162. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_optimization_lineage.py +0 -0
  163. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
  164. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_optimize_failures.py +0 -0
  165. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_optimize_loop.py +0 -0
  166. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_optimize_review_committee.py +0 -0
  167. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
  168. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_parallel_export.py +0 -0
  169. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_project_attribution.py +0 -0
  170. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_react_tool_spans.py +0 -0
  171. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_save_load.py +0 -0
  172. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_trace_structure.py +0 -0
  173. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_traces_read.py +0 -0
  174. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_user_attribution.py +0 -0
  175. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/e2e/test_valsets_cli.py +0 -0
  176. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/identity_vectors/01_predict_qa.json +0 -0
  177. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
  178. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
  179. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
  180. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
  181. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
  182. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
  183. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/identity_vectors/08_opaque_module.json +0 -0
  184. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
  185. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/identity_vectors/10_react.json +0 -0
  186. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/identity_vectors/11_refine.json +0 -0
  187. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/identity_vectors/12_retrieve.json +0 -0
  188. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/identity_vectors/13_knn.json +0 -0
  189. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/identity_vectors/14_parallel.json +0 -0
  190. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/identity_vectors/README.md +0 -0
  191. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/ingest_conformance.py +0 -0
  192. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/integration/__init__.py +0 -0
  193. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/integration/authored_optimize/judge.py +0 -0
  194. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
  195. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/integration/conftest.py +0 -0
  196. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
  197. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/integration/hosted_optimize_probe.py +0 -0
  198. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/integration/optimization_run_probe.py +0 -0
  199. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
  200. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/integration/test_logs_renders_traces.py +0 -0
  201. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/integration/test_optimization_run.py +0 -0
  202. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/integration/test_optimizing_progress.py +0 -0
  203. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/integration/test_unhappy_path.py +0 -0
  204. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/load/__init__.py +0 -0
  205. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/load/__main__.py +0 -0
  206. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/load/drain.py +0 -0
  207. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/load/ingest.py +0 -0
  208. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/load/latency_transport.py +0 -0
  209. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/load/rlm_shape.py +0 -0
  210. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/load/rlm_size_report.py +0 -0
  211. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/load/sweep.py +0 -0
  212. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/load/test_rlm_shape.py +0 -0
  213. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/load/test_rlm_size_report.py +0 -0
  214. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/parity/__init__.py +0 -0
  215. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/parity/conftest.py +0 -0
  216. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/parity/seed_parity.py +0 -0
  217. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/parity/test_auth.py +0 -0
  218. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/parity/test_datasets.py +0 -0
  219. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/parity/test_deployments.py +0 -0
  220. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/parity/test_evals.py +0 -0
  221. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/parity/test_health.py +0 -0
  222. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/parity/test_modules.py +0 -0
  223. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/parity/test_optimizations.py +0 -0
  224. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/parity/test_programs.py +0 -0
  225. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/parity/test_signatures.py +0 -0
  226. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/parity/test_trace_streaming.py +0 -0
  227. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/parity/test_traces.py +0 -0
  228. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/span_reference_fixture.py +0 -0
  229. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/stress/__init__.py +0 -0
  230. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/stress/_readiness.py +0 -0
  231. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/stress/_reliability.py +0 -0
  232. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/stress/_result.py +0 -0
  233. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/stress/mint_bedrock_token.py +0 -0
  234. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/stress/test_deploy_programs.py +0 -0
  235. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/stress/test_multi_client_stress.py +0 -0
  236. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/stress/test_optimize_client_committee.py +0 -0
  237. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/stress/test_optimize_loop.py +0 -0
  238. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
  239. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/stress/test_optimize_stress.py +0 -0
  240. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/stress/test_provider_roles.py +0 -0
  241. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/stress/test_readiness.py +0 -0
  242. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/stress/test_real_provider_twins.py +0 -0
  243. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/stress/test_reliability.py +0 -0
  244. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/stress/test_reliability_classify.py +0 -0
  245. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/stress/test_rlm_wasm.py +0 -0
  246. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/stress/test_trace_search.py +0 -0
  247. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_adapter_reentrancy.py +0 -0
  248. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_assessments_api.py +0 -0
  249. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_authored.py +0 -0
  250. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_authored_bundle_collisions.py +0 -0
  251. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_authored_metric_contract.py +0 -0
  252. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_authored_named_models.py +0 -0
  253. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_authored_optimize.py +0 -0
  254. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_candidate_lineage.py +0 -0
  255. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/__init__.py +0 -0
  256. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/conftest.py +0 -0
  257. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_admin.py +0 -0
  258. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_api_client.py +0 -0
  259. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_assessments.py +0 -0
  260. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_auth.py +0 -0
  261. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_credentials.py +0 -0
  262. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_datasets.py +0 -0
  263. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_deployments.py +0 -0
  264. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_evals.py +0 -0
  265. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_login.py +0 -0
  266. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_main.py +0 -0
  267. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_optimizations.py +0 -0
  268. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_org.py +0 -0
  269. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_pagination.py +0 -0
  270. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_parity.py +0 -0
  271. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_programs.py +0 -0
  272. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_provider_credentials.py +0 -0
  273. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_schema_sample.py +0 -0
  274. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_shell.py +0 -0
  275. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_signatures.py +0 -0
  276. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_tool_servers.py +0 -0
  277. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_top.py +0 -0
  278. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_traces.py +0 -0
  279. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_traces_search_wire.py +0 -0
  280. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_tree_help.py +0 -0
  281. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cli/test_valsets.py +0 -0
  282. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_client.py +0 -0
  283. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_client_floor.py +0 -0
  284. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_client_program_names.py +0 -0
  285. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_clustering/__init__.py +0 -0
  286. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_clustering/conftest.py +0 -0
  287. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_clustering/test_edge_cases.py +0 -0
  288. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_clustering/test_hash_consistency.py +0 -0
  289. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_clustering/test_type_conversion.py +0 -0
  290. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_cmp551_grouping_identity.py +0 -0
  291. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_configuration.py +0 -0
  292. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_context.py +0 -0
  293. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_context_propagation.py +0 -0
  294. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_dataset_polling.py +0 -0
  295. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_dataset_sync.py +0 -0
  296. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_datasets.py +0 -0
  297. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_decision_client_spans.py +0 -0
  298. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_decorators.py +0 -0
  299. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_deploy_mcp_tool_references.py +0 -0
  300. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_deploy_program_id.py +0 -0
  301. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_deployment_metadata.py +0 -0
  302. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_deployment_runs.py +0 -0
  303. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_deployments_read.py +0 -0
  304. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_eval.py +0 -0
  305. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_eval_example_attribution.py +0 -0
  306. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_eval_start.py +0 -0
  307. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_eval_trace_grouping.py +0 -0
  308. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_exception_paths.py +0 -0
  309. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_execute.py +0 -0
  310. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_execute_run_identity.py +0 -0
  311. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_exporter.py +0 -0
  312. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_exporter_program_names.py +0 -0
  313. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_exporter_queue.py +0 -0
  314. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_exporter_real.py +0 -0
  315. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_fake_lm.py +0 -0
  316. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_gepa_callback.py +0 -0
  317. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_gepa_integration.py +0 -0
  318. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_gepa_output_normalization.py +0 -0
  319. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_gepa_resume.py +0 -0
  320. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_identity.py +0 -0
  321. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_imaging.py +0 -0
  322. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_import_weight.py +0 -0
  323. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_ingest_conformance.py +0 -0
  324. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_ingest_second_producer.py +0 -0
  325. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_integration.py +0 -0
  326. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_interpreter_callback.py +0 -0
  327. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_ir_encoder_properties.py +0 -0
  328. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_ir_golden_vectors.py +0 -0
  329. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_ir_roundtrip.py +0 -0
  330. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_lm15_native_calls.py +0 -0
  331. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_lm_configuration.py +0 -0
  332. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_lm_usage_shapes.py +0 -0
  333. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_mcp_tools.py +0 -0
  334. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_models.py +0 -0
  335. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_multi_client_stress_readiness.py +0 -0
  336. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_optimization_progress.py +0 -0
  337. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_optimization_start.py +0 -0
  338. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_optimize.py +0 -0
  339. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_optimize_budget.py +0 -0
  340. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_optimize_lift.py +0 -0
  341. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_optimize_lms.py +0 -0
  342. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_optimize_proposer.py +0 -0
  343. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_optimize_terminal_failure.py +0 -0
  344. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_packaging.py +0 -0
  345. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_packaging_callable_source.py +0 -0
  346. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_packaging_mcp_tools.py +0 -0
  347. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_poll_until.py +0 -0
  348. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_program_identity.py +0 -0
  349. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_program_lineage_persist.py +0 -0
  350. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_programs_create.py +0 -0
  351. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_project_payload.py +0 -0
  352. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_real_provider_twins_readiness.py +0 -0
  353. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_reflector_properties.py +0 -0
  354. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_reflector_smoke.py +0 -0
  355. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_release_stress_script.py +0 -0
  356. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_repro_reactv2_bugs.py +0 -0
  357. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_require_version_bump.py +0 -0
  358. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_retry.py +0 -0
  359. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_reversible_failures.py +0 -0
  360. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_rlm_patch.py +0 -0
  361. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_run_attribution_paths.py +0 -0
  362. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_runtime_tools.py +0 -0
  363. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_schemas.py +0 -0
  364. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_seed_review_run.py +0 -0
  365. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_signature_inputs.py +0 -0
  366. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_span_references.py +0 -0
  367. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_span_sent_once.py +0 -0
  368. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_system_one.py +0 -0
  369. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_trace_context_concurrency.py +0 -0
  370. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_trace_fact_wire_contract.py +0 -0
  371. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_trace_facts.py +0 -0
  372. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_trace_render.py +0 -0
  373. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_traces_read.py +0 -0
  374. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_tracked_gepa.py +0 -0
  375. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_tracker_enrich.py +0 -0
  376. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_truncation.py +0 -0
  377. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_upload_auth_headers.py +0 -0
  378. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_valsets_api.py +0 -0
  379. {cmpnd-0.47.0 → cmpnd-0.49.0}/tests/test_xxh64.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cmpnd
3
- Version: 0.47.0
3
+ Version: 0.49.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>
@@ -1372,10 +1372,10 @@ class _LMMixin(_CallbackBase):
1372
1372
  if self._active_optimization_run_id is not None or context.get_current_optimization_run_id() is not None:
1373
1373
  span.attributes["optimization.role"] = "rollout"
1374
1374
 
1375
- # Capture LM kwargs (exclude secrets)
1376
- lm_kwargs = {
1377
- k: v for k, v in getattr(instance, "kwargs", {}).items() if k not in {"api_key", "api_base", "api_secret"}
1378
- }
1375
+ # The usage fields read one named value each out of the raw kwargs. The
1376
+ # span attributes copy only what helpers.SAFE_LM_KWARGS names, because
1377
+ # the kwargs also hold provider credentials.
1378
+ lm_kwargs = getattr(instance, "kwargs", None) or {}
1379
1379
  span.lm_usage = models.LMUsageObservationV1(
1380
1380
  sdk_call_id=call_id,
1381
1381
  model_requested=span.model_name,
@@ -1387,7 +1387,7 @@ class _LMMixin(_CallbackBase):
1387
1387
  {
1388
1388
  "lm.model_type": getattr(instance, "model_type", "unknown"),
1389
1389
  "lm.cache": str(getattr(instance, "cache", False)),
1390
- **{f"lm.{k}": str(v) for k, v in lm_kwargs.items()},
1390
+ **{f"lm.{k}": str(v) for k, v in helpers.safe_lm_kwargs(lm_kwargs).items()},
1391
1391
  }
1392
1392
  )
1393
1393
 
@@ -73,6 +73,12 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
73
73
  if name == "wait":
74
74
  command.add_argument("--timeout", type=float, default=None, help=TIMEOUT_HELP)
75
75
  command.set_defaults(func=handler)
76
+ cancel = run_sub.add_parser(
77
+ "cancel",
78
+ help="Cancel a queued or running deployment run. It settles as failed with error_code cancelled",
79
+ )
80
+ cancel.add_argument("id", help="Deployment run ID")
81
+ cancel.set_defaults(func=handle_runs_cancel)
76
82
 
77
83
  openapi = sub.add_parser(
78
84
  "openapi",
@@ -251,6 +257,16 @@ def handle_runs_wait(client: api_client.ApiClient, args: Any) -> Any:
251
257
  return settled
252
258
 
253
259
 
260
+ def handle_runs_cancel(client: api_client.ApiClient, args: Any) -> Any:
261
+ """Settle an open run as cancelled and return its header.
262
+
263
+ Serve stops dispatching and collecting the run at once. A run already
264
+ executing on a worker runs on there until its deadline and may spend model
265
+ calls, but its answer is discarded.
266
+ """
267
+ return client.post(f"/api/v1/deployments/runs/{args.id}/cancel")
268
+
269
+
254
270
  def handle_openapi(client: api_client.ApiClient, args: Any) -> None:
255
271
  """Write the deployment's OpenAPI document to stdout.
256
272
 
@@ -152,6 +152,7 @@ DOMAIN_VERBS: dict[str, str] = {
152
152
  "revoke-invite": "withdraws the open owner invites to an address; bare revoke would read as revoking the org",
153
153
  "run": "submits a deployment execution and optionally waits for its retained answer",
154
154
  "wait": "polls one accepted run until it settles or the local budget expires",
155
+ "cancel": "settles an open run as cancelled and keeps its record; delete would read as removing the run",
155
156
  "follow": "tails a record until it settles — a list over time rather than a snapshot",
156
157
  "select": "marks one candidate as the program's chosen version",
157
158
  "refresh": "re-reads a record from the system of record it mirrors; nothing the caller supplies is written",
@@ -100,11 +100,28 @@ class RunSubmissionUncertain(RuntimeError):
100
100
  _set_run_id(self, run_id)
101
101
 
102
102
 
103
+ def _cancel_command(run_id: str) -> str:
104
+ return f"cmpnd deployments runs cancel {run_id}"
105
+
106
+
103
107
  class RunWaitTimeout(TimeoutError):
104
- """The local budget ran out; the accepted run continues."""
108
+ """The local budget ran out; the accepted run continues.
105
109
 
106
- def __init__(self, run_id: str):
107
- super().__init__(f"Deployment run {run_id} has not settled. Resume with `{_wait_command(run_id)}`.")
110
+ ``status`` and ``status_reason`` are the run's last header read, when one
111
+ arrived: why the run is still open, such as a queued run waiting for a
112
+ credential or a worker.
113
+ """
114
+
115
+ def __init__(self, run_id: str, *, status: str | None = None, status_reason: str | None = None):
116
+ state = ""
117
+ if status:
118
+ state = f" ({status}: {status_reason})" if status_reason else f" ({status})"
119
+ super().__init__(
120
+ f"Deployment run {run_id} has not settled{state}. "
121
+ f"Resume with `{_wait_command(run_id)}`, or stop it with `{_cancel_command(run_id)}`."
122
+ )
123
+ self.status = status
124
+ self.status_reason = status_reason
108
125
  _set_run_id(self, run_id)
109
126
 
110
127
 
@@ -325,6 +342,9 @@ class _RunClient:
325
342
  self.api_key = api_key
326
343
  self.client: httpx.Client | None = None
327
344
  self.retry_after = 0.0
345
+ # The last run header a wait read, so a wait that runs out can say why
346
+ # the run is still open.
347
+ self.last_header: dict[str, Any] | None = None
328
348
 
329
349
  def __enter__(self) -> _RunClient:
330
350
  self.client = _http.new_client(headers={"X-API-Key": self.api_key})
@@ -444,8 +464,23 @@ class _RunClient:
444
464
  """Follow an accepted run until its retained answer is read or the budget ends.
445
465
 
446
466
  Reads only. A read refused with 401, 403 or 404 raises at once; timeouts,
447
- 408, 429 and server errors are retried with backoff and jitter.
467
+ 408, 429 and server errors are retried with backoff and jitter. When the
468
+ budget runs out, the timeout names the run's last status and reason.
448
469
  """
470
+ self.last_header = initial
471
+ try:
472
+ return self._follow(run_id, deadline, initial)
473
+ except RunWaitTimeout:
474
+ header = self.last_header
475
+ if header is None:
476
+ raise
477
+ reason = header.get("status_reason")
478
+ raise RunWaitTimeout(
479
+ run_id, status=header.get("status"), status_reason=reason if isinstance(reason, str) else None
480
+ ) from None
481
+
482
+ def _follow(self, run_id: str, deadline: float | None, initial: dict[str, Any] | None) -> dict[str, Any]:
483
+ """The wait loop. It records each header it reads in ``last_header``."""
449
484
  header = initial
450
485
  need_status = header is None
451
486
  delay = _FIRST_POLL_DELAY
@@ -455,6 +490,7 @@ class _RunClient:
455
490
  response = self._poll(status_path, deadline, run_id)
456
491
  if response is not None:
457
492
  header = _validate_run(_json(response, "status", run_id), submitted=False)
493
+ self.last_header = header
458
494
  need_status = True
459
495
  if header is not None and header["status"] in _TERMINAL:
460
496
  # The status is settled, so only the answer remains to be read.
@@ -193,3 +193,55 @@ def infer_provider(instance: Any) -> str:
193
193
  return "together"
194
194
  else:
195
195
  return "unknown"
196
+
197
+
198
+ # The LM kwargs the SDK may copy off an LM: into an ``lm.*`` span attribute,
199
+ # which every member of the org can read, and into the ``dspy.LM(...)`` line of
200
+ # deployed source. An allow-list, because an LM's kwargs carry provider
201
+ # credentials under names no deny-list keeps up with, such as
202
+ # ``aws_secret_access_key``, ``azure_ad_token``, ``vertex_credentials`` and
203
+ # ``extra_headers``, and ``dspy.LM.dump_state()`` drops only ``api_key``. A
204
+ # worker spends the deployment's sealed credential, so neither copy needs one.
205
+ #
206
+ # It holds what a provider is asked for (a superset of
207
+ # ``optimization.SAMPLING_KEYS``) and the ``dspy.LM`` settings that shape a
208
+ # call, and nothing that names a destination: ``api_base`` and ``base_url``
209
+ # stay out.
210
+ SAFE_LM_KWARGS: frozenset[str] = frozenset(
211
+ {
212
+ # Sampling and response shape.
213
+ "temperature",
214
+ "max_tokens",
215
+ "max_completion_tokens",
216
+ "top_p",
217
+ "top_k",
218
+ "n",
219
+ "stop",
220
+ "presence_penalty",
221
+ "frequency_penalty",
222
+ "seed",
223
+ "logprobs",
224
+ "top_logprobs",
225
+ "reasoning_effort",
226
+ "thinking",
227
+ "verbosity",
228
+ "response_format",
229
+ "tools",
230
+ "tool_choice",
231
+ "parallel_tool_calls",
232
+ "service_tier",
233
+ "cache_control_injection_points",
234
+ # dspy.LM call settings.
235
+ "cache",
236
+ "num_retries",
237
+ "use_developer_role",
238
+ "rollout_id",
239
+ }
240
+ )
241
+
242
+
243
+ def safe_lm_kwargs(kwargs: Any) -> dict[str, Any]:
244
+ """The entries of an LM's kwargs or ``dump_state()`` that :data:`SAFE_LM_KWARGS` names."""
245
+ if not isinstance(kwargs, dict):
246
+ return {}
247
+ return {key: value for key, value in kwargs.items() if key in SAFE_LM_KWARGS}
@@ -862,7 +862,12 @@ def optimize(
862
862
  in ``config["reflection_model"]``, which is what the server
863
863
  establishes this run's reflection credential from, and its sampling in
864
864
  ``config["reflection_sampling"]``, which only the reflecting tier
865
- reads. An ``api_key``, ``api_base`` or ``base_url`` on it is refused
865
+ reads. A reflection turn that names no output cap or temperature
866
+ gets the platform's (a large ``max_tokens`` and ``temperature=1.0``).
867
+ To send none and let the model's own defaults apply, pass the model
868
+ name as a string and set those keys to ``None`` in
869
+ ``config["reflection_sampling"]``, e.g.
870
+ ``{"max_tokens": None, "temperature": None}``. An ``api_key``, ``api_base`` or ``base_url`` on it is refused
866
871
  rather than dropped: the credential a run spends and the endpoint it
867
872
  is spent at are both the platform's, so one set here would be
868
873
  discarded without a word. The server refuses a gepa run
@@ -30,6 +30,7 @@ from typing import TYPE_CHECKING, Any
30
30
 
31
31
  import typing_extensions
32
32
 
33
+ from . import helpers
33
34
  from ._runtime_tools import is_runtime_tool
34
35
 
35
36
  if TYPE_CHECKING:
@@ -331,17 +332,6 @@ def _get_module_signature(module: dspy.Module) -> dspy.Signature | None:
331
332
  return None
332
333
 
333
334
 
334
- # Keys to omit when generating a dspy.LM() call
335
- _LM_SKIP_KEYS: set[str] = {
336
- "model",
337
- "model_type",
338
- "api_key",
339
- "api_base",
340
- "base_url",
341
- "model_list",
342
- }
343
-
344
-
345
335
  def _is_user_type(cls: type) -> bool:
346
336
  """True if cls is defined by user code, not stdlib/dspy/third-party."""
347
337
  mod = getattr(cls, "__module__", "") or ""
@@ -894,18 +884,12 @@ def generate_program_source(
894
884
  lm_state = _extract_lm_state(module)
895
885
  if lm_state is not None:
896
886
  model = lm_state.get("model", "")
897
- kwargs_parts = []
898
- for key, value in lm_state.items():
899
- # Skip credentials/config we never codegen, and any dspy-internal
900
- # state marker. dump_state() can emit markers like `_dspy_lm_class`
901
- # (popped by load_state, never a real constructor kwarg); forwarding
902
- # one into dspy.LM(...) makes litellm reject it as an unknown
903
- # argument (a 502 at execute on the dspy versions that emit it).
904
- # LM/litellm kwargs are never underscore-prefixed, so the prefix is
905
- # a safe discriminator.
906
- if key in _LM_SKIP_KEYS or key.startswith("_"):
907
- continue
908
- kwargs_parts.append(f"{key}={value!r}")
887
+ # dump_state() keeps every constructor kwarg but api_key, including
888
+ # cloud credentials, the destination (api_base), and dspy-internal
889
+ # markers such as `_dspy_lm_class` that litellm rejects as unknown
890
+ # arguments. The deployed LM names only what helpers.SAFE_LM_KWARGS
891
+ # allows. The worker supplies the credential and the destination.
892
+ kwargs_parts = [f"{key}={value!r}" for key, value in helpers.safe_lm_kwargs(lm_state).items()]
909
893
  kwargs_str = ", ".join(kwargs_parts)
910
894
  if kwargs_str:
911
895
  lines.append(f'lm = dspy.LM("{model}", {kwargs_str})')
@@ -88,6 +88,9 @@ within the budget.
88
88
 
89
89
  The budget or Ctrl-C stops the local wait, and an accepted run continues.
90
90
  After acceptance the CLI prints `cmpnd deployments runs wait <id>` on stderr.
91
+ A wait that runs out also names the run's last status and `status_reason`,
92
+ such as `queued: execution_authority_unavailable`, and the command that
93
+ cancels it.
91
94
  When the submission reply is lost or fails, acceptance is uncertain. The CLI
92
95
  then prints `cmpnd deployments runs list --deployment <id>`. Check that list
93
96
  before you submit again.
@@ -104,7 +107,13 @@ with `--deployment ID`. It takes these filters:
104
107
  Each page reports `total`, the number of matching runs, and `has_more` when another page follows.
105
108
 
106
109
  `show ID` prints one run, `result ID` reads its retained answer, and
107
- `wait ID [--timeout SECONDS]` resumes waiting. The run commands' JSON output
110
+ `wait ID [--timeout SECONDS]` resumes waiting. `cancel ID` settles a queued or
111
+ running run as `failed` with `error_code` `cancelled` and prints it. Serve
112
+ stops dispatching and collecting the run at once. A run already executing on a
113
+ worker runs on there until its deadline and may spend model calls, but its
114
+ answer is discarded. A key with `deployments:write` cancels any run, and a key
115
+ with only `deployments:execute` cancels the runs it submitted. Cancelling a run
116
+ that already settled exits nonzero. The run commands' JSON output
108
117
  needs no DSPy install.
109
118
 
110
119
  ### A file deploys as the source you wrote
@@ -407,7 +407,9 @@ lost POST reply or a server `5xx` leaves acceptance uncertain, so the SDK raises
407
407
  names the command to list recent runs. Reads that time out or answer `408`,
408
408
  `429` or `5xx` are retried with backoff and jitter, honoring `Retry-After`. A
409
409
  `401`, `403` or `404` read raises at once. A spent budget raises
410
- `RunWaitTimeout`, and the accepted run continues. Ctrl-C prints the run ID and
410
+ `RunWaitTimeout`, and the accepted run continues. Its `status` and
411
+ `status_reason` hold the run's last header read, and its message names them
412
+ with the commands that resume the wait and cancel the run. Ctrl-C prints the run ID and
411
413
  `cmpnd deployments runs wait <id>` on stderr, or before acceptance the list
412
414
  command, and re-raises.
413
415
 
@@ -846,6 +848,8 @@ The callback mints `source_observation_id` when the LM call starts and records `
846
848
 
847
849
  The LM span's `lm.finish_reason` attribute records why the model stopped, in the chat vocabulary serve's truncation warning reads. Each response shape spells it differently. Chat puts it on the first choice and DSPy 3.3's typed `LMResponse` on the first output. The lm15 `Response` a DSPy 3.4 engine returns carries it at the top level and names a tool-call stop `tool_call`, which the SDK records as `tool_calls`. The Responses API reports a `status`, and its `max_output_tokens` cutoff lands as `length`. `_finish_reason` in `python-sdk/cmpnd/callback.py` holds the mapping.
848
850
 
851
+ The LM span's other `lm.*` attributes copy only the LM kwargs that `SAFE_LM_KWARGS` in `python-sdk/cmpnd/helpers.py` names. These are the sampling and response-shape settings, such as `temperature` and `reasoning_effort`, and dspy.LM's own call settings, such as `num_retries`. Every member of the org can read a span. An LM's kwargs can also carry provider credentials under names no deny-list keeps up with, such as `aws_secret_access_key`, `azure_ad_token`, `vertex_credentials` and `extra_headers`. So the list names what may be copied, and a kwarg it does not name stays out. It also leaves out `api_base` and `base_url`, which name a destination. The `dspy.LM(...)` line in a programmatic deploy's generated source goes through the same list, because `dspy.LM.dump_state()` drops only `api_key`. The worker supplies the deployment's credential and destination itself.
852
+
849
853
  `provider_cost` stays `None`. DSPy and LiteLLM can attach a calculated cost to history, but that value is not provider evidence. The SDK will populate this field only when an adapter can prove that the provider supplied both the amount and its currency.
850
854
 
851
855
  ## Field truncation (span inputs/outputs)
@@ -5,7 +5,7 @@ name = "cmpnd"
5
5
  # it's new) — no git tags. Bump with `uv version --bump {minor,patch}` or by
6
6
  # hand. Runtime `cmpnd.__version__` reads installed metadata, so it tracks
7
7
  # this automatically. See docs/plans/2026-07-02-sdk-pypi-tag-free-publish.md.
8
- version = "0.47.0"
8
+ version = "0.49.0"
9
9
  description = "DSPy observability and deployment SDK for cmpnd"
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.11"
@@ -198,8 +198,8 @@ def deployed_lm_kwargs(**overrides: object) -> dict[str, object]:
198
198
  that is where the destination is decided: `deploy` reads the model, the key and the
199
199
  ``api_base`` off `dspy.settings.lm` and forwards them so the server can resolve a
200
200
  provider. Setting the LM on the *module* does not do it — the packaged program's own
201
- LM line comes from there, but `api_base` is stripped out of it (it is in
202
- `cmpnd.packaging._LM_SKIP_KEYS`), so a worker pointed by nothing else answers from
201
+ LM line comes from there, but `api_base` is stripped out of it (it is not in
202
+ `cmpnd.helpers.SAFE_LM_KWARGS`), so a worker pointed by nothing else answers from
203
203
  whatever host it can resolve. Hence `with dspy.context(lm=…): cmpnd.deploy(…)` at the
204
204
  call sites here.
205
205
  """
@@ -433,6 +433,51 @@ class TestCallbackLMHandlers:
433
433
 
434
434
  shutdown_exporter()
435
435
 
436
+ def test_on_lm_start_records_only_allow_listed_lm_kwargs(self):
437
+ """A provider credential set on the LM never reaches the span.
438
+
439
+ The span ships to serve, where every org member can read it, so its
440
+ ``lm.*`` attributes name what is known to be safe rather than filtering
441
+ out what is known to be secret.
442
+ """
443
+ import json
444
+
445
+ import dspy
446
+
447
+ from cmpnd.callback import CmpndCallback
448
+
449
+ with mock.patch("cmpnd.exporter.ensure_exporter_started", autospec=True):
450
+ callback = CmpndCallback()
451
+ lm = dspy.LM(
452
+ "bedrock/anthropic.claude-3-haiku",
453
+ temperature=0.2,
454
+ max_tokens=64,
455
+ api_key="secret-api-key",
456
+ api_base="https://secret-host.invalid",
457
+ aws_access_key_id="secret-access-key-id",
458
+ aws_secret_access_key="secret-access-key",
459
+ aws_session_token="secret-session-token",
460
+ azure_ad_token="secret-azure-token",
461
+ vertex_credentials='{"private_key": "secret-vertex"}',
462
+ extra_headers={"Authorization": "Bearer secret-header"},
463
+ unknown_future_kwarg="secret-unknown",
464
+ )
465
+
466
+ with mock.patch("cmpnd.exporter.get_exporter", autospec=True, return_value=None):
467
+ callback.on_lm_start(call_id="lm_call_1", instance=lm, inputs={"messages": []})
468
+
469
+ span = get_current_span()
470
+ assert span is not None
471
+ assert span.attributes["lm.temperature"] == "0.2"
472
+ assert span.attributes["lm.max_tokens"] == "64"
473
+ assert "secret" not in json.dumps(span.to_api_dict(), default=str)
474
+
475
+ def test_safe_lm_kwargs_cover_the_sampling_an_optimization_forwards(self):
476
+ """A sampling key an optimization may forward to a provider is one a span may show."""
477
+ from cmpnd import helpers, optimization
478
+
479
+ assert set(optimization.SAMPLING_KEYS) <= helpers.SAFE_LM_KWARGS
480
+
436
481
  @pytest.mark.parametrize("optimization_source", [None, "context", "callback"])
437
482
  def test_on_lm_start_creates_span(self, optimization_source):
438
483
  """Test on_lm_start creates LM_CALL span."""
@@ -215,6 +215,51 @@ def test_interrupted_run_names_the_accepted_run(monkeypatch: pytest.MonkeyPatch,
215
215
  assert f"cmpnd deployments runs wait {RUN_ID}" in capsys.readouterr().err
216
216
 
217
217
 
218
+ @pytest.mark.parametrize("verb", [["run", DEPLOYMENT_ID, "{}"], ["deployments", "runs", "wait", RUN_ID]])
219
+ def test_a_wait_that_runs_out_names_why_the_run_is_still_open(monkeypatch: pytest.MonkeyPatch, capsys, verb):
220
+ from cmpnd import deployment_runs
221
+
222
+ clock = iter(float(tick) for tick in range(1000))
223
+ monkeypatch.setattr(deployment_runs, "_now", lambda: next(clock))
224
+ monkeypatch.setattr(deployment_runs, "_sleep", lambda _: None)
225
+ waiting = {**_envelope("queued"), "status_reason": "execution_authority_unavailable"}
226
+
227
+ def answer(req):
228
+ return httpx.Response(202 if req.method == "POST" else 200, json=waiting, request=req)
229
+
230
+ with pytest.raises(SystemExit) as caught:
231
+ _run_cli(monkeypatch, [*verb, "--timeout", "6"], answer)
232
+ assert caught.value.code != 0
233
+ err = capsys.readouterr().err
234
+ assert f"Deployment run {RUN_ID} has not settled" in err
235
+ assert "queued: execution_authority_unavailable" in err
236
+ assert f"cmpnd deployments runs wait {RUN_ID}" in err
237
+ assert f"cmpnd deployments runs cancel {RUN_ID}" in err
238
+
239
+
240
+ def test_runs_cancel_posts_the_cancel_and_prints_the_settled_run(monkeypatch: pytest.MonkeyPatch, capsys):
241
+ seen = []
242
+ cancelled = {**_envelope("failed"), "error_code": "cancelled"}
243
+
244
+ def answer(req):
245
+ seen.append(req)
246
+ return httpx.Response(200, json=cancelled, request=req)
247
+
248
+ _run_cli(monkeypatch, ["deployments", "runs", "cancel", RUN_ID], answer)
249
+ assert [(req.method, req.url.path) for req in seen] == [("POST", f"/api/v1/deployments/runs/{RUN_ID}/cancel")]
250
+ assert json.loads(capsys.readouterr().out)["error_code"] == "cancelled"
251
+
252
+
253
+ def test_runs_cancel_of_a_settled_run_exits_nonzero(monkeypatch: pytest.MonkeyPatch, capsys):
254
+ def answer(req):
255
+ return httpx.Response(409, json={"error": "deployment run already settled as completed"}, request=req)
256
+
257
+ with pytest.raises(SystemExit) as caught:
258
+ _run_cli(monkeypatch, ["deployments", "runs", "cancel", RUN_ID], answer)
259
+ assert caught.value.code != 0
260
+ assert "already settled as completed" in capsys.readouterr().err
261
+
262
+
218
263
  def test_interrupted_submission_reports_uncertainty(monkeypatch: pytest.MonkeyPatch, capsys):
219
264
  def answer(req):
220
265
  raise KeyboardInterrupt
@@ -528,8 +528,8 @@ class TestLMDestinationForwarding:
528
528
 
529
529
  A worker calls the model itself, so `deploy()` forwards both halves of the
530
530
  destination — `litellm_model` and, when the LM carries one, `lm_api_host` from its
531
- ``api_base``. The host cannot ride the packaged program instead: `api_base` is in
532
- `packaging._LM_SKIP_KEYS`, so the generated source never mentions it.
531
+ ``api_base``. The host cannot ride the packaged program instead: `api_base` is not in
532
+ `helpers.SAFE_LM_KWARGS`, so the generated source never mentions it.
533
533
 
534
534
  Both come off ``dspy.settings.lm`` **as of the deploy call**, which is the property a
535
535
  test that deploys under a different LM than it calls in-process depends on
@@ -189,6 +189,18 @@ def test_the_refusal_fires_even_when_the_lm_would_have_overwritten_it():
189
189
  optimization._reflection_config(config, _LM("openai/gpt-4o", top_p=0.9))
190
190
 
191
191
 
192
+ def test_a_none_in_config_sampling_travels_as_none():
193
+ """``None`` asks the reflecting tier to drop its own default for that key, so the
194
+ model's default applies. This layer must carry it rather than filter it out as
195
+ "unset", which is what it means everywhere an LM's own state is read."""
196
+ config = {"reflection_sampling": {"max_tokens": None, "temperature": None}}
197
+ optimization._reflection_config(config, "openai/gpt-4o")
198
+ assert config == {
199
+ "reflection_model": "openai/gpt-4o",
200
+ "reflection_sampling": {"max_tokens": None, "temperature": None},
201
+ }
202
+
203
+
192
204
  def test_a_model_name_beside_config_sampling_stays_legal():
193
205
  """The string path is the one way left to name sampling through ``config``, so it
194
206
  keeps working — a name says nothing about sampling and so contradicts nothing."""
@@ -781,6 +781,41 @@ class TestGenerateProgramSource(unittest.TestCase):
781
781
  self.assertIn("groq/llama-3.1-8b-instant", source)
782
782
  self.assertIn("dspy.configure(lm=lm)", source)
783
783
 
784
+ def test_lm_config_carries_only_allow_listed_kwargs(self):
785
+ """A provider credential set on the LM never reaches the deployed source.
786
+
787
+ ``dspy.LM.dump_state()`` keeps every constructor kwarg except ``api_key``,
788
+ so cloud credentials such as ``aws_secret_access_key`` survive into it.
789
+ The worker spends the deployment's sealed credential, so the source names
790
+ only kwargs known to be safe and still runs.
791
+ """
792
+ lm = dspy.LM(
793
+ "bedrock/anthropic.claude-3-haiku",
794
+ temperature=0.5,
795
+ num_retries=2,
796
+ api_key="secret-api-key",
797
+ api_base="https://secret-host.invalid",
798
+ aws_access_key_id="secret-access-key-id",
799
+ aws_secret_access_key="secret-access-key",
800
+ aws_session_token="secret-session-token",
801
+ azure_ad_token="secret-azure-token",
802
+ vertex_credentials='{"private_key": "secret-vertex"}',
803
+ extra_headers={"Authorization": "Bearer secret-header"},
804
+ unknown_future_kwarg="secret-unknown",
805
+ )
806
+ with dspy.context(lm=lm):
807
+ source = generate_program_source(dspy.Predict("question -> answer"))
808
+ self.assertNotIn("secret", source)
809
+ lm_line = next(line for line in source.splitlines() if line.startswith("lm = dspy.LM("))
810
+ self.assertIn("temperature=0.5", lm_line)
811
+ self.assertIn("num_retries=2", lm_line)
812
+ # The rebuilt LM constructs, so the allow-list names only real dspy.LM kwargs.
813
+ # Only the LM line runs: the source's dspy.configure(lm=lm) would leave this
814
+ # LM in the shared settings for every later test.
815
+ namespace: dict[str, typing.Any] = {"dspy": dspy}
816
+ exec(lm_line, namespace)
817
+ self.assertEqual(namespace["lm"].kwargs["temperature"], 0.5)
818
+
784
819
 
785
820
  class TestProgramStatePackaging(unittest.TestCase):
786
821
  """CMP-289: a deployed program must carry its optimized *state* (demos and
@@ -496,7 +496,7 @@ wheels = [
496
496
 
497
497
  [[package]]
498
498
  name = "cmpnd"
499
- version = "0.47.0"
499
+ version = "0.49.0"
500
500
  source = { editable = "." }
501
501
  dependencies = [
502
502
  { name = "dspy" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes