cmpnd 0.1.0__tar.gz → 0.3.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 (203) hide show
  1. cmpnd-0.3.0/.gitignore +43 -0
  2. cmpnd-0.3.0/CLAUDE.md +116 -0
  3. cmpnd-0.3.0/PKG-INFO +372 -0
  4. cmpnd-0.3.0/README.md +332 -0
  5. cmpnd-0.3.0/cmpnd/__init__.py +132 -0
  6. cmpnd-0.3.0/cmpnd/_gepa_patch.py +156 -0
  7. cmpnd-0.3.0/cmpnd/callback.py +979 -0
  8. cmpnd-0.3.0/cmpnd/cli/__init__.py +163 -0
  9. cmpnd-0.3.0/cmpnd/cli/api_client.py +115 -0
  10. cmpnd-0.3.0/cmpnd/cli/commands/admin.py +59 -0
  11. cmpnd-0.3.0/cmpnd/cli/commands/auth.py +22 -0
  12. cmpnd-0.3.0/cmpnd/cli/commands/datasets.py +31 -0
  13. cmpnd-0.3.0/cmpnd/cli/commands/deployments.py +56 -0
  14. cmpnd-0.3.0/cmpnd/cli/commands/evals.py +42 -0
  15. cmpnd-0.3.0/cmpnd/cli/commands/login.py +251 -0
  16. cmpnd-0.3.0/cmpnd/cli/commands/optimizations.py +60 -0
  17. cmpnd-0.3.0/cmpnd/cli/commands/org.py +115 -0
  18. cmpnd-0.3.0/cmpnd/cli/commands/programs.py +55 -0
  19. cmpnd-0.3.0/cmpnd/cli/commands/top.py +997 -0
  20. cmpnd-0.3.0/cmpnd/cli/commands/traces.py +63 -0
  21. cmpnd-0.3.0/cmpnd/cli/credentials.py +95 -0
  22. cmpnd-0.3.0/cmpnd/cli/shell.py +691 -0
  23. cmpnd-0.3.0/cmpnd/cli/verbs.py +113 -0
  24. cmpnd-0.3.0/cmpnd/configuration.py +196 -0
  25. cmpnd-0.3.0/cmpnd/context.py +119 -0
  26. cmpnd-0.3.0/cmpnd/dataset_sync.py +127 -0
  27. cmpnd-0.3.0/cmpnd/datasets.py +219 -0
  28. cmpnd-0.3.0/cmpnd/decorators.py +143 -0
  29. cmpnd-0.3.0/cmpnd/deployment.py +267 -0
  30. cmpnd-0.3.0/cmpnd/eval_handler.py +368 -0
  31. cmpnd-0.3.0/cmpnd/execution.py +177 -0
  32. cmpnd-0.3.0/cmpnd/exporter.py +785 -0
  33. cmpnd-0.3.0/cmpnd/helpers.py +220 -0
  34. cmpnd-0.3.0/cmpnd/identity.py +247 -0
  35. cmpnd-0.3.0/cmpnd/ir/__init__.py +91 -0
  36. cmpnd-0.3.0/cmpnd/ir/decode.py +230 -0
  37. cmpnd-0.3.0/cmpnd/ir/encode.py +134 -0
  38. cmpnd-0.3.0/cmpnd/ir/hash.py +12 -0
  39. cmpnd-0.3.0/cmpnd/ir/program.py +109 -0
  40. cmpnd-0.3.0/cmpnd/ir/reflection.py +167 -0
  41. cmpnd-0.3.0/cmpnd/ir/types.py +89 -0
  42. cmpnd-0.3.0/cmpnd/models.py +782 -0
  43. cmpnd-0.3.0/cmpnd/optimization.py +451 -0
  44. cmpnd-0.3.0/cmpnd/optimizers/__init__.py +31 -0
  45. cmpnd-0.3.0/cmpnd/optimizers/gepa.py +360 -0
  46. cmpnd-0.3.0/cmpnd/optimizers/gepa_callback.py +475 -0
  47. cmpnd-0.3.0/cmpnd/optimizers/tracker.py +1008 -0
  48. cmpnd-0.3.0/cmpnd/packaging.py +890 -0
  49. cmpnd-0.3.0/docs/2026-04-02-code-review.md +280 -0
  50. cmpnd-0.3.0/docs/plans/2026-04-05-train-val-dataset-capture-design.md +91 -0
  51. cmpnd-0.3.0/docs/plans/2026-04-05-train-val-dataset-capture.md +727 -0
  52. cmpnd-0.3.0/docs/sdk-instrumentation.md +219 -0
  53. cmpnd-0.3.0/examples/local_ollama.py +48 -0
  54. cmpnd-0.3.0/pyproject.toml +182 -0
  55. cmpnd-0.3.0/stubs/dspy/__init__.pyi +134 -0
  56. cmpnd-0.3.0/stubs/dspy/primitives/__init__.pyi +1 -0
  57. cmpnd-0.3.0/stubs/dspy/primitives/prediction.pyi +3 -0
  58. cmpnd-0.3.0/stubs/dspy/utils/__init__.pyi +0 -0
  59. cmpnd-0.3.0/stubs/dspy/utils/callback.pyi +19 -0
  60. cmpnd-0.3.0/tests/__init__.py +1 -0
  61. cmpnd-0.3.0/tests/_exporter_helpers.py +21 -0
  62. cmpnd-0.3.0/tests/_identity_helpers.py +39 -0
  63. cmpnd-0.3.0/tests/e2e/__init__.py +0 -0
  64. cmpnd-0.3.0/tests/e2e/committee_task.py +242 -0
  65. cmpnd-0.3.0/tests/e2e/conftest.py +336 -0
  66. cmpnd-0.3.0/tests/e2e/data/committee_train.ndjson +8 -0
  67. cmpnd-0.3.0/tests/e2e/data/committee_val.ndjson +5 -0
  68. cmpnd-0.3.0/tests/e2e/fake_lm.py +221 -0
  69. cmpnd-0.3.0/tests/e2e/helpers.py +58 -0
  70. cmpnd-0.3.0/tests/e2e/test_capture_prompts.py +129 -0
  71. cmpnd-0.3.0/tests/e2e/test_composite_module.py +86 -0
  72. cmpnd-0.3.0/tests/e2e/test_dataset_auto_creation.py +271 -0
  73. cmpnd-0.3.0/tests/e2e/test_deploy_execute.py +369 -0
  74. cmpnd-0.3.0/tests/e2e/test_deploy_programs.py +277 -0
  75. cmpnd-0.3.0/tests/e2e/test_edge_cases.py +62 -0
  76. cmpnd-0.3.0/tests/e2e/test_example_hash.py +150 -0
  77. cmpnd-0.3.0/tests/e2e/test_gepa_optimization.py +93 -0
  78. cmpnd-0.3.0/tests/e2e/test_lm_accuracy.py +99 -0
  79. cmpnd-0.3.0/tests/e2e/test_module_tracing.py +205 -0
  80. cmpnd-0.3.0/tests/e2e/test_optimization_metric_identity.py +111 -0
  81. cmpnd-0.3.0/tests/e2e/test_optimize_failures.py +79 -0
  82. cmpnd-0.3.0/tests/e2e/test_optimize_loop.py +251 -0
  83. cmpnd-0.3.0/tests/e2e/test_optimize_server_owned_completion.py +203 -0
  84. cmpnd-0.3.0/tests/e2e/test_parallel_export.py +104 -0
  85. cmpnd-0.3.0/tests/e2e/test_project_attribution.py +289 -0
  86. cmpnd-0.3.0/tests/e2e/test_react_tool_spans.py +91 -0
  87. cmpnd-0.3.0/tests/e2e/test_save_load.py +246 -0
  88. cmpnd-0.3.0/tests/e2e/test_trace_structure.py +180 -0
  89. cmpnd-0.3.0/tests/e2e/test_user_attribution.py +181 -0
  90. cmpnd-0.3.0/tests/identity_vectors/01_predict_qa.json +27 -0
  91. cmpnd-0.3.0/tests/identity_vectors/02_predict_renamed_field.json +27 -0
  92. cmpnd-0.3.0/tests/identity_vectors/03_chain_of_thought_qa.json +27 -0
  93. cmpnd-0.3.0/tests/identity_vectors/04_predict_richer_types.json +56 -0
  94. cmpnd-0.3.0/tests/identity_vectors/05_composite_two_predicts.json +55 -0
  95. cmpnd-0.3.0/tests/identity_vectors/06_multichaincomparison.json +27 -0
  96. cmpnd-0.3.0/tests/identity_vectors/07_program_of_thought.json +27 -0
  97. cmpnd-0.3.0/tests/identity_vectors/08_opaque_module.json +8 -0
  98. cmpnd-0.3.0/tests/identity_vectors/09_predict_pydantic_field.json +27 -0
  99. cmpnd-0.3.0/tests/identity_vectors/10_react.json +31 -0
  100. cmpnd-0.3.0/tests/identity_vectors/11_refine.json +31 -0
  101. cmpnd-0.3.0/tests/identity_vectors/12_retrieve.json +7 -0
  102. cmpnd-0.3.0/tests/identity_vectors/13_knn.json +8 -0
  103. cmpnd-0.3.0/tests/identity_vectors/14_parallel.json +55 -0
  104. cmpnd-0.3.0/tests/identity_vectors/README.md +14 -0
  105. cmpnd-0.3.0/tests/integration/__init__.py +0 -0
  106. cmpnd-0.3.0/tests/integration/conftest.py +94 -0
  107. cmpnd-0.3.0/tests/integration/test_cli_deploy_smoketest.py +151 -0
  108. cmpnd-0.3.0/tests/integration/test_logs_renders_traces.py +148 -0
  109. cmpnd-0.3.0/tests/integration/test_optimizing_progress.py +152 -0
  110. cmpnd-0.3.0/tests/integration/test_unhappy_path.py +188 -0
  111. cmpnd-0.3.0/tests/parity/__init__.py +0 -0
  112. cmpnd-0.3.0/tests/parity/conftest.py +101 -0
  113. cmpnd-0.3.0/tests/parity/seed_parity.py +135 -0
  114. cmpnd-0.3.0/tests/parity/test_auth.py +64 -0
  115. cmpnd-0.3.0/tests/parity/test_datasets.py +157 -0
  116. cmpnd-0.3.0/tests/parity/test_deployments.py +63 -0
  117. cmpnd-0.3.0/tests/parity/test_evals.py +90 -0
  118. cmpnd-0.3.0/tests/parity/test_health.py +42 -0
  119. cmpnd-0.3.0/tests/parity/test_modules.py +37 -0
  120. cmpnd-0.3.0/tests/parity/test_optimizations.py +84 -0
  121. cmpnd-0.3.0/tests/parity/test_programs.py +82 -0
  122. cmpnd-0.3.0/tests/parity/test_signatures.py +42 -0
  123. cmpnd-0.3.0/tests/parity/test_trace_streaming.py +216 -0
  124. cmpnd-0.3.0/tests/parity/test_traces.py +220 -0
  125. cmpnd-0.3.0/tests/stress/__init__.py +0 -0
  126. cmpnd-0.3.0/tests/stress/test_deploy_programs.py +165 -0
  127. cmpnd-0.3.0/tests/stress/test_multi_client_stress.py +366 -0
  128. cmpnd-0.3.0/tests/stress/test_optimize_loop.py +278 -0
  129. cmpnd-0.3.0/tests/stress/test_optimize_stress.py +185 -0
  130. cmpnd-0.3.0/tests/stress/test_real_provider_twins.py +177 -0
  131. cmpnd-0.3.0/tests/test_callback.py +2029 -0
  132. cmpnd-0.3.0/tests/test_cli/__init__.py +0 -0
  133. cmpnd-0.3.0/tests/test_cli/conftest.py +15 -0
  134. cmpnd-0.3.0/tests/test_cli/test_admin.py +33 -0
  135. cmpnd-0.3.0/tests/test_cli/test_api_client.py +147 -0
  136. cmpnd-0.3.0/tests/test_cli/test_auth.py +16 -0
  137. cmpnd-0.3.0/tests/test_cli/test_credentials.py +63 -0
  138. cmpnd-0.3.0/tests/test_cli/test_datasets.py +23 -0
  139. cmpnd-0.3.0/tests/test_cli/test_deployments.py +51 -0
  140. cmpnd-0.3.0/tests/test_cli/test_evals.py +33 -0
  141. cmpnd-0.3.0/tests/test_cli/test_login.py +179 -0
  142. cmpnd-0.3.0/tests/test_cli/test_main.py +127 -0
  143. cmpnd-0.3.0/tests/test_cli/test_optimizations.py +52 -0
  144. cmpnd-0.3.0/tests/test_cli/test_org.py +67 -0
  145. cmpnd-0.3.0/tests/test_cli/test_parity.py +169 -0
  146. cmpnd-0.3.0/tests/test_cli/test_programs.py +50 -0
  147. cmpnd-0.3.0/tests/test_cli/test_shell.py +593 -0
  148. cmpnd-0.3.0/tests/test_cli/test_top.py +605 -0
  149. cmpnd-0.3.0/tests/test_cli/test_traces.py +51 -0
  150. cmpnd-0.3.0/tests/test_clustering/__init__.py +6 -0
  151. cmpnd-0.3.0/tests/test_clustering/conftest.py +153 -0
  152. cmpnd-0.3.0/tests/test_clustering/test_edge_cases.py +324 -0
  153. cmpnd-0.3.0/tests/test_clustering/test_hash_consistency.py +266 -0
  154. cmpnd-0.3.0/tests/test_clustering/test_type_conversion.py +214 -0
  155. cmpnd-0.3.0/tests/test_configuration.py +214 -0
  156. cmpnd-0.3.0/tests/test_context.py +226 -0
  157. cmpnd-0.3.0/tests/test_context_propagation.py +49 -0
  158. cmpnd-0.3.0/tests/test_dataset_polling.py +89 -0
  159. cmpnd-0.3.0/tests/test_dataset_sync.py +497 -0
  160. cmpnd-0.3.0/tests/test_datasets.py +568 -0
  161. cmpnd-0.3.0/tests/test_decorators.py +302 -0
  162. cmpnd-0.3.0/tests/test_deploy.py +416 -0
  163. cmpnd-0.3.0/tests/test_eval.py +570 -0
  164. cmpnd-0.3.0/tests/test_eval_start.py +84 -0
  165. cmpnd-0.3.0/tests/test_exception_paths.py +302 -0
  166. cmpnd-0.3.0/tests/test_execute.py +234 -0
  167. cmpnd-0.3.0/tests/test_exporter.py +683 -0
  168. cmpnd-0.3.0/tests/test_exporter_real.py +461 -0
  169. cmpnd-0.3.0/tests/test_fake_lm.py +141 -0
  170. cmpnd-0.3.0/tests/test_gepa_callback.py +1800 -0
  171. cmpnd-0.3.0/tests/test_gepa_integration.py +241 -0
  172. cmpnd-0.3.0/tests/test_identity.py +515 -0
  173. cmpnd-0.3.0/tests/test_integration.py +400 -0
  174. cmpnd-0.3.0/tests/test_ir_encoder_properties.py +340 -0
  175. cmpnd-0.3.0/tests/test_ir_golden_vectors.py +140 -0
  176. cmpnd-0.3.0/tests/test_ir_roundtrip.py +172 -0
  177. cmpnd-0.3.0/tests/test_models.py +428 -0
  178. cmpnd-0.3.0/tests/test_optimization_start.py +93 -0
  179. cmpnd-0.3.0/tests/test_optimize.py +496 -0
  180. cmpnd-0.3.0/tests/test_optimize_lift.py +96 -0
  181. cmpnd-0.3.0/tests/test_packaging.py +374 -0
  182. cmpnd-0.3.0/tests/test_packaging_callable_source.py +239 -0
  183. cmpnd-0.3.0/tests/test_packaging_program_source.py +832 -0
  184. cmpnd-0.3.0/tests/test_project_payload.py +179 -0
  185. cmpnd-0.3.0/tests/test_reflector_properties.py +169 -0
  186. cmpnd-0.3.0/tests/test_reflector_smoke.py +522 -0
  187. cmpnd-0.3.0/tests/test_repro_reactv2_bugs.py +195 -0
  188. cmpnd-0.3.0/tests/test_schemas.py +132 -0
  189. cmpnd-0.3.0/tests/test_stats_export.py +118 -0
  190. cmpnd-0.3.0/tests/test_tracked_gepa.py +995 -0
  191. cmpnd-0.3.0/tests/test_tracker_enrich.py +81 -0
  192. cmpnd-0.3.0/tests/test_truncation.py +167 -0
  193. cmpnd-0.3.0/todo.md +45 -0
  194. cmpnd-0.3.0/uv.lock +3669 -0
  195. cmpnd-0.1.0/PKG-INFO +0 -6
  196. cmpnd-0.1.0/cmpnd.egg-info/PKG-INFO +0 -6
  197. cmpnd-0.1.0/cmpnd.egg-info/SOURCES.txt +0 -7
  198. cmpnd-0.1.0/cmpnd.egg-info/dependency_links.txt +0 -1
  199. cmpnd-0.1.0/cmpnd.egg-info/top_level.txt +0 -1
  200. cmpnd-0.1.0/main.py +0 -6
  201. cmpnd-0.1.0/pyproject.toml +0 -7
  202. cmpnd-0.1.0/setup.cfg +0 -4
  203. /cmpnd-0.1.0/README.md → /cmpnd-0.3.0/cmpnd/cli/commands/__init__.py +0 -0
