cmpnd 0.46.0__tar.gz → 0.47.1__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.46.0 → cmpnd-0.47.1}/PKG-INFO +1 -1
  2. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/callback.py +5 -5
  3. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/deployment.py +4 -5
  4. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/helpers.py +52 -0
  5. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/optimization.py +9 -33
  6. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/packaging.py +8 -24
  7. {cmpnd-0.46.0 → cmpnd-0.47.1}/docs/sdk-instrumentation.md +2 -0
  8. {cmpnd-0.46.0 → cmpnd-0.47.1}/pyproject.toml +1 -1
  9. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_deploy_programs.py +5 -7
  10. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_optimize_server_owned_completion.py +7 -12
  11. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/integration/test_logs_renders_traces.py +1 -2
  12. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/integration/test_unhappy_path.py +1 -1
  13. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/stress/_provider.py +2 -2
  14. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_callback.py +45 -0
  15. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_deploy.py +6 -6
  16. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_optimize.py +42 -52
  17. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_packaging_program_source.py +35 -0
  18. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_upload_auth_headers.py +1 -1
  19. {cmpnd-0.46.0 → cmpnd-0.47.1}/uv.lock +1 -1
  20. {cmpnd-0.46.0 → cmpnd-0.47.1}/.gitignore +0 -0
  21. {cmpnd-0.46.0 → cmpnd-0.47.1}/AGENTS.md +0 -0
  22. {cmpnd-0.46.0 → cmpnd-0.47.1}/CLAUDE.md +0 -0
  23. {cmpnd-0.46.0 → cmpnd-0.47.1}/README.md +0 -0
  24. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/__init__.py +0 -0
  25. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/_assessments.py +0 -0
  26. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/_authored_import.py +0 -0
  27. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/_deployments.py +0 -0
  28. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/_exporter_pool.py +0 -0
  29. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/_gepa_patch.py +0 -0
  30. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/_http.py +0 -0
  31. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/_program_patch.py +0 -0
  32. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/_rlm_patch.py +0 -0
  33. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/_runtime_tools.py +0 -0
  34. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/_timespec.py +0 -0
  35. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/_traces.py +0 -0
  36. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/_valsets.py +0 -0
  37. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/authored.py +0 -0
  38. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/__init__.py +0 -0
  39. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/api_client.py +0 -0
  40. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/commands/__init__.py +0 -0
  41. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/commands/admin.py +0 -0
  42. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/commands/assessments.py +0 -0
  43. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/commands/auth.py +0 -0
  44. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/commands/datasets.py +0 -0
  45. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/commands/deployments.py +0 -0
  46. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/commands/evals.py +0 -0
  47. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/commands/login.py +0 -0
  48. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/commands/optimizations.py +0 -0
  49. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/commands/org.py +0 -0
  50. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/commands/programs.py +0 -0
  51. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/commands/provider_credentials.py +0 -0
  52. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/commands/signatures.py +0 -0
  53. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/commands/tool_servers.py +0 -0
  54. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/commands/top.py +0 -0
  55. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/commands/traces.py +0 -0
  56. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/commands/valsets.py +0 -0
  57. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/credentials.py +0 -0
  58. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/deprecation.py +0 -0
  59. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/pagination.py +0 -0
  60. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/shell.py +0 -0
  61. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/trace_render.py +0 -0
  62. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/tree_help.py +0 -0
  63. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/cli/verbs.py +0 -0
  64. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/client.py +0 -0
  65. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/configuration.py +0 -0
  66. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/context.py +0 -0
  67. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/dataset_sync.py +0 -0
  68. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/datasets.py +0 -0
  69. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/decorators.py +0 -0
  70. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/deployment_metadata.py +0 -0
  71. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/deployment_runs.py +0 -0
  72. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/eval_handler.py +0 -0
  73. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/execution.py +0 -0
  74. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/exporter.py +0 -0
  75. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/exporter_http.py +0 -0
  76. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/identity.py +0 -0
  77. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/imaging.py +0 -0
  78. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/ir/__init__.py +0 -0
  79. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/ir/decode.py +0 -0
  80. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/ir/encode.py +0 -0
  81. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/ir/hash.py +0 -0
  82. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/ir/program.py +0 -0
  83. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/ir/reflection.py +0 -0
  84. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/ir/types.py +0 -0
  85. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/ir/xxh64.py +0 -0
  86. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/lm_configuration.py +0 -0
  87. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/mcp_servers.py +0 -0
  88. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/models.py +0 -0
  89. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/named_programs.py +0 -0
  90. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/optimizers/__init__.py +0 -0
  91. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/optimizers/gepa.py +0 -0
  92. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/optimizers/gepa_callback.py +0 -0
  93. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/optimizers/resume.py +0 -0
  94. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/optimizers/tracker.py +0 -0
  95. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/programs.py +0 -0
  96. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/references.py +0 -0
  97. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/retry.py +0 -0
  98. {cmpnd-0.46.0 → cmpnd-0.47.1}/cmpnd/traces.py +0 -0
  99. {cmpnd-0.46.0 → cmpnd-0.47.1}/docs/2026-04-02-code-review.md +0 -0
  100. {cmpnd-0.46.0 → cmpnd-0.47.1}/docs/cli.md +0 -0
  101. {cmpnd-0.46.0 → cmpnd-0.47.1}/docs/client.md +0 -0
  102. {cmpnd-0.46.0 → cmpnd-0.47.1}/docs/custom-proposer.md +0 -0
  103. {cmpnd-0.46.0 → cmpnd-0.47.1}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
  104. {cmpnd-0.46.0 → cmpnd-0.47.1}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
  105. {cmpnd-0.46.0 → cmpnd-0.47.1}/docs/reading-traces.md +0 -0
  106. {cmpnd-0.46.0 → cmpnd-0.47.1}/examples/generalizing_proposer.py +0 -0
  107. {cmpnd-0.46.0 → cmpnd-0.47.1}/examples/local_ollama.py +0 -0
  108. {cmpnd-0.46.0 → cmpnd-0.47.1}/ingest/LINEAGE.md +0 -0
  109. {cmpnd-0.46.0 → cmpnd-0.47.1}/ingest/README.md +0 -0
  110. {cmpnd-0.46.0 → cmpnd-0.47.1}/ingest/cmpnd_ingest/__init__.py +0 -0
  111. {cmpnd-0.46.0 → cmpnd-0.47.1}/ingest/cmpnd_ingest/bodies.py +0 -0
  112. {cmpnd-0.46.0 → cmpnd-0.47.1}/ingest/cmpnd_ingest/client_floor.py +0 -0
  113. {cmpnd-0.46.0 → cmpnd-0.47.1}/ingest/cmpnd_ingest/exporter_http.py +0 -0
  114. {cmpnd-0.46.0 → cmpnd-0.47.1}/ingest/cmpnd_ingest/program_names.py +0 -0
  115. {cmpnd-0.46.0 → cmpnd-0.47.1}/ingest/cmpnd_ingest/protocols.py +0 -0
  116. {cmpnd-0.46.0 → cmpnd-0.47.1}/ingest/cmpnd_ingest/py.typed +0 -0
  117. {cmpnd-0.46.0 → cmpnd-0.47.1}/ingest/cmpnd_ingest/request_log.py +0 -0
  118. {cmpnd-0.46.0 → cmpnd-0.47.1}/ingest/cmpnd_ingest/retry.py +0 -0
  119. {cmpnd-0.46.0 → cmpnd-0.47.1}/ingest/cmpnd_ingest/trace_facts.py +0 -0
  120. {cmpnd-0.46.0 → cmpnd-0.47.1}/ingest/pyproject.toml +0 -0
  121. {cmpnd-0.46.0 → cmpnd-0.47.1}/scripts/audit-sdist.sh +0 -0
  122. {cmpnd-0.46.0 → cmpnd-0.47.1}/scripts/capture_review_run.py +0 -0
  123. {cmpnd-0.46.0 → cmpnd-0.47.1}/scripts/check-pip-install.sh +0 -0
  124. {cmpnd-0.46.0 → cmpnd-0.47.1}/scripts/check_release_artifacts.py +0 -0
  125. {cmpnd-0.46.0 → cmpnd-0.47.1}/scripts/fixtures/review_run.json.gz +0 -0
  126. {cmpnd-0.46.0 → cmpnd-0.47.1}/scripts/gepa_resume_bug.py +0 -0
  127. {cmpnd-0.46.0 → cmpnd-0.47.1}/scripts/release-stress.sh +0 -0
  128. {cmpnd-0.46.0 → cmpnd-0.47.1}/scripts/require_version_bump.py +0 -0
  129. {cmpnd-0.46.0 → cmpnd-0.47.1}/scripts/seed_review_run.py +0 -0
  130. {cmpnd-0.46.0 → cmpnd-0.47.1}/scripts/test-in-linux.sh +0 -0
  131. {cmpnd-0.46.0 → cmpnd-0.47.1}/stubs/dspy/__init__.pyi +0 -0
  132. {cmpnd-0.46.0 → cmpnd-0.47.1}/stubs/dspy/primitives/__init__.pyi +0 -0
  133. {cmpnd-0.46.0 → cmpnd-0.47.1}/stubs/dspy/primitives/prediction.pyi +0 -0
  134. {cmpnd-0.46.0 → cmpnd-0.47.1}/stubs/dspy/utils/__init__.pyi +0 -0
  135. {cmpnd-0.46.0 → cmpnd-0.47.1}/stubs/dspy/utils/callback.pyi +0 -0
  136. {cmpnd-0.46.0 → cmpnd-0.47.1}/stubs/dspy/utils/dummies.pyi +0 -0
  137. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/__init__.py +0 -0
  138. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/_deployment_signature_helpers.py +0 -0
  139. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/_exporter_helpers.py +0 -0
  140. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/_fake_clock.py +0 -0
  141. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/_identity_helpers.py +0 -0
  142. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/_resolve_refs.py +0 -0
  143. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/_traced_run.py +0 -0
  144. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/conftest.py +0 -0
  145. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/__init__.py +0 -0
  146. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/committee_task.py +0 -0
  147. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/conftest.py +0 -0
  148. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/data/committee_train.ndjson.gz +0 -0
  149. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/data/committee_val.ndjson.gz +0 -0
  150. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/fake_lm.py +0 -0
  151. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/helpers.py +0 -0
  152. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/target_capabilities.py +0 -0
  153. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_assessments_cli.py +0 -0
  154. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_capture_prompts.py +0 -0
  155. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_composite_module.py +0 -0
  156. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_dataset_auto_creation.py +0 -0
  157. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_deploy_execute.py +0 -0
  158. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_deploy_pandas.py +0 -0
  159. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_edge_cases.py +0 -0
  160. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_example_hash.py +0 -0
  161. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_gepa_optimization.py +0 -0
  162. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_interpreter_spans.py +0 -0
  163. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_lm_accuracy.py +0 -0
  164. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_module_tracing.py +0 -0
  165. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_optimization_lineage.py +0 -0
  166. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_optimization_metric_identity.py +0 -0
  167. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_optimize_failures.py +0 -0
  168. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_optimize_loop.py +0 -0
  169. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_optimize_review_committee.py +0 -0
  170. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_parallel_export.py +0 -0
  171. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_project_attribution.py +0 -0
  172. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_react_tool_spans.py +0 -0
  173. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_save_load.py +0 -0
  174. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_trace_structure.py +0 -0
  175. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_traces_read.py +0 -0
  176. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_user_attribution.py +0 -0
  177. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/e2e/test_valsets_cli.py +0 -0
  178. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/identity_vectors/01_predict_qa.json +0 -0
  179. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
  180. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
  181. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/identity_vectors/04_predict_richer_types.json +0 -0
  182. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
  183. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/identity_vectors/06_multichaincomparison.json +0 -0
  184. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/identity_vectors/07_program_of_thought.json +0 -0
  185. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/identity_vectors/08_opaque_module.json +0 -0
  186. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
  187. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/identity_vectors/10_react.json +0 -0
  188. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/identity_vectors/11_refine.json +0 -0
  189. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/identity_vectors/12_retrieve.json +0 -0
  190. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/identity_vectors/13_knn.json +0 -0
  191. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/identity_vectors/14_parallel.json +0 -0
  192. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/identity_vectors/README.md +0 -0
  193. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/ingest_conformance.py +0 -0
  194. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/integration/__init__.py +0 -0
  195. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/integration/authored_optimize/judge.py +0 -0
  196. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
  197. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/integration/conftest.py +0 -0
  198. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
  199. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/integration/hosted_optimize_probe.py +0 -0
  200. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/integration/optimization_run_probe.py +0 -0
  201. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/integration/test_cli_deploy_smoketest.py +0 -0
  202. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/integration/test_optimization_run.py +0 -0
  203. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/integration/test_optimizing_progress.py +0 -0
  204. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/load/__init__.py +0 -0
  205. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/load/__main__.py +0 -0
  206. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/load/drain.py +0 -0
  207. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/load/ingest.py +0 -0
  208. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/load/latency_transport.py +0 -0
  209. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/load/rlm_shape.py +0 -0
  210. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/load/rlm_size_report.py +0 -0
  211. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/load/sweep.py +0 -0
  212. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/load/test_rlm_shape.py +0 -0
  213. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/load/test_rlm_size_report.py +0 -0
  214. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/parity/__init__.py +0 -0
  215. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/parity/conftest.py +0 -0
  216. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/parity/seed_parity.py +0 -0
  217. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/parity/test_auth.py +0 -0
  218. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/parity/test_datasets.py +0 -0
  219. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/parity/test_deployments.py +0 -0
  220. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/parity/test_evals.py +0 -0
  221. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/parity/test_health.py +0 -0
  222. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/parity/test_modules.py +0 -0
  223. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/parity/test_optimizations.py +0 -0
  224. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/parity/test_programs.py +0 -0
  225. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/parity/test_signatures.py +0 -0
  226. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/parity/test_trace_streaming.py +0 -0
  227. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/parity/test_traces.py +0 -0
  228. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/span_reference_fixture.py +0 -0
  229. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/stress/__init__.py +0 -0
  230. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/stress/_readiness.py +0 -0
  231. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/stress/_reliability.py +0 -0
  232. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/stress/_result.py +0 -0
  233. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/stress/mint_bedrock_token.py +0 -0
  234. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/stress/test_deploy_programs.py +0 -0
  235. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/stress/test_multi_client_stress.py +0 -0
  236. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/stress/test_optimize_client_committee.py +0 -0
  237. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/stress/test_optimize_loop.py +0 -0
  238. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/stress/test_optimize_loop_wasm.py +0 -0
  239. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/stress/test_optimize_stress.py +0 -0
  240. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/stress/test_provider_roles.py +0 -0
  241. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/stress/test_readiness.py +0 -0
  242. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/stress/test_real_provider_twins.py +0 -0
  243. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/stress/test_reliability.py +0 -0
  244. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/stress/test_reliability_classify.py +0 -0
  245. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/stress/test_rlm_wasm.py +0 -0
  246. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/stress/test_trace_search.py +0 -0
  247. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_adapter_reentrancy.py +0 -0
  248. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_assessments_api.py +0 -0
  249. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_authored.py +0 -0
  250. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_authored_bundle_collisions.py +0 -0
  251. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_authored_metric_contract.py +0 -0
  252. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_authored_named_models.py +0 -0
  253. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_authored_optimize.py +0 -0
  254. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_candidate_lineage.py +0 -0
  255. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/__init__.py +0 -0
  256. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/conftest.py +0 -0
  257. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_admin.py +0 -0
  258. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_api_client.py +0 -0
  259. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_assessments.py +0 -0
  260. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_auth.py +0 -0
  261. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_credentials.py +0 -0
  262. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_datasets.py +0 -0
  263. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_deployment_runs.py +0 -0
  264. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_deployments.py +0 -0
  265. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_evals.py +0 -0
  266. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_login.py +0 -0
  267. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_main.py +0 -0
  268. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_optimizations.py +0 -0
  269. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_org.py +0 -0
  270. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_pagination.py +0 -0
  271. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_parity.py +0 -0
  272. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_programs.py +0 -0
  273. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_provider_credentials.py +0 -0
  274. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_schema_sample.py +0 -0
  275. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_shell.py +0 -0
  276. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_signatures.py +0 -0
  277. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_tool_servers.py +0 -0
  278. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_top.py +0 -0
  279. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_traces.py +0 -0
  280. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_traces_search_wire.py +0 -0
  281. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_tree_help.py +0 -0
  282. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cli/test_valsets.py +0 -0
  283. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_client.py +0 -0
  284. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_client_floor.py +0 -0
  285. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_client_program_names.py +0 -0
  286. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_clustering/__init__.py +0 -0
  287. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_clustering/conftest.py +0 -0
  288. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_clustering/test_edge_cases.py +0 -0
  289. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_clustering/test_hash_consistency.py +0 -0
  290. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_clustering/test_type_conversion.py +0 -0
  291. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_cmp551_grouping_identity.py +0 -0
  292. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_configuration.py +0 -0
  293. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_context.py +0 -0
  294. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_context_propagation.py +0 -0
  295. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_dataset_polling.py +0 -0
  296. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_dataset_sync.py +0 -0
  297. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_datasets.py +0 -0
  298. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_decision_client_spans.py +0 -0
  299. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_decorators.py +0 -0
  300. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_deploy_mcp_tool_references.py +0 -0
  301. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_deploy_program_id.py +0 -0
  302. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_deployment_metadata.py +0 -0
  303. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_deployment_runs.py +0 -0
  304. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_deployments_read.py +0 -0
  305. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_eval.py +0 -0
  306. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_eval_example_attribution.py +0 -0
  307. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_eval_start.py +0 -0
  308. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_eval_trace_grouping.py +0 -0
  309. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_exception_paths.py +0 -0
  310. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_execute.py +0 -0
  311. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_execute_run_identity.py +0 -0
  312. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_exporter.py +0 -0
  313. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_exporter_program_names.py +0 -0
  314. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_exporter_queue.py +0 -0
  315. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_exporter_real.py +0 -0
  316. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_fake_lm.py +0 -0
  317. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_gepa_callback.py +0 -0
  318. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_gepa_integration.py +0 -0
  319. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_gepa_output_normalization.py +0 -0
  320. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_gepa_resume.py +0 -0
  321. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_identity.py +0 -0
  322. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_imaging.py +0 -0
  323. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_import_weight.py +0 -0
  324. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_ingest_conformance.py +0 -0
  325. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_ingest_second_producer.py +0 -0
  326. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_integration.py +0 -0
  327. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_interpreter_callback.py +0 -0
  328. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_ir_encoder_properties.py +0 -0
  329. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_ir_golden_vectors.py +0 -0
  330. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_ir_roundtrip.py +0 -0
  331. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_lm15_native_calls.py +0 -0
  332. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_lm_configuration.py +0 -0
  333. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_lm_usage_shapes.py +0 -0
  334. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_mcp_tools.py +0 -0
  335. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_models.py +0 -0
  336. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_multi_client_stress_readiness.py +0 -0
  337. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_optimization_progress.py +0 -0
  338. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_optimization_start.py +0 -0
  339. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_optimize_budget.py +0 -0
  340. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_optimize_lift.py +0 -0
  341. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_optimize_lms.py +0 -0
  342. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_optimize_proposer.py +0 -0
  343. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_optimize_reflection_lm.py +0 -0
  344. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_optimize_terminal_failure.py +0 -0
  345. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_packaging.py +0 -0
  346. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_packaging_callable_source.py +0 -0
  347. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_packaging_mcp_tools.py +0 -0
  348. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_poll_until.py +0 -0
  349. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_program_identity.py +0 -0
  350. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_program_lineage_persist.py +0 -0
  351. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_programs_create.py +0 -0
  352. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_project_payload.py +0 -0
  353. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_real_provider_twins_readiness.py +0 -0
  354. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_reflector_properties.py +0 -0
  355. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_reflector_smoke.py +0 -0
  356. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_release_stress_script.py +0 -0
  357. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_repro_reactv2_bugs.py +0 -0
  358. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_require_version_bump.py +0 -0
  359. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_retry.py +0 -0
  360. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_reversible_failures.py +0 -0
  361. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_rlm_patch.py +0 -0
  362. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_run_attribution_paths.py +0 -0
  363. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_runtime_tools.py +0 -0
  364. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_schemas.py +0 -0
  365. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_seed_review_run.py +0 -0
  366. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_signature_inputs.py +0 -0
  367. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_span_references.py +0 -0
  368. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_span_sent_once.py +0 -0
  369. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_system_one.py +0 -0
  370. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_trace_context_concurrency.py +0 -0
  371. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_trace_fact_wire_contract.py +0 -0
  372. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_trace_facts.py +0 -0
  373. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_trace_render.py +0 -0
  374. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_traces_read.py +0 -0
  375. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_tracked_gepa.py +0 -0
  376. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_tracker_enrich.py +0 -0
  377. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_truncation.py +0 -0
  378. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_valsets_api.py +0 -0
  379. {cmpnd-0.46.0 → cmpnd-0.47.1}/tests/test_xxh64.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cmpnd
