cmpnd 0.86.0__tar.gz → 0.87.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 (416) hide show
  1. {cmpnd-0.86.0 → cmpnd-0.87.0}/PKG-INFO +8 -2
  2. {cmpnd-0.86.0 → cmpnd-0.87.0}/README.md +4 -0
  3. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/callback.py +4 -28
  4. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/shell.py +14 -5
  5. {cmpnd-0.86.0 → cmpnd-0.87.0}/docs/cli.md +5 -1
  6. {cmpnd-0.86.0 → cmpnd-0.87.0}/docs/sdk-instrumentation.md +2 -2
  7. {cmpnd-0.86.0 → cmpnd-0.87.0}/pyproject.toml +12 -5
  8. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_shell.py +15 -1
  9. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_lm_usage_shapes.py +0 -70
  10. cmpnd-0.87.0/tests/test_token_attribution.py +122 -0
  11. {cmpnd-0.86.0 → cmpnd-0.87.0}/uv.lock +8 -4
  12. {cmpnd-0.86.0 → cmpnd-0.87.0}/.gitignore +0 -0
  13. {cmpnd-0.86.0 → cmpnd-0.87.0}/AGENTS.md +0 -0
  14. {cmpnd-0.86.0 → cmpnd-0.87.0}/CLAUDE.md +0 -0
  15. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/__init__.py +0 -0
  16. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/_assessments.py +0 -0
  17. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/_authored_import.py +0 -0
  18. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/_deployments.py +0 -0
  19. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/_exporter_pool.py +0 -0
  20. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/_gepa_patch.py +0 -0
  21. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/_http.py +0 -0
  22. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/_program_patch.py +0 -0
  23. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/_rlm_patch.py +0 -0
  24. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/_runtime_tools.py +0 -0
  25. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/_timespec.py +0 -0
  26. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/_traces.py +0 -0
  27. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/_valsets.py +0 -0
  28. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/authored.py +0 -0
  29. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/__init__.py +0 -0
  30. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/api_client.py +0 -0
  31. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/commands/__init__.py +0 -0
  32. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/commands/admin.py +0 -0
  33. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/commands/assessments.py +0 -0
  34. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/commands/auth.py +0 -0
  35. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/commands/datasets.py +0 -0
  36. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/commands/deployments.py +0 -0
  37. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/commands/evals.py +0 -0
  38. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/commands/login.py +0 -0
  39. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/commands/optimizations.py +0 -0
  40. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/commands/org.py +0 -0
  41. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/commands/programs.py +0 -0
  42. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
  43. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/commands/tool_servers.py +0 -0
  44. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/commands/top.py +0 -0
  45. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/commands/traces.py +0 -0
  46. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/commands/valsets.py +0 -0
  47. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/credentials.py +0 -0
  48. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/deprecation.py +0 -0
  49. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/pagination.py +0 -0
  50. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/trace_render.py +0 -0
  51. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/tree_help.py +0 -0
  52. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/verbs.py +0 -0
  53. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/cli/views.py +0 -0
  54. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/client.py +0 -0
  55. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/configuration.py +0 -0
  56. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/context.py +0 -0
  57. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/credentials.py +0 -0
  58. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/dataset_sync.py +0 -0
  59. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/datasets.py +0 -0
  60. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/decorators.py +0 -0
  61. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/deployment.py +0 -0
  62. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/deployment_metadata.py +0 -0
  63. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/deployment_runs.py +0 -0
  64. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/eval_handler.py +0 -0
  65. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/execution.py +0 -0
  66. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/exporter.py +0 -0
  67. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/exporter_http.py +0 -0
  68. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/helpers.py +0 -0
  69. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/identity.py +0 -0
  70. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/imaging.py +0 -0
  71. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/ir/__init__.py +0 -0
  72. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/ir/decode.py +0 -0
  73. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/ir/encode.py +0 -0
  74. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/ir/hash.py +0 -0
  75. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/ir/program.py +0 -0
  76. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/ir/reflection.py +0 -0
  77. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/ir/types.py +0 -0
  78. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/ir/xxh64.py +0 -0
  79. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/lm_configuration.py +0 -0
  80. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/mcp_servers.py +0 -0
  81. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/models.py +0 -0
  82. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/named_programs.py +0 -0
  83. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/optimization.py +0 -0
  84. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/optimizers/__init__.py +0 -0
  85. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/optimizers/gepa.py +0 -0
  86. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/optimizers/gepa_callback.py +0 -0
  87. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/optimizers/resume.py +0 -0
  88. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/optimizers/tracker.py +0 -0
  89. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/packaging.py +0 -0
  90. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/program_naming.py +0 -0
  91. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/programs.py +0 -0
  92. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/references.py +0 -0
  93. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/retry.py +0 -0
  94. {cmpnd-0.86.0 → cmpnd-0.87.0}/cmpnd/traces.py +0 -0
  95. {cmpnd-0.86.0 → cmpnd-0.87.0}/docs/2026-04-02-code-review.md +0 -0
  96. {cmpnd-0.86.0 → cmpnd-0.87.0}/docs/client.md +0 -0
  97. {cmpnd-0.86.0 → cmpnd-0.87.0}/docs/custom-proposer.md +0 -0
  98. {cmpnd-0.86.0 → cmpnd-0.87.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
  99. {cmpnd-0.86.0 → cmpnd-0.87.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
  100. {cmpnd-0.86.0 → cmpnd-0.87.0}/docs/reading-traces.md +0 -0
  101. {cmpnd-0.86.0 → cmpnd-0.87.0}/examples/generalizing_proposer.py +0 -0
  102. {cmpnd-0.86.0 → cmpnd-0.87.0}/examples/local_ollama.py +0 -0
  103. {cmpnd-0.86.0 → cmpnd-0.87.0}/ingest/LINEAGE.md +0 -0
  104. {cmpnd-0.86.0 → cmpnd-0.87.0}/ingest/README.md +0 -0
  105. {cmpnd-0.86.0 → cmpnd-0.87.0}/ingest/cmpnd_ingest/__init__.py +0 -0
  106. {cmpnd-0.86.0 → cmpnd-0.87.0}/ingest/cmpnd_ingest/bodies.py +0 -0
  107. {cmpnd-0.86.0 → cmpnd-0.87.0}/ingest/cmpnd_ingest/client_floor.py +0 -0
  108. {cmpnd-0.86.0 → cmpnd-0.87.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
  109. {cmpnd-0.86.0 → cmpnd-0.87.0}/ingest/cmpnd_ingest/program_names.py +0 -0
  110. {cmpnd-0.86.0 → cmpnd-0.87.0}/ingest/cmpnd_ingest/protocols.py +0 -0
  111. {cmpnd-0.86.0 → cmpnd-0.87.0}/ingest/cmpnd_ingest/py.typed +0 -0
  112. {cmpnd-0.86.0 → cmpnd-0.87.0}/ingest/cmpnd_ingest/request_log.py +0 -0
  113. {cmpnd-0.86.0 → cmpnd-0.87.0}/ingest/cmpnd_ingest/retry.py +0 -0
  114. {cmpnd-0.86.0 → cmpnd-0.87.0}/ingest/cmpnd_ingest/span_context.py +0 -0
  115. {cmpnd-0.86.0 → cmpnd-0.87.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
  116. {cmpnd-0.86.0 → cmpnd-0.87.0}/ingest/cmpnd_ingest/user_agent.py +0 -0
  117. {cmpnd-0.86.0 → cmpnd-0.87.0}/ingest/pyproject.toml +0 -0
  118. {cmpnd-0.86.0 → cmpnd-0.87.0}/scripts/audit-sdist.sh +0 -0
  119. {cmpnd-0.86.0 → cmpnd-0.87.0}/scripts/capture_review_run.py +0 -0
  120. {cmpnd-0.86.0 → cmpnd-0.87.0}/scripts/check-pip-install.sh +0 -0
  121. {cmpnd-0.86.0 → cmpnd-0.87.0}/scripts/check_release_artifacts.py +0 -0
  122. {cmpnd-0.86.0 → cmpnd-0.87.0}/scripts/fixtures/review_run.json.gz +0 -0
  123. {cmpnd-0.86.0 → cmpnd-0.87.0}/scripts/gepa_resume_bug.py +0 -0
  124. {cmpnd-0.86.0 → cmpnd-0.87.0}/scripts/install-test-env.sh +0 -0
  125. {cmpnd-0.86.0 → cmpnd-0.87.0}/scripts/release-gate.sh +0 -0
  126. {cmpnd-0.86.0 → cmpnd-0.87.0}/scripts/release-real-models.sh +0 -0
  127. {cmpnd-0.86.0 → cmpnd-0.87.0}/scripts/require_version_bump.py +0 -0
  128. {cmpnd-0.86.0 → cmpnd-0.87.0}/scripts/seed_review_run.py +0 -0
  129. {cmpnd-0.86.0 → cmpnd-0.87.0}/scripts/selfhost_compat.py +0 -0
  130. {cmpnd-0.86.0 → cmpnd-0.87.0}/scripts/test-in-linux.sh +0 -0
  131. {cmpnd-0.86.0 → cmpnd-0.87.0}/stubs/dspy/__init__.pyi +0 -0
  132. {cmpnd-0.86.0 → cmpnd-0.87.0}/stubs/dspy/primitives/__init__.pyi +0 -0
  133. {cmpnd-0.86.0 → cmpnd-0.87.0}/stubs/dspy/primitives/prediction.pyi +0 -0
  134. {cmpnd-0.86.0 → cmpnd-0.87.0}/stubs/dspy/utils/__init__.pyi +0 -0
  135. {cmpnd-0.86.0 → cmpnd-0.87.0}/stubs/dspy/utils/callback.pyi +0 -0
  136. {cmpnd-0.86.0 → cmpnd-0.87.0}/stubs/dspy/utils/dummies.pyi +0 -0
  137. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/__init__.py +0 -0
  138. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/_credential_route.py +0 -0
  139. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/_deployment_signature_helpers.py +0 -0
  140. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/_exporter_helpers.py +0 -0
  141. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/_fake_clock.py +0 -0
  142. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/_identity_helpers.py +0 -0
  143. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/_resolve_refs.py +0 -0
  144. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/_traced_run.py +0 -0
  145. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/conftest.py +0 -0
  146. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/__init__.py +0 -0
  147. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/committee_task.py +0 -0
  148. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/conftest.py +0 -0
  149. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/continuation_task.py +0 -0
  150. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
  151. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
  152. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/fake_lm.py +0 -0
  153. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/helpers.py +0 -0
  154. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/target_capabilities.py +0 -0
  155. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_assessments_cli.py +0 -0
  156. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_capture_prompts.py +0 -0
  157. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_composite_module.py +0 -0
  158. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
  159. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_deploy_execute.py +0 -0
  160. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_deploy_pandas.py +0 -0
  161. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_deploy_programs.py +0 -0
  162. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_edge_cases.py +0 -0
  163. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_example_hash.py +0 -0
  164. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_gepa_optimization.py +0 -0
  165. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_interpreter_spans.py +0 -0
  166. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_lm_accuracy.py +0 -0
  167. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_module_tracing.py +0 -0
  168. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_optimization_lineage.py +0 -0
  169. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
  170. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_optimize_authored_metric.py +0 -0
  171. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_optimize_continuation.py +0 -0
  172. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_optimize_custom_proposer.py +0 -0
  173. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_optimize_failures.py +0 -0
  174. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_optimize_loop.py +0 -0
  175. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_optimize_review_committee.py +0 -0
  176. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
  177. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_parallel_export.py +0 -0
  178. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_project_attribution.py +0 -0
  179. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_react_tool_spans.py +0 -0
  180. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_save_load.py +0 -0
  181. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_trace_structure.py +0 -0
  182. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_traces_read.py +0 -0
  183. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_user_attribution.py +0 -0
  184. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/e2e/test_valsets_cli.py +0 -0
  185. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/identity_vectors/01_predict_qa.json +0 -0
  186. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
  187. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
  188. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
  189. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
  190. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
  191. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
  192. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/identity_vectors/08_opaque_module.json +0 -0
  193. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
  194. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/identity_vectors/10_react.json +0 -0
  195. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/identity_vectors/11_refine.json +0 -0
  196. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/identity_vectors/12_retrieve.json +0 -0
  197. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/identity_vectors/13_knn.json +0 -0
  198. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/identity_vectors/14_parallel.json +0 -0
  199. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/identity_vectors/README.md +0 -0
  200. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/ingest_conformance.py +0 -0
  201. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/integration/__init__.py +0 -0
  202. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/integration/authored_optimize/judge.py +0 -0
  203. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
  204. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/integration/conftest.py +0 -0
  205. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
  206. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/integration/hosted_optimize_probe.py +0 -0
  207. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/integration/optimization_run_probe.py +0 -0
  208. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
  209. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/integration/test_logs_renders_traces.py +0 -0
  210. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/integration/test_optimization_run.py +0 -0
  211. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/integration/test_optimizing_progress.py +0 -0
  212. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/integration/test_unhappy_path.py +0 -0
  213. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/lm15_prompt_fixture.py +0 -0
  214. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/lm15_prompts/four_turn_predict.json +0 -0
  215. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/lm15_prompts/rlm_six_turns.json +0 -0
  216. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/load/__init__.py +0 -0
  217. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/load/__main__.py +0 -0
  218. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/load/drain.py +0 -0
  219. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/load/ingest.py +0 -0
  220. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/load/latency_transport.py +0 -0
  221. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/load/rlm_shape.py +0 -0
  222. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/load/rlm_size_report.py +0 -0
  223. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/load/sweep.py +0 -0
  224. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/load/test_rlm_shape.py +0 -0
  225. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/load/test_rlm_size_report.py +0 -0
  226. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/parity/__init__.py +0 -0
  227. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/parity/conftest.py +0 -0
  228. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/parity/seed_parity.py +0 -0
  229. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/parity/test_auth.py +0 -0
  230. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/parity/test_datasets.py +0 -0
  231. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/parity/test_deployments.py +0 -0
  232. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/parity/test_evals.py +0 -0
  233. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/parity/test_health.py +0 -0
  234. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/parity/test_modules.py +0 -0
  235. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/parity/test_optimizations.py +0 -0
  236. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/parity/test_programs.py +0 -0
  237. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/parity/test_signatures.py +0 -0
  238. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/parity/test_trace_streaming.py +0 -0
  239. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/parity/test_traces.py +0 -0
  240. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/__init__.py +0 -0
  241. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/_provider.py +0 -0
  242. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/_readiness.py +0 -0
  243. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/_reliability.py +0 -0
  244. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/_result.py +0 -0
  245. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/mint_bedrock_token.py +0 -0
  246. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/test_bedrock_converse.py +0 -0
  247. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/test_deploy_programs.py +0 -0
  248. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/test_multi_client_stress.py +0 -0
  249. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/test_optimize_client_committee.py +0 -0
  250. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/test_optimize_continuation.py +0 -0
  251. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/test_optimize_loop.py +0 -0
  252. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/test_optimize_loop_wasm.py +0 -0
  253. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/test_optimize_stress.py +0 -0
  254. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/test_provider_roles.py +0 -0
  255. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/test_readiness.py +0 -0
  256. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/test_real_provider_twins.py +0 -0
  257. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/test_reliability.py +0 -0
  258. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/test_reliability_classify.py +0 -0
  259. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/test_rlm_wasm.py +0 -0
  260. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/real_models/test_trace_search.py +0 -0
  261. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/span_reference_fixture.py +0 -0
  262. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_adapter_reentrancy.py +0 -0
  263. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_assessments_api.py +0 -0
  264. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_authored.py +0 -0
  265. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_authored_binding_credentials.py +0 -0
  266. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_authored_bundle_collisions.py +0 -0
  267. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_authored_metric_contract.py +0 -0
  268. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_authored_named_models.py +0 -0
  269. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_authored_optimize.py +0 -0
  270. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_callback.py +0 -0
  271. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_candidate_lineage.py +0 -0
  272. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/__init__.py +0 -0
  273. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/conftest.py +0 -0
  274. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_admin.py +0 -0
  275. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_api_client.py +0 -0
  276. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_assessments.py +0 -0
  277. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_auth.py +0 -0
  278. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_credentials.py +0 -0
  279. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_datasets.py +0 -0
  280. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_deployment_runs.py +0 -0
  281. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_deployments.py +0 -0
  282. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_evals.py +0 -0
  283. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_login.py +0 -0
  284. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_main.py +0 -0
  285. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_optimizations.py +0 -0
  286. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_org.py +0 -0
  287. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_pagination.py +0 -0
  288. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_parity.py +0 -0
  289. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_programs.py +0 -0
  290. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_provider_credentials.py +0 -0
  291. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_schema_sample.py +0 -0
  292. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_tool_servers.py +0 -0
  293. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_top.py +0 -0
  294. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_traces.py +0 -0
  295. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_traces_program_and_module.py +0 -0
  296. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_traces_search_wire.py +0 -0
  297. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_tree_help.py +0 -0
  298. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cli/test_valsets.py +0 -0
  299. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_client.py +0 -0
  300. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_client_floor.py +0 -0
  301. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_client_program_names.py +0 -0
  302. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_clustering/__init__.py +0 -0
  303. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_clustering/conftest.py +0 -0
  304. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_clustering/test_edge_cases.py +0 -0
  305. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_clustering/test_hash_consistency.py +0 -0
  306. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_clustering/test_type_conversion.py +0 -0
  307. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_cmp551_grouping_identity.py +0 -0
  308. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_configuration.py +0 -0
  309. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_context.py +0 -0
  310. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_context_propagation.py +0 -0
  311. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_credential_kwargs.py +0 -0
  312. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_credential_selection.py +0 -0
  313. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_credential_values.py +0 -0
  314. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_dataset_polling.py +0 -0
  315. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_dataset_sync.py +0 -0
  316. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_datasets.py +0 -0
  317. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_decision_client_spans.py +0 -0
  318. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_decorators.py +0 -0
  319. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_deploy.py +0 -0
  320. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_deploy_mcp_tool_references.py +0 -0
  321. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_deploy_program_id.py +0 -0
  322. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_deployment_metadata.py +0 -0
  323. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_deployment_runs.py +0 -0
  324. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_deployments_read.py +0 -0
  325. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_eval.py +0 -0
  326. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_eval_example_attribution.py +0 -0
  327. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_eval_program_id.py +0 -0
  328. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_eval_start.py +0 -0
  329. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_eval_trace_grouping.py +0 -0
  330. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_exception_paths.py +0 -0
  331. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_execute.py +0 -0
  332. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_execute_run_identity.py +0 -0
  333. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_exporter.py +0 -0
  334. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_exporter_program_names.py +0 -0
  335. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_exporter_queue.py +0 -0
  336. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_exporter_real.py +0 -0
  337. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_fake_lm.py +0 -0
  338. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_gepa_callback.py +0 -0
  339. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_gepa_integration.py +0 -0
  340. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_gepa_output_normalization.py +0 -0
  341. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_gepa_resume.py +0 -0
  342. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_identity.py +0 -0
  343. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_imaging.py +0 -0
  344. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_import_weight.py +0 -0
  345. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_ingest_conformance.py +0 -0
  346. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_ingest_second_producer.py +0 -0
  347. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_integration.py +0 -0
  348. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_interpreter_callback.py +0 -0
  349. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_ir_encoder_properties.py +0 -0
  350. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_ir_golden_vectors.py +0 -0
  351. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_ir_roundtrip.py +0 -0
  352. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_lm15_native_calls.py +0 -0
  353. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_lm15_prompt_fixtures.py +0 -0
  354. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_lm15_prompt_references.py +0 -0
  355. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_lm_configuration.py +0 -0
  356. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_mcp_tools.py +0 -0
  357. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_models.py +0 -0
  358. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_multi_client_stress_readiness.py +0 -0
  359. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_no_signature_hash_on_the_wire.py +0 -0
  360. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_optimization_phase_callback.py +0 -0
  361. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_optimization_program_names.py +0 -0
  362. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_optimization_progress.py +0 -0
  363. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_optimization_start.py +0 -0
  364. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_optimize.py +0 -0
  365. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_optimize_budget.py +0 -0
  366. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_optimize_lift.py +0 -0
  367. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_optimize_lms.py +0 -0
  368. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_optimize_proposer.py +0 -0
  369. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_optimize_reflection_lm.py +0 -0
  370. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_optimize_terminal_failure.py +0 -0
  371. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_packaging.py +0 -0
  372. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_packaging_callable_source.py +0 -0
  373. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_packaging_mcp_tools.py +0 -0
  374. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_packaging_program_source.py +0 -0
  375. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_poll_until.py +0 -0
  376. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_program_id_deprecation.py +0 -0
  377. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_program_identity.py +0 -0
  378. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_program_lineage_persist.py +0 -0
  379. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_program_names_in_deploy.py +0 -0
  380. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_program_naming.py +0 -0
  381. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_programs_create.py +0 -0
  382. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_project_payload.py +0 -0
  383. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_real_provider_twins_readiness.py +0 -0
  384. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_reflector_properties.py +0 -0
  385. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_reflector_smoke.py +0 -0
  386. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_release_gate_script.py +0 -0
  387. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_release_real_models_script.py +0 -0
  388. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_replaced_names.py +0 -0
  389. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_repro_reactv2_bugs.py +0 -0
  390. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_require_version_bump.py +0 -0
  391. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_retry.py +0 -0
  392. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_reversible_failures.py +0 -0
  393. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_rlm_patch.py +0 -0
  394. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_run_attribution_paths.py +0 -0
  395. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_runtime_tools.py +0 -0
  396. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_schemas.py +0 -0
  397. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_seal_lm_credential.py +0 -0
  398. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_seed_review_run.py +0 -0
  399. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_signature_inputs.py +0 -0
  400. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_span_references.py +0 -0
  401. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_span_sent_once.py +0 -0
  402. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_span_submission.py +0 -0
  403. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_span_submission_wire_contract.py +0 -0
  404. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_system_one.py +0 -0
  405. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_trace_context_concurrency.py +0 -0
  406. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_trace_fact_wire_contract.py +0 -0
  407. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_trace_facts.py +0 -0
  408. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_trace_render.py +0 -0
  409. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_traces_read.py +0 -0
  410. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_tracked_gepa.py +0 -0
  411. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_tracker_enrich.py +0 -0
  412. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_truncation.py +0 -0
  413. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_upload_auth_headers.py +0 -0
  414. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_user_agent.py +0 -0
  415. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_valsets_api.py +0 -0
  416. {cmpnd-0.86.0 → cmpnd-0.87.0}/tests/test_xxh64.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cmpnd
3
- Version: 0.86.0
3
+ Version: 0.87.0
4
4
  Summary: DSPy observability and deployment SDK for cmpnd
5
5
  Project-URL: Homepage, https://cmpnd.ai
6
6
  Author-email: cmpnd <hello@cmpnd.ai>
@@ -19,7 +19,6 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
19
  Requires-Python: >=3.11
20
20
  Requires-Dist: dspy>=3.3.0
21
21
  Requires-Dist: httpx>=0.27.0
22
- Requires-Dist: ipython>=8.20
23
22
  Requires-Dist: pydantic>=2.0
24
23
  Requires-Dist: typing-extensions>=4.1.0
25
24
  Requires-Dist: xxhash>=3.0.0
@@ -28,6 +27,7 @@ Requires-Dist: basedpyright>=1.39; extra == 'dev'
28
27
  Requires-Dist: boto3>=1.34; extra == 'dev'
29
28
  Requires-Dist: gepa; extra == 'dev'
30
29
  Requires-Dist: hypothesis>=6.100; extra == 'dev'
30
+ Requires-Dist: ipython>=8.20; extra == 'dev'
31
31
  Requires-Dist: jsonschema>=4.0; extra == 'dev'
32
32
  Requires-Dist: litellm; extra == 'dev'
33
33
  Requires-Dist: litellm<1.102; (python_version >= '3.15') and extra == 'dev'
@@ -47,6 +47,8 @@ Requires-Dist: pillow>=10.0; extra == 'images'
47
47
  Provides-Extra: mcp
48
48
  Requires-Dist: dspy>=3.3.1; extra == 'mcp'
49
49
  Requires-Dist: mcp>=1.9; extra == 'mcp'
50
+ Provides-Extra: shell
51
+ Requires-Dist: ipython>=8.20; extra == 'shell'
50
52
  Description-Content-Type: text/markdown
51
53
 
52
54
  # cmpnd
@@ -61,6 +63,10 @@ deployments and optimization runs under that name.
61
63
  uv add cmpnd # or: pip install cmpnd
62
64
  ```
63
65
 
66
+ The interactive `cmpnd` shell uses IPython when you install the `shell` extra:
67
+ `uv add 'cmpnd[shell]'` or `pip install 'cmpnd[shell]'`. The plain install
68
+ leaves IPython out, and the shell then runs on the standard Python prompt.
69
+
64
70
  ## Authentication
65
71
 
66
72
  The `cmpnd` library works with the cmpnd service at
@@ -10,6 +10,10 @@ deployments and optimization runs under that name.
10
10
  uv add cmpnd # or: pip install cmpnd
11
11
  ```
12
12
 
13
+ The interactive `cmpnd` shell uses IPython when you install the `shell` extra:
14
+ `uv add 'cmpnd[shell]'` or `pip install 'cmpnd[shell]'`. The plain install
15
+ leaves IPython out, and the shell then runs on the standard Python prompt.
16
+
13
17
  ## Authentication
14
18
 
15
19
  The `cmpnd` library works with the cmpnd service at
@@ -1052,8 +1052,10 @@ class _ModuleMixin(_CallbackBase):
1052
1052
  cleaned_outputs = helpers.clean_outputs(captured_outputs, instance, dspy, self._max_output_field_size)
1053
1053
  span.set_outputs(cleaned_outputs)
1054
1054
 
1055
- # Extract token usage if available
1056
- self._extract_usage_from_prediction(span, captured_outputs, dspy)
1055
+ # A module span records no token usage. Its LM call spans carry each
1056
+ # call's usage, and a trace's totals sum every span, so usage copied
1057
+ # from the Prediction here would count each call once more per
1058
+ # enclosing module.
1057
1059
 
1058
1060
  # Pop from context stack
1059
1061
  context.pop_span()
@@ -1462,32 +1464,6 @@ class _ModuleMixin(_CallbackBase):
1462
1464
  trace.tags["optimization_phase"] = phase
1463
1465
  return trace
1464
1466
 
1465
- def _extract_usage_from_prediction(self, span: models.SpanBuilder, outputs: Any, dspy: Any) -> None:
1466
- """Sum the LM usage a DSPy Prediction carries onto its module span.
1467
-
1468
- This is what fills the trace row's own token totals, so a shape read
1469
- wrongly here reports a trace as having spent no tokens even when the LM
1470
- spans below it carry the right counts.
1471
- """
1472
- if not isinstance(outputs, dspy.Prediction):
1473
- return
1474
- usage_by_model = outputs.get_lm_usage() if hasattr(outputs, "get_lm_usage") else None
1475
- if not usage_by_model:
1476
- return
1477
- for model_usage in usage_by_model.values():
1478
- counts = _normalize_usage(model_usage)
1479
- span.prompt_tokens += counts["prompt_tokens"] or 0
1480
- span.completion_tokens += counts["completion_tokens"] or 0
1481
- span.total_tokens += counts["total_tokens"] or 0
1482
- span.cached_prompt_tokens += counts["cached_prompt_tokens"] or 0
1483
- # Summed only over the models that reported one, so a span whose
1484
- # models all stayed silent keeps the field absent rather than 0.
1485
- for name in ("reasoning_tokens", "cache_write_tokens"):
1486
- reported = counts[name]
1487
- if reported is None:
1488
- continue
1489
- setattr(span, name, (getattr(span, name) or 0) + reported)
1490
-
1491
1467
 
1492
1468
  class _LMMixin(_CallbackBase):
1493
1469
  """DSPy LM callbacks (on_lm_start/on_lm_end)."""
@@ -648,28 +648,37 @@ def _enable_readline(namespace: dict[str, Any]) -> None:
648
648
  atexit.register(_save_history)
649
649
 
650
650
 
651
+ _NO_IPYTHON_HINT = "IPython is not installed. For completion and history, run: pip install 'cmpnd[shell]'\n"
652
+
653
+
651
654
  def _launch_repl(namespace: dict[str, Any], banner: str) -> None:
652
- """Prefer IPython (Jedi completion, history, multi-line); fall back to stdlib."""
655
+ """Prefer IPython (Jedi completion, history, multi-line); fall back to stdlib.
656
+
657
+ IPython ships in the optional ``cmpnd[shell]`` extra. Without it, the shell
658
+ prints one line that names the extra and runs on ``code.interact``.
659
+ """
653
660
  try:
654
- from IPython import start_ipython
655
- from traitlets.config import Config
661
+ import IPython
662
+ import traitlets.config
656
663
  except ImportError:
657
664
  import code
658
665
 
666
+ sys.stderr.write(_NO_IPYTHON_HINT)
667
+ sys.stderr.flush()
659
668
  _enable_readline(namespace)
660
669
  sys.stdout.write(banner)
661
670
  sys.stdout.flush()
662
671
  code.interact(banner="", local=namespace, exitmsg="")
663
672
  return
664
673
 
665
- cfg = Config()
674
+ cfg = traitlets.config.Config()
666
675
  cfg.TerminalInteractiveShell.banner1 = banner
667
676
  cfg.TerminalInteractiveShell.banner2 = ""
668
677
  cfg.TerminalInteractiveShell.confirm_exit = False
669
678
  # IPython 9.x appends a random "Tip: use %timeit ..." after banner2.
670
679
  # Suppress — our own banner already tells users what to type.
671
680
  cfg.TerminalInteractiveShell.enable_tip = False
672
- start_ipython(argv=[], user_ns=namespace, config=cfg)
681
+ IPython.start_ipython(argv=[], user_ns=namespace, config=cfg)
673
682
 
674
683
 
675
684
  def launch(api_key: str | None = None, endpoint: str | None = None) -> None:
@@ -6,7 +6,11 @@ runs and inspects DSPy programs on the platform. Every command reads
6
6
  `--api-key` and `--endpoint`. `cmpnd auth me` prints the resolved identity.
7
7
 
8
8
  Bare `cmpnd`, with no subcommand, opens an interactive Python shell with the
9
- platform verbs loaded (`cmpnd.cli.shell`).
9
+ platform verbs loaded (`cmpnd.cli.shell`). The shell runs on IPython when the
10
+ `cmpnd[shell]` extra is installed (`pip install 'cmpnd[shell]'`), which adds
11
+ Jedi completion, multi-line input and history search. Without the extra, the
12
+ shell prints one line that names it and runs on the standard library's
13
+ `code.interact` with readline completion.
10
14
 
11
15
  ## Program names
12
16
 
@@ -115,7 +115,7 @@ bindings, the judge picks one through `lms[name]`. A `TypeSafe` object passed in
115
115
  The hook methods, in the order you'd expect them to fire for a typical `dspy.ChainOfThought` call:
116
116
 
117
117
  - **Module lifecycle** — `on_module_start` (`callback.py`) creates a new `SpanBuilder`, and, if this is the root module call, also creates a new `TraceBuilder` via `_create_trace`. `on_module_end` closes the span, attaches outputs, and, if it was the root, exports the trace. A call is root when no span is on the stack — or when the only span above it is the `Evaluate` span, which contains many independent program runs and parents none of them, so each run under a `dspy.Evaluate` roots its own trace. Such a root span is built with `create_child_span(..., root=True)` so it does not adopt the `Evaluate` span as its parent.
118
- - **LM calls.** `on_lm_start` and `on_lm_end` wrap the provider call. The callback records the model, the current span token projections, and a versioned `lm_usage` evidence object. `capture_prompts` controls whether the prompt payload is recorded.
118
+ - **LM calls.** `on_lm_start` and `on_lm_end` wrap the provider call. The callback records the model, the LM call span's token counts, and a versioned `lm_usage` evidence object. `capture_prompts` controls whether the prompt payload is recorded. Only an LM call span carries token counts. A module span records none, even when DSPy's `track_usage` puts the usage on its `Prediction`, because the SDK's trace totals and serve's trace fold both add up every span, and a module span holding its calls' usage would count each call again for every module that encloses it (`tests/test_token_attribution.py`).
119
119
  - **Adapter format/parse** — `on_adapter_format_start` / `_end` and `on_adapter_parse_start` / `_end`. Both route through the shared helpers `_start_adapter_span` and `_end_adapter_span`. The callback records one span per adapter call. DSPy's `Adapter.__init_subclass__` wraps `format` and `parse` on every subclass, so a `JSONAdapter.format` call fires the hooks twice and a `BAMLAdapter.format` call three times, each with a fresh `call_id` and the same instance. `_start_adapter_span` skips a call when the current span is the same method's adapter span on the same instance, and `_end_adapter_span` returns early for the skipped `call_id`. The outermost span is kept, since its output is what reaches the LM. `tests/test_adapter_reentrancy.py` runs the real adapters under DSPy's dispatch. An adapter span's inputs are the method's arguments, so they include the predictor's `signature`. `helpers.clean_inputs` records any signature class among a span's inputs by its label, the class name or the `in1, in2 -> out1` fields of a string signature. It never records the repr, which embeds the whole instructions and every field's metadata. The same rule covers a `Predict` called with `signature=`, as DSPy's RLM does on every turn from DSPy `main` on. The span records the signature's fields and instructions once, as its own attributes.
120
120
  - **Tools** — `on_tool_start` / `on_tool_end` (`callback.py`). One span per `dspy.Tool` invocation, with the tool's args as inputs.
121
121
  - **Interpreter (code execution)** — `_InterpreterMixin` (`callback.py`), for the DSPy `CodeInterpreter` hooks DSPy >= 3.3.1 fires around a program running code in the interpreter (`RLM`, `ProgramOfThought`, `CodeAct`, `Flex` — every module reaching it through `_create_interpreter`; `ReAct` / `ReActV2` use no interpreter and fire none). `on_interpreter_execute_start` / `_end` emit one `INTERPRETER_EXECUTE` span per REPL cell — the code in, its captured stdout out, and the `SUBMIT()` result (a DSPy `FinalOutput`) recorded distinctly from stdout. On DSPy 3.4.1 and later, `RLM` runs the sandbox side of its `dspy` stand-in as each session's first cell, marked by the `__dspy_host_token` variable (`dspy.primitives.facade.HOST_TOKEN_VAR`). That cell is DSPy's machinery rather than a step the program took, so it emits no span. `on_interpreter_tool_call_start` / `_end` synthesize a `TOOL` span for a tool called from interpreter code **only when no real `dspy.Tool` span covered the call** (a user tool routes through `Tool.__call__` and fires `on_tool_*`, so its row is suppressed to avoid a duplicate; the `llm_query` builtins fire no tool hook, so they get the synthesized span). `on_interpreter_startup_*` / `on_interpreter_shutdown_*` emit no span — their duration folds into an `interpreter.startup_ms` / `interpreter.shutdown_ms` attribute on the enclosing span, and an event with no enclosing span (a pooled pre-warm) is dropped.
@@ -840,7 +840,7 @@ A span's dict looks roughly like this:
840
840
  "span_id": "...",
841
841
  "trace_id": "...",
842
842
  "parent_span_id": "...",
843
- "span_type": "predict",
843
+ "span_type": "lm_call",
844
844
  "start_time": "2020-01-02T00:00:00+00:00",
845
845
  "status": "ok",
846
846
  "project_tags": [],
@@ -5,7 +5,7 @@ name = "cmpnd"
5
5
  # it's new) — no git tags. Bump with `uv version --bump {minor,patch}` or by
6
6
  # hand. Runtime `cmpnd.__version__` reads installed metadata, so it tracks
7
7
  # this automatically. See docs/plans/2026-07-02-sdk-pypi-tag-free-publish.md.
8
- version = "0.86.0"
8
+ version = "0.87.0"
9
9
  description = "DSPy observability and deployment SDK for cmpnd"
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.11"
@@ -45,10 +45,6 @@ dependencies = [
45
45
  # basis; the C module is an order of magnitude faster and every ordinary
46
46
  # install should have it.
47
47
  "xxhash>=3.0.0",
48
- # Powers the interactive `cmpnd` shell (Jedi completion, multi-line,
49
- # history search, syntax highlighting). No upper cap. The shell falls back
50
- # to stdlib `code.interact` + rlcompleter if IPython somehow can't import.
51
- "ipython>=8.20",
52
48
  ]
53
49
 
54
50
  [project.optional-dependencies]
@@ -59,6 +55,14 @@ dependencies = [
59
55
  images = [
60
56
  "pillow>=10.0",
61
57
  ]
58
+ # The interactive `cmpnd` shell's IPython front end: Jedi completion, multi-line
59
+ # input, history search and syntax highlighting. Optional, because IPython and its
60
+ # dependency tree would otherwise land in every environment that only traces or
61
+ # deploys programs. Without it, the shell runs on stdlib `code.interact` with
62
+ # rlcompleter and prints one line that names this extra. No upper cap.
63
+ shell = [
64
+ "ipython>=8.20",
65
+ ]
62
66
  # The MCP client library, needed only by `cmpnd.mcp_tools` when it connects to a tool
63
67
  # server from a notebook. Optional and lazily imported: it pulls a transport stack that
64
68
  # an SDK used purely for tracing, deploying and optimizing never touches, and its
@@ -110,6 +114,9 @@ dev = [
110
114
  # Exercises the image-thumbnail path (the `images` extra) so the positive
111
115
  # branch is covered in CI, not just the Pillow-absent fallback.
112
116
  "pillow>=10.0",
117
+ # Exercises the shell's IPython front end (the `shell` extra) so both REPL
118
+ # branches are covered in CI.
119
+ "ipython>=8.20",
113
120
  # The metadata validator the release path runs, brought in-tree so sdk-lint
114
121
  # can check a freshly built wheel + sdist BEFORE release day. Floor is the
115
122
  # first twine whose `packaging` understands `Metadata-Version: 2.5`, which
@@ -708,7 +708,21 @@ class TestDeployHelper:
708
708
  class TestReplFallback:
709
709
  def test_falls_back_when_ipython_missing(self):
710
710
  with mock.patch.dict("sys.modules", {"IPython": None}):
711
- with mock.patch("code.interact") as interact:
711
+ with mock.patch("code.interact", autospec=True) as interact:
712
712
  shell._launch_repl({"x": 1}, "BANNER\n")
713
713
  interact.assert_called_once()
714
714
  assert interact.call_args.kwargs["local"] == {"x": 1}
715
+
716
+ def test_fallback_names_the_shell_extra(self, capsys):
717
+ with mock.patch.dict("sys.modules", {"IPython": None}):
718
+ with mock.patch("code.interact", autospec=True):
719
+ shell._launch_repl({}, "BANNER\n")
720
+ err = capsys.readouterr().err
721
+ assert err.count("\n") == 1
722
+ assert "pip install 'cmpnd[shell]'" in err
723
+
724
+ def test_ipython_prints_no_fallback_line(self, capsys):
725
+ with mock.patch("IPython.start_ipython", autospec=True) as start:
726
+ shell._launch_repl({}, "BANNER\n")
727
+ start.assert_called_once()
728
+ assert "cmpnd[shell]" not in capsys.readouterr().err
@@ -575,73 +575,3 @@ def test_finish_reason_from_typed_response():
575
575
  def test_finish_reason_absent_when_nothing_reports_one():
576
576
  span = _span_for(_responses_usage(), response=_ResponsesResponse("in_progress"))
577
577
  assert span.attributes.get("lm.finish_reason") == "in_progress"
578
-
579
-
580
- # --- Module-level aggregation (the trace's own totals) ---------------------
581
-
582
-
583
- @pytest.mark.parametrize("shape", _SHAPE_PARAMS)
584
- def test_prediction_usage_aggregates_on_every_shape(shape: str):
585
- """``Prediction.get_lm_usage()`` feeds the module span, and so the trace row.
586
-
587
- A trace reporting no prompt or completion tokens at all, while its LM spans
588
- below carry counts, is this path reading zeros one level above them.
589
- """
590
- import dspy
591
-
592
- from cmpnd.callback import CmpndCallback
593
-
594
- with mock.patch("cmpnd.exporter.ensure_exporter_started", autospec=True):
595
- callback = CmpndCallback()
596
-
597
- span = mock.MagicMock()
598
- span.prompt_tokens = 0
599
- span.completion_tokens = 0
600
- span.total_tokens = 0
601
- span.cached_prompt_tokens = 0
602
- span.reasoning_tokens = None
603
- span.cache_write_tokens = None
604
-
605
- prediction = dspy.Prediction(answer="Paris")
606
- prediction.set_lm_usage({"openai/gpt-5.6-luna": _SHAPES[shape]()})
607
-
608
- callback._extract_usage_from_prediction(span, prediction, dspy)
609
-
610
- assert span.prompt_tokens == 2900
611
- assert span.completion_tokens == 17
612
- assert span.total_tokens == 2917
613
- assert span.cached_prompt_tokens == 1024
614
- assert span.reasoning_tokens == 12
615
-
616
-
617
- def test_prediction_usage_sums_across_models():
618
- import dspy
619
-
620
- from cmpnd.callback import CmpndCallback
621
-
622
- with mock.patch("cmpnd.exporter.ensure_exporter_started", autospec=True):
623
- callback = CmpndCallback()
624
-
625
- span = mock.MagicMock()
626
- span.prompt_tokens = 0
627
- span.completion_tokens = 0
628
- span.total_tokens = 0
629
- span.cached_prompt_tokens = 0
630
- span.reasoning_tokens = None
631
- span.cache_write_tokens = None
632
-
633
- prediction = dspy.Prediction(answer="Paris")
634
- prediction.set_lm_usage(
635
- {
636
- "openai/gpt-5.6-luna": _chat_usage(),
637
- "anthropic/claude-sonnet-4": _responses_usage(),
638
- }
639
- )
640
-
641
- callback._extract_usage_from_prediction(span, prediction, dspy)
642
-
643
- assert span.prompt_tokens == 5800
644
- assert span.completion_tokens == 34
645
- assert span.reasoning_tokens == 24
646
- # Nothing reported a cache write, so the sum stays absent rather than 0.
647
- assert span.cache_write_tokens is None
@@ -0,0 +1,122 @@
1
+ """Each LM call's tokens land on exactly one span: its own LM call span.
2
+
3
+ A trace's token total is the sum over its spans, both in the SDK's own trace
4
+ roll-up and in serve's trace fold. A module span that also carried the usage of
5
+ the LM calls beneath it would count every call once per enclosing module.
6
+
7
+ These tests run real DSPy modules over the in-process fake provider, with DSPy's
8
+ ``track_usage`` on so each ``Prediction`` reports its LM usage, and check what
9
+ the callback hands the exporter.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import Any
15
+ from unittest import mock
16
+
17
+ import pytest
18
+
19
+ dspy = pytest.importorskip("dspy")
20
+ litellm = pytest.importorskip("litellm")
21
+
22
+ from cmpnd import configure, shutdown_exporter # noqa: E402
23
+ from cmpnd.callback import CmpndCallback # noqa: E402
24
+ from cmpnd.context import clear_context # noqa: E402
25
+ from cmpnd.models import SpanType # noqa: E402
26
+ from tests.e2e import fake_lm # noqa: E402
27
+
28
+
29
+ class _RecordingExporter:
30
+ def __init__(self) -> None:
31
+ self.traces: list[Any] = []
32
+
33
+ def export_trace_start(self, trace: Any) -> None:
34
+ pass
35
+
36
+ def export_trace_finalize(self, trace: Any) -> None:
37
+ self.traces.append(trace)
38
+
39
+ def export_span(self, span: Any) -> None:
40
+ pass
41
+
42
+ def export_trace(self, trace: Any) -> None:
43
+ self.traces.append(trace)
44
+
45
+
46
+ @pytest.fixture
47
+ def traced():
48
+ """Run a callable under the cmpnd callback and return the exported trace."""
49
+ saved_providers = list(litellm.custom_provider_map)
50
+ fake_lm.register()
51
+ configure(api_key="test_key")
52
+ exporter = _RecordingExporter()
53
+
54
+ def run(program: Any, **kwargs: Any) -> Any:
55
+ with (
56
+ mock.patch("cmpnd.exporter.get_exporter", autospec=True, return_value=exporter),
57
+ mock.patch("cmpnd.exporter.ensure_exporter_started", autospec=True),
58
+ ):
59
+ callback = CmpndCallback()
60
+ lm = dspy.LM(fake_lm.FAKE_MODEL, cache=False)
61
+ with dspy.context(lm=lm, callbacks=[callback], track_usage=True):
62
+ prediction = program(**kwargs)
63
+ # The fixture only matters if DSPy really reported usage on the Prediction.
64
+ assert prediction.get_lm_usage(), "track_usage did not attach LM usage"
65
+ assert len(exporter.traces) == 1
66
+ return exporter.traces[0]
67
+
68
+ yield run
69
+
70
+ litellm.custom_provider_map = saved_providers
71
+ clear_context()
72
+ import cmpnd.configuration
73
+
74
+ cmpnd.configuration._config = None
75
+ shutdown_exporter()
76
+
77
+
78
+ def _spans(trace: Any) -> list[Any]:
79
+ return list(trace._batch_spans)
80
+
81
+
82
+ def _lm_spans(trace: Any) -> list[Any]:
83
+ return [s for s in _spans(trace) if s.span_type == SpanType.LM_CALL]
84
+
85
+
86
+ def _assert_tokens_only_on_lm_calls(trace: Any) -> None:
87
+ for span in _spans(trace):
88
+ if span.span_type == SpanType.LM_CALL:
89
+ continue
90
+ assert (span.prompt_tokens, span.completion_tokens, span.total_tokens, span.cached_prompt_tokens) == (
91
+ 0,
92
+ 0,
93
+ 0,
94
+ 0,
95
+ ), f"{span.span_type.value} span carries tokens"
96
+ assert span.reasoning_tokens is None
97
+ assert span.cache_write_tokens is None
98
+
99
+
100
+ def test_module_over_one_lm_call_counts_the_call_once(traced):
101
+ trace = traced(dspy.Predict("text -> sentiment"), text="I love it")
102
+
103
+ [lm_span] = _lm_spans(trace)
104
+ assert lm_span.prompt_tokens > 0
105
+ assert lm_span.completion_tokens > 0
106
+ _assert_tokens_only_on_lm_calls(trace)
107
+ assert trace.prompt_tokens == lm_span.prompt_tokens
108
+ assert trace.completion_tokens == lm_span.completion_tokens
109
+ assert trace.total_tokens == lm_span.total_tokens
110
+
111
+
112
+ def test_nested_modules_count_each_call_once(traced):
113
+ """ChainOfThought wraps a Predict: two module spans over one LM call."""
114
+ trace = traced(dspy.ChainOfThought("question -> answer"), question="What is 2 + 2?")
115
+
116
+ lm_spans = _lm_spans(trace)
117
+ assert len(lm_spans) == 1
118
+ assert len([s for s in _spans(trace) if s.span_type != SpanType.LM_CALL]) >= 2
119
+ _assert_tokens_only_on_lm_calls(trace)
120
+ assert trace.prompt_tokens == sum(s.prompt_tokens for s in lm_spans)
121
+ assert trace.completion_tokens == sum(s.completion_tokens for s in lm_spans)
122
+ assert trace.total_tokens == sum(s.total_tokens for s in lm_spans)
@@ -505,7 +505,7 @@ wheels = [
505
505
 
506
506
  [[package]]
507
507
  name = "cmpnd"
508
- version = "0.86.0"
508
+ version = "0.87.0"
509
509
  source = { editable = "." }
510
510
  default-groups = ["dspy-3-4-1"]
511
511
  dependencies = [
@@ -514,7 +514,6 @@ dependencies = [
514
514
  { name = "dspy", version = "3.4.0", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-5-cmpnd-dspy-3-4-0' or (extra == 'extra-5-cmpnd-mcp' and extra == 'group-5-cmpnd-dspy-3-3-0') or (extra == 'group-5-cmpnd-dspy-3-3-0' and extra == 'group-5-cmpnd-dspy-3-3-1') or (extra == 'group-5-cmpnd-dspy-3-3-0' and extra == 'group-5-cmpnd-dspy-3-4-1') or (extra == 'group-5-cmpnd-dspy-3-3-1' and extra == 'group-5-cmpnd-dspy-3-4-1')" },
515
515
  { name = "dspy", version = "3.4.1", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-5-cmpnd-dspy-3-4-1' or (extra == 'group-5-cmpnd-dspy-3-3-0' and extra == 'group-5-cmpnd-dspy-3-3-1') or (extra == 'group-5-cmpnd-dspy-3-3-0' and extra == 'group-5-cmpnd-dspy-3-4-0') or (extra == 'group-5-cmpnd-dspy-3-3-1' and extra == 'group-5-cmpnd-dspy-3-4-0') or (extra == 'extra-5-cmpnd-mcp' and extra != 'group-5-cmpnd-dspy-3-3-1' and extra != 'group-5-cmpnd-dspy-3-4-0') or (extra != 'group-5-cmpnd-dspy-3-3-0' and extra != 'group-5-cmpnd-dspy-3-3-1' and extra != 'group-5-cmpnd-dspy-3-4-0')" },
516
516
  { name = "httpx" },
517
- { name = "ipython" },
518
517
  { name = "pydantic" },
519
518
  { name = "typing-extensions" },
520
519
  { name = "xxhash" },
@@ -527,6 +526,7 @@ dev = [
527
526
  { name = "gepa", version = "0.1.1", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-5-cmpnd-dspy-3-3-0' or (extra == 'group-5-cmpnd-dspy-3-3-1' and extra == 'group-5-cmpnd-dspy-3-4-0') or (extra == 'group-5-cmpnd-dspy-3-3-1' and extra == 'group-5-cmpnd-dspy-3-4-1') or (extra == 'group-5-cmpnd-dspy-3-4-0' and extra == 'group-5-cmpnd-dspy-3-4-1')" },
528
527
  { name = "gepa", version = "0.1.4", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-5-cmpnd-mcp' or extra != 'group-5-cmpnd-dspy-3-3-0' or extra == 'group-5-cmpnd-dspy-3-3-1' or extra == 'group-5-cmpnd-dspy-3-4-0' or extra == 'group-5-cmpnd-dspy-3-4-1'" },
529
528
  { name = "hypothesis" },
529
+ { name = "ipython" },
530
530
  { name = "jsonschema" },
531
531
  { name = "litellm" },
532
532
  { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
@@ -552,6 +552,9 @@ mcp = [
552
552
  { name = "dspy", version = "3.4.1", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-5-cmpnd-dspy-3-4-1' or (extra == 'group-5-cmpnd-dspy-3-3-0' and extra == 'group-5-cmpnd-dspy-3-3-1') or (extra == 'group-5-cmpnd-dspy-3-3-0' and extra == 'group-5-cmpnd-dspy-3-4-0') or (extra == 'group-5-cmpnd-dspy-3-3-1' and extra == 'group-5-cmpnd-dspy-3-4-0') or (extra == 'extra-5-cmpnd-mcp' and extra != 'group-5-cmpnd-dspy-3-3-1' and extra != 'group-5-cmpnd-dspy-3-4-0') or (extra != 'group-5-cmpnd-dspy-3-3-0' and extra != 'group-5-cmpnd-dspy-3-3-1' and extra != 'group-5-cmpnd-dspy-3-4-0')" },
553
553
  { name = "mcp" },
554
554
  ]
555
+ shell = [
556
+ { name = "ipython" },
557
+ ]
555
558
 
556
559
  [package.dev-dependencies]
557
560
  dspy-3-3-0 = [
@@ -577,7 +580,8 @@ requires-dist = [
577
580
  { name = "gepa", marker = "extra == 'dev'" },
578
581
  { name = "httpx", specifier = ">=0.27.0" },
579
582
  { name = "hypothesis", marker = "extra == 'dev'", specifier = ">=6.100" },
580
- { name = "ipython", specifier = ">=8.20" },
583
+ { name = "ipython", marker = "extra == 'dev'", specifier = ">=8.20" },
584
+ { name = "ipython", marker = "extra == 'shell'", specifier = ">=8.20" },
581
585
  { name = "jsonschema", marker = "extra == 'dev'", specifier = ">=4.0" },
582
586
  { name = "litellm", marker = "python_full_version >= '3.15' and extra == 'dev'", specifier = "<1.102" },
583
587
  { name = "litellm", marker = "extra == 'dev'" },
@@ -596,7 +600,7 @@ requires-dist = [
596
600
  { name = "typing-extensions", specifier = ">=4.1.0" },
597
601
  { name = "xxhash", specifier = ">=3.0.0" },
598
602
  ]
599
- provides-extras = ["images", "mcp", "dev", "fake-provider"]
603
+ provides-extras = ["images", "shell", "mcp", "dev", "fake-provider"]
600
604
 
601
605
  [package.metadata.requires-dev]
602
606
  dspy-3-3-0 = [{ name = "dspy", specifier = "==3.3.0" }]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes