cmpnd 0.85.2__tar.gz → 0.86.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cmpnd-0.85.2 → cmpnd-0.86.1}/PKG-INFO +1 -1
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/callback.py +4 -28
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/commands/admin.py +59 -19
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/verbs.py +2 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/docs/cli.md +29 -14
- {cmpnd-0.85.2 → cmpnd-0.86.1}/docs/sdk-instrumentation.md +2 -2
- {cmpnd-0.85.2 → cmpnd-0.86.1}/pyproject.toml +1 -1
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_admin.py +51 -10
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_lm_usage_shapes.py +0 -70
- cmpnd-0.86.1/tests/test_token_attribution.py +122 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/uv.lock +1 -1
- {cmpnd-0.85.2 → cmpnd-0.86.1}/.gitignore +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/AGENTS.md +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/CLAUDE.md +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/README.md +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/__init__.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/_assessments.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/_authored_import.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/_deployments.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/_gepa_patch.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/_http.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/_runtime_tools.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/_traces.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/_valsets.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/authored.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/__init__.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/commands/assessments.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/commands/deployments.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/commands/evals.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/commands/org.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/commands/programs.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/commands/provider_credentials.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/commands/top.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/commands/traces.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/commands/valsets.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/shell.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/trace_render.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/tree_help.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/cli/views.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/client.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/configuration.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/context.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/credentials.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/dataset_sync.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/datasets.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/decorators.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/deployment.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/deployment_metadata.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/deployment_runs.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/execution.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/exporter.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/helpers.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/identity.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/imaging.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/ir/reflection.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/lm_configuration.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/mcp_servers.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/models.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/named_programs.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/optimization.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/optimizers/gepa.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/optimizers/gepa_callback.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/optimizers/tracker.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/packaging.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/program_naming.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/programs.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/references.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/retry.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/cmpnd/traces.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/docs/2026-04-02-code-review.md +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/docs/client.md +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/docs/custom-proposer.md +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/docs/reading-traces.md +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/examples/local_ollama.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/ingest/README.md +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/ingest/cmpnd_ingest/bodies.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/ingest/cmpnd_ingest/client_floor.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/ingest/cmpnd_ingest/exporter_http.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/ingest/cmpnd_ingest/program_names.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/ingest/cmpnd_ingest/protocols.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/ingest/cmpnd_ingest/span_context.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/ingest/cmpnd_ingest/trace_facts.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/ingest/cmpnd_ingest/user_agent.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/ingest/pyproject.toml +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/scripts/capture_review_run.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/scripts/check-pip-install.sh +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/scripts/install-test-env.sh +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/scripts/release-gate.sh +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/scripts/release-real-models.sh +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/scripts/require_version_bump.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/scripts/seed_review_run.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/scripts/selfhost_compat.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/scripts/test-in-linux.sh +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/stubs/dspy/utils/dummies.pyi +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/__init__.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/_credential_route.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/_deployment_signature_helpers.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/_fake_clock.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/_resolve_refs.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/_traced_run.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/conftest.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/committee_task.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/continuation_task.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/helpers.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/target_capabilities.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_assessments_cli.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_composite_module.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_deploy_execute.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_deploy_pandas.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_deploy_programs.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_optimize_authored_metric.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_optimize_continuation.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_optimize_custom_proposer.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_optimize_failures.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_parallel_export.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_project_attribution.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_traces_read.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_user_attribution.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/e2e/test_valsets_cli.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/integration/__init__.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/integration/authored_optimize/judge.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/integration/conftest.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/integration/hosted_optimize_probe.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/integration/test_logs_renders_traces.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/integration/test_unhappy_path.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/lm15_prompt_fixture.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/lm15_prompts/four_turn_predict.json +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/lm15_prompts/rlm_six_turns.json +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/load/__init__.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/load/__main__.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/load/drain.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/load/ingest.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/load/rlm_shape.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/load/rlm_size_report.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/load/sweep.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/load/test_rlm_size_report.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/parity/__init__.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/parity/conftest.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/parity/test_health.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/parity/test_trace_streaming.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/parity/test_traces.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/__init__.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/_provider.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/_readiness.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/_reliability.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/_result.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/mint_bedrock_token.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/test_bedrock_converse.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/test_deploy_programs.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/test_multi_client_stress.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/test_optimize_client_committee.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/test_optimize_continuation.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/test_optimize_loop.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/test_optimize_loop_wasm.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/test_optimize_stress.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/test_provider_roles.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/test_readiness.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/test_real_provider_twins.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/test_reliability.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/test_reliability_classify.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/test_rlm_wasm.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/real_models/test_trace_search.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/span_reference_fixture.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_adapter_reentrancy.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_assessments_api.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_authored.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_authored_binding_credentials.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_authored_bundle_collisions.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_authored_metric_contract.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_authored_named_models.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_authored_optimize.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_callback.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_api_client.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_assessments.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_deployment_runs.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_deployments.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_evals.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_main.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_org.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_parity.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_programs.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_provider_credentials.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_schema_sample.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_shell.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_top.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_traces.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_traces_program_and_module.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_traces_search_wire.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_tree_help.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cli/test_valsets.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_client.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_client_floor.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_client_program_names.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_configuration.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_context.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_context_propagation.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_credential_kwargs.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_credential_selection.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_credential_values.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_dataset_sync.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_datasets.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_decision_client_spans.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_decorators.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_deploy.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_deploy_program_id.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_deployment_metadata.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_deployment_runs.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_deployments_read.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_eval.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_eval_example_attribution.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_eval_program_id.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_eval_start.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_eval_trace_grouping.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_execute.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_execute_run_identity.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_exporter.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_exporter_program_names.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_exporter_queue.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_exporter_real.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_gepa_output_normalization.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_identity.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_imaging.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_import_weight.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_ingest_conformance.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_integration.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_interpreter_callback.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_lm15_native_calls.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_lm15_prompt_fixtures.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_lm15_prompt_references.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_lm_configuration.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_mcp_tools.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_models.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_multi_client_stress_readiness.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_no_signature_hash_on_the_wire.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_optimization_phase_callback.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_optimization_program_names.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_optimize.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_optimize_lms.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_optimize_reflection_lm.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_packaging.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_packaging_mcp_tools.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_packaging_program_source.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_poll_until.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_program_id_deprecation.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_program_identity.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_program_lineage_persist.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_program_names_in_deploy.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_program_naming.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_programs_create.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_project_payload.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_real_provider_twins_readiness.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_reflector_properties.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_reflector_smoke.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_release_gate_script.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_release_real_models_script.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_replaced_names.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_repro_reactv2_bugs.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_retry.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_rlm_patch.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_run_attribution_paths.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_runtime_tools.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_schemas.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_seal_lm_credential.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_signature_inputs.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_span_references.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_span_sent_once.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_span_submission.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_span_submission_wire_contract.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_system_one.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_trace_context_concurrency.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_trace_fact_wire_contract.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_trace_facts.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_trace_render.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_traces_read.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_tracked_gepa.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_truncation.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_upload_auth_headers.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_user_agent.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_valsets_api.py +0 -0
- {cmpnd-0.85.2 → cmpnd-0.86.1}/tests/test_xxh64.py +0 -0
|
@@ -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
|
-
|
|
1056
|
-
|
|
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)."""
|
|
@@ -10,8 +10,8 @@ inspect, create, approve/reject the pending-signup queue, promote/demote admin,
|
|
|
10
10
|
disable/enable, reset a password, and manage a user's API-key scopes). `cmpnd
|
|
11
11
|
admin org plan …` and `cmpnd admin org overrides …` move an org between plans
|
|
12
12
|
and override one of its limits, `cmpnd admin org flags …` pins a feature flag
|
|
13
|
-
for one org, `cmpnd admin plans …` edits
|
|
14
|
-
|
|
13
|
+
for one org, `cmpnd admin plans …` edits a plan's draft, publishes it as a new
|
|
14
|
+
version, moves orgs between versions and pins a plan's feature flags, and `cmpnd admin signup …` sets who may sign up
|
|
15
15
|
and the plan a new account's org lands on. `cmpnd admin feature-flags …` reads and
|
|
16
16
|
toggles the server's runtime flags. These hit
|
|
17
17
|
/api/v1/admin/* and require an API key carrying the admin:* scopes — the server
|
|
@@ -259,24 +259,55 @@ def _register_user(sub: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
259
259
|
set_scopes.set_defaults(func=handle_user_set_scopes)
|
|
260
260
|
|
|
261
261
|
|
|
262
|
+
def _positive_version(raw: str) -> int:
|
|
263
|
+
"""A plan version is a whole number of 1 or more."""
|
|
264
|
+
try:
|
|
265
|
+
version = int(raw)
|
|
266
|
+
except ValueError:
|
|
267
|
+
version = 0
|
|
268
|
+
if version < 1:
|
|
269
|
+
raise ArgumentTypeError(f"{raw!r} is not a version: give a whole number of 1 or more")
|
|
270
|
+
return version
|
|
271
|
+
|
|
272
|
+
|
|
262
273
|
def _register_plans(sub: _SubParsersAction[ArgumentParser]) -> None:
|
|
263
|
-
plans = sub.add_parser("plans", help="Each plan's
|
|
274
|
+
plans = sub.add_parser("plans", help="Each plan's versions, draft and feature flags")
|
|
264
275
|
psub = plans.add_subparsers(dest="plans_action")
|
|
265
276
|
|
|
266
|
-
ls = psub.add_parser(
|
|
277
|
+
ls = psub.add_parser(
|
|
278
|
+
"list", help="List every plan's versions with their values and org counts, its draft and its feature flags"
|
|
279
|
+
)
|
|
267
280
|
ls.set_defaults(func=handle_plans_list)
|
|
268
281
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
282
|
+
# A published version never changes, so every value write goes to the plan's
|
|
283
|
+
# draft, which serve opens from the current version on the first write.
|
|
284
|
+
draft = psub.add_parser("draft", help="A plan's draft, the next version before it is published")
|
|
285
|
+
dsub = draft.add_subparsers(dest="plans_draft_action")
|
|
286
|
+
draft_update = dsub.add_parser(
|
|
287
|
+
"update", help="Set a value in the plan's draft, opening one if needed; 'none' means no limit"
|
|
288
|
+
)
|
|
289
|
+
draft_update.add_argument("plan", help=PLAN_HELP)
|
|
290
|
+
draft_update.add_argument("name", help="Limit name, such as optimizations.concurrent")
|
|
291
|
+
draft_update.add_argument("value", type=_limit_value, help="A whole number, or 'none' for no limit")
|
|
292
|
+
draft_update.set_defaults(func=handle_plans_draft_update)
|
|
293
|
+
draft_delete = dsub.add_parser("delete", help="Delete a value from the plan's draft, so it sets no limit")
|
|
294
|
+
draft_delete.add_argument("plan", help=PLAN_HELP)
|
|
295
|
+
draft_delete.add_argument("name", help="Limit name")
|
|
296
|
+
draft_delete.set_defaults(func=handle_plans_draft_delete)
|
|
297
|
+
draft_publish = dsub.add_parser("publish", help="Publish the plan's draft as its current version; it moves no org")
|
|
298
|
+
draft_publish.add_argument("plan", help=PLAN_HELP)
|
|
299
|
+
draft_publish.add_argument("--note", default="", help="Why this version exists")
|
|
300
|
+
draft_publish.set_defaults(func=handle_plans_draft_publish)
|
|
301
|
+
|
|
302
|
+
move = psub.add_parser("move", help="Move orgs to a published version of their plan; their overrides stay")
|
|
303
|
+
move.add_argument("plan", help=PLAN_HELP)
|
|
304
|
+
move.add_argument("version", type=_positive_version, help="The published version to move to")
|
|
305
|
+
who = move.add_mutually_exclusive_group(required=True)
|
|
306
|
+
who.add_argument(
|
|
307
|
+
"--from-version", dest="from_version", type=_positive_version, help="Move every org on this version"
|
|
308
|
+
)
|
|
309
|
+
who.add_argument("--org", dest="org_id", help="Move this one org")
|
|
310
|
+
move.set_defaults(func=handle_plans_move)
|
|
280
311
|
|
|
281
312
|
flags = psub.add_parser("flags", help="A plan's feature flags")
|
|
282
313
|
fsub = flags.add_subparsers(dest="plans_flags_action")
|
|
@@ -430,12 +461,21 @@ def handle_plans_list(client: api_client.ApiClient, _args: Any) -> Any:
|
|
|
430
461
|
return client.get("/api/v1/admin/plans")
|
|
431
462
|
|
|
432
463
|
|
|
433
|
-
def
|
|
434
|
-
return client.put(f"/api/v1/admin/plans/{args.plan}/limits/{args.name}", json={"value": args.value})
|
|
464
|
+
def handle_plans_draft_update(client: api_client.ApiClient, args: Any) -> Any:
|
|
465
|
+
return client.put(f"/api/v1/admin/plans/{args.plan}/draft/limits/{args.name}", json={"value": args.value})
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def handle_plans_draft_delete(client: api_client.ApiClient, args: Any) -> Any:
|
|
469
|
+
return client.delete(f"/api/v1/admin/plans/{args.plan}/draft/limits/{args.name}")
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def handle_plans_draft_publish(client: api_client.ApiClient, args: Any) -> Any:
|
|
473
|
+
return client.post(f"/api/v1/admin/plans/{args.plan}/draft/publish", json={"note": args.note})
|
|
435
474
|
|
|
436
475
|
|
|
437
|
-
def
|
|
438
|
-
|
|
476
|
+
def handle_plans_move(client: api_client.ApiClient, args: Any) -> Any:
|
|
477
|
+
body: dict[str, Any] = {"org_id": args.org_id} if args.org_id is not None else {"from_version": args.from_version}
|
|
478
|
+
return client.post(f"/api/v1/admin/plans/{args.plan}/versions/{args.version}/move", json=body)
|
|
439
479
|
|
|
440
480
|
|
|
441
481
|
def handle_plans_flags_update(client: api_client.ApiClient, args: Any) -> Any:
|
|
@@ -168,6 +168,8 @@ DOMAIN_VERBS: dict[str, str] = {
|
|
|
168
168
|
"restore": "returns a program from the Trash with its history, which create would not",
|
|
169
169
|
"purge": "deletes a program in the Trash for good, the irreversible step after trash",
|
|
170
170
|
"rename": "gives a program a new current name and frees the old one for any program, which update would hide",
|
|
171
|
+
"publish": "freezes a plan's draft as its next version, which no write changes again and update would not say",
|
|
172
|
+
"move": "puts orgs on another version of their plan; the version and its values stay as they are",
|
|
171
173
|
}
|
|
172
174
|
|
|
173
175
|
#: Leaves naming a related collection or sub-record rather than an action.
|
|
@@ -944,15 +944,17 @@ reject USER_ID --org-id ORG_ID` declines a pending membership.
|
|
|
944
944
|
|
|
945
945
|
### Plans, limits and signups
|
|
946
946
|
|
|
947
|
-
Every org has a plan, `free`, `pro` or `enterprise`, and
|
|
948
|
-
|
|
949
|
-
`cmpnd admin org list` shows each org's plan, and
|
|
950
|
-
orgs on Free. `cmpnd admin org plan show ORG_ID`
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
`cmpnd admin org overrides
|
|
955
|
-
|
|
947
|
+
Every org has a plan, `free`, `pro` or `enterprise`, and follows one published
|
|
948
|
+
version of it. A version sets the org's limits and never changes. An org's own
|
|
949
|
+
override beats its version. `cmpnd admin org list` shows each org's plan, and
|
|
950
|
+
`--plan free` keeps only the orgs on Free. `cmpnd admin org plan show ORG_ID`
|
|
951
|
+
shows the org's plan, `plan_version`, the plan's `current_version` and every
|
|
952
|
+
limit, with the layer that set it. `cmpnd admin org plan update ORG_ID PLAN`
|
|
953
|
+
moves the org to that plan's current version, and leaves an org already on the
|
|
954
|
+
plan where it is. `cmpnd admin org overrides update ORG_ID NAME VALUE`
|
|
955
|
+
overrides one limit, and a value of `none` lifts the version's limit for that
|
|
956
|
+
org. `cmpnd admin org overrides delete ORG_ID NAME` deletes the override, so
|
|
957
|
+
the org follows its version again. Overrides stay through every move.
|
|
956
958
|
|
|
957
959
|
`cmpnd admin org usage ORG_ID` shows what each limit bounds now, in `in_use`,
|
|
958
960
|
and the org's activity in the last 24 hours, 7 days and 30 days, in `windows`.
|
|
@@ -971,11 +973,24 @@ their date survives only while a membership keeps it. `oldest_key_at` and
|
|
|
971
973
|
the user still holds, because leaving an org deletes the user's keys there.
|
|
972
974
|
Sign-ins are not recorded, and deployment keys do not count as the user's keys.
|
|
973
975
|
|
|
974
|
-
`cmpnd admin plans list` shows
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
976
|
+
`cmpnd admin plans list` shows each plan's current version and its values,
|
|
977
|
+
every published version with its values, publisher, note and org count, the
|
|
978
|
+
draft, and the plan's feature flags. A change to a plan's limits takes three
|
|
979
|
+
steps, and no org moves until the last:
|
|
980
|
+
|
|
981
|
+
1. `cmpnd admin plans draft update PLAN NAME VALUE` sets a value in the plan's
|
|
982
|
+
draft, and `draft delete PLAN NAME` removes one, so the draft sets no limit
|
|
983
|
+
for it. The first edit opens the draft as a copy of the current version.
|
|
984
|
+
2. `cmpnd admin plans draft publish PLAN --note TEXT` publishes the draft as the
|
|
985
|
+
plan's current version. New orgs land on it.
|
|
986
|
+
3. `cmpnd admin plans move PLAN VERSION --from-version N` moves every org on
|
|
987
|
+
version N to VERSION, and `--org ORG_ID` moves one org. It answers `moved`,
|
|
988
|
+
the number of orgs it moved, and refuses a draft or a version the plan
|
|
989
|
+
lacks.
|
|
990
|
+
|
|
991
|
+
`cmpnd admin plans flags update PLAN NAME [--disabled]` pins a flag for the
|
|
992
|
+
plan, and `flags delete` hands the plan back to the global default. Flags have
|
|
993
|
+
no versions. A plan's flag must already exist, so create
|
|
979
994
|
it first with `cmpnd admin feature-flags set`.
|
|
980
995
|
|
|
981
996
|
`cmpnd admin org flags list ORG_ID` shows every flag with the org's own row,
|
|
@@ -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
|
|
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": "
|
|
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.
|
|
8
|
+
version = "0.86.1"
|
|
9
9
|
description = "DSPy observability and deployment SDK for cmpnd"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.11"
|
|
@@ -360,20 +360,61 @@ class TestAdminPlans:
|
|
|
360
360
|
assert args.func(mock_client, args) == {"plans": []}
|
|
361
361
|
mock_client.get.assert_called_once_with("/api/v1/admin/plans")
|
|
362
362
|
|
|
363
|
-
def
|
|
364
|
-
args = _parse("plans", "
|
|
363
|
+
def test_draft_update_writes_the_draft_and_none_sends_null(self, mock_client):
|
|
364
|
+
args = _parse("plans", "draft", "update", "pro", "optimizations.concurrent", "5")
|
|
365
365
|
args.func(mock_client, args)
|
|
366
|
-
mock_client.put.assert_called_with(
|
|
367
|
-
|
|
366
|
+
mock_client.put.assert_called_with(
|
|
367
|
+
"/api/v1/admin/plans/pro/draft/limits/optimizations.concurrent", json={"value": 5}
|
|
368
|
+
)
|
|
369
|
+
args = _parse("plans", "draft", "update", "free", "optimizations.concurrent", "none")
|
|
368
370
|
args.func(mock_client, args)
|
|
369
371
|
mock_client.put.assert_called_with(
|
|
370
|
-
"/api/v1/admin/plans/free/limits/optimizations.concurrent", json={"value": None}
|
|
372
|
+
"/api/v1/admin/plans/free/draft/limits/optimizations.concurrent", json={"value": None}
|
|
371
373
|
)
|
|
372
374
|
|
|
373
|
-
def
|
|
374
|
-
args = _parse("plans", "
|
|
375
|
+
def test_draft_delete(self, mock_client):
|
|
376
|
+
args = _parse("plans", "draft", "delete", "pro", "optimizations.concurrent")
|
|
377
|
+
args.func(mock_client, args)
|
|
378
|
+
mock_client.delete.assert_called_once_with("/api/v1/admin/plans/pro/draft/limits/optimizations.concurrent")
|
|
379
|
+
|
|
380
|
+
def test_draft_publish_sends_the_note(self, mock_client):
|
|
381
|
+
mock_client.post.return_value = {"plan": "pro", "version": 2}
|
|
382
|
+
args = _parse("plans", "draft", "publish", "pro", "--note", "Five at a time")
|
|
383
|
+
assert args.func(mock_client, args) == {"plan": "pro", "version": 2}
|
|
384
|
+
mock_client.post.assert_called_once_with(
|
|
385
|
+
"/api/v1/admin/plans/pro/draft/publish", json={"note": "Five at a time"}
|
|
386
|
+
)
|
|
387
|
+
args = _parse("plans", "draft", "publish", "free")
|
|
388
|
+
args.func(mock_client, args)
|
|
389
|
+
mock_client.post.assert_called_with("/api/v1/admin/plans/free/draft/publish", json={"note": ""})
|
|
390
|
+
|
|
391
|
+
def test_move_by_version_or_by_org(self, mock_client):
|
|
392
|
+
mock_client.post.return_value = {"plan": "pro", "version": 2, "moved": 3}
|
|
393
|
+
args = _parse("plans", "move", "pro", "2", "--from-version", "1")
|
|
394
|
+
assert args.func(mock_client, args)["moved"] == 3
|
|
395
|
+
mock_client.post.assert_called_with("/api/v1/admin/plans/pro/versions/2/move", json={"from_version": 1})
|
|
396
|
+
args = _parse("plans", "move", "pro", "2", "--org", "o_1")
|
|
375
397
|
args.func(mock_client, args)
|
|
376
|
-
mock_client.
|
|
398
|
+
mock_client.post.assert_called_with("/api/v1/admin/plans/pro/versions/2/move", json={"org_id": "o_1"})
|
|
399
|
+
|
|
400
|
+
def test_move_needs_exactly_one_source_and_a_whole_version(self):
|
|
401
|
+
import pytest
|
|
402
|
+
|
|
403
|
+
for argv in (
|
|
404
|
+
("plans", "move", "pro", "2"),
|
|
405
|
+
("plans", "move", "pro", "2", "--from-version", "1", "--org", "o_1"),
|
|
406
|
+
("plans", "move", "pro", "two", "--org", "o_1"),
|
|
407
|
+
("plans", "move", "pro", "0", "--org", "o_1"),
|
|
408
|
+
("plans", "move", "pro", "2", "--from-version", "zero"),
|
|
409
|
+
):
|
|
410
|
+
with pytest.raises(SystemExit):
|
|
411
|
+
_parse(*argv)
|
|
412
|
+
|
|
413
|
+
def test_the_in_place_limit_commands_are_gone(self):
|
|
414
|
+
import pytest
|
|
415
|
+
|
|
416
|
+
with pytest.raises(SystemExit):
|
|
417
|
+
_parse("plans", "limits", "update", "pro", "optimizations.concurrent", "5")
|
|
377
418
|
|
|
378
419
|
def test_set_flag_enables_by_default_and_disables_with_the_flag(self, mock_client):
|
|
379
420
|
args = _parse("plans", "flags", "update", "free", "beta")
|
|
@@ -389,10 +430,10 @@ class TestAdminPlans:
|
|
|
389
430
|
mock_client.delete.assert_called_once_with("/api/v1/admin/plans/free/feature-flags/beta")
|
|
390
431
|
|
|
391
432
|
def test_plan_names_are_serves_to_check(self, mock_client):
|
|
392
|
-
args = _parse("plans", "
|
|
433
|
+
args = _parse("plans", "draft", "update", "platinum", "optimizations.concurrent", "5")
|
|
393
434
|
args.func(mock_client, args)
|
|
394
435
|
mock_client.put.assert_called_once_with(
|
|
395
|
-
"/api/v1/admin/plans/platinum/limits/optimizations.concurrent", json={"value": 5}
|
|
436
|
+
"/api/v1/admin/plans/platinum/draft/limits/optimizations.concurrent", json={"value": 5}
|
|
396
437
|
)
|
|
397
438
|
|
|
398
439
|
|
|
@@ -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)
|
|
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
|