cmpnd-0.3.0/.gitignore ADDED
@@ -0,0 +1,43 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ parts/
14
+ sdist/
15
+ var/
16
+ wheels/
17
+ *.egg-info/
18
+ .installed.cfg
19
+ *.egg
20
+
21
+ # Virtual environments
22
+ .env
23
+ .venv
24
+ env/
25
+ venv/
26
+ ENV/
27
+
28
+ # IDE
29
+ .idea/
30
+ .vscode/
31
+ *.swp
32
+ *.swo
33
+
34
+ # Testing
35
+ .pytest_cache/
36
+ .coverage
37
+ htmlcov/
38
+ .tox/
39
+ .nox/
40
+
41
+ # Misc
42
+ *.log
43
+ .DS_Store
cmpnd-0.3.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 -> Rails -> 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`.
cmpnd-0.3.0/PKG-INFO ADDED
@@ -0,0 +1,372 @@
1
+ Metadata-Version: 2.4
2
+ Name: cmpnd
3
+ Version: 0.3.0
4
+ Summary: DSPy observability and deployment SDK for cmpnd
5
+ Project-URL: Homepage, https://cmpnd.ai
6
+ Author-email: cmpnd <hello@cmpnd.ai>
7
+ License: MIT
8
+ Keywords: dspy,llm,observability,tracing
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: dspy>=3.1.3
21
+ Requires-Dist: httpx>=0.27.0
22
+ Requires-Dist: ipython>=8.20
23
+ Requires-Dist: pydantic>=2.0
24
+ Requires-Dist: xxhash>=3.0.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: basedpyright>=1.39; extra == 'dev'
27
+ Requires-Dist: hypothesis>=6.100; extra == 'dev'
28
+ Requires-Dist: jsonschema>=4.0; extra == 'dev'
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'
32
+ Requires-Dist: pytest>=8.0; extra == 'dev'
33
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
34
+ Provides-Extra: stub
35
+ Requires-Dist: cmpnd-deploy; (python_version >= '3.11') and extra == 'stub'
36
+ Requires-Dist: fastapi>=0.110; extra == 'stub'
37
+ Requires-Dist: python-multipart>=0.0.9; extra == 'stub'
38
+ Requires-Dist: uvicorn[standard]>=0.27; extra == 'stub'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # cmpnd
42
+
43
+ DSPy observability and deployment SDK for cmpnd.
44
+
45
+ Automatically trace your DSPy programs with one line of code, and deploy them with one more.
46
+
47
+ ## Installation
48
+
49
+ ```bash
50
+ uv add cmpnd # or: pip install cmpnd
51
+ ```
52
+
53
+ ## Quick Start
54
+
55
+ ```python
56
+ import cmpnd
57
+
58
+ # Configure with your API key
59
+ cmpnd.configure(api_key="ck_xxx", project="my-project")
60
+
61
+ # Enable automatic DSPy instrumentation
62
+ cmpnd.auto_instrument()
63
+
64
+ # Your DSPy code is now automatically traced!
65
+ import dspy
66
+
67
+ lm = dspy.LM("openai/gpt-4o-mini")
68
+ dspy.configure(lm=lm)
69
+
70
+ cot = dspy.ChainOfThought("question -> answer")
71
+ result = cot(question="What is DSPy?")
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)
76
+ ```
77
+
78
+ ## Configuration
79
+
80
+ ### Using environment variables
81
+
82
+ ```bash
83
+ export CMPND_API_KEY="ck_your_api_key"
84
+ export CMPND_ENDPOINT="https://platform.cmpnd.ai" # optional
85
+ ```
86
+
87
+ ```python
88
+ import cmpnd
89
+
90
+ cmpnd.configure() # Reads from environment
91
+ cmpnd.auto_instrument()
92
+ ```
93
+
94
+ ### Configuration options
95
+
96
+ ```python
97
+ cmpnd.configure(
98
+ api_key="ck_xxx", # Required: API key
99
+ endpoint="https://platform.cmpnd.ai", # Optional: Backend URL
100
+ project="my-project", # Optional: Project name
101
+ batch_size=100, # Optional: Batch size for export
102
+ flush_interval_seconds=5.0, # Optional: Flush interval
103
+ capture_inputs=True, # Optional: Capture function inputs
104
+ capture_outputs=True, # Optional: Capture function outputs
105
+ default_tags={"env": "prod"}, # Optional: Tags for all traces
106
+ )
107
+ ```
108
+
109
+ ## Deployment
110
+
111
+ Deploy a DSPy module to the cmpnd platform for server-side execution:
112
+
113
+ ```python
114
+ import cmpnd
115
+ import dspy
116
+
117
+ cmpnd.configure(api_key="ck_xxx")
118
+
119
+ lm = dspy.LM("groq/llama-3.1-8b-instant", temperature=0.0, max_tokens=16384)
120
+ dspy.configure(lm=lm)
121
+
122
+ module = dspy.Predict("question -> answer")
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)
128
+ ```
129
+
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.
131
+
132
+ ### With a metric (for optimization)
133
+
134
+ ```python
135
+ def accuracy(example, pred, trace=None):
136
+ return example.answer == pred.answer
137
+
138
+ deployment_id = cmpnd.deploy(module, metric=accuracy)
139
+ ```
140
+
141
+ ### Options
142
+
143
+ ```python
144
+ cmpnd.deploy(
145
+ module, # Required: a DSPy module
146
+ metric=None, # Optional: metric callable for optimization support
147
+ timeout=300, # Optional: seconds to wait for parsing (default 300)
148
+ )
149
+ ```
150
+
151
+ ### How it works
152
+
153
+ 1. The SDK generates source code from the live module (LM config, signature, types)
154
+ 2. `POST /api/v1/deployments` creates a deployment and returns a presigned S3 upload URL
155
+ 3. The source ZIP is uploaded directly to S3
156
+ 4. The server-side parser extracts the program and LM configuration
157
+ 5. The SDK polls until status is `ready`, then returns the `DeployedProgram`
158
+
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.
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
+
233
+ ## Custom Spans
234
+
235
+ Add custom spans to trace non-DSPy code:
236
+
237
+ ### Using the decorator
238
+
239
+ ```python
240
+ from cmpnd import trace, SpanType
241
+
242
+ @trace(name="fetch_documents", span_type=SpanType.RETRIEVE)
243
+ def fetch_documents(query: str) -> list[str]:
244
+ # Your retrieval logic
245
+ return documents
246
+ ```
247
+
248
+ ### Using the context manager
249
+
250
+ ```python
251
+ from cmpnd import start_span, SpanType
252
+
253
+ def run_pipeline(query: str):
254
+ with start_span("vector_search", span_type=SpanType.RETRIEVE) as span:
255
+ span.set_attribute("index", "my-faiss-index")
256
+ docs = search(query)
257
+ span.set_outputs({"doc_count": len(docs)})
258
+
259
+ return generate(query, docs)
260
+ ```
261
+
262
+ ## What Gets Traced
263
+
264
+ The SDK automatically captures:
265
+
266
+ ### Module Execution
267
+ - Module type (Predict, ChainOfThought, ReAct, etc.)
268
+ - Signature name and instructions
269
+ - Input/output field names
270
+ - Demo count
271
+
272
+ ### LM Calls
273
+ - Model name and provider
274
+ - Token usage (prompt, completion, total)
275
+ - Request/response content
276
+
277
+ ### Adapters
278
+ - Format and parse operations
279
+ - Input/output transformations
280
+
281
+ ### Tools
282
+ - Tool name and description
283
+ - Invocation inputs/outputs
284
+
285
+ ### Evaluations
286
+ - Evaluation scores
287
+ - Program being evaluated
288
+
289
+ ## Span Types
290
+
291
+ Available span types for categorization:
292
+
293
+ - `SpanType.MODULE` - Generic DSPy module
294
+ - `SpanType.PREDICT` - Predict module
295
+ - `SpanType.CHAIN_OF_THOUGHT` - ChainOfThought module
296
+ - `SpanType.REACT` - ReAct agent
297
+ - `SpanType.RETRIEVE` - Retrieval operations
298
+ - `SpanType.LM_CALL` - Language model calls
299
+ - `SpanType.ADAPTER_FORMAT` - Adapter formatting
300
+ - `SpanType.ADAPTER_PARSE` - Adapter parsing
301
+ - `SpanType.TOOL` - Tool invocations
302
+ - `SpanType.EVALUATION` - Evaluation runs
303
+
304
+ ## API Reference
305
+
306
+ ### `cmpnd.configure()`
307
+
308
+ Initialize the SDK with your API key and options.
309
+
310
+ ### `cmpnd.auto_instrument()`
311
+
312
+ Automatically register the callback with DSPy.
313
+
314
+ ### `cmpnd.CmpndCallback`
315
+
316
+ The callback class for manual registration:
317
+
318
+ ```python
319
+ import dspy
320
+ import cmpnd
321
+
322
+ cmpnd.configure(api_key="ck_xxx")
323
+ dspy.configure(callbacks=[cmpnd.CmpndCallback()])
324
+ ```
325
+
326
+ ### `cmpnd.deploy()`
327
+
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`).
341
+
342
+ ### `cmpnd.trace()`
343
+
344
+ Decorator for custom traced functions.
345
+
346
+ ### `cmpnd.start_span()`
347
+
348
+ Context manager for custom spans.
349
+
350
+ ### `cmpnd.get_current_trace()`
351
+
352
+ Get the current trace (if any).
353
+
354
+ ### `cmpnd.get_current_span()`
355
+
356
+ Get the current span (if any).
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
+
366
+ ### `cmpnd.shutdown_exporter()`
367
+
368
+ Gracefully shutdown the background exporter.
369
+
370
+ ## License
371
+
372
+ MIT