cmpnd 0.31.0__tar.gz → 0.32.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cmpnd-0.31.0 → cmpnd-0.32.0}/PKG-INFO +1 -1
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/__init__.py +2 -1
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/_gepa_patch.py +11 -9
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/_http.py +56 -5
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/callback.py +52 -13
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/commands/deployments.py +7 -1
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/commands/top.py +20 -3
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/execution.py +40 -14
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/models.py +7 -1
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/optimizers/gepa.py +10 -7
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/optimizers/gepa_callback.py +21 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/docs/sdk-instrumentation.md +73 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/pyproject.toml +1 -1
- {cmpnd-0.31.0 → cmpnd-0.32.0}/scripts/test-in-linux.sh +13 -1
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_callback.py +203 -9
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_deployments.py +4 -2
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_top.py +8 -4
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_execute.py +8 -7
- cmpnd-0.32.0/tests/test_execute_run_identity.py +203 -0
- cmpnd-0.32.0/tests/test_run_attribution_paths.py +368 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_tracked_gepa.py +25 -18
- {cmpnd-0.31.0 → cmpnd-0.32.0}/uv.lock +1 -1
- {cmpnd-0.31.0 → cmpnd-0.32.0}/.gitignore +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/AGENTS.md +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/CLAUDE.md +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/README.md +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/_assessments.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/_authored_import.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/_deployments.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/_runtime_tools.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/_traces.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/_valsets.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/authored.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/__init__.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/commands/admin.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/commands/assessments.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/commands/evals.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/commands/org.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/commands/programs.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/commands/signatures.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/commands/traces.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/commands/valsets.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/shell.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/trace_render.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/tree_help.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/cli/verbs.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/client.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/configuration.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/context.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/dataset_sync.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/datasets.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/decorators.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/deployment.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/deployment_metadata.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/exporter.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/helpers.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/identity.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/imaging.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/ir/reflection.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/lm_configuration.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/mcp_servers.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/optimization.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/optimizers/tracker.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/packaging.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/references.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/retry.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/cmpnd/traces.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/docs/2026-04-02-code-review.md +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/docs/cli.md +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/docs/client.md +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/docs/custom-proposer.md +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/docs/reading-traces.md +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/examples/local_ollama.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/ingest/README.md +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/ingest/cmpnd_ingest/bodies.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/ingest/cmpnd_ingest/protocols.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/ingest/pyproject.toml +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/scripts/capture_review_run.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/scripts/release-stress.sh +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/scripts/require_version_bump.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/scripts/seed_review_run.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/stubs/dspy/utils/dummies.pyi +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/__init__.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/_deployment_signature_helpers.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/_fake_clock.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/_resolve_refs.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/_traced_run.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/conftest.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/committee_task.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/helpers.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/target_capabilities.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_assessments_cli.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_composite_module.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_deploy_execute.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_deploy_programs.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_optimize_failures.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_parallel_export.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_project_attribution.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_traces_read.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_user_attribution.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/e2e/test_valsets_cli.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/integration/__init__.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/integration/authored_optimize/judge.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/integration/conftest.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/integration/hosted_optimize_probe.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/integration/test_logs_renders_traces.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/integration/test_unhappy_path.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/load/__init__.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/load/__main__.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/load/drain.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/load/ingest.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/load/rlm_shape.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/load/rlm_size_report.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/load/sweep.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/load/test_rlm_size_report.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/parity/__init__.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/parity/conftest.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/parity/test_health.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/parity/test_trace_streaming.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/parity/test_traces.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/span_reference_fixture.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/stress/__init__.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/stress/_provider.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/stress/_readiness.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/stress/_reliability.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/stress/_result.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/stress/mint_bedrock_token.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/stress/test_deploy_programs.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/stress/test_multi_client_stress.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/stress/test_optimize_client_committee.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/stress/test_optimize_loop.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/stress/test_optimize_stress.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/stress/test_provider_roles.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/stress/test_readiness.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/stress/test_real_provider_twins.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/stress/test_reliability.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/stress/test_reliability_classify.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/stress/test_rlm_wasm.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/stress/test_trace_search.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_adapter_reentrancy.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_assessments_api.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_authored.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_authored_bundle_collisions.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_authored_metric_contract.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_authored_named_models.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_authored_optimize.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_admin.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_api_client.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_assessments.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_evals.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_main.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_org.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_parity.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_programs.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_provider_credentials.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_schema_sample.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_shell.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_signatures.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_traces.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_traces_search_wire.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_tree_help.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cli/test_valsets.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_client.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_configuration.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_context.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_context_propagation.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_dataset_sync.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_datasets.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_decorators.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_deploy.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_deployment_metadata.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_deployments_read.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_eval.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_eval_example_attribution.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_eval_start.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_eval_trace_grouping.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_exporter.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_exporter_queue.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_exporter_real.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_gepa_output_normalization.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_identity.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_imaging.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_import_weight.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_ingest_conformance.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_integration.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_interpreter_callback.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_lm_configuration.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_lm_usage_shapes.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_mcp_tools.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_models.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_optimize.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_optimize_lms.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_optimize_reflection_lm.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_packaging.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_packaging_mcp_tools.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_packaging_program_source.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_poll_until.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_program_lineage_persist.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_project_payload.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_reflector_properties.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_reflector_smoke.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_release_stress_script.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_repro_reactv2_bugs.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_retry.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_rlm_patch.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_runtime_tools.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_schemas.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_span_references.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_span_sent_once.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_trace_render.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_traces_read.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_truncation.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_valsets_api.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/tests/test_xxh64.py +0 -0
- {cmpnd-0.31.0 → cmpnd-0.32.0}/todo.md +0 -0
|
@@ -33,7 +33,7 @@ from .deployment_metadata import (
|
|
|
33
33
|
DeploymentSignatureResponse,
|
|
34
34
|
UnsupportedDeploymentMetadataVersion,
|
|
35
35
|
)
|
|
36
|
-
from .execution import DeployedProgram, RetryableProviderError, execute
|
|
36
|
+
from .execution import DeployedProgram, RetryableProviderError, deployment_run_id, execute
|
|
37
37
|
from .exporter import flush_exporter, shutdown_exporter
|
|
38
38
|
from .lm_configuration import configure_lm
|
|
39
39
|
from .mcp_servers import (
|
|
@@ -104,6 +104,7 @@ __all__ = [
|
|
|
104
104
|
"DeploymentMetadataError",
|
|
105
105
|
"UnsupportedDeploymentMetadataVersion",
|
|
106
106
|
"execute",
|
|
107
|
+
"deployment_run_id",
|
|
107
108
|
"DeployedProgram",
|
|
108
109
|
"RetryableProviderError",
|
|
109
110
|
# Optimize
|
|
@@ -10,8 +10,6 @@ module-style (``from . import identity``); basedpyright can't resolve
|
|
|
10
10
|
|
|
11
11
|
import contextvars
|
|
12
12
|
|
|
13
|
-
from . import context
|
|
14
|
-
|
|
15
13
|
_gepa_patched = False
|
|
16
14
|
|
|
17
15
|
# Set by a client's run_optimization while it owns the run. The class patch reads
|
|
@@ -22,6 +20,12 @@ _client_owns_optimization: contextvars.ContextVar[bool] = contextvars.ContextVar
|
|
|
22
20
|
"cmpnd_client_owns_optimization", default=False
|
|
23
21
|
)
|
|
24
22
|
|
|
23
|
+
# TrackedGEPA reserves its own run before invoking this native optimizer. Match
|
|
24
|
+
# the instance so an unrelated optimizer nested inside it still gets a run.
|
|
25
|
+
_tracked_compile_optimizer: contextvars.ContextVar[object | None] = contextvars.ContextVar(
|
|
26
|
+
"cmpnd_tracked_compile_optimizer", default=None
|
|
27
|
+
)
|
|
28
|
+
|
|
25
29
|
|
|
26
30
|
def _gepa_supports_callbacks() -> bool:
|
|
27
31
|
"""Check if the installed gepa version supports callbacks in optimize()."""
|
|
@@ -110,8 +114,7 @@ def _run_tracked_compile(
|
|
|
110
114
|
max_metric_calls=max_metric_calls,
|
|
111
115
|
)
|
|
112
116
|
|
|
113
|
-
|
|
114
|
-
context.set_current_optimization_run_id(trk.optimization_run_id)
|
|
117
|
+
restore_attribution = callback.start_trace_attribution()
|
|
115
118
|
|
|
116
119
|
try:
|
|
117
120
|
result = run_original()
|
|
@@ -139,8 +142,7 @@ def _run_tracked_compile(
|
|
|
139
142
|
raise
|
|
140
143
|
|
|
141
144
|
finally:
|
|
142
|
-
|
|
143
|
-
callback.clear_trace_association_state()
|
|
145
|
+
restore_attribution()
|
|
144
146
|
|
|
145
147
|
|
|
146
148
|
def patch_gepa() -> None:
|
|
@@ -176,9 +178,9 @@ def patch_gepa() -> None:
|
|
|
176
178
|
type(self)._cmpnd_original_init(self, *args, **kwargs)
|
|
177
179
|
|
|
178
180
|
def _patched_compile(self, student, *, trainset, teacher=None, valset=None, **kwargs):
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
if _client_owns_optimization.get():
|
|
181
|
+
# A client or TrackedGEPA already records this run. TrackedGEPA claims
|
|
182
|
+
# only its own optimizer instance, leaving distinct nested runs tracked.
|
|
183
|
+
if _client_owns_optimization.get() or _tracked_compile_optimizer.get() is self:
|
|
182
184
|
return type(self)._cmpnd_original_compile(
|
|
183
185
|
self, student, trainset=trainset, teacher=teacher, valset=valset, **kwargs
|
|
184
186
|
)
|
|
@@ -11,6 +11,7 @@ resolution; this module is only the wire.
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
13
|
import os
|
|
14
|
+
import re
|
|
14
15
|
from typing import Any
|
|
15
16
|
|
|
16
17
|
import httpx
|
|
@@ -24,10 +25,45 @@ class ApiError(Exception):
|
|
|
24
25
|
is the process exit the CLI maps the error to; the library client ignores it.
|
|
25
26
|
"""
|
|
26
27
|
|
|
27
|
-
def __init__(
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
message: str,
|
|
31
|
+
exit_code: int = 1,
|
|
32
|
+
*,
|
|
33
|
+
status_code: int | None = None,
|
|
34
|
+
deployment_run_id: str | None = None,
|
|
35
|
+
):
|
|
28
36
|
super().__init__(message)
|
|
29
37
|
self.exit_code = exit_code
|
|
30
38
|
self.status_code = status_code
|
|
39
|
+
self._cmpnd_deployment_run_id = deployment_run_id
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def response_deployment_run_id(response: httpx.Response) -> str | None:
|
|
43
|
+
"""Read the server's execute correlation key from its JSON response body."""
|
|
44
|
+
try:
|
|
45
|
+
body = response.json()
|
|
46
|
+
except ValueError:
|
|
47
|
+
return None
|
|
48
|
+
return _body_deployment_run_id(body)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _body_deployment_run_id(body: object) -> str | None:
|
|
52
|
+
run_id = body.get("deployment_run_id") if isinstance(body, dict) else None
|
|
53
|
+
if isinstance(run_id, str) and re.fullmatch(r"dr_[A-Za-z0-9]{12}", run_id):
|
|
54
|
+
return run_id
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def execute_response_output(response: httpx.Response) -> tuple[dict[str, Any], str]:
|
|
59
|
+
"""Read program output and serve's run id from the execute envelope."""
|
|
60
|
+
body = response.json()
|
|
61
|
+
run_id = _body_deployment_run_id(body)
|
|
62
|
+
if not isinstance(body, dict) or run_id is None or not isinstance(body.get("output"), dict):
|
|
63
|
+
error = RuntimeError("Execute response has an invalid run envelope")
|
|
64
|
+
error._cmpnd_deployment_run_id = run_id # pyright: ignore[reportAttributeAccessIssue]
|
|
65
|
+
raise error
|
|
66
|
+
return body["output"], run_id
|
|
31
67
|
|
|
32
68
|
|
|
33
69
|
def server_detail(response: httpx.Response) -> str:
|
|
@@ -81,6 +117,10 @@ class ApiClient:
|
|
|
81
117
|
def post(self, path: str, json: dict[str, Any] | None = None) -> Any:
|
|
82
118
|
return self._request("POST", path, json=json)
|
|
83
119
|
|
|
120
|
+
def post_response(self, path: str, json: dict[str, Any] | None = None) -> httpx.Response:
|
|
121
|
+
"""POST through the shared error ladder and return the complete response."""
|
|
122
|
+
return self._request("POST", path, json=json, return_response=True)
|
|
123
|
+
|
|
84
124
|
def patch(self, path: str, json: dict[str, Any] | None = None) -> Any:
|
|
85
125
|
return self._request("PATCH", path, json=json)
|
|
86
126
|
|
|
@@ -100,6 +140,7 @@ class ApiClient:
|
|
|
100
140
|
params: dict[str, str] | None = None,
|
|
101
141
|
json: dict[str, Any] | None = None,
|
|
102
142
|
decode: bool | None = True,
|
|
143
|
+
return_response: bool = False,
|
|
103
144
|
) -> Any:
|
|
104
145
|
url = f"{self._endpoint}{path}"
|
|
105
146
|
# 180s default: deploy/optimize/run wait in-band on an LM round-trip
|
|
@@ -137,15 +178,22 @@ class ApiClient:
|
|
|
137
178
|
except httpx.TimeoutException:
|
|
138
179
|
raise ApiError(f"Request timed out connecting to {self._endpoint}.")
|
|
139
180
|
|
|
181
|
+
run_id = response_deployment_run_id(response) if response.status_code >= 400 else None
|
|
182
|
+
|
|
183
|
+
def error(message: str, exit_code: int = 1) -> ApiError:
|
|
184
|
+
if run_id:
|
|
185
|
+
message += f"\nDeployment run: {run_id}"
|
|
186
|
+
return ApiError(message, exit_code, status_code=response.status_code, deployment_run_id=run_id)
|
|
187
|
+
|
|
140
188
|
if response.status_code == 401:
|
|
141
|
-
raise
|
|
189
|
+
raise error("Invalid or missing API key. Check CMPND_API_KEY or --api-key.")
|
|
142
190
|
if response.status_code == 404:
|
|
143
191
|
# The server's own words, not a generic stand-in. Every route here 404s
|
|
144
192
|
# for a reason it already states — "signature not found", "deployment
|
|
145
193
|
# not found", "no such organization" — and flattening them into one
|
|
146
194
|
# sentence makes a missing record and a mistyped URL read identically,
|
|
147
195
|
# which is the difference a user needs most and had the least of.
|
|
148
|
-
raise
|
|
196
|
+
raise error(server_detail(response) or "Resource not found.")
|
|
149
197
|
if response.status_code >= 500:
|
|
150
198
|
# 5xx usually means the server is up but blew up handling the
|
|
151
199
|
# request — a database hiccup, an unhandled exception, etc.
|
|
@@ -157,13 +205,16 @@ class ApiClient:
|
|
|
157
205
|
if body_excerpt:
|
|
158
206
|
msg += f": {body_excerpt}"
|
|
159
207
|
msg += "\n If you operate this server, check its logs for the underlying exception."
|
|
160
|
-
raise
|
|
208
|
+
raise error(msg, exit_code=2)
|
|
161
209
|
if response.status_code >= 400:
|
|
162
210
|
try:
|
|
163
211
|
detail = response.json()
|
|
164
212
|
except Exception:
|
|
165
213
|
detail = response.text
|
|
166
|
-
raise
|
|
214
|
+
raise error(f"Request failed ({response.status_code}): {detail}")
|
|
215
|
+
|
|
216
|
+
if return_response:
|
|
217
|
+
return response
|
|
167
218
|
|
|
168
219
|
# A bytes route hands the raw body back before httpx decodes it.
|
|
169
220
|
if decode is None:
|
|
@@ -929,19 +929,21 @@ class _ModuleMixin(_CallbackBase):
|
|
|
929
929
|
trace.span_count = len(trace_spans)
|
|
930
930
|
logger.debug(f"Trace totals: {trace.total_tokens} tokens, ${trace.total_cost}")
|
|
931
931
|
|
|
932
|
-
#
|
|
933
|
-
#
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
#
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
932
|
+
# trace.optimization_run_id and trace.eval_run_id were attached
|
|
933
|
+
# in _create_trace, from this callback's own active-run state
|
|
934
|
+
# when the trace started. Do not re-read that state here: by
|
|
935
|
+
# finalize the run may already have ended (a trace that
|
|
936
|
+
# outlives it), and re-reading would drop the attribution the
|
|
937
|
+
# start row already sent — the trace keeps the ids it started
|
|
938
|
+
# with either way.
|
|
939
|
+
|
|
940
|
+
# If this trace's eval run is still collecting traces for
|
|
941
|
+
# metric correlation, add it — keyed by the id the trace
|
|
942
|
+
# already carries, not by whether the eval is still the live
|
|
943
|
+
# one (it may have finalized already, in which case there is
|
|
944
|
+
# nothing left to add to).
|
|
945
|
+
if trace.eval_run_id:
|
|
946
|
+
eval_run_id = str(trace.eval_run_id)
|
|
945
947
|
eval_traces = self._eval_handler._eval_traces
|
|
946
948
|
if eval_run_id in eval_traces:
|
|
947
949
|
eval_traces[eval_run_id].append(trace)
|
|
@@ -1085,6 +1087,41 @@ class _ModuleMixin(_CallbackBase):
|
|
|
1085
1087
|
)
|
|
1086
1088
|
self._stamp(trace)
|
|
1087
1089
|
|
|
1090
|
+
# Attach the run ids this trace starts under, from this callback's own
|
|
1091
|
+
# state right now — not at finalize (on_module_end), where the
|
|
1092
|
+
# optimization or eval may already have ended for a trace that
|
|
1093
|
+
# outlives it. These are the same sources finalization read before
|
|
1094
|
+
# this change; reading them here only moves *when* they're read, not
|
|
1095
|
+
# what they read.
|
|
1096
|
+
#
|
|
1097
|
+
# Optimization reads the callback field first, then the shared
|
|
1098
|
+
# contextvar. GEPA's start hook and both SDK compile wrappers
|
|
1099
|
+
# both publish the reserved id on the installed callback before
|
|
1100
|
+
# evaluator workers start. The contextvar supports a call with no
|
|
1101
|
+
# active optimization field on this callback.
|
|
1102
|
+
#
|
|
1103
|
+
# Eval has no such gap — on_evaluate_start always sets the instance
|
|
1104
|
+
# field in the same call that sets the contextvar — so it stays
|
|
1105
|
+
# instance-field only, deliberately without the same fallback: adding
|
|
1106
|
+
# one would read a DIFFERENT callback's eval off the same process-wide
|
|
1107
|
+
# contextvar for a nested scoped client with no eval of its own.
|
|
1108
|
+
#
|
|
1109
|
+
# Both are callback-wide fields, not thread- or call-scoped: a
|
|
1110
|
+
# concurrent unrelated call on another thread sharing this callback,
|
|
1111
|
+
# or a nested scoped client's call inside another client's
|
|
1112
|
+
# optimization metric, can still adopt a run it did not start via the
|
|
1113
|
+
# contextvar fallback. Pre-existing behavior, unchanged by this
|
|
1114
|
+
# attach-at-start change.
|
|
1115
|
+
if self._active_optimization_run_id:
|
|
1116
|
+
trace.optimization_run_id = self._active_optimization_run_id
|
|
1117
|
+
else:
|
|
1118
|
+
ctx_opt_run_id = context.get_current_optimization_run_id()
|
|
1119
|
+
if ctx_opt_run_id:
|
|
1120
|
+
trace.optimization_run_id = ctx_opt_run_id
|
|
1121
|
+
|
|
1122
|
+
if self._active_eval_run:
|
|
1123
|
+
trace.eval_run_id = self._active_eval_run.eval_run_id
|
|
1124
|
+
|
|
1088
1125
|
# Capture signature info for the root module - this identifies the program
|
|
1089
1126
|
signature = None
|
|
1090
1127
|
if hasattr(instance, "signature"):
|
|
@@ -1494,6 +1531,8 @@ class _LMMixin(_CallbackBase):
|
|
|
1494
1531
|
trace.signature_name = span.model_name or phase
|
|
1495
1532
|
trace.status = span.status
|
|
1496
1533
|
trace.optimization_run_id = run_id
|
|
1534
|
+
if self._active_eval_run is not None:
|
|
1535
|
+
trace.eval_run_id = self._active_eval_run.eval_run_id
|
|
1497
1536
|
trace.tags["optimization_phase"] = phase
|
|
1498
1537
|
|
|
1499
1538
|
# Single-span trace: roll the span's usage up onto the trace so the
|
|
@@ -6,9 +6,11 @@ top-level `cmpnd ps`, `status`, `logs`, `inspect`, `run` shortcuts.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import sys
|
|
9
10
|
from argparse import ArgumentParser, _SubParsersAction
|
|
10
11
|
from typing import Any
|
|
11
12
|
|
|
13
|
+
from ... import _http
|
|
12
14
|
from .. import api_client, deprecation, pagination
|
|
13
15
|
|
|
14
16
|
|
|
@@ -64,7 +66,11 @@ def handle_run(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
64
66
|
body = json.loads(args.body)
|
|
65
67
|
except json.JSONDecodeError as exc:
|
|
66
68
|
raise api_client.CliError(f"Invalid JSON for run body: {exc}")
|
|
67
|
-
|
|
69
|
+
response = client.post_response(f"/api/v1/deployments/{args.id}/execute", json=body)
|
|
70
|
+
output, run_id = _http.execute_response_output(response)
|
|
71
|
+
if run_id:
|
|
72
|
+
print(f"Deployment run: {run_id}", file=sys.stderr)
|
|
73
|
+
return output
|
|
68
74
|
|
|
69
75
|
|
|
70
76
|
def handle_openapi(client: api_client.ApiClient, args: Any) -> None:
|
|
@@ -863,6 +863,11 @@ def cmd_deploy(client: api_client.ApiClient, args: Any) -> None:
|
|
|
863
863
|
raise api_client.CliError(f"--run JSON is invalid: {exc}")
|
|
864
864
|
print(_c(f"executing {_short_id(deployment_id)}…", "dim"), file=sys.stderr)
|
|
865
865
|
result = deployed(**inputs)
|
|
866
|
+
from ...execution import deployment_run_id
|
|
867
|
+
|
|
868
|
+
run_id = deployment_run_id(result)
|
|
869
|
+
if run_id:
|
|
870
|
+
print(f"Deployment run: {run_id}", file=sys.stderr)
|
|
866
871
|
if args.json:
|
|
867
872
|
# result is a dspy.Prediction; json.dumps would fall through to
|
|
868
873
|
# str(result) and emit the repr. Coerce to a dict first.
|
|
@@ -990,20 +995,32 @@ def cmd_run(client: api_client.ApiClient, args: Any) -> None:
|
|
|
990
995
|
raise api_client.CliError(f"Invalid JSON: {exc}")
|
|
991
996
|
|
|
992
997
|
try:
|
|
993
|
-
|
|
998
|
+
response = client.post_response(f"/api/v1/deployments/{did}/execute", json=body)
|
|
994
999
|
except api_client.CliError as e:
|
|
995
1000
|
# Map known 409/422/502 errors to friendlier hints. ApiClient already
|
|
996
1001
|
# formats the message; just append next-step hints when we recognize
|
|
997
1002
|
# the pattern.
|
|
998
1003
|
msg = str(e)
|
|
999
1004
|
if "409" in msg or "is parsing" in msg or "is pending" in msg:
|
|
1000
|
-
raise api_client.CliError(
|
|
1005
|
+
raise api_client.CliError(
|
|
1006
|
+
f"{msg}\nWait for it: {_hint_status(did, follow=True)}",
|
|
1007
|
+
status_code=e.status_code,
|
|
1008
|
+
deployment_run_id=e._cmpnd_deployment_run_id,
|
|
1009
|
+
)
|
|
1001
1010
|
if "422" in msg and "provider" in msg.lower():
|
|
1002
1011
|
raise api_client.CliError(
|
|
1003
|
-
f"{msg}\nNo provider key configured. Provider-key management is not yet available."
|
|
1012
|
+
f"{msg}\nNo provider key configured. Provider-key management is not yet available.",
|
|
1013
|
+
status_code=e.status_code,
|
|
1014
|
+
deployment_run_id=e._cmpnd_deployment_run_id,
|
|
1004
1015
|
)
|
|
1005
1016
|
raise
|
|
1006
1017
|
|
|
1018
|
+
from ... import _http
|
|
1019
|
+
|
|
1020
|
+
result, run_id = _http.execute_response_output(response)
|
|
1021
|
+
if run_id:
|
|
1022
|
+
print(f"Deployment run: {run_id}", file=sys.stderr)
|
|
1023
|
+
|
|
1007
1024
|
if args.json:
|
|
1008
1025
|
print(json.dumps(result, indent=2, default=str))
|
|
1009
1026
|
return
|
|
@@ -8,7 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import inspect
|
|
10
10
|
import keyword
|
|
11
|
-
from typing import TYPE_CHECKING
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
12
|
|
|
13
13
|
import httpx
|
|
14
14
|
|
|
@@ -25,6 +25,21 @@ if TYPE_CHECKING:
|
|
|
25
25
|
from dspy.primitives.prediction import Prediction
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def deployment_run_id(result_or_error: Any) -> str | None:
|
|
29
|
+
"""Return the server-owned run id from an execute prediction or exception.
|
|
30
|
+
|
|
31
|
+
The id lives outside the prediction's program output fields. Older servers
|
|
32
|
+
and failures before the server resolves the deployment supply no run id and
|
|
33
|
+
return ``None``.
|
|
34
|
+
"""
|
|
35
|
+
return getattr(result_or_error, "_cmpnd_deployment_run_id", None)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _with_run_id(error: Exception, response: httpx.Response) -> Exception:
|
|
39
|
+
error._cmpnd_deployment_run_id = _http.response_deployment_run_id(response) # pyright: ignore[reportAttributeAccessIssue]
|
|
40
|
+
return error
|
|
41
|
+
|
|
42
|
+
|
|
28
43
|
class RetryableProviderError(HTTPStatusError):
|
|
29
44
|
"""A provider failure the platform identified as safe to retry.
|
|
30
45
|
|
|
@@ -256,7 +271,9 @@ def execute(deployment_id, timeout=30, metadata=None, tags=None, **inputs):
|
|
|
256
271
|
keys (``lms``, ``deployment_id``, ...).
|
|
257
272
|
|
|
258
273
|
Returns:
|
|
259
|
-
A dspy.Prediction with the program's output fields.
|
|
274
|
+
A dspy.Prediction with the program's output fields. Read the run id with
|
|
275
|
+
``cmpnd.deployment_run_id(result)``. The same accessor accepts an
|
|
276
|
+
exception from a failed execute.
|
|
260
277
|
|
|
261
278
|
Raises:
|
|
262
279
|
TypeError: If metadata or tags is provided and is not a ``dict[str, str]``.
|
|
@@ -288,10 +305,10 @@ def execute(deployment_id, timeout=30, metadata=None, tags=None, **inputs):
|
|
|
288
305
|
)
|
|
289
306
|
|
|
290
307
|
if resp.status_code == 409:
|
|
291
|
-
raise RuntimeError(f"Deployment {dep_id} is not ready")
|
|
308
|
+
raise _with_run_id(RuntimeError(f"Deployment {dep_id} is not ready"), resp)
|
|
292
309
|
if resp.status_code == 422:
|
|
293
310
|
data = resp.json()
|
|
294
|
-
raise RuntimeError(data.get("error", "Execution failed"))
|
|
311
|
+
raise _with_run_id(RuntimeError(data.get("error", "Execution failed")), resp)
|
|
295
312
|
|
|
296
313
|
if resp.status_code >= 400:
|
|
297
314
|
# Same exception `raise_for_status()` would raise — the type is a contract:
|
|
@@ -310,17 +327,26 @@ def execute(deployment_id, timeout=30, metadata=None, tags=None, **inputs):
|
|
|
310
327
|
provider_failure = _retryable_provider_failure(resp)
|
|
311
328
|
if provider_failure is not None:
|
|
312
329
|
provider_status, attempts, retry_after = provider_failure
|
|
313
|
-
raise
|
|
330
|
+
raise _with_run_id(
|
|
331
|
+
RetryableProviderError(
|
|
332
|
+
message,
|
|
333
|
+
request=resp.request,
|
|
334
|
+
response=resp,
|
|
335
|
+
provider_status=provider_status,
|
|
336
|
+
attempts=attempts,
|
|
337
|
+
retry_after=retry_after,
|
|
338
|
+
),
|
|
339
|
+
resp,
|
|
340
|
+
)
|
|
341
|
+
raise _with_run_id(
|
|
342
|
+
HTTPStatusError(
|
|
314
343
|
message,
|
|
315
344
|
request=resp.request,
|
|
316
345
|
response=resp,
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
retry_after=retry_after,
|
|
320
|
-
)
|
|
321
|
-
raise HTTPStatusError(
|
|
322
|
-
message,
|
|
323
|
-
request=resp.request,
|
|
324
|
-
response=resp,
|
|
346
|
+
),
|
|
347
|
+
resp,
|
|
325
348
|
)
|
|
326
|
-
|
|
349
|
+
output, run_id = _http.execute_response_output(resp)
|
|
350
|
+
prediction = Prediction(**output)
|
|
351
|
+
setattr(prediction, "_cmpnd_deployment_run_id", run_id)
|
|
352
|
+
return prediction
|
|
@@ -949,7 +949,7 @@ class TraceBuilder:
|
|
|
949
949
|
}
|
|
950
950
|
|
|
951
951
|
def to_start_dict(self) -> dict[str, Any]:
|
|
952
|
-
"""Lightweight payload for trace
|
|
952
|
+
"""Lightweight payload for trace identity, signature and run attribution."""
|
|
953
953
|
return {
|
|
954
954
|
"start_time": self.start_time.isoformat() if self.start_time else None,
|
|
955
955
|
"program_name": self.program_name,
|
|
@@ -963,8 +963,14 @@ class TraceBuilder:
|
|
|
963
963
|
"signature_summary": self.signature_summary,
|
|
964
964
|
"component_signatures": _safe_serialize(self.component_signatures),
|
|
965
965
|
"root_span_type": self.root_span_type,
|
|
966
|
+
"compiled_from_run_id": self.compiled_from_run_id,
|
|
966
967
|
"project": self.project_tags[0] if self.project_tags else None,
|
|
967
968
|
"project_tags": self.project_tags if self.project_tags else [],
|
|
969
|
+
# Known at start (the callback attaches them in _create_trace), so
|
|
970
|
+
# the start row carries them from the first send instead of the
|
|
971
|
+
# finalize row re-attributing them later.
|
|
972
|
+
"eval_run_id": str(self.eval_run_id) if self.eval_run_id else None,
|
|
973
|
+
"optimization_run_id": (str(self.optimization_run_id) if self.optimization_run_id else None),
|
|
968
974
|
}
|
|
969
975
|
|
|
970
976
|
def to_finalize_dict(self) -> dict[str, Any]:
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import logging
|
|
6
6
|
from typing import TYPE_CHECKING, Any
|
|
7
7
|
|
|
8
|
-
from .. import identity
|
|
8
|
+
from .. import _gepa_patch, identity
|
|
9
9
|
from . import gepa_callback, tracker
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
@@ -199,10 +199,16 @@ class TrackedGEPA:
|
|
|
199
199
|
# Inject callback into GEPA's gepa_kwargs
|
|
200
200
|
existing_callbacks = self._gepa.gepa_kwargs.get("callbacks") or []
|
|
201
201
|
self._gepa.gepa_kwargs["callbacks"] = existing_callbacks + [gcb]
|
|
202
|
+
restore_attribution = None
|
|
202
203
|
|
|
203
204
|
try:
|
|
205
|
+
restore_attribution = gcb.start_trace_attribution()
|
|
204
206
|
# Run optimization (trainset/valset are keyword-only in GEPA)
|
|
205
|
-
|
|
207
|
+
compile_token = _gepa_patch._tracked_compile_optimizer.set(self._gepa)
|
|
208
|
+
try:
|
|
209
|
+
result = self._gepa.compile(student, trainset=trainset, teacher=teacher, valset=valset)
|
|
210
|
+
finally:
|
|
211
|
+
_gepa_patch._tracked_compile_optimizer.reset(compile_token)
|
|
206
212
|
|
|
207
213
|
# Capture the compiled program state
|
|
208
214
|
self._tracker.capture_compiled_program(result)
|
|
@@ -235,11 +241,8 @@ class TrackedGEPA:
|
|
|
235
241
|
raise
|
|
236
242
|
|
|
237
243
|
finally:
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
context.set_current_optimization_run_id(None)
|
|
242
|
-
gcb.clear_trace_association_state()
|
|
244
|
+
if restore_attribution is not None:
|
|
245
|
+
restore_attribution()
|
|
243
246
|
# Remove our callback from gepa_kwargs
|
|
244
247
|
cbs = self._gepa.gepa_kwargs.get("callbacks", [])
|
|
245
248
|
self._gepa.gepa_kwargs["callbacks"] = [cb for cb in cbs if cb is not gcb]
|
|
@@ -14,6 +14,7 @@ from __future__ import annotations
|
|
|
14
14
|
|
|
15
15
|
import logging
|
|
16
16
|
import time
|
|
17
|
+
from collections.abc import Callable
|
|
17
18
|
from typing import Any
|
|
18
19
|
from uuid import UUID
|
|
19
20
|
|
|
@@ -171,6 +172,26 @@ class CmpndGEPACallback:
|
|
|
171
172
|
if cmpnd_cb:
|
|
172
173
|
cmpnd_cb._clear_optimization_trace_state()
|
|
173
174
|
|
|
175
|
+
def start_trace_attribution(self) -> Callable[[], None]:
|
|
176
|
+
"""Reserve and publish this run before compile, then return its cleanup."""
|
|
177
|
+
cmpnd_cb = self._get_cmpnd_callback()
|
|
178
|
+
prior_callback_run_id = cmpnd_cb._active_optimization_run_id if cmpnd_cb is not None else None
|
|
179
|
+
prior_context_run_id = context.get_current_optimization_run_id()
|
|
180
|
+
self._tracker.start()
|
|
181
|
+
context.set_current_optimization_run_id(self._tracker.optimization_run_id)
|
|
182
|
+
if cmpnd_cb is not None:
|
|
183
|
+
# Evaluator threads need not inherit contextvars, and older GEPA
|
|
184
|
+
# versions notify their start hook after the seed evaluation.
|
|
185
|
+
cmpnd_cb._active_optimization_run_id = self._tracker.optimization_run_id
|
|
186
|
+
|
|
187
|
+
def restore() -> None:
|
|
188
|
+
if cmpnd_cb is not None:
|
|
189
|
+
cmpnd_cb._clear_optimization_trace_state()
|
|
190
|
+
cmpnd_cb._active_optimization_run_id = prior_callback_run_id
|
|
191
|
+
context.set_current_optimization_run_id(prior_context_run_id)
|
|
192
|
+
|
|
193
|
+
return restore
|
|
194
|
+
|
|
174
195
|
def on_optimization_start(self, event: dict[str, Any]) -> None:
|
|
175
196
|
"""Handle optimization start: set context and start tracker."""
|
|
176
197
|
try:
|
|
@@ -345,6 +345,38 @@ The create POST is the one that mints something, so it carries the [minting-POST
|
|
|
345
345
|
|
|
346
346
|
The poll loop has no retry budget of its own: the caller's `timeout` is the only bound, since giving up early on a deployment that is still progressing is the failure it exists to avoid. Running that budget out is still a `TimeoutError`, now naming what it rode through. A `4xx` and a `status: "failed"` are answers, not blips, and still raise (`RuntimeError` / `DeployFailed`).
|
|
347
347
|
|
|
348
|
+
### Execute returns a deployment run id
|
|
349
|
+
|
|
350
|
+
Serve mints a deployment run id before it sends an execute to the worker. A
|
|
351
|
+
successful JSON response contains `deployment_run_id` and `output`. A failed
|
|
352
|
+
execution keeps its error body and adds the run id. Errors before serve resolves
|
|
353
|
+
the deployment have no id.
|
|
354
|
+
|
|
355
|
+
`cmpnd.execute()` and a callable `DeployedProgram` return the program's output
|
|
356
|
+
as a `dspy.Prediction`. `cmpnd.deployment_run_id(result)` reads the id from
|
|
357
|
+
private prediction metadata, so a program can use `output` or
|
|
358
|
+
`deployment_run_id` as an output field. The accessor also accepts an execute
|
|
359
|
+
exception. Trace delivery can finish after the execute response arrives.
|
|
360
|
+
|
|
361
|
+
```python
|
|
362
|
+
try:
|
|
363
|
+
result = deployed(question="What changed?")
|
|
364
|
+
run_id = cmpnd.deployment_run_id(result)
|
|
365
|
+
except Exception as error:
|
|
366
|
+
run_id = cmpnd.deployment_run_id(error)
|
|
367
|
+
raise
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
The SDK reads the run id and program output from the response body.
|
|
371
|
+
`cmpnd run`, `cmpnd deployments run` and `cmpnd deploy --run` print the run id
|
|
372
|
+
on stderr and program output on stdout. `--json` keeps stdout as the program's
|
|
373
|
+
JSON output. A CLI execution
|
|
374
|
+
error carries the id in its message and exception metadata.
|
|
375
|
+
|
|
376
|
+
SDK and CLI version 0.32.0 support this envelope. The API does not yet guarantee
|
|
377
|
+
response compatibility across client versions. Upgrade `cmpnd` to the latest
|
|
378
|
+
version when a client has trouble with execute responses.
|
|
379
|
+
|
|
348
380
|
### A programmatic deploy carries the types it names as generated source
|
|
349
381
|
|
|
350
382
|
`cmpnd.deploy(module)` receives a live object rather than an authored file. Its
|
|
@@ -510,6 +542,47 @@ Two producers put a `span` item and its trace's `trace` item in the same flush.
|
|
|
510
542
|
|
|
511
543
|
`trace.streaming` (set only for RLM) is a separate axis: it still governs whether individual spans stream (`export_span`) and whether finalize goes through `/finalize` (streaming) vs a bundled `export_trace` (batch). CMP-257 generalized only the start emission, not the whole streaming model.
|
|
512
544
|
|
|
545
|
+
`_create_trace` also attaches `optimization_run_id` and `eval_run_id` at trace start, so the start row carries both from its first send. `on_module_end` never re-reads either source at finalize: a trace's module call can outlive the optimization or eval it started under, and re-reading at finalize would send a later update that clears the ids the start row already carried. A finalizing trace always keeps the ids it started with.
|
|
546
|
+
|
|
547
|
+
Optimization reads this callback's `_active_optimization_run_id` field first,
|
|
548
|
+
then the shared `cmpnd.context` contextvar. Both SDK compile wrappers publish
|
|
549
|
+
the reserved id on the installed callback before evaluator workers start,
|
|
550
|
+
including when native GEPA fires its start hook after the seed evaluation or
|
|
551
|
+
has no callback support. The wrappers restore the previous
|
|
552
|
+
callback and context ids when the search returns or raises. Eval reads only
|
|
553
|
+
the callback field. `on_evaluate_start` sets it before the evaluator workers
|
|
554
|
+
start, so eval needs no contextvar fallback.
|
|
555
|
+
|
|
556
|
+
The callback fields let evaluator threads read the active run ids. A plain
|
|
557
|
+
thread need not inherit Python contextvars, and DSPy versions and wrappers
|
|
558
|
+
differ in how they carry context to workers. The fields also let an unrelated
|
|
559
|
+
concurrent call sharing the callback adopt the active run. A nested scoped
|
|
560
|
+
client can adopt another client's optimization through the contextvar
|
|
561
|
+
fallback. Run attribution therefore requires calls that belong to the run
|
|
562
|
+
owning this callback. Callback-wide state does not prove ownership across
|
|
563
|
+
overlapping runs or scoped clients.
|
|
564
|
+
|
|
565
|
+
The callback has two automatic trace constructors. `_create_trace` snapshots
|
|
566
|
+
the root module's run ids before it exports a start. `EvalHandler` obtains the
|
|
567
|
+
eval id synchronously before evaluator workers call the program.
|
|
568
|
+
`OptimizationTracker.start()` obtains the optimization id before rollouts.
|
|
569
|
+
Both SDK compile wrappers set that id on the installed callback and in the
|
|
570
|
+
contextvar before compile. GEPA's start callback also sets it when the event
|
|
571
|
+
arrives. The tracker reserves one id even when both paths call `start()`.
|
|
572
|
+
`TrackedGEPA` also claims its native optimizer while calling compile. The
|
|
573
|
+
automatic class patch then runs that instance's search without reserving a
|
|
574
|
+
second id. The claim ends when compile returns or raises. An instance override
|
|
575
|
+
still runs, and a distinct optimizer nested inside it gets its own run.
|
|
576
|
+
The start payload also carries the compiled program's `compiled_from_run_id`.
|
|
577
|
+
|
|
578
|
+
Nested modules add spans to their root trace. `asyncify`, native `acall` and
|
|
579
|
+
`streamify` dispatch through the module hooks. RLM exports its start before it
|
|
580
|
+
streams spans and keeps those run ids through finalize. A standalone GEPA LM
|
|
581
|
+
trace is constructed when the LM call ends and exports as one complete trace.
|
|
582
|
+
Its first payload carries the optimization id, plus the active eval id for a
|
|
583
|
+
metric call. A reflection call outside evaluation has no eval id. Manual
|
|
584
|
+
decorators and `start_span` create spans rather than another automatic trace.
|
|
585
|
+
|
|
513
586
|
Two things keep the extra start cheap and correct:
|
|
514
587
|
|
|
515
588
|
- **Upsert, not duplicate.** The batch finalize (`export_trace`) and the start write the same row: serve's `traceUpsertSQL` keys `ON CONFLICT (org_id, trace_id, start_time)`, and the SDK's `start_time` is immutable across a trace's life, so the batch overwrites the `in_progress` row with the final status. A trace that never finalizes (crash / cancel) correctly stays `in_progress`.
|
|
@@ -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.32.0"
|
|
9
9
|
description = "DSPy observability and deployment SDK for cmpnd"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.10"
|