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.
- cmpnd-0.3.0/.gitignore +43 -0
- cmpnd-0.3.0/CLAUDE.md +116 -0
- cmpnd-0.3.0/PKG-INFO +372 -0
- cmpnd-0.3.0/README.md +332 -0
- cmpnd-0.3.0/cmpnd/__init__.py +132 -0
- cmpnd-0.3.0/cmpnd/_gepa_patch.py +156 -0
- cmpnd-0.3.0/cmpnd/callback.py +979 -0
- cmpnd-0.3.0/cmpnd/cli/__init__.py +163 -0
- cmpnd-0.3.0/cmpnd/cli/api_client.py +115 -0
- cmpnd-0.3.0/cmpnd/cli/commands/admin.py +59 -0
- cmpnd-0.3.0/cmpnd/cli/commands/auth.py +22 -0
- cmpnd-0.3.0/cmpnd/cli/commands/datasets.py +31 -0
- cmpnd-0.3.0/cmpnd/cli/commands/deployments.py +56 -0
- cmpnd-0.3.0/cmpnd/cli/commands/evals.py +42 -0
- cmpnd-0.3.0/cmpnd/cli/commands/login.py +251 -0
- cmpnd-0.3.0/cmpnd/cli/commands/optimizations.py +60 -0
- cmpnd-0.3.0/cmpnd/cli/commands/org.py +115 -0
- cmpnd-0.3.0/cmpnd/cli/commands/programs.py +55 -0
- cmpnd-0.3.0/cmpnd/cli/commands/top.py +997 -0
- cmpnd-0.3.0/cmpnd/cli/commands/traces.py +63 -0
- cmpnd-0.3.0/cmpnd/cli/credentials.py +95 -0
- cmpnd-0.3.0/cmpnd/cli/shell.py +691 -0
- cmpnd-0.3.0/cmpnd/cli/verbs.py +113 -0
- cmpnd-0.3.0/cmpnd/configuration.py +196 -0
- cmpnd-0.3.0/cmpnd/context.py +119 -0
- cmpnd-0.3.0/cmpnd/dataset_sync.py +127 -0
- cmpnd-0.3.0/cmpnd/datasets.py +219 -0
- cmpnd-0.3.0/cmpnd/decorators.py +143 -0
- cmpnd-0.3.0/cmpnd/deployment.py +267 -0
- cmpnd-0.3.0/cmpnd/eval_handler.py +368 -0
- cmpnd-0.3.0/cmpnd/execution.py +177 -0
- cmpnd-0.3.0/cmpnd/exporter.py +785 -0
- cmpnd-0.3.0/cmpnd/helpers.py +220 -0
- cmpnd-0.3.0/cmpnd/identity.py +247 -0
- cmpnd-0.3.0/cmpnd/ir/__init__.py +91 -0
- cmpnd-0.3.0/cmpnd/ir/decode.py +230 -0
- cmpnd-0.3.0/cmpnd/ir/encode.py +134 -0
- cmpnd-0.3.0/cmpnd/ir/hash.py +12 -0
- cmpnd-0.3.0/cmpnd/ir/program.py +109 -0
- cmpnd-0.3.0/cmpnd/ir/reflection.py +167 -0
- cmpnd-0.3.0/cmpnd/ir/types.py +89 -0
- cmpnd-0.3.0/cmpnd/models.py +782 -0
- cmpnd-0.3.0/cmpnd/optimization.py +451 -0
- cmpnd-0.3.0/cmpnd/optimizers/__init__.py +31 -0
- cmpnd-0.3.0/cmpnd/optimizers/gepa.py +360 -0
- cmpnd-0.3.0/cmpnd/optimizers/gepa_callback.py +475 -0
- cmpnd-0.3.0/cmpnd/optimizers/tracker.py +1008 -0
- cmpnd-0.3.0/cmpnd/packaging.py +890 -0
- cmpnd-0.3.0/docs/2026-04-02-code-review.md +280 -0
- cmpnd-0.3.0/docs/plans/2026-04-05-train-val-dataset-capture-design.md +91 -0
- cmpnd-0.3.0/docs/plans/2026-04-05-train-val-dataset-capture.md +727 -0
- cmpnd-0.3.0/docs/sdk-instrumentation.md +219 -0
- cmpnd-0.3.0/examples/local_ollama.py +48 -0
- cmpnd-0.3.0/pyproject.toml +182 -0
- cmpnd-0.3.0/stubs/dspy/__init__.pyi +134 -0
- cmpnd-0.3.0/stubs/dspy/primitives/__init__.pyi +1 -0
- cmpnd-0.3.0/stubs/dspy/primitives/prediction.pyi +3 -0
- cmpnd-0.3.0/stubs/dspy/utils/__init__.pyi +0 -0
- cmpnd-0.3.0/stubs/dspy/utils/callback.pyi +19 -0
- cmpnd-0.3.0/tests/__init__.py +1 -0
- cmpnd-0.3.0/tests/_exporter_helpers.py +21 -0
- cmpnd-0.3.0/tests/_identity_helpers.py +39 -0
- cmpnd-0.3.0/tests/e2e/__init__.py +0 -0
- cmpnd-0.3.0/tests/e2e/committee_task.py +242 -0
- cmpnd-0.3.0/tests/e2e/conftest.py +336 -0
- cmpnd-0.3.0/tests/e2e/data/committee_train.ndjson +8 -0
- cmpnd-0.3.0/tests/e2e/data/committee_val.ndjson +5 -0
- cmpnd-0.3.0/tests/e2e/fake_lm.py +221 -0
- cmpnd-0.3.0/tests/e2e/helpers.py +58 -0
- cmpnd-0.3.0/tests/e2e/test_capture_prompts.py +129 -0
- cmpnd-0.3.0/tests/e2e/test_composite_module.py +86 -0
- cmpnd-0.3.0/tests/e2e/test_dataset_auto_creation.py +271 -0
- cmpnd-0.3.0/tests/e2e/test_deploy_execute.py +369 -0
- cmpnd-0.3.0/tests/e2e/test_deploy_programs.py +277 -0
- cmpnd-0.3.0/tests/e2e/test_edge_cases.py +62 -0
- cmpnd-0.3.0/tests/e2e/test_example_hash.py +150 -0
- cmpnd-0.3.0/tests/e2e/test_gepa_optimization.py +93 -0
- cmpnd-0.3.0/tests/e2e/test_lm_accuracy.py +99 -0
- cmpnd-0.3.0/tests/e2e/test_module_tracing.py +205 -0
- cmpnd-0.3.0/tests/e2e/test_optimization_metric_identity.py +111 -0
- cmpnd-0.3.0/tests/e2e/test_optimize_failures.py +79 -0
- cmpnd-0.3.0/tests/e2e/test_optimize_loop.py +251 -0
- cmpnd-0.3.0/tests/e2e/test_optimize_server_owned_completion.py +203 -0
- cmpnd-0.3.0/tests/e2e/test_parallel_export.py +104 -0
- cmpnd-0.3.0/tests/e2e/test_project_attribution.py +289 -0
- cmpnd-0.3.0/tests/e2e/test_react_tool_spans.py +91 -0
- cmpnd-0.3.0/tests/e2e/test_save_load.py +246 -0
- cmpnd-0.3.0/tests/e2e/test_trace_structure.py +180 -0
- cmpnd-0.3.0/tests/e2e/test_user_attribution.py +181 -0
- cmpnd-0.3.0/tests/identity_vectors/01_predict_qa.json +27 -0
- cmpnd-0.3.0/tests/identity_vectors/02_predict_renamed_field.json +27 -0
- cmpnd-0.3.0/tests/identity_vectors/03_chain_of_thought_qa.json +27 -0
- cmpnd-0.3.0/tests/identity_vectors/04_predict_richer_types.json +56 -0
- cmpnd-0.3.0/tests/identity_vectors/05_composite_two_predicts.json +55 -0
- cmpnd-0.3.0/tests/identity_vectors/06_multichaincomparison.json +27 -0
- cmpnd-0.3.0/tests/identity_vectors/07_program_of_thought.json +27 -0
- cmpnd-0.3.0/tests/identity_vectors/08_opaque_module.json +8 -0
- cmpnd-0.3.0/tests/identity_vectors/09_predict_pydantic_field.json +27 -0
- cmpnd-0.3.0/tests/identity_vectors/10_react.json +31 -0
- cmpnd-0.3.0/tests/identity_vectors/11_refine.json +31 -0
- cmpnd-0.3.0/tests/identity_vectors/12_retrieve.json +7 -0
- cmpnd-0.3.0/tests/identity_vectors/13_knn.json +8 -0
- cmpnd-0.3.0/tests/identity_vectors/14_parallel.json +55 -0
- cmpnd-0.3.0/tests/identity_vectors/README.md +14 -0
- cmpnd-0.3.0/tests/integration/__init__.py +0 -0
- cmpnd-0.3.0/tests/integration/conftest.py +94 -0
- cmpnd-0.3.0/tests/integration/test_cli_deploy_smoketest.py +151 -0
- cmpnd-0.3.0/tests/integration/test_logs_renders_traces.py +148 -0
- cmpnd-0.3.0/tests/integration/test_optimizing_progress.py +152 -0
- cmpnd-0.3.0/tests/integration/test_unhappy_path.py +188 -0
- cmpnd-0.3.0/tests/parity/__init__.py +0 -0
- cmpnd-0.3.0/tests/parity/conftest.py +101 -0
- cmpnd-0.3.0/tests/parity/seed_parity.py +135 -0
- cmpnd-0.3.0/tests/parity/test_auth.py +64 -0
- cmpnd-0.3.0/tests/parity/test_datasets.py +157 -0
- cmpnd-0.3.0/tests/parity/test_deployments.py +63 -0
- cmpnd-0.3.0/tests/parity/test_evals.py +90 -0
- cmpnd-0.3.0/tests/parity/test_health.py +42 -0
- cmpnd-0.3.0/tests/parity/test_modules.py +37 -0
- cmpnd-0.3.0/tests/parity/test_optimizations.py +84 -0
- cmpnd-0.3.0/tests/parity/test_programs.py +82 -0
- cmpnd-0.3.0/tests/parity/test_signatures.py +42 -0
- cmpnd-0.3.0/tests/parity/test_trace_streaming.py +216 -0
- cmpnd-0.3.0/tests/parity/test_traces.py +220 -0
- cmpnd-0.3.0/tests/stress/__init__.py +0 -0
- cmpnd-0.3.0/tests/stress/test_deploy_programs.py +165 -0
- cmpnd-0.3.0/tests/stress/test_multi_client_stress.py +366 -0
- cmpnd-0.3.0/tests/stress/test_optimize_loop.py +278 -0
- cmpnd-0.3.0/tests/stress/test_optimize_stress.py +185 -0
- cmpnd-0.3.0/tests/stress/test_real_provider_twins.py +177 -0
- cmpnd-0.3.0/tests/test_callback.py +2029 -0
- cmpnd-0.3.0/tests/test_cli/__init__.py +0 -0
- cmpnd-0.3.0/tests/test_cli/conftest.py +15 -0
- cmpnd-0.3.0/tests/test_cli/test_admin.py +33 -0
- cmpnd-0.3.0/tests/test_cli/test_api_client.py +147 -0
- cmpnd-0.3.0/tests/test_cli/test_auth.py +16 -0
- cmpnd-0.3.0/tests/test_cli/test_credentials.py +63 -0
- cmpnd-0.3.0/tests/test_cli/test_datasets.py +23 -0
- cmpnd-0.3.0/tests/test_cli/test_deployments.py +51 -0
- cmpnd-0.3.0/tests/test_cli/test_evals.py +33 -0
- cmpnd-0.3.0/tests/test_cli/test_login.py +179 -0
- cmpnd-0.3.0/tests/test_cli/test_main.py +127 -0
- cmpnd-0.3.0/tests/test_cli/test_optimizations.py +52 -0
- cmpnd-0.3.0/tests/test_cli/test_org.py +67 -0
- cmpnd-0.3.0/tests/test_cli/test_parity.py +169 -0
- cmpnd-0.3.0/tests/test_cli/test_programs.py +50 -0
- cmpnd-0.3.0/tests/test_cli/test_shell.py +593 -0
- cmpnd-0.3.0/tests/test_cli/test_top.py +605 -0
- cmpnd-0.3.0/tests/test_cli/test_traces.py +51 -0
- cmpnd-0.3.0/tests/test_clustering/__init__.py +6 -0
- cmpnd-0.3.0/tests/test_clustering/conftest.py +153 -0
- cmpnd-0.3.0/tests/test_clustering/test_edge_cases.py +324 -0
- cmpnd-0.3.0/tests/test_clustering/test_hash_consistency.py +266 -0
- cmpnd-0.3.0/tests/test_clustering/test_type_conversion.py +214 -0
- cmpnd-0.3.0/tests/test_configuration.py +214 -0
- cmpnd-0.3.0/tests/test_context.py +226 -0
- cmpnd-0.3.0/tests/test_context_propagation.py +49 -0
- cmpnd-0.3.0/tests/test_dataset_polling.py +89 -0
- cmpnd-0.3.0/tests/test_dataset_sync.py +497 -0
- cmpnd-0.3.0/tests/test_datasets.py +568 -0
- cmpnd-0.3.0/tests/test_decorators.py +302 -0
- cmpnd-0.3.0/tests/test_deploy.py +416 -0
- cmpnd-0.3.0/tests/test_eval.py +570 -0
- cmpnd-0.3.0/tests/test_eval_start.py +84 -0
- cmpnd-0.3.0/tests/test_exception_paths.py +302 -0
- cmpnd-0.3.0/tests/test_execute.py +234 -0
- cmpnd-0.3.0/tests/test_exporter.py +683 -0
- cmpnd-0.3.0/tests/test_exporter_real.py +461 -0
- cmpnd-0.3.0/tests/test_fake_lm.py +141 -0
- cmpnd-0.3.0/tests/test_gepa_callback.py +1800 -0
- cmpnd-0.3.0/tests/test_gepa_integration.py +241 -0
- cmpnd-0.3.0/tests/test_identity.py +515 -0
- cmpnd-0.3.0/tests/test_integration.py +400 -0
- cmpnd-0.3.0/tests/test_ir_encoder_properties.py +340 -0
- cmpnd-0.3.0/tests/test_ir_golden_vectors.py +140 -0
- cmpnd-0.3.0/tests/test_ir_roundtrip.py +172 -0
- cmpnd-0.3.0/tests/test_models.py +428 -0
- cmpnd-0.3.0/tests/test_optimization_start.py +93 -0
- cmpnd-0.3.0/tests/test_optimize.py +496 -0
- cmpnd-0.3.0/tests/test_optimize_lift.py +96 -0
- cmpnd-0.3.0/tests/test_packaging.py +374 -0
- cmpnd-0.3.0/tests/test_packaging_callable_source.py +239 -0
- cmpnd-0.3.0/tests/test_packaging_program_source.py +832 -0
- cmpnd-0.3.0/tests/test_project_payload.py +179 -0
- cmpnd-0.3.0/tests/test_reflector_properties.py +169 -0
- cmpnd-0.3.0/tests/test_reflector_smoke.py +522 -0
- cmpnd-0.3.0/tests/test_repro_reactv2_bugs.py +195 -0
- cmpnd-0.3.0/tests/test_schemas.py +132 -0
- cmpnd-0.3.0/tests/test_stats_export.py +118 -0
- cmpnd-0.3.0/tests/test_tracked_gepa.py +995 -0
- cmpnd-0.3.0/tests/test_tracker_enrich.py +81 -0
- cmpnd-0.3.0/tests/test_truncation.py +167 -0
- cmpnd-0.3.0/todo.md +45 -0
- cmpnd-0.3.0/uv.lock +3669 -0
- cmpnd-0.1.0/PKG-INFO +0 -6
- cmpnd-0.1.0/cmpnd.egg-info/PKG-INFO +0 -6
- cmpnd-0.1.0/cmpnd.egg-info/SOURCES.txt +0 -7
- cmpnd-0.1.0/cmpnd.egg-info/dependency_links.txt +0 -1
- cmpnd-0.1.0/cmpnd.egg-info/top_level.txt +0 -1
- cmpnd-0.1.0/main.py +0 -6
- cmpnd-0.1.0/pyproject.toml +0 -7
- cmpnd-0.1.0/setup.cfg +0 -4
- /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
|