3
- Version: 0.46.0
3
+ Version: 0.47.1
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
 
@@ -2,9 +2,8 @@
2
2
 
3
3
  The deploy flow:
4
4
  1. Package the module into a source ZIP
5
- 2. POST to the server to create a deployment (returns a presigned upload slot)
6
- 3. PUT the ZIP to that slot (one path for both postures; bytes go direct to the
7
- worker, raw body, no form)
5
+ 2. POST to the server to create a deployment (returns an upload slot on serve)
6
+ 3. PUT the ZIP to that slot as the raw body
8
7
  4. Poll until the parser finishes (status becomes "ready" or "failed")
9
8
  5. Return the deployment_id
10
9
  """
@@ -451,8 +450,8 @@ def deploy(module, metric=None, timeout=300, metadata=None, *, program_id=None):
451
450
 
452
451
  deploy_body: dict[str, Any] = {
453
452
  "program_signature_hash": identity.format_signature_hash(program_signature_hash),
454
- # Declare the ZIP's exact length so the server can size-bound the upload
455
- # slot and reject an oversized deploy before signing it (Slice E).
453
+ # Declare the ZIP's exact length so the server can reject an oversized
454
+ # deploy before the upload.
456
455
  "declared_content_length": len(zip_bytes),
457
456
  }
458
457
  # Left off when undeclared, so a serve from before program ids reads the body it
@@ -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}
@@ -11,8 +11,8 @@ Flow per :func:`optimize` call:
11
11
  1. Build the optimization bundle (``trainset.ndjson`` + ``valset.ndjson`` +
12
12
  ``metric.py``) and, if no ``deployment_id`` was given, :func:`deploy` the
13
13
  program to materialize the starting deployment A.
14
- 2. Step 0 — ``data:"upload"``: POST to ``/optimize/{optimizer}`` to get a
15
- presigned upload, PUT the bundle, then poll until the step completes. The
14
+ 2. Step 0 — ``data:"upload"``: POST to ``/optimize/{optimizer}`` to get an
15
+ upload slot, PUT the bundle, then poll until the step completes. The
16
16
  result becomes a first-class child deployment B.
17
17
  3. Steps 1..n — ``data:"reuse"`` on the latest result deployment, advancing the
18
18
  seed each step (the key quality lever). Track the best score; stop after
@@ -1417,14 +1417,12 @@ def _initiate_step(
1417
1417
  def _upload_bundle(
1418
1418
  client: httpx.Client, initiated: dict[str, Any], bundle: bytes, *, endpoint: str, api_key: str
1419
1419
  ) -> None:
1420
- """Upload the optimization bundle to the slot the initiate step returned.
1420
+ """PUT the optimization bundle, raw, to the slot the initiate step returned.
1421
1421
 
1422
- Serve answers an ``awaiting_data`` initiate with ``upload_url``, ``upload_method:
1423
- "PUT"`` and empty ``upload_fields``, and the bundle goes out as the raw PUT body.
1424
- Serve authenticates that route with the API key, which rides the PUT only when
1425
- ``upload_url`` is on ``endpoint``'s origin (``_http.upload_auth_headers``). A
1426
- non-empty ``upload_fields`` is a presigned form, which no current server sends.
1427
- The bundle then rides as the ``file`` part of a multipart POST.
1422
+ Serve answers an ``awaiting_data`` initiate with an ``upload_url`` on its own
1423
+ bundle route, and authenticates that route with the API key. The key rides
1424
+ the PUT only when ``upload_url`` is on ``endpoint``'s origin
1425
+ (``_http.upload_auth_headers``).
1428
1426
 
1429
1427
  The slot pins its destination, so re-sending the same bytes writes the same
1430
1428
  object. That makes this call idempotent, so it rides through a transient answer
@@ -1433,31 +1431,9 @@ def _upload_bundle(
1433
1431
  upload_url = initiated.get("upload_url")
1434
1432
  if not upload_url:
1435
1433
  raise OptimizeError("upload step did not return an upload_url")
1436
- upload_fields = initiated.get("upload_fields")
1437
- method = (initiated.get("upload_method") or ("POST" if upload_fields else "PUT")).upper()
1438
1434
  what = "upload optimization bundle"
1439
- auth = _http.upload_auth_headers(upload_url, endpoint, api_key)
1440
-
1441
- if upload_fields:
1442
- resp = _request(
1443
- lambda: client.post(
1444
- upload_url,
1445
- data=dict(upload_fields),
1446
- files={"file": ("data.zip", bundle, "application/zip")},
1447
- headers=auth,
1448
- ),
1449
- what,
1450
- )
1451
- else:
1452
- resp = _request(
1453
- lambda: client.request(
1454
- method,
1455
- upload_url,
1456
- content=bundle,
1457
- headers={"Content-Type": "application/zip", **auth},
1458
- ),
1459
- what,
1460
- )
1435
+ headers = {"Content-Type": "application/zip", **_http.upload_auth_headers(upload_url, endpoint, api_key)}
1436
+ resp = _request(lambda: client.put(upload_url, content=bundle, headers=headers), what)
1461
1437
  if not resp.is_success:
1462
1438
  body = (resp.text or "").strip()[:400]
1463
1439
  raise OptimizeError(f"failed to {what}: HTTP {resp.status_code}: {body}")
@@ -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})')
@@ -1428,7 +1412,7 @@ def generate_source_zip(
1428
1412
  ) -> bytes:
1429
1413
  """Build a ZIP archive with program source, optional metric, and manifest.
1430
1414
 
1431
- The ZIP is suitable for upload to the deploy API via presigned S3 POST.
1415
+ The ZIP is the body the deploy API's source upload takes.
1432
1416
  """
1433
1417
  import dspy
1434
1418
 
@@ -846,6 +846,8 @@ The callback mints `source_observation_id` when the LM call starts and records `
846
846
 
847
847
  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
848
 
849
+ 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.
850
+
849
851
  `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
852
 
851
853
  ## 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.46.0"
8
+ version = "0.47.1"
9
9
  description = "DSPy observability and deployment SDK for cmpnd"
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.11"
@@ -212,11 +212,8 @@ class TestDeployBadImportRejected:
212
212
  headers = {"X-API-Key": config.api_key, "Content-Type": "application/json"}
213
213
 
214
214
  with httpx.Client(timeout=30.0) as client:
215
- # Step 1: create deployment. declared_content_length is required
216
- # (Slice E): the hosted presigned PUT is signed with the exact ZIP
217
- # length, so it must be declared up front — exactly as cmpnd/deploy.py
218
- # does, and a deployment names its program. This hand-crafted client
219
- # mirrors that contract.
215
+ # Step 1: create the deployment, declaring the ZIP's length and
216
+ # naming its program as cmpnd.deploy does.
220
217
  create_resp = client.post(
221
218
  f"{config.endpoint}/api/v1/deployments",
222
219
  json={
@@ -232,11 +229,12 @@ class TestDeployBadImportRejected:
232
229
  deployment_id = data["deployment_id"]
233
230
  upload_url = data["upload_url"]
234
231
 
235
- # Step 2: PUT the bad ZIP to the single upload slot (Slice E).
232
+ # Step 2: PUT the bad ZIP to the upload slot, which serve
233
+ # authenticates with the API key.
236
234
  upload_resp = client.put(
237
235
  upload_url,
238
236
  content=zip_bytes,
239
- headers={"Content-Type": "application/zip"},
237
+ headers={"X-API-Key": config.api_key, "Content-Type": "application/zip"},
240
238
  )
241
239
  upload_resp.raise_for_status()
242
240
 
@@ -106,18 +106,13 @@ def _initiate_and_abandon(deployment_id: str, trainset, valset, metric) -> str:
106
106
  resp.raise_for_status()
107
107
  initiated = resp.json()
108
108
 
109
- # serve normalizes the awaiting_data slot so upload_fields is always
110
- # present: a non-empty form means presigned POST, empty means raw PUT.
111
- upload_url = initiated["upload_url"]
112
- upload_fields = initiated.get("upload_fields") or {}
113
- if upload_fields:
114
- up = client.post(
115
- upload_url,
116
- data=dict(upload_fields),
117
- files={"file": ("data.zip", bundle, "application/zip")},
118
- )
119
- else:
120
- up = client.put(upload_url, content=bundle, headers={"Content-Type": "application/zip"})
109
+ # The slot is serve's bundle route, which takes the raw bundle as a PUT
110
+ # and authenticates it with the API key.
111
+ up = client.put(
112
+ initiated["upload_url"],
113
+ content=bundle,
114
+ headers={"X-API-Key": cfg.api_key, "Content-Type": "application/zip"},
115
+ )
121
116
  up.raise_for_status()
122
117
 
123
118
  # Deliberately NO status poll — the server-owned watcher must finish it.
@@ -64,8 +64,7 @@ def _create_and_upload_real_module(rails: httpx.Client, stub_url: str) -> str:
64
64
  created = resp.json()
65
65
  deployment_id = created["deployment_id"]
66
66
 
67
- # Upload the real source ZIP using whichever transport Rails returned
68
- # — POST presigned (Lambda) or PUT (cmpnd-serve).
67
+ # PUT the real source ZIP to the slot the create response names.
69
68
  zip_bytes = generate_source_zip(module, None)
70
69
  upload_deploy_zip(created, zip_bytes)
71
70
 
@@ -59,7 +59,7 @@ def _create_deployment(rails: httpx.Client, *, signature_hash: int) -> dict:
59
59
 
60
60
  def _upload_to_stub(stub_url: str, create_resp: dict, zip_bytes: bytes) -> None:
61
61
  """Mirror the SDK's deploy upload step — a single PUT to the upload slot
