cmpnd 0.2.0__tar.gz → 0.4.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 (223) hide show
  1. cmpnd-0.4.0/CLAUDE.md +116 -0
  2. {cmpnd-0.2.0 → cmpnd-0.4.0}/PKG-INFO +116 -13
  3. cmpnd-0.4.0/README.md +332 -0
  4. cmpnd-0.4.0/cmpnd/__init__.py +139 -0
  5. cmpnd-0.4.0/cmpnd/_gepa_patch.py +158 -0
  6. cmpnd-0.4.0/cmpnd/_program_patch.py +112 -0
  7. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/callback.py +349 -355
  8. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/cli/__init__.py +49 -18
  9. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/cli/api_client.py +34 -6
  10. cmpnd-0.4.0/cmpnd/cli/commands/admin.py +59 -0
  11. cmpnd-0.4.0/cmpnd/cli/commands/auth.py +22 -0
  12. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/cli/commands/datasets.py +5 -5
  13. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/cli/commands/deployments.py +7 -9
  14. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/cli/commands/evals.py +5 -5
  15. cmpnd-0.4.0/cmpnd/cli/commands/login.py +251 -0
  16. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/cli/commands/optimizations.py +7 -7
  17. cmpnd-0.4.0/cmpnd/cli/commands/org.py +115 -0
  18. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/cli/commands/programs.py +6 -6
  19. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/cli/commands/top.py +379 -58
  20. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/cli/commands/traces.py +5 -5
  21. cmpnd-0.4.0/cmpnd/cli/credentials.py +95 -0
  22. cmpnd-0.4.0/cmpnd/cli/shell.py +691 -0
  23. cmpnd-0.4.0/cmpnd/cli/verbs.py +113 -0
  24. cmpnd-0.2.0/cmpnd/config.py → cmpnd-0.4.0/cmpnd/configuration.py +67 -31
  25. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/context.py +20 -19
  26. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/dataset_sync.py +3 -4
  27. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/datasets.py +4 -4
  28. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/decorators.py +18 -19
  29. cmpnd-0.2.0/cmpnd/deploy.py → cmpnd-0.4.0/cmpnd/deployment.py +98 -43
  30. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/eval_handler.py +54 -70
  31. cmpnd-0.2.0/cmpnd/execute.py → cmpnd-0.4.0/cmpnd/execution.py +52 -7
  32. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/exporter.py +305 -121
  33. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/helpers.py +9 -9
  34. cmpnd-0.4.0/cmpnd/identity.py +247 -0
  35. cmpnd-0.4.0/cmpnd/ir/__init__.py +91 -0
  36. cmpnd-0.4.0/cmpnd/ir/decode.py +230 -0
  37. cmpnd-0.4.0/cmpnd/ir/encode.py +134 -0
  38. cmpnd-0.4.0/cmpnd/ir/hash.py +12 -0
  39. cmpnd-0.4.0/cmpnd/ir/program.py +109 -0
  40. cmpnd-0.4.0/cmpnd/ir/reflection.py +167 -0
  41. cmpnd-0.4.0/cmpnd/ir/types.py +89 -0
  42. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/models.py +79 -52
  43. cmpnd-0.4.0/cmpnd/optimization.py +451 -0
  44. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/optimizers/__init__.py +3 -3
  45. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/optimizers/gepa.py +26 -54
  46. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/optimizers/gepa_callback.py +29 -22
  47. cmpnd-0.4.0/cmpnd/optimizers/resume.py +91 -0
  48. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/optimizers/tracker.py +218 -57
  49. cmpnd-0.4.0/cmpnd/packaging.py +890 -0
  50. cmpnd-0.4.0/docs/sdk-instrumentation.md +219 -0
  51. {cmpnd-0.2.0 → cmpnd-0.4.0}/examples/local_ollama.py +21 -13
  52. cmpnd-0.4.0/pyproject.toml +182 -0
  53. cmpnd-0.4.0/scripts/fixtures/review_run.json.gz +0 -0
  54. cmpnd-0.4.0/scripts/gepa_resume_bug.py +187 -0
  55. cmpnd-0.4.0/scripts/seed_review_run.py +504 -0
  56. cmpnd-0.4.0/stubs/dspy/__init__.pyi +134 -0
  57. cmpnd-0.4.0/stubs/dspy/primitives/__init__.pyi +1 -0
  58. cmpnd-0.4.0/stubs/dspy/primitives/prediction.pyi +3 -0
  59. cmpnd-0.4.0/stubs/dspy/utils/callback.pyi +19 -0
  60. cmpnd-0.4.0/tests/_exporter_helpers.py +21 -0
  61. cmpnd-0.4.0/tests/_identity_helpers.py +39 -0
  62. cmpnd-0.4.0/tests/e2e/committee_task.py +244 -0
  63. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/e2e/conftest.py +67 -11
  64. cmpnd-0.4.0/tests/e2e/data/committee_train.ndjson.gz +0 -0
  65. cmpnd-0.4.0/tests/e2e/data/committee_val.ndjson.gz +0 -0
  66. cmpnd-0.4.0/tests/e2e/fake_lm.py +221 -0
  67. cmpnd-0.4.0/tests/e2e/helpers.py +58 -0
  68. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/e2e/test_dataset_auto_creation.py +53 -53
  69. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/e2e/test_deploy_execute.py +61 -3
  70. cmpnd-0.4.0/tests/e2e/test_deploy_programs.py +277 -0
  71. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/e2e/test_gepa_optimization.py +5 -4
  72. cmpnd-0.4.0/tests/e2e/test_optimization_lineage.py +96 -0
  73. cmpnd-0.4.0/tests/e2e/test_optimize_failures.py +79 -0
  74. cmpnd-0.4.0/tests/e2e/test_optimize_loop.py +251 -0
  75. cmpnd-0.4.0/tests/e2e/test_optimize_review_committee.py +161 -0
  76. cmpnd-0.4.0/tests/e2e/test_optimize_server_owned_completion.py +203 -0
  77. cmpnd-0.4.0/tests/e2e/test_project_attribution.py +289 -0
  78. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/e2e/test_react_tool_spans.py +12 -0
  79. cmpnd-0.4.0/tests/e2e/test_save_load.py +246 -0
  80. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/e2e/test_user_attribution.py +1 -1
  81. cmpnd-0.4.0/tests/identity_vectors/01_predict_qa.json +27 -0
  82. cmpnd-0.4.0/tests/identity_vectors/02_predict_renamed_field.json +27 -0
  83. cmpnd-0.4.0/tests/identity_vectors/03_chain_of_thought_qa.json +27 -0
  84. cmpnd-0.4.0/tests/identity_vectors/04_predict_richer_types.json +56 -0
  85. cmpnd-0.4.0/tests/identity_vectors/05_composite_two_predicts.json +55 -0
  86. cmpnd-0.4.0/tests/identity_vectors/06_multichaincomparison.json +27 -0
  87. cmpnd-0.4.0/tests/identity_vectors/07_program_of_thought.json +27 -0
  88. cmpnd-0.4.0/tests/identity_vectors/08_opaque_module.json +8 -0
  89. cmpnd-0.4.0/tests/identity_vectors/09_predict_pydantic_field.json +27 -0
  90. cmpnd-0.4.0/tests/identity_vectors/10_react.json +31 -0
  91. cmpnd-0.4.0/tests/identity_vectors/11_refine.json +31 -0
  92. cmpnd-0.4.0/tests/identity_vectors/12_retrieve.json +7 -0
  93. cmpnd-0.4.0/tests/identity_vectors/13_knn.json +8 -0
  94. cmpnd-0.4.0/tests/identity_vectors/14_parallel.json +55 -0
  95. cmpnd-0.4.0/tests/identity_vectors/README.md +14 -0
  96. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/integration/conftest.py +18 -0
  97. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/integration/test_cli_deploy_smoketest.py +28 -1
  98. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/integration/test_logs_renders_traces.py +13 -17
  99. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/integration/test_optimizing_progress.py +26 -9
  100. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/integration/test_unhappy_path.py +10 -18
  101. cmpnd-0.4.0/tests/parity/__init__.py +0 -0
  102. cmpnd-0.4.0/tests/parity/conftest.py +88 -0
  103. cmpnd-0.4.0/tests/parity/seed_parity.py +100 -0
  104. cmpnd-0.4.0/tests/parity/test_auth.py +67 -0
  105. cmpnd-0.4.0/tests/parity/test_datasets.py +159 -0
  106. cmpnd-0.4.0/tests/parity/test_deployments.py +66 -0
  107. cmpnd-0.4.0/tests/parity/test_evals.py +94 -0
  108. cmpnd-0.4.0/tests/parity/test_health.py +44 -0
  109. cmpnd-0.4.0/tests/parity/test_modules.py +39 -0
  110. cmpnd-0.4.0/tests/parity/test_optimizations.py +87 -0
  111. cmpnd-0.4.0/tests/parity/test_programs.py +85 -0
  112. cmpnd-0.4.0/tests/parity/test_signatures.py +45 -0
  113. cmpnd-0.4.0/tests/parity/test_trace_streaming.py +213 -0
  114. cmpnd-0.4.0/tests/parity/test_traces.py +224 -0
  115. cmpnd-0.4.0/tests/stress/__init__.py +0 -0
  116. cmpnd-0.4.0/tests/stress/_reliability.py +81 -0
  117. cmpnd-0.4.0/tests/stress/test_deploy_programs.py +199 -0
  118. cmpnd-0.4.0/tests/stress/test_multi_client_stress.py +366 -0
  119. cmpnd-0.4.0/tests/stress/test_optimize_client_committee.py +83 -0
  120. cmpnd-0.4.0/tests/stress/test_optimize_loop.py +282 -0
  121. cmpnd-0.4.0/tests/stress/test_optimize_stress.py +189 -0
  122. cmpnd-0.4.0/tests/stress/test_real_provider_twins.py +191 -0
  123. cmpnd-0.4.0/tests/stress/test_reliability.py +55 -0
  124. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_callback.py +262 -89
  125. cmpnd-0.4.0/tests/test_cli/__init__.py +0 -0
  126. cmpnd-0.4.0/tests/test_cli/test_admin.py +33 -0
  127. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_cli/test_api_client.py +27 -3
  128. cmpnd-0.4.0/tests/test_cli/test_credentials.py +63 -0
  129. cmpnd-0.4.0/tests/test_cli/test_login.py +179 -0
  130. cmpnd-0.4.0/tests/test_cli/test_main.py +127 -0
  131. cmpnd-0.4.0/tests/test_cli/test_org.py +67 -0
  132. cmpnd-0.4.0/tests/test_cli/test_parity.py +169 -0
  133. cmpnd-0.4.0/tests/test_cli/test_shell.py +593 -0
  134. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_cli/test_top.py +146 -21
  135. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_clustering/test_edge_cases.py +43 -43
  136. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_clustering/test_hash_consistency.py +31 -30
  137. cmpnd-0.2.0/tests/test_config.py → cmpnd-0.4.0/tests/test_configuration.py +66 -30
  138. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_context_propagation.py +2 -2
  139. cmpnd-0.4.0/tests/test_dataset_polling.py +89 -0
  140. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_dataset_sync.py +5 -5
  141. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_datasets.py +6 -6
  142. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_decorators.py +23 -23
  143. cmpnd-0.4.0/tests/test_deploy.py +416 -0
  144. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_eval.py +52 -46
  145. cmpnd-0.4.0/tests/test_eval_start.py +84 -0
  146. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_exception_paths.py +4 -4
  147. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_execute.py +97 -23
  148. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_exporter.py +264 -9
  149. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_exporter_real.py +6 -6
  150. cmpnd-0.4.0/tests/test_fake_lm.py +141 -0
  151. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_gepa_callback.py +105 -45
  152. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_gepa_integration.py +2 -2
  153. cmpnd-0.4.0/tests/test_gepa_resume.py +145 -0
  154. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_identity.py +48 -9
  155. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_integration.py +26 -23
  156. cmpnd-0.4.0/tests/test_ir_encoder_properties.py +340 -0
  157. cmpnd-0.4.0/tests/test_ir_golden_vectors.py +140 -0
  158. cmpnd-0.4.0/tests/test_ir_roundtrip.py +172 -0
  159. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_models.py +1 -30
  160. cmpnd-0.4.0/tests/test_optimization_start.py +93 -0
  161. cmpnd-0.4.0/tests/test_optimize.py +496 -0
  162. cmpnd-0.4.0/tests/test_optimize_lift.py +96 -0
  163. cmpnd-0.4.0/tests/test_optimize_terminal_failure.py +110 -0
  164. cmpnd-0.4.0/tests/test_packaging.py +374 -0
  165. cmpnd-0.4.0/tests/test_packaging_callable_source.py +239 -0
  166. cmpnd-0.4.0/tests/test_packaging_program_source.py +832 -0
  167. cmpnd-0.4.0/tests/test_program_lineage_persist.py +122 -0
  168. cmpnd-0.4.0/tests/test_project_payload.py +179 -0
  169. cmpnd-0.4.0/tests/test_reflector_properties.py +169 -0
  170. cmpnd-0.4.0/tests/test_reflector_smoke.py +522 -0
  171. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_repro_reactv2_bugs.py +7 -7
  172. cmpnd-0.4.0/tests/test_seed_review_run.py +120 -0
  173. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_stats_export.py +2 -2
  174. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_tracked_gepa.py +32 -52
  175. cmpnd-0.4.0/tests/test_tracker_enrich.py +81 -0
  176. {cmpnd-0.2.0 → cmpnd-0.4.0}/uv.lock +664 -155
  177. cmpnd-0.2.0/CLAUDE.md +0 -24
  178. cmpnd-0.2.0/README.md +0 -233
  179. cmpnd-0.2.0/cmpnd/__init__.py +0 -338
  180. cmpnd-0.2.0/cmpnd/cli/commands/auth.py +0 -19
  181. cmpnd-0.2.0/cmpnd/identity.py +0 -433
  182. cmpnd-0.2.0/cmpnd/packaging.py +0 -331
  183. cmpnd-0.2.0/pyproject.toml +0 -90
  184. cmpnd-0.2.0/tests/e2e/test_project_attribution.py +0 -147
  185. cmpnd-0.2.0/tests/e2e/test_span_thinning.py +0 -67
  186. cmpnd-0.2.0/tests/test_cli/test_main.py +0 -44
  187. cmpnd-0.2.0/tests/test_deploy.py +0 -279
  188. cmpnd-0.2.0/tests/test_packaging.py +0 -179
  189. cmpnd-0.2.0/tests/test_project_payload.py +0 -147
  190. cmpnd-0.2.0/tests/test_span_thinning.py +0 -183
  191. {cmpnd-0.2.0 → cmpnd-0.4.0}/.gitignore +0 -0
  192. {cmpnd-0.2.0 → cmpnd-0.4.0}/cmpnd/cli/commands/__init__.py +0 -0
  193. {cmpnd-0.2.0 → cmpnd-0.4.0}/docs/2026-04-02-code-review.md +0 -0
  194. {cmpnd-0.2.0 → cmpnd-0.4.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
  195. {cmpnd-0.2.0 → cmpnd-0.4.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
  196. /cmpnd-0.2.0/tests/e2e/__init__.py → /cmpnd-0.4.0/stubs/dspy/utils/__init__.pyi +0 -0
  197. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/__init__.py +0 -0
  198. {cmpnd-0.2.0/tests/integration → cmpnd-0.4.0/tests/e2e}/__init__.py +0 -0
  199. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/e2e/test_capture_prompts.py +0 -0
  200. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/e2e/test_composite_module.py +0 -0
  201. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/e2e/test_edge_cases.py +0 -0
  202. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/e2e/test_example_hash.py +0 -0
  203. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/e2e/test_lm_accuracy.py +0 -0
  204. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/e2e/test_module_tracing.py +0 -0
  205. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
  206. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/e2e/test_parallel_export.py +0 -0
  207. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/e2e/test_trace_structure.py +0 -0
  208. {cmpnd-0.2.0/tests/test_cli → cmpnd-0.4.0/tests/integration}/__init__.py +0 -0
  209. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_cli/conftest.py +0 -0
  210. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_cli/test_auth.py +0 -0
  211. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_cli/test_datasets.py +0 -0
  212. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_cli/test_deployments.py +0 -0
  213. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_cli/test_evals.py +0 -0
  214. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_cli/test_optimizations.py +0 -0
  215. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_cli/test_programs.py +0 -0
  216. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_cli/test_traces.py +0 -0
  217. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_clustering/__init__.py +0 -0
  218. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_clustering/conftest.py +0 -0
  219. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_clustering/test_type_conversion.py +0 -0
  220. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_context.py +0 -0
  221. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_schemas.py +0 -0
  222. {cmpnd-0.2.0 → cmpnd-0.4.0}/tests/test_truncation.py +0 -0
  223. {cmpnd-0.2.0 → cmpnd-0.4.0}/todo.md +0 -0
cmpnd-0.4.0/CLAUDE.md ADDED
@@ -0,0 +1,116 @@
1
+ # Claude Code Instructions
2
+
3
+ Part of the CMPND monorepo. See `../CLAUDE.md` for cross-cutting rules (hash architecture, null handling, testing philosophy).
4
+
5
+ ## Documentation discipline (docs are as-built, updated in the same commit)
6
+
7
+ This mirrors the practice that has kept `serve/docs/` in sync with the Go
8
+ code while the top-level `docs/` rotted: **the docs are part of the diff.**
9
+
10
+ - This component's docs live in `python-sdk/docs/`. They describe what the
11
+ SDK does **today**, not what it should do.
12
+ - A change that adds or removes a subsystem, swaps a seam (the callback, the
13
+ exporter transport, the IR/hash, the optimizer tracker), or changes the wire
14
+ shape the SDK emits **updates the matching doc in the same commit.** If a
15
+ change leaves a doc describing the old behavior, the change isn't done.
16
+ - The `cmpnd-*` skills under `.claude/skills/` point at these docs rather than
17
+ duplicating them — keep the doc authoritative and let the skill be the thin
18
+ trigger. (Anchor paths are checked by `bin/check-skill-anchors`.)
19
+ - `docs/plans/*` is append-only design history and is exempt — don't rewrite it.
20
+
21
+ ## Running Tests & Checks
22
+
23
+ ```bash
24
+ # uv provides the interpreter (managed Python); no venv activate needed.
25
+ uv run python -m pytest tests/ # all tests (excluding e2e)
26
+ uv run python -m pytest tests/ -k "not e2e" # skip e2e explicitly
27
+ uv run ruff check . # lint
28
+ uv run ruff format --check . # format check
29
+ ```
30
+
31
+ ## Key Files
32
+
33
+ - `cmpnd/identity.py` - Unified hash computations (xxhash, SHA256)
34
+ - `cmpnd/ir/` - Structural program-identity IR (algebra, encoder, hash, reflector)
35
+ - `cmpnd/callback.py` - DSPy instrumentation, signature extraction
36
+ - `cmpnd/optimizers/tracker.py` - Optimization tracking
37
+ - `cmpnd/packaging.py` - Source ZIP generation for deploy (ported from deploy/local_cmpnd.py)
38
+ - `cmpnd/deployment.py` - `deploy()` function: SDK -> serve -> AWS deploy stack
39
+
40
+ ## Reflector ↔ DSPy compatibility
41
+
42
+ `cmpnd/ir/reflection.py` reads internal attributes off live `dspy.Module`
43
+ instances (`m.signature`, `m.predict.signature`, `m.tools`, `m.module`,
44
+ `m.trainset`, …). A DSPy release that renames or removes any of those
45
+ breaks every customer's traces and optimization runs with an unhandled
46
+ `AttributeError`.
47
+
48
+ `tests/test_reflector_smoke.py` is the contract. Its `KIND_FIXTURES`
49
+ table declares one fixture per IR kind (the DSPy class to construct,
50
+ the attributes the reflector reads, the expected IR shape). Two
51
+ meta-tests fail loudly when this table drifts out of sync:
52
+
53
+ - new `Module` kind in `cmpnd/ir/program.py` without a fixture
54
+ - new `isinstance(m, dspy.X)` or `_opt("X")` branch in
55
+ `cmpnd/ir/reflection.py` without a fixture
56
+
57
+ **When you change the reflector or add an IR kind, update
58
+ `KIND_FIXTURES` in the same change.** When you bump DSPy, run
59
+ `pytest tests/test_reflector_smoke.py` first — failures point at the
60
+ exact attribute or class that moved.
61
+ ## CLI has two front-ends — kept in sync by `cli/verbs.py`
62
+
63
+ `cmpnd <subcommand>` (argparse, one-shot) and bare `cmpnd` (interactive
64
+ Python shell, `cmpnd/cli/shell.py`) drive the **same** handler functions
65
+ in `cmpnd/cli/commands/`. The dual binding for every cross-cutting verb
66
+ (ps, deploy, status, logs, inspect, run) is declared **once** in
67
+ `cmpnd/cli/verbs.py`. Both front-ends consume that list:
68
+
69
+ - `top.register()` resolves `set_defaults(func=...)` through `verbs.by_name`.
70
+ - `Deployment` in `shell.py` is decorated with `@verbs.assert_entity_methods`,
71
+ which fails at import time if a verb's method is missing.
72
+ - `tests/test_cli/test_parity.py` enumerates the actual argparse choices,
73
+ REPL namespace, and entity methods and cross-checks them against VERBS.
74
+ Adding a CLI verb without a shell binding (or vice versa) fails CI.
75
+
76
+ **To add a new dual-front-end verb**: add a `Verb(...)` row to
77
+ `cli/verbs.py`, add the `add_parser` call in `top.register()`, and add
78
+ the method on the entity (or function in `_make_namespace`). All three
79
+ must agree; the parity test asserts so.
80
+
81
+ When you touch a handler:
82
+
83
+ - **User-facing hints must use `_hint_*` helpers** in `cli/commands/top.py`
84
+ (`_hint_ps`, `_hint_run`, `_hint_status`, `_hint_inspect`,
85
+ `_hint_traces_search`). They branch on `_SHELL_MODE` to emit
86
+ `deps.d_xxx.run({...})` inside the shell vs `cmpnd run d_xxx '{...}'`
87
+ outside. Never hardcode `cmpnd <verb>` strings in handler output.
88
+
89
+ - **New shell-addressable entity**: subclass `Entity` in `cli/shell.py`,
90
+ add a `_<entity>_key` function returning a Python-safe attribute name
91
+ (UUID-leading entities need a non-digit prefix like `d_` / `t_`), and
92
+ expose it via `Registry(...)` in `_make_namespace` (top-level) or as a
93
+ `@property` on a parent entity (scoped, see `Deployment.traces`).
94
+
95
+ - **Table-as-repr**: if you want typing the registry at the REPL to
96
+ render the same table as the corresponding CLI `ps`-style command,
97
+ give the Entity subclass a `_render_table(cls, entities) -> str`
98
+ classmethod. Reuse the CLI's renderer by extracting it from the
99
+ handler (see `top.render_deployments_table`) so the two front-ends
100
+ produce byte-identical tables.
101
+
102
+ - **Attribute-name scheme has one home**: `top._attr_name` (deployments,
103
+ `d_<8hex>`) and `top._trace_attr_name` (traces, `t_<8hex>`) are the
104
+ canonical sources. `shell._deployment_key` / `shell._trace_key` are
105
+ thin record-adapters over them, and the CLI's table renderers swap
106
+ the id column to the attribute form when `_SHELL_MODE` is on, so what
107
+ the user reads is exactly what they tab-complete.
108
+
109
+ - **Test isolation**: `shell.launch()` flips the module-level
110
+ `top._SHELL_MODE`. The autouse `_reset_shell_mode` fixture in
111
+ `tests/test_cli/test_shell.py` restores it; any new test that mutates
112
+ `_SHELL_MODE` outside that file must restore it itself.
113
+
114
+ ## Release
115
+
116
+ Tag-free. Bump `[project] version` in `pyproject.toml` (`uv version --bump {minor,patch}` or by hand), open a PR, merge to `main`. A change to that version on `main` triggers `.github/workflows/release-sdk.yml`, which publishes to PyPI only when the version is new (build → TestPyPI → PyPI); a merge that doesn't bump the version no-ops. Runtime `cmpnd.__version__` reads installed metadata. Full flow: [`docs/releasing.md`](../docs/releasing.md) Part 3 and `docs/plans/2026-07-02-sdk-pypi-tag-free-publish.md`.
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmpnd
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: DSPy observability and deployment SDK for cmpnd
5
5
  Project-URL: Homepage, https://cmpnd.ai
6
- Author-email: cmpnd <hello@cmpnd.io>
6
+ Author-email: cmpnd <hello@cmpnd.ai>
7
7
  License: MIT
8
8
  Keywords: dspy,llm,observability,tracing
9
9
  Classifier: Development Status :: 3 - Alpha
@@ -19,12 +19,16 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
19
  Requires-Python: >=3.10
20
20
  Requires-Dist: dspy>=3.1.3
21
21
  Requires-Dist: httpx>=0.27.0
22
+ Requires-Dist: ipython>=8.20
22
23
  Requires-Dist: pydantic>=2.0
23
24
  Requires-Dist: xxhash>=3.0.0
24
25
  Provides-Extra: dev
26
+ Requires-Dist: basedpyright>=1.39; extra == 'dev'
27
+ Requires-Dist: hypothesis>=6.100; extra == 'dev'
25
28
  Requires-Dist: jsonschema>=4.0; extra == 'dev'
26
- Requires-Dist: mypy>=1.0; extra == 'dev'
27
29
  Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
30
+ Requires-Dist: pytest-rerunfailures>=14.0; extra == 'dev'
31
+ Requires-Dist: pytest-xdist>=3.0; extra == 'dev'
28
32
  Requires-Dist: pytest>=8.0; extra == 'dev'
29
33
  Requires-Dist: ruff>=0.4.0; extra == 'dev'
30
34
  Provides-Extra: stub
@@ -43,7 +47,7 @@ Automatically trace your DSPy programs with one line of code, and deploy them wi
43
47
  ## Installation
44
48
 
45
49
  ```bash
46
- pip install cmpnd
50
+ uv add cmpnd # or: pip install cmpnd
47
51
  ```
48
52
 
49
53
  ## Quick Start
@@ -52,7 +56,7 @@ pip install cmpnd
52
56
  import cmpnd
53
57
 
54
58
  # Configure with your API key
55
- cmpnd.configure(api_key="ct_xxx", project="my-project")
59
+ cmpnd.configure(api_key="ck_xxx", project="my-project")
56
60
 
57
61
  # Enable automatic DSPy instrumentation
58
62
  cmpnd.auto_instrument()
@@ -66,6 +70,9 @@ dspy.configure(lm=lm)
66
70
  cot = dspy.ChainOfThought("question -> answer")
67
71
  result = cot(question="What is DSPy?")
68
72
  # Trace is automatically sent to cmpnd
73
+
74
+ # Correlate the result with its platform trace (e.g. to attach feedback later):
75
+ cmpnd.trace_id(result) # "a1b2c3d4-..." (None if not produced under instrumentation)
69
76
  ```
70
77
 
71
78
  ## Configuration
@@ -73,7 +80,7 @@ result = cot(question="What is DSPy?")
73
80
  ### Using environment variables
74
81
 
75
82
  ```bash
76
- export CMPND_API_KEY="ct_your_api_key"
83
+ export CMPND_API_KEY="ck_your_api_key"
77
84
  export CMPND_ENDPOINT="https://platform.cmpnd.ai" # optional
78
85
  ```
79
86
 
@@ -88,7 +95,7 @@ cmpnd.auto_instrument()
88
95
 
89
96
  ```python
90
97
  cmpnd.configure(
91
- api_key="ct_xxx", # Required: API key
98
+ api_key="ck_xxx", # Required: API key
92
99
  endpoint="https://platform.cmpnd.ai", # Optional: Backend URL
93
100
  project="my-project", # Optional: Project name
94
101
  batch_size=100, # Optional: Batch size for export
@@ -107,16 +114,20 @@ Deploy a DSPy module to the cmpnd platform for server-side execution:
107
114
  import cmpnd
108
115
  import dspy
109
116
 
110
- cmpnd.configure(api_key="ct_xxx")
117
+ cmpnd.configure(api_key="ck_xxx")
111
118
 
112
119
  lm = dspy.LM("groq/llama-3.1-8b-instant", temperature=0.0, max_tokens=16384)
113
120
  dspy.configure(lm=lm)
114
121
 
115
122
  module = dspy.Predict("question -> answer")
116
- deployment_id = cmpnd.deploy(module)
123
+ deployed = cmpnd.deploy(module)
124
+
125
+ # The returned program is callable — run it server-side like the local module:
126
+ result = deployed(question="What is DSPy?")
127
+ print(result.answer)
117
128
  ```
118
129
 
119
- The SDK packages the module's source code into a ZIP, uploads it to the platform, and polls until the server-side parser finishes. Returns a `deployment_id` for future use with `cmpnd.execute()` (coming soon).
130
+ The SDK packages the module's source code into a ZIP, uploads it to the platform, and polls until the server-side parser finishes. Returns a `DeployedProgram` you can **call directly** to execute server-side; `str(deployed)` is its `deployment_id` and `deployed.deployment_id` exposes it explicitly.
120
131
 
121
132
  ### With a metric (for optimization)
122
133
 
@@ -143,10 +154,82 @@ cmpnd.deploy(
143
154
  2. `POST /api/v1/deployments` creates a deployment and returns a presigned S3 upload URL
144
155
  3. The source ZIP is uploaded directly to S3
145
156
  4. The server-side parser extracts the program and LM configuration
146
- 5. The SDK polls until status is `ready`, then returns the `deployment_id`
157
+ 5. The SDK polls until status is `ready`, then returns the `DeployedProgram`
147
158
 
148
159
  The deployed program's LM model string (e.g. `groq/llama-3.1-8b-instant`) and configuration (temperature, max_tokens) are extracted automatically from the source.
149
160
 
161
+ ## Optimization
162
+
163
+ Optimize a DSPy program through the platform. `cmpnd.optimize()` mirrors `cmpnd.deploy()`: **it returns a `DeployedProgram` you call directly** — it just also tells you the program's `score`.
164
+
165
+ ```python
166
+ import cmpnd
167
+ import dspy
168
+
169
+ cmpnd.configure(api_key="ck_xxx")
170
+ dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))
171
+
172
+ program = dspy.ChainOfThought("topic -> haiku")
173
+
174
+ # gepa needs a 5-arg feedback metric returning a dspy.Prediction(score=, feedback=)
175
+ def metric(example, pred, trace=None, pred_name=None, pred_trace=None):
176
+ ok = example.season.lower() not in pred.haiku.lower()
177
+ return dspy.Prediction(score=float(ok),
178
+ feedback=None if ok else "Don't name the season.")
179
+
180
+ best = cmpnd.optimize(
181
+ program,
182
+ optimizer="gepa", # or "bootstrap_fewshot", "random_search"
183
+ trainset=train,
184
+ valset=val,
185
+ metric=metric,
186
+ reflection_model="groq/llama-3.3-70b-versatile", # gepa only
187
+ )
188
+
189
+ print(best.score) # best score found
190
+ print(best(topic="late autumn").haiku) # call it directly — same as deploy()
191
+ ```
192
+
193
+ The metric's arity must match the optimizer (checked before any network call): **gepa** needs a 5-arg feedback metric, while **bootstrap_fewshot** / **random_search** need a `<=3`-arg scalar metric.
194
+
195
+ ### Reusing an existing deployment
196
+
197
+ Skip the initial deploy by passing the `deployment_id` of a ready deployment as the starting program:
198
+
199
+ ```python
200
+ best = cmpnd.optimize(program, optimizer="bootstrap_fewshot",
201
+ trainset=train, valset=val, metric=metric,
202
+ deployment_id=deployed.deployment_id)
203
+ ```
204
+
205
+ ### Options
206
+
207
+ ```python
208
+ cmpnd.optimize(
209
+ program, # Required: the DSPy module to optimize
210
+ optimizer="gepa", # Required: "gepa" | "bootstrap_fewshot" | "random_search"
211
+ trainset=train, # Required: training examples
212
+ valset=val, # Required: validation examples
213
+ metric=metric, # Required: the evaluation metric (arity-checked)
214
+ config=None, # Optional: optimizer-specific config (allow-listed server-side)
215
+ deployment_id=None, # Optional: reuse a ready deployment as the starting program
216
+ reflection_model=None, # Optional (gepa): litellm model for the reflection LM
217
+ max_steps=10, # Optional: maximum optimize steps
218
+ patience=2, # Optional: stop after N consecutive non-improving steps
219
+ seed=0, # Optional: starting seed; advanced each step
220
+ )
221
+ ```
222
+
223
+ On failure, `optimize()` raises `cmpnd.OptimizeError` (a 4xx initiating a step — e.g. 409 no-data / mismatch) or `cmpnd.OptimizeFailed` (a step that transitioned to `failed`).
224
+
225
+ ### From the CLI
226
+
227
+ ```bash
228
+ cmpnd optimize gepa task.py --reflection-model groq/llama-3.3-70b-versatile
229
+ ```
230
+
231
+ The snippet (a `.py` file or `-` for stdin) binds `program`, `trainset`, `valset`, and `metric`; the CLI prints the best deployment id.
232
+
150
233
  ## Custom Spans
151
234
 
152
235
  Add custom spans to trace non-DSPy code:
@@ -236,13 +319,25 @@ The callback class for manual registration:
236
319
  import dspy
237
320
  import cmpnd
238
321
 
239
- cmpnd.configure(api_key="ct_xxx")
322
+ cmpnd.configure(api_key="ck_xxx")
240
323
  dspy.configure(callbacks=[cmpnd.CmpndCallback()])
241
324
  ```
242
325
 
243
326
  ### `cmpnd.deploy()`
244
327
 
245
- Deploy a DSPy module to the cmpnd platform. Returns the `deployment_id`.
328
+ Deploy a DSPy module to the cmpnd platform. Returns a callable `DeployedProgram`.
329
+
330
+ ### `cmpnd.optimize()`
331
+
332
+ Optimize a DSPy program through the platform. Returns the best `DeployedProgram` (call it directly; `.score` is the best score found). Raises `cmpnd.OptimizeError` / `cmpnd.OptimizeFailed` on failure.
333
+
334
+ ### `cmpnd.execute()`
335
+
336
+ Execute a deployed program by `deployment_id`, returning a `dspy.Prediction`. Usually you just call a `DeployedProgram` instead, which dispatches here.
337
+
338
+ ### `cmpnd.DeployedProgram`
339
+
340
+ A deployed, callable program returned by `cmpnd.deploy()` and `cmpnd.optimize()`. Call it with the original module's input fields; `str()` and `.deployment_id` give its id, and `.score` is its score when it came from `optimize()` (else `None`).
246
341
 
247
342
  ### `cmpnd.trace()`
248
343
 
@@ -260,6 +355,14 @@ Get the current trace (if any).
260
355
 
261
356
  Get the current span (if any).
262
357
 
358
+ ### `cmpnd.trace_id()`
359
+
360
+ Return the platform trace id stamped on a prediction returned by an instrumented program, or `None` if it wasn't produced under instrumentation. Use it to correlate a prediction with its trace, e.g. to attach feedback via `POST /api/v1/traces/{cmpnd.trace_id(pred)}/metadata`.
361
+
362
+ ### `cmpnd.flush_exporter()`
363
+
364
+ Force-publish everything the background exporter has queued, keeping it running. Use this when a mid-run reader needs a just-produced trace to exist server-side before fetching it — unlike `shutdown_exporter()`, the exporter stays alive so later spans still publish. Returns `True` if the drain completed within the timeout, `False` if it timed out or no exporter is running — check it before fetching so you don't read a trace that hasn't published yet.
365
+
263
366
  ### `cmpnd.shutdown_exporter()`
264
367
 
265
368
  Gracefully shutdown the background exporter.
cmpnd-0.4.0/README.md ADDED
@@ -0,0 +1,332 @@
1
+ # cmpnd
2
+
3
+ DSPy observability and deployment SDK for cmpnd.
4
+
5
+ Automatically trace your DSPy programs with one line of code, and deploy them with one more.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ uv add cmpnd # or: pip install cmpnd
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ import cmpnd
17
+
18
+ # Configure with your API key
19
+ cmpnd.configure(api_key="ck_xxx", project="my-project")
20
+
21
+ # Enable automatic DSPy instrumentation
22
+ cmpnd.auto_instrument()
23
+
24
+ # Your DSPy code is now automatically traced!
25
+ import dspy
26
+
27
+ lm = dspy.LM("openai/gpt-4o-mini")
28
+ dspy.configure(lm=lm)
29
+
30
+ cot = dspy.ChainOfThought("question -> answer")
31
+ result = cot(question="What is DSPy?")
32
+ # Trace is automatically sent to cmpnd
33
+
34
+ # Correlate the result with its platform trace (e.g. to attach feedback later):
35
+ cmpnd.trace_id(result) # "a1b2c3d4-..." (None if not produced under instrumentation)
36
+ ```
37
+
38
+ ## Configuration
39
+
40
+ ### Using environment variables
41
+
42
+ ```bash
43
+ export CMPND_API_KEY="ck_your_api_key"
44
+ export CMPND_ENDPOINT="https://platform.cmpnd.ai" # optional
45
+ ```
46
+
47
+ ```python
48
+ import cmpnd
49
+
50
+ cmpnd.configure() # Reads from environment
51
+ cmpnd.auto_instrument()
52
+ ```
53
+
54
+ ### Configuration options
55
+
56
+ ```python
57
+ cmpnd.configure(
58
+ api_key="ck_xxx", # Required: API key
59
+ endpoint="https://platform.cmpnd.ai", # Optional: Backend URL
60
+ project="my-project", # Optional: Project name
61
+ batch_size=100, # Optional: Batch size for export
62
+ flush_interval_seconds=5.0, # Optional: Flush interval
63
+ capture_inputs=True, # Optional: Capture function inputs
64
+ capture_outputs=True, # Optional: Capture function outputs
65
+ default_tags={"env": "prod"}, # Optional: Tags for all traces
66
+ )
67
+ ```
68
+
69
+ ## Deployment
70
+
71
+ Deploy a DSPy module to the cmpnd platform for server-side execution:
72
+
73
+ ```python
74
+ import cmpnd
75
+ import dspy
76
+
77
+ cmpnd.configure(api_key="ck_xxx")
78
+
79
+ lm = dspy.LM("groq/llama-3.1-8b-instant", temperature=0.0, max_tokens=16384)
80
+ dspy.configure(lm=lm)
81
+
82
+ module = dspy.Predict("question -> answer")
83
+ deployed = cmpnd.deploy(module)
84
+
85
+ # The returned program is callable — run it server-side like the local module:
86
+ result = deployed(question="What is DSPy?")
87
+ print(result.answer)
88
+ ```
89
+
90
+ The SDK packages the module's source code into a ZIP, uploads it to the platform, and polls until the server-side parser finishes. Returns a `DeployedProgram` you can **call directly** to execute server-side; `str(deployed)` is its `deployment_id` and `deployed.deployment_id` exposes it explicitly.
91
+
92
+ ### With a metric (for optimization)
93
+
94
+ ```python
95
+ def accuracy(example, pred, trace=None):
96
+ return example.answer == pred.answer
97
+
98
+ deployment_id = cmpnd.deploy(module, metric=accuracy)
99
+ ```
100
+
101
+ ### Options
102
+
103
+ ```python
104
+ cmpnd.deploy(
105
+ module, # Required: a DSPy module
106
+ metric=None, # Optional: metric callable for optimization support
107
+ timeout=300, # Optional: seconds to wait for parsing (default 300)
108
+ )
109
+ ```
110
+
111
+ ### How it works
112
+
113
+ 1. The SDK generates source code from the live module (LM config, signature, types)
114
+ 2. `POST /api/v1/deployments` creates a deployment and returns a presigned S3 upload URL
115
+ 3. The source ZIP is uploaded directly to S3
116
+ 4. The server-side parser extracts the program and LM configuration
117
+ 5. The SDK polls until status is `ready`, then returns the `DeployedProgram`
118
+
119
+ The deployed program's LM model string (e.g. `groq/llama-3.1-8b-instant`) and configuration (temperature, max_tokens) are extracted automatically from the source.
120
+
121
+ ## Optimization
122
+
123
+ Optimize a DSPy program through the platform. `cmpnd.optimize()` mirrors `cmpnd.deploy()`: **it returns a `DeployedProgram` you call directly** — it just also tells you the program's `score`.
124
+
125
+ ```python
126
+ import cmpnd
127
+ import dspy
128
+
129
+ cmpnd.configure(api_key="ck_xxx")
130
+ dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))
131
+
132
+ program = dspy.ChainOfThought("topic -> haiku")
133
+
134
+ # gepa needs a 5-arg feedback metric returning a dspy.Prediction(score=, feedback=)
135
+ def metric(example, pred, trace=None, pred_name=None, pred_trace=None):
136
+ ok = example.season.lower() not in pred.haiku.lower()
137
+ return dspy.Prediction(score=float(ok),
138
+ feedback=None if ok else "Don't name the season.")
139
+
140
+ best = cmpnd.optimize(
141
+ program,
142
+ optimizer="gepa", # or "bootstrap_fewshot", "random_search"
143
+ trainset=train,
144
+ valset=val,
145
+ metric=metric,
146
+ reflection_model="groq/llama-3.3-70b-versatile", # gepa only
147
+ )
148
+
149
+ print(best.score) # best score found
150
+ print(best(topic="late autumn").haiku) # call it directly — same as deploy()
151
+ ```
152
+
153
+ The metric's arity must match the optimizer (checked before any network call): **gepa** needs a 5-arg feedback metric, while **bootstrap_fewshot** / **random_search** need a `<=3`-arg scalar metric.
154
+
155
+ ### Reusing an existing deployment
156
+
157
+ Skip the initial deploy by passing the `deployment_id` of a ready deployment as the starting program:
158
+
159
+ ```python
160
+ best = cmpnd.optimize(program, optimizer="bootstrap_fewshot",
161
+ trainset=train, valset=val, metric=metric,
162
+ deployment_id=deployed.deployment_id)
163
+ ```
164
+
165
+ ### Options
166
+
167
+ ```python
168
+ cmpnd.optimize(
169
+ program, # Required: the DSPy module to optimize
170
+ optimizer="gepa", # Required: "gepa" | "bootstrap_fewshot" | "random_search"
171
+ trainset=train, # Required: training examples
172
+ valset=val, # Required: validation examples
173
+ metric=metric, # Required: the evaluation metric (arity-checked)
174
+ config=None, # Optional: optimizer-specific config (allow-listed server-side)
175
+ deployment_id=None, # Optional: reuse a ready deployment as the starting program
176
+ reflection_model=None, # Optional (gepa): litellm model for the reflection LM
177
+ max_steps=10, # Optional: maximum optimize steps
178
+ patience=2, # Optional: stop after N consecutive non-improving steps
179
+ seed=0, # Optional: starting seed; advanced each step
180
+ )
181
+ ```
182
+
183
+ On failure, `optimize()` raises `cmpnd.OptimizeError` (a 4xx initiating a step — e.g. 409 no-data / mismatch) or `cmpnd.OptimizeFailed` (a step that transitioned to `failed`).
184
+
185
+ ### From the CLI
186
+
187
+ ```bash
188
+ cmpnd optimize gepa task.py --reflection-model groq/llama-3.3-70b-versatile
189
+ ```
190
+
191
+ The snippet (a `.py` file or `-` for stdin) binds `program`, `trainset`, `valset`, and `metric`; the CLI prints the best deployment id.
192
+
193
+ ## Custom Spans
194
+
195
+ Add custom spans to trace non-DSPy code:
196
+
197
+ ### Using the decorator
198
+
199
+ ```python
200
+ from cmpnd import trace, SpanType
201
+
202
+ @trace(name="fetch_documents", span_type=SpanType.RETRIEVE)
203
+ def fetch_documents(query: str) -> list[str]:
204
+ # Your retrieval logic
205
+ return documents
206
+ ```
207
+
208
+ ### Using the context manager
209
+
210
+ ```python
211
+ from cmpnd import start_span, SpanType
212
+
213
+ def run_pipeline(query: str):
214
+ with start_span("vector_search", span_type=SpanType.RETRIEVE) as span:
215
+ span.set_attribute("index", "my-faiss-index")
216
+ docs = search(query)
217
+ span.set_outputs({"doc_count": len(docs)})
218
+
219
+ return generate(query, docs)
220
+ ```
221
+
222
+ ## What Gets Traced
223
+
224
+ The SDK automatically captures:
225
+
226
+ ### Module Execution
227
+ - Module type (Predict, ChainOfThought, ReAct, etc.)
228
+ - Signature name and instructions
229
+ - Input/output field names
230
+ - Demo count
231
+
232
+ ### LM Calls
233
+ - Model name and provider
234
+ - Token usage (prompt, completion, total)
235
+ - Request/response content
236
+
237
+ ### Adapters
238
+ - Format and parse operations
239
+ - Input/output transformations
240
+
241
+ ### Tools
242
+ - Tool name and description
243
+ - Invocation inputs/outputs
244
+
245
+ ### Evaluations
246
+ - Evaluation scores
247
+ - Program being evaluated
248
+
249
+ ## Span Types
250
+
251
+ Available span types for categorization:
252
+
253
+ - `SpanType.MODULE` - Generic DSPy module
254
+ - `SpanType.PREDICT` - Predict module
255
+ - `SpanType.CHAIN_OF_THOUGHT` - ChainOfThought module
256
+ - `SpanType.REACT` - ReAct agent
257
+ - `SpanType.RETRIEVE` - Retrieval operations
258
+ - `SpanType.LM_CALL` - Language model calls
259
+ - `SpanType.ADAPTER_FORMAT` - Adapter formatting
260
+ - `SpanType.ADAPTER_PARSE` - Adapter parsing
261
+ - `SpanType.TOOL` - Tool invocations
262
+ - `SpanType.EVALUATION` - Evaluation runs
263
+
264
+ ## API Reference
265
+
266
+ ### `cmpnd.configure()`
267
+
268
+ Initialize the SDK with your API key and options.
269
+
270
+ ### `cmpnd.auto_instrument()`
271
+
272
+ Automatically register the callback with DSPy.
273
+
274
+ ### `cmpnd.CmpndCallback`
275
+
276
+ The callback class for manual registration:
277
+
278
+ ```python
279
+ import dspy
280
+ import cmpnd
281
+
282
+ cmpnd.configure(api_key="ck_xxx")
283
+ dspy.configure(callbacks=[cmpnd.CmpndCallback()])
284
+ ```
285
+
286
+ ### `cmpnd.deploy()`
287
+
288
+ Deploy a DSPy module to the cmpnd platform. Returns a callable `DeployedProgram`.
289
+
290
+ ### `cmpnd.optimize()`
291
+
292
+ Optimize a DSPy program through the platform. Returns the best `DeployedProgram` (call it directly; `.score` is the best score found). Raises `cmpnd.OptimizeError` / `cmpnd.OptimizeFailed` on failure.
293
+
294
+ ### `cmpnd.execute()`
295
+
296
+ Execute a deployed program by `deployment_id`, returning a `dspy.Prediction`. Usually you just call a `DeployedProgram` instead, which dispatches here.
297
+
298
+ ### `cmpnd.DeployedProgram`
299
+
300
+ A deployed, callable program returned by `cmpnd.deploy()` and `cmpnd.optimize()`. Call it with the original module's input fields; `str()` and `.deployment_id` give its id, and `.score` is its score when it came from `optimize()` (else `None`).
301
+
302
+ ### `cmpnd.trace()`
303
+
304
+ Decorator for custom traced functions.
305
+
306
+ ### `cmpnd.start_span()`
307
+
308
+ Context manager for custom spans.
309
+
310
+ ### `cmpnd.get_current_trace()`
311
+
312
+ Get the current trace (if any).
313
+
314
+ ### `cmpnd.get_current_span()`
315
+
316
+ Get the current span (if any).
317
+
318
+ ### `cmpnd.trace_id()`
319
+
320
+ Return the platform trace id stamped on a prediction returned by an instrumented program, or `None` if it wasn't produced under instrumentation. Use it to correlate a prediction with its trace, e.g. to attach feedback via `POST /api/v1/traces/{cmpnd.trace_id(pred)}/metadata`.
321
+
322
+ ### `cmpnd.flush_exporter()`
323
+
324
+ Force-publish everything the background exporter has queued, keeping it running. Use this when a mid-run reader needs a just-produced trace to exist server-side before fetching it — unlike `shutdown_exporter()`, the exporter stays alive so later spans still publish. Returns `True` if the drain completed within the timeout, `False` if it timed out or no exporter is running — check it before fetching so you don't read a trace that hasn't published yet.
325
+
326
+ ### `cmpnd.shutdown_exporter()`
327
+
328
+ Gracefully shutdown the background exporter.
329
+
330
+ ## License
331
+
332
+ MIT