cmpnd 0.84.0__tar.gz → 0.85.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 (414) hide show
  1. {cmpnd-0.84.0 → cmpnd-0.85.0}/PKG-INFO +1 -1
  2. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/callback.py +112 -89
  3. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/helpers.py +6 -4
  4. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/ir/reflection.py +4 -1
  5. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/models.py +36 -0
  6. cmpnd-0.85.0/cmpnd/references.py +180 -0
  7. {cmpnd-0.84.0 → cmpnd-0.85.0}/docs/sdk-instrumentation.md +7 -3
  8. {cmpnd-0.84.0 → cmpnd-0.85.0}/pyproject.toml +1 -1
  9. cmpnd-0.85.0/tests/lm15_prompt_fixture.py +350 -0
  10. cmpnd-0.85.0/tests/lm15_prompts/four_turn_predict.json +851 -0
  11. cmpnd-0.85.0/tests/lm15_prompts/rlm_six_turns.json +1726 -0
  12. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_credential_kwargs.py +27 -0
  13. cmpnd-0.85.0/tests/test_lm15_prompt_fixtures.py +94 -0
  14. cmpnd-0.85.0/tests/test_lm15_prompt_references.py +309 -0
  15. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_program_naming.py +39 -0
  16. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_reflector_smoke.py +2 -0
  17. {cmpnd-0.84.0 → cmpnd-0.85.0}/uv.lock +1 -1
  18. cmpnd-0.84.0/cmpnd/references.py +0 -80
  19. {cmpnd-0.84.0 → cmpnd-0.85.0}/.gitignore +0 -0
  20. {cmpnd-0.84.0 → cmpnd-0.85.0}/AGENTS.md +0 -0
  21. {cmpnd-0.84.0 → cmpnd-0.85.0}/CLAUDE.md +0 -0
  22. {cmpnd-0.84.0 → cmpnd-0.85.0}/README.md +0 -0
  23. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/__init__.py +0 -0
  24. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/_assessments.py +0 -0
  25. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/_authored_import.py +0 -0
  26. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/_deployments.py +0 -0
  27. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/_exporter_pool.py +0 -0
  28. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/_gepa_patch.py +0 -0
  29. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/_http.py +0 -0
  30. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/_program_patch.py +0 -0
  31. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/_rlm_patch.py +0 -0
  32. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/_runtime_tools.py +0 -0
  33. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/_timespec.py +0 -0
  34. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/_traces.py +0 -0
  35. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/_valsets.py +0 -0
  36. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/authored.py +0 -0
  37. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/__init__.py +0 -0
  38. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/api_client.py +0 -0
  39. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/commands/__init__.py +0 -0
  40. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/commands/admin.py +0 -0
  41. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/commands/assessments.py +0 -0
  42. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/commands/auth.py +0 -0
  43. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/commands/datasets.py +0 -0
  44. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/commands/deployments.py +0 -0
  45. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/commands/evals.py +0 -0
  46. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/commands/login.py +0 -0
  47. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/commands/optimizations.py +0 -0
  48. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/commands/org.py +0 -0
  49. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/commands/programs.py +0 -0
  50. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
  51. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/commands/tool_servers.py +0 -0
  52. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/commands/top.py +0 -0
  53. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/commands/traces.py +0 -0
  54. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/commands/valsets.py +0 -0
  55. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/credentials.py +0 -0
  56. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/deprecation.py +0 -0
  57. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/pagination.py +0 -0
  58. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/shell.py +0 -0
  59. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/trace_render.py +0 -0
  60. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/tree_help.py +0 -0
  61. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/verbs.py +0 -0
  62. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/cli/views.py +0 -0
  63. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/client.py +0 -0
  64. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/configuration.py +0 -0
  65. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/context.py +0 -0
  66. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/credentials.py +0 -0
  67. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/dataset_sync.py +0 -0
  68. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/datasets.py +0 -0
  69. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/decorators.py +0 -0
  70. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/deployment.py +0 -0
  71. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/deployment_metadata.py +0 -0
  72. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/deployment_runs.py +0 -0
  73. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/eval_handler.py +0 -0
  74. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/execution.py +0 -0
  75. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/exporter.py +0 -0
  76. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/exporter_http.py +0 -0
  77. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/identity.py +0 -0
  78. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/imaging.py +0 -0
  79. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/ir/__init__.py +0 -0
  80. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/ir/decode.py +0 -0
  81. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/ir/encode.py +0 -0
  82. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/ir/hash.py +0 -0
  83. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/ir/program.py +0 -0
  84. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/ir/types.py +0 -0
  85. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/ir/xxh64.py +0 -0
  86. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/lm_configuration.py +0 -0
  87. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/mcp_servers.py +0 -0
  88. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/named_programs.py +0 -0
  89. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/optimization.py +0 -0
  90. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/optimizers/__init__.py +0 -0
  91. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/optimizers/gepa.py +0 -0
  92. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/optimizers/gepa_callback.py +0 -0
  93. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/optimizers/resume.py +0 -0
  94. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/optimizers/tracker.py +0 -0
  95. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/packaging.py +0 -0
  96. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/program_naming.py +0 -0
  97. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/programs.py +0 -0
  98. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/retry.py +0 -0
  99. {cmpnd-0.84.0 → cmpnd-0.85.0}/cmpnd/traces.py +0 -0
  100. {cmpnd-0.84.0 → cmpnd-0.85.0}/docs/2026-04-02-code-review.md +0 -0
  101. {cmpnd-0.84.0 → cmpnd-0.85.0}/docs/cli.md +0 -0
  102. {cmpnd-0.84.0 → cmpnd-0.85.0}/docs/client.md +0 -0
  103. {cmpnd-0.84.0 → cmpnd-0.85.0}/docs/custom-proposer.md +0 -0
  104. {cmpnd-0.84.0 → cmpnd-0.85.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
  105. {cmpnd-0.84.0 → cmpnd-0.85.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
  106. {cmpnd-0.84.0 → cmpnd-0.85.0}/docs/reading-traces.md +0 -0
  107. {cmpnd-0.84.0 → cmpnd-0.85.0}/examples/generalizing_proposer.py +0 -0
  108. {cmpnd-0.84.0 → cmpnd-0.85.0}/examples/local_ollama.py +0 -0
  109. {cmpnd-0.84.0 → cmpnd-0.85.0}/ingest/LINEAGE.md +0 -0
  110. {cmpnd-0.84.0 → cmpnd-0.85.0}/ingest/README.md +0 -0
  111. {cmpnd-0.84.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/__init__.py +0 -0
  112. {cmpnd-0.84.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/bodies.py +0 -0
  113. {cmpnd-0.84.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/client_floor.py +0 -0
  114. {cmpnd-0.84.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
  115. {cmpnd-0.84.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/program_names.py +0 -0
  116. {cmpnd-0.84.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/protocols.py +0 -0
  117. {cmpnd-0.84.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/py.typed +0 -0
  118. {cmpnd-0.84.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/request_log.py +0 -0
  119. {cmpnd-0.84.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/retry.py +0 -0
  120. {cmpnd-0.84.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/span_context.py +0 -0
  121. {cmpnd-0.84.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
  122. {cmpnd-0.84.0 → cmpnd-0.85.0}/ingest/pyproject.toml +0 -0
  123. {cmpnd-0.84.0 → cmpnd-0.85.0}/scripts/audit-sdist.sh +0 -0
  124. {cmpnd-0.84.0 → cmpnd-0.85.0}/scripts/capture_review_run.py +0 -0
  125. {cmpnd-0.84.0 → cmpnd-0.85.0}/scripts/check-pip-install.sh +0 -0
  126. {cmpnd-0.84.0 → cmpnd-0.85.0}/scripts/check_release_artifacts.py +0 -0
  127. {cmpnd-0.84.0 → cmpnd-0.85.0}/scripts/fixtures/review_run.json.gz +0 -0
  128. {cmpnd-0.84.0 → cmpnd-0.85.0}/scripts/gepa_resume_bug.py +0 -0
  129. {cmpnd-0.84.0 → cmpnd-0.85.0}/scripts/install-test-env.sh +0 -0
  130. {cmpnd-0.84.0 → cmpnd-0.85.0}/scripts/release-gate.sh +0 -0
  131. {cmpnd-0.84.0 → cmpnd-0.85.0}/scripts/release-real-models.sh +0 -0
  132. {cmpnd-0.84.0 → cmpnd-0.85.0}/scripts/require_version_bump.py +0 -0
  133. {cmpnd-0.84.0 → cmpnd-0.85.0}/scripts/seed_review_run.py +0 -0
  134. {cmpnd-0.84.0 → cmpnd-0.85.0}/scripts/selfhost_compat.py +0 -0
  135. {cmpnd-0.84.0 → cmpnd-0.85.0}/scripts/test-in-linux.sh +0 -0
  136. {cmpnd-0.84.0 → cmpnd-0.85.0}/stubs/dspy/__init__.pyi +0 -0
  137. {cmpnd-0.84.0 → cmpnd-0.85.0}/stubs/dspy/primitives/__init__.pyi +0 -0
  138. {cmpnd-0.84.0 → cmpnd-0.85.0}/stubs/dspy/primitives/prediction.pyi +0 -0
  139. {cmpnd-0.84.0 → cmpnd-0.85.0}/stubs/dspy/utils/__init__.pyi +0 -0
  140. {cmpnd-0.84.0 → cmpnd-0.85.0}/stubs/dspy/utils/callback.pyi +0 -0
  141. {cmpnd-0.84.0 → cmpnd-0.85.0}/stubs/dspy/utils/dummies.pyi +0 -0
  142. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/__init__.py +0 -0
  143. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/_credential_route.py +0 -0
  144. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/_deployment_signature_helpers.py +0 -0
  145. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/_exporter_helpers.py +0 -0
  146. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/_fake_clock.py +0 -0
  147. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/_identity_helpers.py +0 -0
  148. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/_resolve_refs.py +0 -0
  149. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/_traced_run.py +0 -0
  150. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/conftest.py +0 -0
  151. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/__init__.py +0 -0
  152. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/committee_task.py +0 -0
  153. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/conftest.py +0 -0
  154. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/continuation_task.py +0 -0
  155. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
  156. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
  157. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/fake_lm.py +0 -0
  158. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/helpers.py +0 -0
  159. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/target_capabilities.py +0 -0
  160. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_assessments_cli.py +0 -0
  161. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_capture_prompts.py +0 -0
  162. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_composite_module.py +0 -0
  163. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
  164. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_deploy_execute.py +0 -0
  165. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_deploy_pandas.py +0 -0
  166. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_deploy_programs.py +0 -0
  167. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_edge_cases.py +0 -0
  168. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_example_hash.py +0 -0
  169. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_gepa_optimization.py +0 -0
  170. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_interpreter_spans.py +0 -0
  171. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_lm_accuracy.py +0 -0
  172. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_module_tracing.py +0 -0
  173. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_optimization_lineage.py +0 -0
  174. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
  175. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_optimize_authored_metric.py +0 -0
  176. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_optimize_continuation.py +0 -0
  177. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_optimize_custom_proposer.py +0 -0
  178. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_optimize_failures.py +0 -0
  179. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_optimize_loop.py +0 -0
  180. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_optimize_review_committee.py +0 -0
  181. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
  182. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_parallel_export.py +0 -0
  183. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_project_attribution.py +0 -0
  184. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_react_tool_spans.py +0 -0
  185. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_save_load.py +0 -0
  186. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_trace_structure.py +0 -0
  187. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_traces_read.py +0 -0
  188. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_user_attribution.py +0 -0
  189. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/e2e/test_valsets_cli.py +0 -0
  190. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/identity_vectors/01_predict_qa.json +0 -0
  191. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
  192. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
  193. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
  194. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
  195. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
  196. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
  197. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/identity_vectors/08_opaque_module.json +0 -0
  198. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
  199. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/identity_vectors/10_react.json +0 -0
  200. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/identity_vectors/11_refine.json +0 -0
  201. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/identity_vectors/12_retrieve.json +0 -0
  202. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/identity_vectors/13_knn.json +0 -0
  203. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/identity_vectors/14_parallel.json +0 -0
  204. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/identity_vectors/README.md +0 -0
  205. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/ingest_conformance.py +0 -0
  206. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/integration/__init__.py +0 -0
  207. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/integration/authored_optimize/judge.py +0 -0
  208. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
  209. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/integration/conftest.py +0 -0
  210. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
  211. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/integration/hosted_optimize_probe.py +0 -0
  212. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/integration/optimization_run_probe.py +0 -0
  213. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
  214. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/integration/test_logs_renders_traces.py +0 -0
  215. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/integration/test_optimization_run.py +0 -0
  216. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/integration/test_optimizing_progress.py +0 -0
  217. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/integration/test_unhappy_path.py +0 -0
  218. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/load/__init__.py +0 -0
  219. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/load/__main__.py +0 -0
  220. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/load/drain.py +0 -0
  221. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/load/ingest.py +0 -0
  222. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/load/latency_transport.py +0 -0
  223. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/load/rlm_shape.py +0 -0
  224. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/load/rlm_size_report.py +0 -0
  225. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/load/sweep.py +0 -0
  226. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/load/test_rlm_shape.py +0 -0
  227. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/load/test_rlm_size_report.py +0 -0
  228. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/parity/__init__.py +0 -0
  229. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/parity/conftest.py +0 -0
  230. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/parity/seed_parity.py +0 -0
  231. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/parity/test_auth.py +0 -0
  232. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/parity/test_datasets.py +0 -0
  233. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/parity/test_deployments.py +0 -0
  234. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/parity/test_evals.py +0 -0
  235. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/parity/test_health.py +0 -0
  236. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/parity/test_modules.py +0 -0
  237. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/parity/test_optimizations.py +0 -0
  238. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/parity/test_programs.py +0 -0
  239. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/parity/test_signatures.py +0 -0
  240. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/parity/test_trace_streaming.py +0 -0
  241. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/parity/test_traces.py +0 -0
  242. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/__init__.py +0 -0
  243. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/_provider.py +0 -0
  244. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/_readiness.py +0 -0
  245. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/_reliability.py +0 -0
  246. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/_result.py +0 -0
  247. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/mint_bedrock_token.py +0 -0
  248. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/test_bedrock_converse.py +0 -0
  249. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/test_deploy_programs.py +0 -0
  250. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/test_multi_client_stress.py +0 -0
  251. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/test_optimize_client_committee.py +0 -0
  252. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/test_optimize_continuation.py +0 -0
  253. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/test_optimize_loop.py +0 -0
  254. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/test_optimize_loop_wasm.py +0 -0
  255. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/test_optimize_stress.py +0 -0
  256. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/test_provider_roles.py +0 -0
  257. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/test_readiness.py +0 -0
  258. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/test_real_provider_twins.py +0 -0
  259. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/test_reliability.py +0 -0
  260. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/test_reliability_classify.py +0 -0
  261. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/test_rlm_wasm.py +0 -0
  262. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/real_models/test_trace_search.py +0 -0
  263. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/span_reference_fixture.py +0 -0
  264. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_adapter_reentrancy.py +0 -0
  265. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_assessments_api.py +0 -0
  266. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_authored.py +0 -0
  267. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_authored_binding_credentials.py +0 -0
  268. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_authored_bundle_collisions.py +0 -0
  269. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_authored_metric_contract.py +0 -0
  270. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_authored_named_models.py +0 -0
  271. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_authored_optimize.py +0 -0
  272. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_callback.py +0 -0
  273. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_candidate_lineage.py +0 -0
  274. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/__init__.py +0 -0
  275. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/conftest.py +0 -0
  276. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_admin.py +0 -0
  277. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_api_client.py +0 -0
  278. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_assessments.py +0 -0
  279. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_auth.py +0 -0
  280. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_credentials.py +0 -0
  281. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_datasets.py +0 -0
  282. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_deployment_runs.py +0 -0
  283. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_deployments.py +0 -0
  284. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_evals.py +0 -0
  285. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_login.py +0 -0
  286. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_main.py +0 -0
  287. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_optimizations.py +0 -0
  288. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_org.py +0 -0
  289. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_pagination.py +0 -0
  290. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_parity.py +0 -0
  291. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_programs.py +0 -0
  292. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_provider_credentials.py +0 -0
  293. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_schema_sample.py +0 -0
  294. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_shell.py +0 -0
  295. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_tool_servers.py +0 -0
  296. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_top.py +0 -0
  297. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_traces.py +0 -0
  298. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_traces_program_and_module.py +0 -0
  299. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_traces_search_wire.py +0 -0
  300. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_tree_help.py +0 -0
  301. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cli/test_valsets.py +0 -0
  302. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_client.py +0 -0
  303. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_client_floor.py +0 -0
  304. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_client_program_names.py +0 -0
  305. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_clustering/__init__.py +0 -0
  306. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_clustering/conftest.py +0 -0
  307. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_clustering/test_edge_cases.py +0 -0
  308. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_clustering/test_hash_consistency.py +0 -0
  309. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_clustering/test_type_conversion.py +0 -0
  310. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_cmp551_grouping_identity.py +0 -0
  311. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_configuration.py +0 -0
  312. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_context.py +0 -0
  313. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_context_propagation.py +0 -0
  314. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_credential_selection.py +0 -0
  315. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_credential_values.py +0 -0
  316. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_dataset_polling.py +0 -0
  317. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_dataset_sync.py +0 -0
  318. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_datasets.py +0 -0
  319. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_decision_client_spans.py +0 -0
  320. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_decorators.py +0 -0
  321. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_deploy.py +0 -0
  322. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_deploy_mcp_tool_references.py +0 -0
  323. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_deploy_program_id.py +0 -0
  324. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_deployment_metadata.py +0 -0
  325. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_deployment_runs.py +0 -0
  326. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_deployments_read.py +0 -0
  327. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_eval.py +0 -0
  328. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_eval_example_attribution.py +0 -0
  329. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_eval_program_id.py +0 -0
  330. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_eval_start.py +0 -0
  331. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_eval_trace_grouping.py +0 -0
  332. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_exception_paths.py +0 -0
  333. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_execute.py +0 -0
  334. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_execute_run_identity.py +0 -0
  335. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_exporter.py +0 -0
  336. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_exporter_program_names.py +0 -0
  337. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_exporter_queue.py +0 -0
  338. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_exporter_real.py +0 -0
  339. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_fake_lm.py +0 -0
  340. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_gepa_callback.py +0 -0
  341. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_gepa_integration.py +0 -0
  342. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_gepa_output_normalization.py +0 -0
  343. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_gepa_resume.py +0 -0
  344. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_identity.py +0 -0
  345. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_imaging.py +0 -0
  346. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_import_weight.py +0 -0
  347. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_ingest_conformance.py +0 -0
  348. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_ingest_second_producer.py +0 -0
  349. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_integration.py +0 -0
  350. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_interpreter_callback.py +0 -0
  351. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_ir_encoder_properties.py +0 -0
  352. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_ir_golden_vectors.py +0 -0
  353. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_ir_roundtrip.py +0 -0
  354. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_lm15_native_calls.py +0 -0
  355. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_lm_configuration.py +0 -0
  356. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_lm_usage_shapes.py +0 -0
  357. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_mcp_tools.py +0 -0
  358. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_models.py +0 -0
  359. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_multi_client_stress_readiness.py +0 -0
  360. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_no_signature_hash_on_the_wire.py +0 -0
  361. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_optimization_phase_callback.py +0 -0
  362. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_optimization_program_names.py +0 -0
  363. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_optimization_progress.py +0 -0
  364. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_optimization_start.py +0 -0
  365. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_optimize.py +0 -0
  366. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_optimize_budget.py +0 -0
  367. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_optimize_lift.py +0 -0
  368. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_optimize_lms.py +0 -0
  369. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_optimize_proposer.py +0 -0
  370. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_optimize_reflection_lm.py +0 -0
  371. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_optimize_terminal_failure.py +0 -0
  372. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_packaging.py +0 -0
  373. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_packaging_callable_source.py +0 -0
  374. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_packaging_mcp_tools.py +0 -0
  375. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_packaging_program_source.py +0 -0
  376. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_poll_until.py +0 -0
  377. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_program_id_deprecation.py +0 -0
  378. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_program_identity.py +0 -0
  379. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_program_lineage_persist.py +0 -0
  380. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_program_names_in_deploy.py +0 -0
  381. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_programs_create.py +0 -0
  382. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_project_payload.py +0 -0
  383. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_real_provider_twins_readiness.py +0 -0
  384. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_reflector_properties.py +0 -0
  385. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_release_gate_script.py +0 -0
  386. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_release_real_models_script.py +0 -0
  387. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_replaced_names.py +0 -0
  388. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_repro_reactv2_bugs.py +0 -0
  389. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_require_version_bump.py +0 -0
  390. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_retry.py +0 -0
  391. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_reversible_failures.py +0 -0
  392. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_rlm_patch.py +0 -0
  393. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_run_attribution_paths.py +0 -0
  394. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_runtime_tools.py +0 -0
  395. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_schemas.py +0 -0
  396. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_seal_lm_credential.py +0 -0
  397. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_seed_review_run.py +0 -0
  398. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_signature_inputs.py +0 -0
  399. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_span_references.py +0 -0
  400. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_span_sent_once.py +0 -0
  401. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_span_submission.py +0 -0
  402. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_span_submission_wire_contract.py +0 -0
  403. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_system_one.py +0 -0
  404. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_trace_context_concurrency.py +0 -0
  405. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_trace_fact_wire_contract.py +0 -0
  406. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_trace_facts.py +0 -0
  407. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_trace_render.py +0 -0
  408. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_traces_read.py +0 -0
  409. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_tracked_gepa.py +0 -0
  410. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_tracker_enrich.py +0 -0
  411. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_truncation.py +0 -0
  412. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_upload_auth_headers.py +0 -0
  413. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_valsets_api.py +0 -0
  414. {cmpnd-0.84.0 → cmpnd-0.85.0}/tests/test_xxh64.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cmpnd
3
- Version: 0.84.0
3
+ Version: 0.85.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>
@@ -687,18 +687,20 @@ class _CallbackBase:
687
687
  # repl_history (see _AdapterMixin._thin_rlm_adapter_inputs).
688
688
  self._rlm_turn_adapter_spans: set[UUID] = set()
689
689
 
690
- # Parent span -> the last adapter ``format`` span under it and its full
691
- # rendered messages, for the LM call that follows to reference. See
692
- # _AdapterMixin._remember_rendered_messages and _LMMixin.on_lm_start.
693
- self._rendered_messages: dict[UUID, tuple[models.SpanBuilder, Any]] = {}
690
+ # Parent span -> the last adapter ``format`` span under it and the full
691
+ # value of each prompt key it stored whole, for the LM calls that follow
692
+ # to reference. See _AdapterMixin._remember_rendered_prompt and
693
+ # _LMMixin._reference_rendered_prompt.
694
+ self._rendered_prompts: dict[UUID, tuple[models.SpanBuilder, dict[str, Any]]] = {}
694
695
 
695
696
  # Open module span -> id() of its module instance, so an adapter span can
696
697
  # name the predictor it renders for. And (trace, predictor) -> the last
697
- # rendering of each message index that serve can rebuild in full, as
698
- # (the format span storing it, its full text). Dropped when the trace
699
- # ends. See _AdapterMixin._extend_previous_rendering.
698
+ # rendering of each prompt text that serve can rebuild in full, by its
699
+ # path in the format span's outputs, as (the format span storing it, its
700
+ # full text). Dropped when the trace ends. See
701
+ # _AdapterMixin._extend_previous_rendering.
700
702
  self._module_instance_by_span: dict[UUID, int] = {}
701
- self._last_rendering: dict[tuple[str, int], list[tuple[UUID, str] | None]] = {}
703
+ self._last_rendering: dict[tuple[str, int], dict[tuple[str | int, ...], tuple[UUID, str]]] = {}
702
704
 
703
705
  # Ensure exporter is running
704
706
  self._ensure_exporter()
@@ -1058,7 +1060,7 @@ class _ModuleMixin(_CallbackBase):
1058
1060
 
1059
1061
  # An adapter rendering this module's prompt is only ever referenced by an
1060
1062
  # LM call under this module, which has ended with it.
1061
- self._rendered_messages.pop(span.span_id, None)
1063
+ self._rendered_prompts.pop(span.span_id, None)
1062
1064
  self._module_instance_by_span.pop(span.span_id, None)
1063
1065
 
1064
1066
  # RLM repl_history: append trajectory data from PREDICT children
@@ -1213,9 +1215,9 @@ class _ModuleMixin(_CallbackBase):
1213
1215
 
1214
1216
  def _forget_trace_references(self, trace_id: UUID) -> None:
1215
1217
  """Drop the per-trace state reference encoding keeps, once the trace ends."""
1216
- for parent, (fmt_span, _) in list(self._rendered_messages.items()):
1218
+ for parent, (fmt_span, _) in list(self._rendered_prompts.items()):
1217
1219
  if fmt_span.trace_id == trace_id:
1218
- self._rendered_messages.pop(parent, None)
1220
+ self._rendered_prompts.pop(parent, None)
1219
1221
  for key in [k for k in self._last_rendering if k[0] == str(trace_id)]:
1220
1222
  self._last_rendering.pop(key, None)
1221
1223
 
@@ -1540,39 +1542,53 @@ class _LMMixin(_CallbackBase):
1540
1542
 
1541
1543
  # Capture prompt/messages if enabled. A credential passed to the call itself, as in
1542
1544
  # `lm(prompt, api_key=...)`, is stripped first: every member of the org reads a span.
1545
+ # The rendered prompt becomes a reference before truncation, so the rest of the
1546
+ # inputs keep the whole budget.
1543
1547
  if self._capture_prompts:
1544
- cleaned_inputs = helpers.clean_inputs(helpers.without_credentials(inputs), dspy, self._max_input_field_size)
1545
- self._reference_rendered_messages(parent_span, inputs, cleaned_inputs)
1546
- span.set_inputs(cleaned_inputs)
1548
+ referenced = self._reference_rendered_prompt(parent_span, helpers.without_credentials(inputs))
1549
+ span.set_inputs(helpers.clean_inputs(referenced, dspy, self._max_input_field_size))
1547
1550
 
1548
1551
  self._call_id_to_span[call_id] = span
1549
1552
  # Store LM instance reference to access history for token usage
1550
1553
  self._call_id_to_instance[call_id] = instance
1551
1554
  context.push_span(span)
1552
1555
 
1553
- def _reference_rendered_messages(
1554
- self, parent_span: models.SpanBuilder | None, inputs: dict[str, Any], cleaned: dict[str, Any]
1555
- ) -> None:
1556
- """Store the LM's messages as a reference to the adapter output they came from.
1556
+ def _reference_rendered_prompt(
1557
+ self, parent_span: models.SpanBuilder | None, inputs: dict[str, Any]
1558
+ ) -> dict[str, Any]:
1559
+ """``inputs`` with the rendered prompt as references to the adapter output it came from.
1557
1560
 
1558
1561
  The adapter's ``format`` span ended just before this call started, under
1559
- the same parent, and its ``outputs.result`` holds the rendered messages.
1560
- When the LM received exactly those, compared on the full values before any
1561
- truncation, ``messages`` becomes a ``_same_as`` reference to them; serve
1562
- resolves it on read. Anything else — messages the adapter changed after
1563
- rendering, or an LM call no adapter rendered for, such as an RLM
1564
- ``llm_query`` — keeps a full copy, so a mismatch costs bytes, never
1565
- correctness.
1562
+ the same parent, and stored the rendered prompt (``references.prompt_keys``).
1563
+ In the list shape, ``messages`` equal to the stored ``result`` becomes a
1564
+ ``_same_as`` reference to it. In the typed shape, each of ``prompt.system``
1565
+ and ``prompt.messages`` equal to the stored key becomes a reference to that
1566
+ key, and the rest of the request stays inline. Values are compared in full,
1567
+ before any truncation, and serve resolves the references on read.
1568
+ Anything else, such as messages the adapter changed after rendering or an
1569
+ LM call no adapter rendered for, like an RLM ``llm_query``, keeps a full
1570
+ copy, so a mismatch costs bytes, never correctness. Each LM call the
1571
+ adapter makes from one rendering gets the references.
1566
1572
  """
1567
- if parent_span is None or "messages" not in cleaned:
1568
- return
1569
- remembered = self._rendered_messages.pop(parent_span.span_id, None)
1573
+ remembered = self._rendered_prompts.get(parent_span.span_id) if parent_span is not None else None
1570
1574
  if remembered is None:
1571
- return
1575
+ return inputs
1572
1576
  fmt_span, rendered = remembered
1573
- if models._safe_serialize(inputs.get("messages")) != rendered:
1574
- return
1575
- cleaned["messages"] = references.same_as(str(fmt_span.span_id), "outputs", ["result"])
1577
+ fmt_id = str(fmt_span.span_id)
1578
+ if "result" in rendered:
1579
+ if "messages" in inputs and models._safe_serialize(inputs["messages"]) == rendered["result"]:
1580
+ return {**inputs, "messages": references.same_as(fmt_id, "outputs", ["result"])}
1581
+ return inputs
1582
+ if inputs.get("prompt") is None:
1583
+ return inputs
1584
+ prompt = models._safe_serialize(inputs["prompt"])
1585
+ if not isinstance(prompt, dict):
1586
+ return inputs
1587
+ referenced = dict(prompt)
1588
+ for key in references.TYPED_PROMPT_KEYS:
1589
+ if key in rendered and key in prompt and prompt[key] == rendered[key]:
1590
+ referenced[key] = references.same_as(fmt_id, "outputs", [key])
1591
+ return inputs if referenced == prompt else {**inputs, "prompt": referenced}
1576
1592
 
1577
1593
  def on_lm_end(
1578
1594
  self,
@@ -1962,12 +1978,16 @@ class _AdapterMixin(_CallbackBase):
1962
1978
  if self._capture_outputs and outputs:
1963
1979
  rendered = models._safe_serialize(outputs)
1964
1980
  if span.span_type == models.SpanType.ADAPTER_FORMAT:
1965
- # Encode against the previous rendering first, then truncate:
1966
- # only a suffix that alone overflows the budget is cut.
1967
- encoded = self._extend_previous_rendering(span, rendered)
1968
- span.set_outputs(helpers.truncate_outputs(encoded, self._max_output_field_size))
1969
- self._record_rendering(span, rendered)
1970
- self._remember_rendered_messages(span, rendered)
1981
+ # The outputs as stored: a typed Prompt as its own dict, a list
1982
+ # of messages under "result". Encode against the previous
1983
+ # rendering first, then truncate: only a suffix that alone
1984
+ # overflows the budget is cut.
1985
+ body = rendered if isinstance(rendered, dict) else {"result": rendered}
1986
+ encoded = self._extend_previous_rendering(span, body)
1987
+ stored = encoded if isinstance(rendered, dict) else encoded["result"]
1988
+ span.set_outputs(helpers.truncate_outputs(stored, self._max_output_field_size))
1989
+ self._record_rendering(span, body)
1990
+ self._remember_rendered_prompt(span, body)
1971
1991
  else:
1972
1992
  span.set_outputs(helpers.truncate_outputs(rendered, self._max_output_field_size))
1973
1993
 
@@ -1992,78 +2012,81 @@ class _AdapterMixin(_CallbackBase):
1992
2012
  predictor = self._module_instance_by_span.get(span.parent_span_id)
1993
2013
  return None if predictor is None else (str(span.trace_id), predictor)
1994
2014
 
1995
- def _extend_previous_rendering(self, span: models.SpanBuilder, rendered: Any) -> Any:
1996
- """``rendered`` with each message that extends the predictor's last one encoded.
2015
+ def _extend_previous_rendering(self, span: models.SpanBuilder, body: dict[str, Any]) -> dict[str, Any]:
2016
+ """``body`` with each prompt text that extends the predictor's last one encoded.
1997
2017
 
1998
2018
  A predictor called once per turn — an RLM's action step, a ReAct step —
1999
- renders the previous turn's prompt plus what the turn added. For each
2000
- message whose content begins with the same-index message of the last
2001
- rendering this predictor stored in this trace, the content becomes an
2002
- ``_extends`` envelope over that stored message. Matching compares the full
2003
- rendered text and needs no knowledge of the module. The base may itself be
2004
- an extension, and serve resolves the chain.
2019
+ renders the previous turn's prompt plus what the turn added. For each text
2020
+ of the prompt (``references.prompt_text_leaves``) that begins with the text
2021
+ at the same path in the last rendering this predictor stored in this trace,
2022
+ the text becomes an ``_extends`` envelope over that stored text. In the
2023
+ typed shape this covers the system prompt, which is the same on every turn,
2024
+ as well as each message's text parts. Matching compares the full rendered
2025
+ text and needs no knowledge of the module. The base may itself be an
2026
+ extension, and serve resolves the chain.
2005
2027
  """
2006
2028
  key = self._rendering_key(span)
2007
2029
  previous = self._last_rendering.get(key) if key else None
2008
- if not previous or not isinstance(rendered, list):
2009
- return rendered
2010
- encoded = list(rendered)
2011
- for i, message in enumerate(rendered):
2012
- base = previous[i] if i < len(previous) else None
2013
- content = message.get("content") if isinstance(message, dict) else None
2014
- if base is None or not isinstance(content, str):
2030
+ if not previous:
2031
+ return body
2032
+ encoded = body
2033
+ for path, text in references.prompt_text_leaves(body):
2034
+ base = previous.get(path)
2035
+ if base is None:
2015
2036
  continue
2016
- base_span_id, base_content = base
2017
- env = references.extends(
2018
- base_content, content, references.ref(str(base_span_id), "outputs", ["result", i, "content"])
2019
- )
2037
+ base_span_id, base_text = base
2038
+ env = references.extends(base_text, text, references.ref(str(base_span_id), "outputs", list(path)))
2020
2039
  if env is not None:
2021
- encoded[i] = {**message, "content": env}
2040
+ encoded = references.with_value_at(encoded, path, env)
2022
2041
  return encoded
2023
2042
 
2024
- def _record_rendering(self, span: models.SpanBuilder, rendered: Any) -> None:
2043
+ def _record_rendering(self, span: models.SpanBuilder, body: dict[str, Any]) -> None:
2025
2044
  """Keep this rendering as the base for the predictor's next one.
2026
2045
 
2027
- A message becomes the next base only where its stored form resolves to the
2046
+ A text becomes the next base only where its stored form resolves to the
2028
2047
  full text: a whole string, or an extension whose suffix survived
2029
- truncation. Where it was cut, the previous base stays, so the next turn
2030
- extends a message serve can still rebuild.
2048
+ truncation. Where it was cut, the previous base at that path stays, so the
2049
+ next turn extends a text serve can still rebuild.
2031
2050
  """
2032
2051
  key = self._rendering_key(span)
2033
- stored = span.outputs.get("result") if isinstance(span.outputs, dict) else None
2034
- if key is None or not isinstance(rendered, list):
2052
+ if key is None or not references.prompt_keys(body):
2035
2053
  return
2036
- previous = self._last_rendering.get(key) or []
2037
- current: list[tuple[UUID, str] | None] = []
2038
- for i, message in enumerate(rendered):
2039
- content = message.get("content") if isinstance(message, dict) else None
2040
- kept = stored[i] if isinstance(stored, list) and len(stored) == len(rendered) else None
2041
- kept_content = kept.get("content") if isinstance(kept, dict) else None
2042
- resolvable = kept_content == content or (
2043
- isinstance(kept_content, dict)
2044
- and set(kept_content) == {"_extends", "_prefix_chars", "_suffix"}
2045
- and isinstance(kept_content["_suffix"], str)
2054
+ previous = self._last_rendering.get(key) or {}
2055
+ current: dict[tuple[str | int, ...], tuple[UUID, str]] = {}
2056
+ for path, text in references.prompt_text_leaves(body):
2057
+ kept = references.value_at(span.outputs, path)
2058
+ resolvable = kept == text or (
2059
+ isinstance(kept, dict)
2060
+ and set(kept) == {"_extends", "_prefix_chars", "_suffix"}
2061
+ and isinstance(kept["_suffix"], str)
2046
2062
  )
2047
- if isinstance(content, str) and resolvable:
2048
- current.append((span.span_id, content))
2049
- else:
2050
- current.append(previous[i] if i < len(previous) else None)
2063
+ if resolvable:
2064
+ current[path] = (span.span_id, text)
2065
+ elif path in previous:
2066
+ current[path] = previous[path]
2051
2067
  self._last_rendering[key] = current
2052
2068
 
2053
- def _remember_rendered_messages(self, span: models.SpanBuilder, rendered: Any) -> None:
2054
- """Keep a ``format`` span's full rendered messages for the LM call that follows.
2069
+ def _remember_rendered_prompt(self, span: models.SpanBuilder, body: dict[str, Any]) -> None:
2070
+ """Keep a ``format`` span's full rendered prompt for the LM calls that follow.
2055
2071
 
2056
- The adapter hands exactly these messages to the LM, which starts after
2057
- this span ended, as its sibling under the same parent. ``on_lm_start``
2058
- compares its messages with them and writes a reference when they are
2059
- equal. Kept only when the stored output still holds the list at
2060
- ``outputs.result``: a list truncated whole into an envelope cannot be
2061
- walked into, and the LM span then keeps its own copy.
2072
+ The adapter hands exactly this prompt to the LM, which starts after this
2073
+ span ended, as its sibling under the same parent.
2074
+ ``_reference_rendered_prompt`` compares the LM's prompt with it and writes
2075
+ references where they are equal. A prompt key is kept only when its stored
2076
+ value resolves in full: a list truncated into an envelope cannot be walked
2077
+ into, and the LM span then keeps its own copy of that key.
2062
2078
  """
2063
- stored = span.outputs.get("result") if isinstance(span.outputs, dict) else None
2064
- if span.parent_span_id is None or not isinstance(rendered, list) or not isinstance(stored, list):
2079
+ if span.parent_span_id is None:
2065
2080
  return
2066
- self._rendered_messages[span.parent_span_id] = (span, rendered)
2081
+ kept = {}
2082
+ for key in references.prompt_keys(body):
2083
+ stored = references.value_at(span.outputs, (key,))
2084
+ if stored is not None and references.resolves_in_full(stored):
2085
+ kept[key] = body[key]
2086
+ if kept:
2087
+ self._rendered_prompts[span.parent_span_id] = (span, kept)
2088
+ else:
2089
+ self._rendered_prompts.pop(span.parent_span_id, None)
2067
2090
 
2068
2091
 
2069
2092
  class _ToolMixin(_CallbackBase):
@@ -305,13 +305,15 @@ def credential_fields(kwargs: Any) -> list[str]:
305
305
 
306
306
  def without_credentials(inputs: Any) -> Any:
307
307
  """``inputs`` with every credential :func:`credential_fields` names removed, at the top
308
- level and in the nested ``kwargs`` an LM call's hook inputs carry."""
308
+ level and in the nested keyword arguments an LM call's hook inputs carry: ``kwargs``
309
+ through DSPy 3.4, and ``options`` where the LM takes an lm15 ``Request``."""
309
310
  if not isinstance(inputs, dict):
310
311
  return inputs
311
312
  kept = _without_credential_kwargs(inputs)
312
- nested = kept.get("kwargs")
313
- if isinstance(nested, dict):
314
- kept["kwargs"] = _without_credential_kwargs(nested)
313
+ for holder in ("kwargs", "options"):
314
+ nested = kept.get(holder)
315
+ if isinstance(nested, dict):
316
+ kept[holder] = _without_credential_kwargs(nested)
315
317
  return kept
316
318
 
317
319
 
@@ -114,7 +114,10 @@ def reflect(m: Any) -> program.Module:
114
114
  return program.ChainOfThought(project_sig(_user_sig_of_cot(m)))
115
115
  if isinstance(m, dspy.MultiChainComparison):
116
116
  return program.MultiChainComparison(project_sig(m.predict.signature)) # pyright: ignore[reportAttributeAccessIssue]
117
- if isinstance(m, dspy.ProgramOfThought):
117
+ # DSPy main removed ProgramOfThought after 3.4, so it is looked up like the
118
+ # kinds a release may lack.
119
+ pot_cls = _opt("ProgramOfThought")
120
+ if pot_cls is not None and isinstance(m, pot_cls):
118
121
  return program.ProgramOfThought(project_sig(m.signature))
119
122
 
120
123
  rlm_cls = _opt("RLM")
@@ -63,6 +63,40 @@ def _type_name(value: Any) -> str:
63
63
  return _strip_unstorable(type(value).__name__)
64
64
 
65
65
 
66
+ def _is_lm15_value(value: Any) -> bool:
67
+ """Whether ``value`` is an lm15 object, or the typed ``Prompt`` a DSPy adapter renders from them.
68
+
69
+ DSPy vendors lm15 as ``dspy._vendor.lm15``. Its ``Prompt`` lives in
70
+ ``dspy.adapters.base`` and holds lm15 messages.
71
+ """
72
+ module = type(value).__module__
73
+ if module == "lm15" or module.startswith(("lm15.", "dspy._vendor.lm15")):
74
+ return True
75
+ return module == "dspy.adapters.base" and type(value).__name__ == "Prompt"
76
+
77
+
78
+ def _compact_dataclass(value: Any, _stack: set[int]) -> dict[str, Any]:
79
+ """An lm15 dataclass as a dict without its null and empty fields.
80
+
81
+ lm15 objects carry many optional fields, such as a request's eighteen
82
+ ``config`` settings and an empty ``continuation`` on every message and part,
83
+ that are null or empty on almost every call. lm15's own serializer omits
84
+ them, and so does this. ``0``, ``False`` and ``""`` stay. The adapter's
85
+ ``Prompt`` and the LM call's ``Request`` serialize their messages by the
86
+ same rule, so the span references that compare them still match. Nested
87
+ lm15 objects are compacted too, and ``provider_data`` stays.
88
+ """
89
+ import dataclasses
90
+
91
+ compact: dict[str, Any] = {}
92
+ for member in dataclasses.fields(value):
93
+ serialized = _safe_serialize(getattr(value, member.name), _stack)
94
+ if serialized is None or serialized == [] or serialized == {}:
95
+ continue
96
+ compact[_strip_unstorable(member.name)] = serialized
97
+ return compact
98
+
99
+
66
100
  def _safe_serialize(value: Any, _stack: set[int] | None = None) -> Any:
67
101
  """Recursively serialize a value to JSON-safe types.
68
102
 
@@ -147,6 +181,8 @@ def _safe_serialize(value: Any, _stack: set[int] | None = None) -> Any:
147
181
  import dataclasses
148
182
 
149
183
  if dataclasses.is_dataclass(value) and not isinstance(value, type):
184
+ if _is_lm15_value(value):
185
+ return _compact_dataclass(value, _stack)
150
186
  return _safe_serialize(dataclasses.asdict(value), _stack)
151
187
  except Exception:
152
188
  pass
@@ -0,0 +1,180 @@
1
+ """Reference envelopes: a span body that names a value an earlier span recorded.
2
+
3
+ A trace records the same prompt on several spans: the adapter renders the
4
+ messages, and the LM call receives them. Rather than store a second copy, the
5
+ SDK writes the later occurrence as a reference to the earlier one:
6
+
7
+ {"_same_as": {"span_id": "<uuid>", "field": "outputs", "path": ["result"]}}
8
+
9
+ ``field`` is ``inputs``, ``outputs`` or ``attributes``, and ``path`` walks object
10
+ keys (strings) and list indexes (integers) inside it. Like the truncation
11
+ envelope, a reference envelope carries only its marker keys, so serve can tell
12
+ it from a user object that happens to hold one of them.
13
+
14
+ A string that begins with a string an earlier span recorded is written as an
15
+ extension of it: the length of the shared prefix and what follows.
16
+
17
+ {"_extends": {"span_id": "<uuid>", "field": "outputs", "path": ["result", 1, "content"]},
18
+ "_prefix_chars": 71234, "_suffix": "=== Step 9 ===\\n..."}
19
+
20
+ ``_prefix_chars`` counts code points, the unit a Python ``str`` indexes, and
21
+ serve slices the base by code point too. A turn of an RLM or ReAct program
22
+ renders the previous turn's prompt plus one more step, so each turn is stored
23
+ as a small suffix and the whole trace grows linearly with its turns.
24
+
25
+ An adapter's ``format`` span stores its rendered prompt in one of two shapes,
26
+ and references name text inside either:
27
+
28
+ * DSPy 3.4 and earlier render a list of ``{role, content}`` messages, stored at
29
+ ``outputs.result``. The LM call receives the same list as ``inputs.messages``.
30
+ * lm15's typed ``Prompt`` stores ``outputs.system`` and ``outputs.messages``,
31
+ where each message holds ``parts`` and each text part holds ``text``. The LM
32
+ call receives both inside ``inputs.prompt``, an lm15 ``Request``.
33
+
34
+ A reference always names a span that ended before the referring span started,
35
+ so its base has already been exported when the reference is written. serve
36
+ resolves references when it reads a trace, so every reader sees the full value;
37
+ the SDK never has to.
38
+ """
39
+
40
+ from __future__ import annotations
41
+
42
+ from typing import Any
43
+
44
+ FIELDS = ("inputs", "outputs", "attributes")
45
+
46
+ # An extension must share at least this many code points with its base, and at
47
+ # least half of its own length: below that the envelope saves too little to be
48
+ # worth a hop on read.
49
+ MIN_PREFIX_CHARS = 1024
50
+
51
+
52
+ def ref(span_id: str, field: str, path: list[Any]) -> dict[str, Any]:
53
+ """The location of a stored value: a span, one of its bodies, and a path into it."""
54
+ if field not in FIELDS:
55
+ raise ValueError(f"field must be one of {FIELDS}, not {field!r}")
56
+ return {"span_id": span_id, "field": field, "path": list(path)}
57
+
58
+
59
+ def same_as(span_id: str, field: str, path: list[Any]) -> dict[str, Any]:
60
+ """A value equal to the one stored at ``span_id``'s ``field`` under ``path``."""
61
+ return {"_same_as": ref(span_id, field, path)}
62
+
63
+
64
+ def common_prefix_chars(a: str, b: str) -> int:
65
+ """The number of leading code points ``a`` and ``b`` share.
66
+
67
+ A binary search over slice comparisons, so the character loop runs in C:
68
+ prompts reach hundreds of kilobytes and a turn compares two of them.
69
+ """
70
+ lo, hi = 0, min(len(a), len(b))
71
+ while lo < hi:
72
+ mid = (lo + hi + 1) // 2
73
+ if a[:mid] == b[:mid]:
74
+ lo = mid
75
+ else:
76
+ hi = mid - 1
77
+ return lo
78
+
79
+
80
+ def extends(base: str, value: str, base_ref: dict[str, Any]) -> dict[str, Any] | None:
81
+ """``value`` as an extension of ``base``, stored at ``base_ref``, or None.
82
+
83
+ None when the shared prefix is shorter than ``MIN_PREFIX_CHARS`` or than half
84
+ of ``value``; the caller then stores ``value`` in full.
85
+ """
86
+ n = common_prefix_chars(base, value)
87
+ if n < MIN_PREFIX_CHARS or 2 * n < len(value):
88
+ return None
89
+ return {"_extends": base_ref, "_prefix_chars": n, "_suffix": value[n:]}
90
+
91
+
92
+ # The keys of a rendered prompt an LM call receives, in each shape: the list at
93
+ # ``outputs.result``, or the typed ``Prompt``'s two halves.
94
+ LIST_PROMPT_KEYS = ("result",)
95
+ TYPED_PROMPT_KEYS = ("system", "messages")
96
+
97
+
98
+ def prompt_keys(outputs: Any) -> tuple[str, ...]:
99
+ """The keys of an adapter ``format`` span's outputs that hold its rendered prompt.
100
+
101
+ Empty when ``outputs`` is neither shape, such as a custom adapter's output.
102
+ """
103
+ if not isinstance(outputs, dict):
104
+ return ()
105
+ if isinstance(outputs.get("result"), list):
106
+ return LIST_PROMPT_KEYS
107
+ if isinstance(outputs.get("messages"), list):
108
+ return TYPED_PROMPT_KEYS
109
+ return ()
110
+
111
+
112
+ def prompt_text_leaves(outputs: Any) -> list[tuple[tuple[str | int, ...], str]]:
113
+ """Each text of a rendered prompt, with its path inside the adapter span's outputs.
114
+
115
+ In the list shape, each message's string ``content``. In the typed shape, the
116
+ ``system`` text and each message's text parts. A message or part of another
117
+ form, such as an image, has no text leaf and is left out.
118
+ """
119
+ keys = prompt_keys(outputs)
120
+ leaves: list[tuple[tuple[str | int, ...], str]] = []
121
+ if keys == LIST_PROMPT_KEYS:
122
+ for i, message in enumerate(outputs["result"]):
123
+ content = message.get("content") if isinstance(message, dict) else None
124
+ if isinstance(content, str):
125
+ leaves.append((("result", i, "content"), content))
126
+ elif keys == TYPED_PROMPT_KEYS:
127
+ if isinstance(outputs.get("system"), str):
128
+ leaves.append((("system",), outputs["system"]))
129
+ for i, message in enumerate(outputs["messages"]):
130
+ parts = message.get("parts") if isinstance(message, dict) else None
131
+ for j, part in enumerate(parts if isinstance(parts, list) else []):
132
+ text = part.get("text") if isinstance(part, dict) and part.get("type") == "text" else None
133
+ if isinstance(text, str):
134
+ leaves.append((("messages", i, "parts", j, "text"), text))
135
+ return leaves
136
+
137
+
138
+ def value_at(value: Any, path: tuple[str | int, ...]) -> Any:
139
+ """The value under ``path``, or None when the path does not exist in ``value``."""
140
+ for step in path:
141
+ if isinstance(step, str) and isinstance(value, dict):
142
+ value = value.get(step)
143
+ elif isinstance(step, int) and isinstance(value, list) and 0 <= step < len(value):
144
+ value = value[step]
145
+ else:
146
+ return None
147
+ return value
148
+
149
+
150
+ def with_value_at(value: Any, path: tuple[str | int, ...], new: Any) -> Any:
151
+ """A copy of ``value`` with ``new`` under ``path``.
152
+
153
+ Only the dicts and lists along the path are copied; everything else is shared.
154
+ """
155
+ if not path:
156
+ return new
157
+ step, rest = path[0], path[1:]
158
+ if isinstance(value, dict):
159
+ return {**value, step: with_value_at(value[step], rest, new)}
160
+ if not isinstance(step, int):
161
+ raise TypeError(f"a list is indexed by an int, not {step!r}")
162
+ copied = list(value)
163
+ copied[step] = with_value_at(copied[step], rest, new)
164
+ return copied
165
+
166
+
167
+ def resolves_in_full(value: Any) -> bool:
168
+ """Whether a stored value holds its whole text: no truncation envelope inside it.
169
+
170
+ A reference to such a value, or an extension of it, gives a reader the full
171
+ value. An ``_extends`` envelope counts when its suffix survived; its base was
172
+ itself checked when it was stored.
173
+ """
174
+ if isinstance(value, dict):
175
+ if value.get("_truncated") is True:
176
+ return False
177
+ return all(resolves_in_full(v) for v in value.values())
178
+ if isinstance(value, list):
179
+ return all(resolves_in_full(v) for v in value)
180
+ return True
@@ -1027,6 +1027,8 @@ The callback mints `source_observation_id` when the LM call starts and records `
1027
1027
 
1028
1028
  The LM span's `lm.finish_reason` attribute records why the model stopped, in the chat vocabulary serve's truncation warning reads. Each response shape spells it differently. Chat puts it on the first choice and DSPy 3.3's typed `LMResponse` on the first output. The lm15 `Response` a DSPy 3.4 engine returns carries it at the top level and names a tool-call stop `tool_call`, which the SDK records as `tool_calls`. The Responses API reports a `status`, and its `max_output_tokens` cutoff lands as `length`. `_finish_reason` in `python-sdk/cmpnd/callback.py` holds the mapping.
1029
1029
 
1030
+ An lm15 object in a span body, such as an LM call's `Request` and `Response` or an adapter's typed `Prompt`, is stored without its null and empty fields. A request's `config` holds eighteen settings that are null on almost every call, and every message and part holds an empty `continuation`. lm15's own serializer omits them, and `_compact_dataclass` in `python-sdk/cmpnd/models.py` does the same for lm15 types only. `0`, `False`, `""` and `provider_data` stay. A `Prompt` and a `Request` serialize their messages by the same rule, so the references in the next section still match.
1031
+
1030
1032
  The LM span's other `lm.*` attributes copy only the LM kwargs that `SAFE_LM_KWARGS` in `python-sdk/cmpnd/helpers.py` names. These are the sampling and response-shape settings, such as `temperature` and `reasoning_effort`, and dspy.LM's own call settings, such as `num_retries`. Every member of the org can read a span. An LM's kwargs can also carry provider credentials under names no deny-list keeps up with, such as `aws_secret_access_key`, `azure_ad_token`, `vertex_credentials` and `extra_headers`. So the list names what may be copied, and a kwarg it does not name stays out. It also leaves out `api_base` and `base_url`, which name a destination. The `dspy.LM(...)` line in a programmatic deploy's generated source goes through the same list, because `dspy.LM.dump_state()` drops only `api_key`. The worker supplies the deployment's credential and destination itself.
1031
1033
 
1032
1034
  `CREDENTIAL_LM_KWARGS` in the same file names the credential arguments themselves: `api_key` and LiteLLM's others, such as `aws_secret_access_key`, `aws_session_token`, `vertex_credentials` and `azure_ad_token`. The callback strips each of them from an LM call's span inputs, so a key passed to the call itself, as in `lm(prompt, api_key=...)`, never reaches a span. A header in `extra_headers` or `headers` whose name carries `auth`, `key`, `token`, `secret`, `cookie` or `password`, such as `Authorization` or `x-api-key`, counts as a credential too, and the callback drops it while keeping a plain header such as `X-Title`. The same rules drive `cmpnd deploy FILE`'s import check and `cmpnd.Client.optimize_deployment`'s refusal of a keyed LM. `api_base` and `base_url` are not on it: an address holds nothing secret, and a program keeps its `api_base` for its laptop run.
@@ -1076,16 +1078,18 @@ A string that begins with a string an earlier span recorded is stored as an exte
1076
1078
 
1077
1079
  `field` is `inputs`, `outputs` or `attributes`, and `path` walks object keys and list indexes inside it. `_prefix_chars` counts code points. Like a truncation envelope, a reference carries only its marker keys. serve resolves references when it reads a trace (`serve/internal/platform/spanrefs`), so every reader sees the full value, and a reference serve cannot resolve renders as a truncation band naming the span.
1078
1080
 
1081
+ An adapter's `format` span stores its rendered prompt in one of two shapes. DSPy 3.4 and earlier render a list of `{role, content}` messages at `outputs.result`, and the LM call receives that list as `inputs.messages`. A DSPy with lm15 typed prompts renders a `Prompt` stored as `outputs.system` and `outputs.messages`, where each message holds `parts` and each text part holds `text`. Its LM call receives an lm15 `Request` whose `inputs.prompt.system` and `inputs.prompt.messages` hold the same values. `references.prompt_keys` and `references.prompt_text_leaves` read either shape.
1082
+
1079
1083
  Where the SDK writes one:
1080
1084
 
1081
- - **`lm_call.inputs.messages`** names the `adapter_format` span's `outputs.result`. `_AdapterMixin._remember_rendered_messages` keeps each `format` span's full rendered messages, keyed by its parent span. `_LMMixin._reference_rendered_messages` compares the LM's messages with them on the full values, before truncation, and writes the reference only when they are equal. Messages the adapter changed after rendering, an LM call with no adapter above it (an RLM `llm_query`), an adapter output that was not captured, and an adapter output truncated whole into a list envelope all keep a full copy.
1082
- - **`adapter_format.outputs.result[i].content`** extends the same message of the previous `format` call by the same module instance in the trace. An RLM or ReAct turn renders the previous turn's prompt plus one step, so each turn stores only its new step and the trace grows linearly with its turns rather than quadratically. `_AdapterMixin._extend_previous_rendering` compares the full rendered content, before truncation, and writes `_extends` only when the shared prefix is at least `MIN_PREFIX_CHARS` (1024) code points and at least half the message; otherwise the message is stored whole. The base is the most recent rendering whose stored form resolves back to the full text: a message stored truncated is never a base, so a later turn extends the last one that was stored intact. A suffix larger than the field budget is stored truncated like any other string, and the next turn extends an earlier base instead. A different predictor, such as RLM's extract fallback, has no previous rendering and stores its prompt whole.
1085
+ - **The LM call's prompt** names the `adapter_format` span's output. In the list shape, `lm_call.inputs.messages` names `outputs.result`. In the typed shape, `lm_call.inputs.prompt.system` and `lm_call.inputs.prompt.messages` each name the same key of the outputs, and the rest of the request (`model`, `tools`, `config`) stays inline. `_AdapterMixin._remember_rendered_prompt` keeps each `format` span's full rendered prompt, keyed by its parent span, for every prompt key whose stored value resolves in full. `_LMMixin._reference_rendered_prompt` compares the LM's prompt with it on the full values, before truncation, and writes a reference for each key that is equal. Every LM call the adapter makes from one rendering gets the references. Messages the adapter changed after rendering, an LM call with no adapter above it (an RLM `llm_query`), an adapter output that was not captured, and an adapter output truncated into a list envelope all keep a full copy.
1086
+ - **Each text of an adapter rendering** extends the text at the same path in the previous `format` call by the same module instance in the trace. In the list shape that is `outputs.result[i].content`. In the typed shape it is `outputs.system` and `outputs.messages[i].parts[j].text`, so the system prompt, which is the same on every turn, stores as an extension with an empty suffix. An RLM or ReAct turn renders the previous turn's prompt plus one step, so each turn stores only its new step and the trace grows linearly with its turns rather than quadratically. `_AdapterMixin._extend_previous_rendering` compares the full rendered text, before truncation, and writes `_extends` only when the shared prefix is at least `MIN_PREFIX_CHARS` (1024) code points and at least half the text; otherwise the text is stored whole. A typed `system` made of content parts has no text leaf and stays whole. The base is the most recent rendering whose stored form resolves back to the full text: a text stored truncated is never a base, so a later turn extends the last one that was stored intact. A suffix larger than the field budget is stored truncated like any other string, and the next turn extends an earlier base instead. A different predictor, such as RLM's extract fallback, has no previous rendering and stores its prompt whole.
1083
1087
 
1084
1088
  A reference always names a span that ended before the referring span started, so the base has been exported when the reference is written, including on the streaming (RLM) path.
1085
1089
 
1086
1090
  The adapter span under an RLM turn also drops `repl_history` from its inputs and carries `thinned="true"`, the same thinning `on_module_end` applies to the turn's `Predict` span. The RLM span's `repl_history` attribute holds the trajectory once, and the adapter's rendered messages still hold the history the LM saw.
1087
1091
 
1088
- `docs/schemas/span-references/vectors.json` holds the golden vectors. `tests/test_span_references.py` checks the SDK's emitter against them, and serve's `spanrefs` tests resolve them. `docs/schemas/span-references/rlm-30-turns.json.gz` is the SDK's own output for a 30-turn RLM run, with a digest of every prompt the LM received; serve ingests it into Postgres and checks each prompt resolves (`serve/internal/platform/pg/traces_spanrefs_integration_test.go`). `tests/span_reference_fixture.py` regenerates it, and `tests/test_span_references.py` fails when the committed copy is stale on the DSPy version `uv.lock` pins.
1092
+ `docs/schemas/span-references/vectors.json` holds the golden vectors. `tests/test_span_references.py` checks the SDK's emitter against them, and serve's `spanrefs` tests resolve them. `docs/schemas/span-references/rlm-30-turns.json.gz` is the SDK's own output for a 30-turn RLM run, with a digest of every prompt the LM received; serve ingests it into Postgres and checks each prompt resolves (`serve/internal/platform/pg/traces_spanrefs_integration_test.go`). `tests/span_reference_fixture.py` regenerates it, and `tests/test_span_references.py` fails when the committed copy is stale on the DSPy version `uv.lock` pins. `tests/test_lm15_prompt_references.py` drives the callback with typed prompts, because no DSPy in the test matrix renders them.
1089
1093
 
1090
1094
  ## Storability: what the SDK guarantees about the payloads it emits
1091
1095
 
@@ -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.84.0"
8
+ version = "0.85.0"
9
9
  description = "DSPy observability and deployment SDK for cmpnd"
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.11"