62
- (Slice E) via the shared conftest helper. ``stub_url`` is unused — kept in
62
+ via the shared conftest helper. ``stub_url`` is unused — kept in
63
63
  the signature so existing call-sites don't need editing."""
64
64
  del stub_url # historical; the helper reads upload_url from create_resp
65
65
  upload_deploy_zip(create_resp, zip_bytes)
@@ -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."""
@@ -59,7 +59,7 @@ class TestDeploy:
59
59
  }
60
60
  create_response.raise_for_status = mock.MagicMock()
61
61
 
62
- # S3 upload response (presigned PUT, Slice E)
62
+ # Upload slot response
63
63
  upload_response = mock.MagicMock()
64
64
  upload_response.status_code = 200
65
65
  upload_response.raise_for_status = mock.MagicMock()
@@ -88,7 +88,7 @@ class TestDeploy:
88
88
  signature.json.return_value = signature_response
89
89
  signature.raise_for_status = mock.MagicMock()
90
90
 
91
- # Wire up: POST = create; PUT = upload slot (Slice E); GET = polls.
91
+ # Wire up: POST = create; PUT = upload slot; GET = polls.
92
92
  client.post.side_effect = [create_response]
93
93
  client.put.side_effect = [upload_response]
94
94
  client.get.side_effect = [*poll_responses, signature]
@@ -178,7 +178,7 @@ class TestDeploy:
178
178
 
179
179
  deployment.deploy(dspy.Predict("question -> answer"))
180
180
 
181
- # Slice E: one PUT of the raw ZIP to the presigned upload slot.
181
+ # One PUT of the raw ZIP to the upload slot.
182
182
  client.post.assert_called_once()
183
183
  upload_call = client.put.call_args
184
184
  assert "s3.amazonaws.com" in upload_call.args[0]
@@ -194,7 +194,7 @@ class TestDeploy:
194
194
 
195
195
  deployment.deploy(dspy.Predict("question -> answer"))
196
196
 
197
- # The create body declares the ZIP length for the size cap (Slice E).
197
+ # The create body declares the ZIP length for the size cap.
198
198
  create_call = client.post.call_args_list[0]
199
199
  body = create_call.kwargs["json"]
200
200
  assert isinstance(body["declared_content_length"], int)
@@ -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