cmpnd 0.42.0__tar.gz → 0.42.2__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.42.0 → cmpnd-0.42.2}/PKG-INFO +1 -1
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/authored.py +1 -1
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/models.py +4 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/docs/sdk-instrumentation.md +27 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/ingest/cmpnd_ingest/exporter_http.py +157 -12
- cmpnd-0.42.2/ingest/cmpnd_ingest/program_names.py +90 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/ingest/cmpnd_ingest/protocols.py +7 -1
- {cmpnd-0.42.0 → cmpnd-0.42.2}/pyproject.toml +1 -1
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_authored.py +2 -2
- cmpnd-0.42.2/tests/test_exporter_program_names.py +440 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/uv.lock +1 -1
- {cmpnd-0.42.0 → cmpnd-0.42.2}/.gitignore +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/AGENTS.md +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/CLAUDE.md +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/README.md +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/__init__.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/_assessments.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/_authored_import.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/_deployments.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/_gepa_patch.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/_http.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/_runtime_tools.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/_traces.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/_valsets.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/callback.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/__init__.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/commands/admin.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/commands/assessments.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/commands/deployments.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/commands/evals.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/commands/org.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/commands/programs.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/commands/provider_credentials.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/commands/signatures.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/commands/top.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/commands/traces.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/commands/valsets.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/shell.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/trace_render.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/tree_help.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/cli/verbs.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/client.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/configuration.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/context.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/dataset_sync.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/datasets.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/decorators.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/deployment.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/deployment_metadata.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/deployment_runs.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/execution.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/exporter.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/helpers.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/identity.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/imaging.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/ir/reflection.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/lm_configuration.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/mcp_servers.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/optimization.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/optimizers/gepa.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/optimizers/gepa_callback.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/optimizers/tracker.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/packaging.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/programs.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/references.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/retry.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/cmpnd/traces.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/docs/2026-04-02-code-review.md +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/docs/cli.md +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/docs/client.md +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/docs/custom-proposer.md +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/docs/reading-traces.md +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/examples/local_ollama.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/ingest/README.md +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/ingest/cmpnd_ingest/bodies.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/ingest/cmpnd_ingest/trace_facts.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/ingest/pyproject.toml +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/scripts/capture_review_run.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/scripts/check-pip-install.sh +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/scripts/release-stress.sh +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/scripts/require_version_bump.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/scripts/seed_review_run.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/scripts/test-in-linux.sh +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/stubs/dspy/utils/dummies.pyi +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/__init__.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/_deployment_signature_helpers.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/_fake_clock.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/_resolve_refs.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/_traced_run.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/conftest.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/committee_task.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/helpers.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/target_capabilities.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_assessments_cli.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_composite_module.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_deploy_execute.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_deploy_pandas.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_deploy_programs.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_optimize_failures.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_parallel_export.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_project_attribution.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_traces_read.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_user_attribution.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/e2e/test_valsets_cli.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/integration/__init__.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/integration/authored_optimize/judge.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/integration/conftest.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/integration/hosted_optimize_probe.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/integration/test_logs_renders_traces.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/integration/test_unhappy_path.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/load/__init__.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/load/__main__.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/load/drain.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/load/ingest.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/load/rlm_shape.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/load/rlm_size_report.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/load/sweep.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/load/test_rlm_size_report.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/parity/__init__.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/parity/conftest.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/parity/test_health.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/parity/test_trace_streaming.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/parity/test_traces.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/span_reference_fixture.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/stress/__init__.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/stress/_provider.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/stress/_readiness.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/stress/_reliability.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/stress/_result.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/stress/mint_bedrock_token.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/stress/test_deploy_programs.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/stress/test_multi_client_stress.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/stress/test_optimize_client_committee.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/stress/test_optimize_loop.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/stress/test_optimize_loop_wasm.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/stress/test_optimize_stress.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/stress/test_provider_roles.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/stress/test_readiness.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/stress/test_real_provider_twins.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/stress/test_reliability.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/stress/test_reliability_classify.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/stress/test_rlm_wasm.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/stress/test_trace_search.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_adapter_reentrancy.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_assessments_api.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_authored_bundle_collisions.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_authored_metric_contract.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_authored_named_models.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_authored_optimize.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_callback.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_admin.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_api_client.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_assessments.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_deployment_runs.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_deployments.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_evals.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_main.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_org.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_parity.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_programs.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_provider_credentials.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_schema_sample.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_shell.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_signatures.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_top.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_traces.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_traces_search_wire.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_tree_help.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cli/test_valsets.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_client.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_configuration.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_context.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_context_propagation.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_dataset_sync.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_datasets.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_decorators.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_deploy.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_deploy_program_id.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_deployment_metadata.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_deployment_runs.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_deployments_read.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_eval.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_eval_example_attribution.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_eval_start.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_eval_trace_grouping.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_execute.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_execute_run_identity.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_exporter.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_exporter_queue.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_exporter_real.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_gepa_output_normalization.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_identity.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_imaging.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_import_weight.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_ingest_conformance.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_integration.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_interpreter_callback.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_lm15_native_calls.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_lm_configuration.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_lm_usage_shapes.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_mcp_tools.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_models.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_multi_client_stress_readiness.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_optimize.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_optimize_lms.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_optimize_reflection_lm.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_packaging.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_packaging_mcp_tools.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_packaging_program_source.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_poll_until.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_program_identity.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_program_lineage_persist.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_programs_create.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_project_payload.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_real_provider_twins_readiness.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_reflector_properties.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_reflector_smoke.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_release_stress_script.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_repro_reactv2_bugs.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_retry.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_rlm_patch.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_run_attribution_paths.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_runtime_tools.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_schemas.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_signature_inputs.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_span_references.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_span_sent_once.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_trace_context_concurrency.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_trace_fact_wire_contract.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_trace_facts.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_trace_render.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_traces_read.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_tracked_gepa.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_truncation.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_valsets_api.py +0 -0
- {cmpnd-0.42.0 → cmpnd-0.42.2}/tests/test_xxh64.py +0 -0
|
@@ -32,7 +32,7 @@ from typing import Any
|
|
|
32
32
|
_MAX_BUNDLE_BYTES = 50 * 1024 * 1024
|
|
33
33
|
_CHECK_TIMEOUT_SECONDS = 60.0
|
|
34
34
|
_CHECK_TIMEOUT_ENV = "CMPND_AUTHORED_IMPORT_TIMEOUT_SECONDS"
|
|
35
|
-
_WORKER_DSPY_BASELINE = (3,
|
|
35
|
+
_WORKER_DSPY_BASELINE = (3, 4, 1)
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
class SourceCheckError(ValueError):
|
|
@@ -961,6 +961,10 @@ class TraceBuilder:
|
|
|
961
961
|
# the configuration at construction like project_tags. None declares none,
|
|
962
962
|
# and the key is then left off the wire rather than sent as null.
|
|
963
963
|
program_id: str | None = field(default_factory=configuration._current_program_id)
|
|
964
|
+
# The program name the trace declares, or None. It never goes on the wire: the
|
|
965
|
+
# exporter resolves it to a program id and writes that into ``program_id`` on the
|
|
966
|
+
# bodies it sends, unless the trace already carries an id.
|
|
967
|
+
declared_name: str | None = None
|
|
964
968
|
|
|
965
969
|
# Optimization lineage - tracks which optimization produced this program
|
|
966
970
|
compiled_from_run_id: str | None = None
|
|
@@ -768,6 +768,33 @@ names its program, so the id is optional there. Later steps
|
|
|
768
768
|
optimize a result deployment the platform created and whose program it allocated, so
|
|
769
769
|
the caller's id rides only the steps on the caller's own deployment.
|
|
770
770
|
|
|
771
|
+
#### A declared program name resolves to an id in the exporter
|
|
772
|
+
|
|
773
|
+
A trace can declare its program by name instead of by id, in `TraceBuilder.declared_name`.
|
|
774
|
+
No public API sets it yet. The name never goes on the wire. The exporter resolves it with
|
|
775
|
+
`PUT /api/v1/programs/by-name/{name}` and writes the id into the trace start and full
|
|
776
|
+
trace bodies, at the top level and in `started_fact`, before it compares or sends them.
|
|
777
|
+
`cmpnd_ingest/program_names.py` keeps the answers, and
|
|
778
|
+
`BatchExporter._stamp_program_ids` makes the requests:
|
|
779
|
+
|
|
780
|
+
- **Once per name per process.** Each exporter keeps its answers for its own life, and
|
|
781
|
+
the pool keeps one exporter per endpoint and key, so each name resolves once for each.
|
|
782
|
+
A forked child keeps its parent's answers.
|
|
783
|
+
- **On the export thread.** The batch waits for the resolve, and the producer never
|
|
784
|
+
does. The exit flush resolves the names still waiting, with no retries.
|
|
785
|
+
- **An id already on the trace stands.** Inside a worker, a deployment's traces carry
|
|
786
|
+
its id from the start, and no name is resolved.
|
|
787
|
+
- **A refusal settles the name with no id.** Serve answers 404 or 405 when it has no
|
|
788
|
+
program names, as self-host release 2026-001 does, and 409, 422 or 403 for a name it
|
|
789
|
+
will not resolve. The traces go without an id, and the exporter warns once per name.
|
|
790
|
+
- **An outage leaves the name unsettled.** A 5xx, a 429 or an unreachable server is
|
|
791
|
+
retried with backoff, wider than the data sends' rule because the request is a
|
|
792
|
+
get-or-create that a repeat cannot double. The names in one batch share one retry
|
|
793
|
+
budget, so an outage delays a batch by one budget however many new names it holds.
|
|
794
|
+
When the budget is spent, the batch's traces go without an id, and later batches try
|
|
795
|
+
the names again. A trace keeps the decision its first body carried,
|
|
796
|
+
because serve refuses a trace whose started fact changes between sends.
|
|
797
|
+
|
|
771
798
|
### LM usage evidence
|
|
772
799
|
|
|
773
800
|
Optimization LM spans carry `optimization.role` in their string attributes.
|
|
@@ -25,6 +25,7 @@ import os
|
|
|
25
25
|
import queue
|
|
26
26
|
import threading
|
|
27
27
|
import time
|
|
28
|
+
import urllib.parse
|
|
28
29
|
import weakref
|
|
29
30
|
from collections.abc import Callable
|
|
30
31
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
@@ -33,7 +34,7 @@ from typing import Any, cast
|
|
|
33
34
|
|
|
34
35
|
import httpx
|
|
35
36
|
|
|
36
|
-
from . import request_log, retry
|
|
37
|
+
from . import program_names, request_log, retry
|
|
37
38
|
from .bodies import EncodedTraceBody, TraceBody
|
|
38
39
|
from .protocols import (
|
|
39
40
|
EvalRunRecord,
|
|
@@ -96,6 +97,16 @@ def _send_encoded(client: httpx.Client, method: str, path: str, body: EncodedTra
|
|
|
96
97
|
_SHED_STATUS = 429
|
|
97
98
|
|
|
98
99
|
|
|
100
|
+
def _error_text(response: httpx.Response) -> str:
|
|
101
|
+
"""The ``error`` field of a refusal, else its body, shortened for a log line."""
|
|
102
|
+
try:
|
|
103
|
+
error = response.json().get("error")
|
|
104
|
+
except (ValueError, AttributeError):
|
|
105
|
+
error = None
|
|
106
|
+
text = error if isinstance(error, str) and error else (response.text or "").strip()
|
|
107
|
+
return text[:200] or "no detail"
|
|
108
|
+
|
|
109
|
+
|
|
99
110
|
class ExportRetriesExhausted(RuntimeError):
|
|
100
111
|
"""A data POST rode through transient answers until its budget ran out.
|
|
101
112
|
|
|
@@ -183,6 +194,22 @@ def _is_retryable_export_error(exc: BaseException) -> bool:
|
|
|
183
194
|
return isinstance(exc, (httpx.TimeoutException, httpx.TransportError))
|
|
184
195
|
|
|
185
196
|
|
|
197
|
+
def _may_retry_a_name_resolve(exc: BaseException) -> bool:
|
|
198
|
+
"""Whether a failed program-name resolve is worth asking again.
|
|
199
|
+
|
|
200
|
+
Wider than the data sends' rule, for two reasons. A failed resolve sends its batch
|
|
201
|
+
without program ids, and a trace whose start went without an id keeps none, so
|
|
202
|
+
giving up early costs more than waiting. And the request is a get-or-create that
|
|
203
|
+
returns one id per name however often it is repeated, so a repeat can never
|
|
204
|
+
allocate twice. So a 429 and a plain 500 are retried here too, with ``Retry-After``
|
|
205
|
+
honoured, while a 4xx refusal is final.
|
|
206
|
+
"""
|
|
207
|
+
if isinstance(exc, httpx.HTTPStatusError):
|
|
208
|
+
status = exc.response.status_code
|
|
209
|
+
return status == _SHED_STATUS or status >= 500 or retry.is_retryable(exc.response)
|
|
210
|
+
return isinstance(exc, (httpx.TimeoutException, httpx.TransportError))
|
|
211
|
+
|
|
212
|
+
|
|
186
213
|
def _may_resend_a_minting_post(exc: BaseException) -> bool:
|
|
187
214
|
"""The same question as above, asked of a POST that BOTH mints an id and
|
|
188
215
|
creates the record under it — on this contract, only ``POST /api/v1/evals``.
|
|
@@ -225,6 +252,20 @@ class ExportItem:
|
|
|
225
252
|
# documentation and to catch misuse at enqueue.
|
|
226
253
|
data: TraceRecord | SpanRecord | EvalRunRecord | OptimizationRunRecord | EncodedTraceBody
|
|
227
254
|
timestamp: float
|
|
255
|
+
# The program name a trace declared, which the flush resolves to an id. It rides the
|
|
256
|
+
# queue item because the body is serialized at enqueue and the name never goes on
|
|
257
|
+
# the wire.
|
|
258
|
+
declared_name: str | None = None
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _declared_name(trace: TraceRecord) -> str | None:
|
|
262
|
+
"""The program name a trace record declares, or None.
|
|
263
|
+
|
|
264
|
+
Read with ``getattr`` because the member is optional: a ``Protocol`` cannot declare
|
|
265
|
+
an optional member, and a producer that names no programs should not need one.
|
|
266
|
+
"""
|
|
267
|
+
name = getattr(trace, "declared_name", None)
|
|
268
|
+
return name if isinstance(name, str) and name else None
|
|
228
269
|
|
|
229
270
|
|
|
230
271
|
# The only record the exporter will shed under load, and the reason overflow can
|
|
@@ -366,6 +407,10 @@ class BatchExporter:
|
|
|
366
407
|
self._dropped_at_last_log = 0
|
|
367
408
|
self._stats_lock = threading.Lock()
|
|
368
409
|
|
|
410
|
+
# The program ids serve gave for the names traces declare. Kept across a fork:
|
|
411
|
+
# see _reinit_after_fork.
|
|
412
|
+
self._program_names = program_names.ProgramNames()
|
|
413
|
+
|
|
369
414
|
def start(self) -> None:
|
|
370
415
|
"""Start the background export thread."""
|
|
371
416
|
# Before the liveness check, not after: in a forked child `_thread` is a
|
|
@@ -472,6 +517,8 @@ class BatchExporter:
|
|
|
472
517
|
|
|
473
518
|
self._sync_client_lock = threading.Lock()
|
|
474
519
|
self._stats_lock = threading.Lock()
|
|
520
|
+
# The resolved program names stay: the ids are still right in the child.
|
|
521
|
+
self._program_names.after_fork()
|
|
475
522
|
|
|
476
523
|
self._flush_cond = threading.Condition()
|
|
477
524
|
self._flush_requested_gen = 0
|
|
@@ -584,7 +631,7 @@ class BatchExporter:
|
|
|
584
631
|
|
|
585
632
|
def export_trace(self, trace: TraceRecord) -> None:
|
|
586
633
|
"""Queue a trace for export (non-blocking)."""
|
|
587
|
-
self._enqueue(ExportItem("trace", TraceBody(trace.to_api_dict()), time.time()))
|
|
634
|
+
self._enqueue(ExportItem("trace", TraceBody(trace.to_api_dict()), time.time(), _declared_name(trace)))
|
|
588
635
|
|
|
589
636
|
def export_encoded_trace(self, trace: EncodedTraceBody) -> None:
|
|
590
637
|
"""Queue a compressed finished trace for export (non-blocking).
|
|
@@ -622,12 +669,12 @@ class BatchExporter:
|
|
|
622
669
|
def export_trace_start(self, trace: TraceRecord) -> None:
|
|
623
670
|
"""Queue a trace start for export (non-blocking)."""
|
|
624
671
|
body = {**trace.to_start_dict(), "trace_id": str(trace.trace_id)}
|
|
625
|
-
self._enqueue(ExportItem("trace_start", TraceBody(body), time.time()))
|
|
672
|
+
self._enqueue(ExportItem("trace_start", TraceBody(body), time.time(), _declared_name(trace)))
|
|
626
673
|
|
|
627
674
|
def export_trace_finalize(self, trace: TraceRecord) -> None:
|
|
628
675
|
"""Queue a trace finalization for export (non-blocking)."""
|
|
629
676
|
body = {**trace.to_finalize_dict(), "trace_id": str(trace.trace_id)}
|
|
630
|
-
self._enqueue(ExportItem("trace_finalize", TraceBody(body), time.time()))
|
|
677
|
+
self._enqueue(ExportItem("trace_finalize", TraceBody(body), time.time(), _declared_name(trace)))
|
|
631
678
|
|
|
632
679
|
def export_optimization_start(self, optimization_run: OptimizationRunRecord) -> str:
|
|
633
680
|
"""Create the optimization record server-side and return the
|
|
@@ -1089,7 +1136,15 @@ class BatchExporter:
|
|
|
1089
1136
|
def _send_json_retrying(self, client: httpx.Client, method: str, path: str, payload: Any) -> httpx.Response:
|
|
1090
1137
|
return self._send_retrying(method, path, lambda: _send_json(client, method, path, payload))
|
|
1091
1138
|
|
|
1092
|
-
def _send_retrying(
|
|
1139
|
+
def _send_retrying(
|
|
1140
|
+
self,
|
|
1141
|
+
method: str,
|
|
1142
|
+
path: str,
|
|
1143
|
+
send: Callable[[], httpx.Response],
|
|
1144
|
+
*,
|
|
1145
|
+
retryable: Callable[[BaseException], bool] = _is_retryable_export_error,
|
|
1146
|
+
tally: retry.TransientTally | None = None,
|
|
1147
|
+
) -> httpx.Response:
|
|
1093
1148
|
"""`_send_json` + `raise_for_status`, riding through transient blips.
|
|
1094
1149
|
|
|
1095
1150
|
Retries a failure the retryable-error contract calls transient (see
|
|
@@ -1103,21 +1158,26 @@ class BatchExporter:
|
|
|
1103
1158
|
|
|
1104
1159
|
An exhausted budget raises ExportRetriesExhausted naming the count and
|
|
1105
1160
|
the window, so a run that could not be saved says what it endured.
|
|
1161
|
+
|
|
1162
|
+
``retryable`` replaces the data sends' rule for a caller with a different one.
|
|
1163
|
+
``tally`` lets several calls share one budget: a caller passes the same tally
|
|
1164
|
+
to each, and once it is spent every later call gives up at its first failure.
|
|
1106
1165
|
"""
|
|
1107
|
-
tally = retry.TransientTally()
|
|
1166
|
+
tally = tally if tally is not None else retry.TransientTally()
|
|
1167
|
+
ridden_before = tally.count
|
|
1108
1168
|
while True:
|
|
1109
1169
|
try:
|
|
1110
1170
|
response = send()
|
|
1111
1171
|
response.raise_for_status()
|
|
1112
|
-
if tally.count:
|
|
1113
|
-
self._note_ridden_through(tally.count)
|
|
1172
|
+
if tally.count > ridden_before:
|
|
1173
|
+
self._note_ridden_through(tally.count - ridden_before)
|
|
1114
1174
|
logger.warning("cmpnd: rode through %s to %s %s", tally.describe(time.monotonic()), method, path)
|
|
1115
1175
|
return response
|
|
1116
1176
|
except Exception as e:
|
|
1117
1177
|
resp = e.response if isinstance(e, httpx.HTTPStatusError) else None
|
|
1118
1178
|
if resp is not None:
|
|
1119
1179
|
self._note_status(resp)
|
|
1120
|
-
if not
|
|
1180
|
+
if not retryable(e):
|
|
1121
1181
|
raise
|
|
1122
1182
|
tally.record(
|
|
1123
1183
|
str(resp.status_code) if resp is not None else type(e).__name__,
|
|
@@ -1126,7 +1186,7 @@ class BatchExporter:
|
|
|
1126
1186
|
)
|
|
1127
1187
|
now = time.monotonic()
|
|
1128
1188
|
if tally.exhausted(now) or not self._wait_before_retry(retry.delay_for(tally.count - 1, resp)):
|
|
1129
|
-
self._note_ridden_through(tally.count)
|
|
1189
|
+
self._note_ridden_through(tally.count - ridden_before)
|
|
1130
1190
|
raise ExportRetriesExhausted(f"{method} {path} gave up after {tally.describe(now)}: {e}") from e
|
|
1131
1191
|
|
|
1132
1192
|
def _note_status(self, response: httpx.Response) -> None:
|
|
@@ -1184,10 +1244,21 @@ class BatchExporter:
|
|
|
1184
1244
|
trace_starts: list[tuple[str, dict[str, Any]]] = []
|
|
1185
1245
|
trace_finalizes: list[tuple[str, dict[str, Any]]] = []
|
|
1186
1246
|
spans_by_trace: dict[str, list[dict[str, Any]]] = {}
|
|
1247
|
+
# Bodies that declare a program by name, as (name, trace id, body), so the
|
|
1248
|
+
# names can be resolved and the ids written before anything compares or sends
|
|
1249
|
+
# them. A finalize body restates no creation field and carries no id, but it
|
|
1250
|
+
# and a full trace body are a trace's last, so both end the trace's decision.
|
|
1251
|
+
named_bodies: list[tuple[str, str, dict[str, Any]]] = []
|
|
1252
|
+
ended_traces: list[str] = []
|
|
1187
1253
|
|
|
1188
1254
|
for item in batch:
|
|
1189
1255
|
if item.item_type == "trace":
|
|
1190
|
-
|
|
1256
|
+
body = cast("TraceRecord", item.data).to_api_dict()
|
|
1257
|
+
traces_to_send.append(body)
|
|
1258
|
+
if item.declared_name is not None:
|
|
1259
|
+
trace_id = str(body.get("trace_id"))
|
|
1260
|
+
named_bodies.append((item.declared_name, trace_id, body))
|
|
1261
|
+
ended_traces.append(trace_id)
|
|
1191
1262
|
elif item.item_type == "encoded_trace":
|
|
1192
1263
|
encoded_traces.append(cast("EncodedTraceBody", item.data))
|
|
1193
1264
|
elif item.item_type == "eval_run":
|
|
@@ -1205,11 +1276,16 @@ class BatchExporter:
|
|
|
1205
1276
|
elif item.item_type == "trace_start":
|
|
1206
1277
|
trace = cast("TraceRecord", item.data)
|
|
1207
1278
|
trace_id = str(trace.trace_id)
|
|
1208
|
-
|
|
1279
|
+
body = trace.to_start_dict()
|
|
1280
|
+
trace_starts.append((trace_id, body))
|
|
1281
|
+
if item.declared_name is not None:
|
|
1282
|
+
named_bodies.append((item.declared_name, trace_id, body))
|
|
1209
1283
|
elif item.item_type == "trace_finalize":
|
|
1210
1284
|
trace = cast("TraceRecord", item.data)
|
|
1211
1285
|
trace_id = str(trace.trace_id)
|
|
1212
1286
|
trace_finalizes.append((trace_id, trace.to_finalize_dict()))
|
|
1287
|
+
if item.declared_name is not None:
|
|
1288
|
+
ended_traces.append(trace_id)
|
|
1213
1289
|
elif item.item_type == "span":
|
|
1214
1290
|
span_dict = cast("SpanRecord", item.data).to_api_dict()
|
|
1215
1291
|
trace_id = str(span_dict.get("trace_id") or "")
|
|
@@ -1218,6 +1294,9 @@ class BatchExporter:
|
|
|
1218
1294
|
continue
|
|
1219
1295
|
spans_by_trace.setdefault(trace_id, []).append(span_dict)
|
|
1220
1296
|
|
|
1297
|
+
if named_bodies or ended_traces:
|
|
1298
|
+
self._stamp_program_ids(named_bodies, ended_traces)
|
|
1299
|
+
|
|
1221
1300
|
# Coalesce start + batch within one flush: if a trace's full
|
|
1222
1301
|
# batch trace is in this same flush, sending it upserts the row to its
|
|
1223
1302
|
# final status directly (serve traceUpsertSQL, ON CONFLICT on
|
|
@@ -1329,6 +1408,72 @@ class BatchExporter:
|
|
|
1329
1408
|
errored,
|
|
1330
1409
|
)
|
|
1331
1410
|
|
|
1411
|
+
# ------------------------------------------------------------------
|
|
1412
|
+
# Declared program names
|
|
1413
|
+
# ------------------------------------------------------------------
|
|
1414
|
+
|
|
1415
|
+
def _stamp_program_ids(self, named_bodies: list[tuple[str, str, dict[str, Any]]], ended_traces: list[str]) -> None:
|
|
1416
|
+
"""Resolve the batch's declared names, then write their ids into its bodies.
|
|
1417
|
+
|
|
1418
|
+
Runs on the export thread, so the batch waits for the resolve and the
|
|
1419
|
+
producer never does. A body that already declares an id keeps it: inside a
|
|
1420
|
+
worker, a deployment's traces carry its id from the start.
|
|
1421
|
+
|
|
1422
|
+
The batch's names share one retry budget, so an outage delays the batch by one
|
|
1423
|
+
budget however many new names it holds. Once the budget is spent, the names not
|
|
1424
|
+
yet asked stay unsettled for the next batch.
|
|
1425
|
+
"""
|
|
1426
|
+
waiting = [name for name, _, body in named_bodies if "program_id" not in body]
|
|
1427
|
+
tally = retry.TransientTally()
|
|
1428
|
+
for name in self._program_names.unsettled(waiting):
|
|
1429
|
+
if tally.exhausted(time.monotonic()):
|
|
1430
|
+
self._warn_program_name(name, "the batch's retry budget is spent; later traces will try again")
|
|
1431
|
+
continue
|
|
1432
|
+
self._resolve_program_name(name, tally)
|
|
1433
|
+
for name, trace_id, body in named_bodies:
|
|
1434
|
+
self._program_names.stamp(name, trace_id, body)
|
|
1435
|
+
for trace_id in ended_traces:
|
|
1436
|
+
self._program_names.trace_ended(trace_id)
|
|
1437
|
+
|
|
1438
|
+
def _resolve_program_name(self, name: str, tally: retry.TransientTally) -> None:
|
|
1439
|
+
"""Ask serve for a name's program id, and settle the name unless serve was unreachable.
|
|
1440
|
+
|
|
1441
|
+
The request has no body. A 429, a 5xx or an unreachable server is retried on
|
|
1442
|
+
the batch's shared budget (see `_may_retry_a_name_resolve`), and a spent budget
|
|
1443
|
+
leaves the name unsettled for the next batch. A refusal settles the name with
|
|
1444
|
+
no id. Each name warns once.
|
|
1445
|
+
"""
|
|
1446
|
+
client = self._require_client()
|
|
1447
|
+
path = "/api/v1/programs/by-name/" + urllib.parse.quote(name, safe="")
|
|
1448
|
+
try:
|
|
1449
|
+
response = self._send_retrying(
|
|
1450
|
+
"PUT", path, lambda: client.request("PUT", path), retryable=_may_retry_a_name_resolve, tally=tally
|
|
1451
|
+
)
|
|
1452
|
+
except httpx.HTTPStatusError as e:
|
|
1453
|
+
status = e.response.status_code
|
|
1454
|
+
self._program_names.settle(name, None)
|
|
1455
|
+
if status in (404, 405):
|
|
1456
|
+
self._warn_program_name(name, "the server has no program names")
|
|
1457
|
+
else:
|
|
1458
|
+
self._warn_program_name(name, f"the server refused it with {status}: {_error_text(e.response)}")
|
|
1459
|
+
return
|
|
1460
|
+
except Exception as e:
|
|
1461
|
+
self._warn_program_name(name, f"the server gave no answer ({e}); later traces will try again")
|
|
1462
|
+
return
|
|
1463
|
+
try:
|
|
1464
|
+
program_id = response.json().get("program_id")
|
|
1465
|
+
except (ValueError, AttributeError):
|
|
1466
|
+
program_id = None
|
|
1467
|
+
if not isinstance(program_id, str) or not program_id:
|
|
1468
|
+
self._program_names.settle(name, None)
|
|
1469
|
+
self._warn_program_name(name, "the server's answer had no program id")
|
|
1470
|
+
return
|
|
1471
|
+
self._program_names.settle(name, program_id)
|
|
1472
|
+
|
|
1473
|
+
def _warn_program_name(self, name: str, reason: str) -> None:
|
|
1474
|
+
if self._program_names.first_warning(name):
|
|
1475
|
+
logger.warning("cmpnd: traces of program %r are sent without a program id: %s.", name, reason)
|
|
1476
|
+
|
|
1332
1477
|
# ------------------------------------------------------------------
|
|
1333
1478
|
# Individual send methods (run in thread pool)
|
|
1334
1479
|
# ------------------------------------------------------------------
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""The program ids serve gave for the program names this process declared.
|
|
2
|
+
|
|
3
|
+
A trace can declare the name of its program instead of its id. The name never goes on
|
|
4
|
+
the wire: the transport asks serve for the name's program id with
|
|
5
|
+
``PUT /api/v1/programs/by-name/{name}``, once per name per process, and writes the id
|
|
6
|
+
into each trace body before it sends it. This module keeps the answers. The transport
|
|
7
|
+
makes the requests, because they ride its client and its retry budget.
|
|
8
|
+
|
|
9
|
+
An answer is kept for the life of the process. A name that serve later points at
|
|
10
|
+
another program reaches a process only when the process starts again, and a renamed
|
|
11
|
+
program's old name keeps resolving to it.
|
|
12
|
+
|
|
13
|
+
A name settles in one of two ways. Serve gives an id, or serve refuses the name, as a
|
|
14
|
+
server without program names does with ``405``. A refused name sends its traces without
|
|
15
|
+
an id for the rest of the process. A name that could not be resolved because serve was
|
|
16
|
+
unreachable stays unsettled, and the transport tries it again for later traces.
|
|
17
|
+
|
|
18
|
+
A trace keeps the decision its first body carried. Serve refuses a trace whose started
|
|
19
|
+
fact changes between sends, so a trace whose start went without an id during an outage
|
|
20
|
+
sends every later body without one too, even after the name resolves.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import threading
|
|
26
|
+
from collections.abc import Iterable
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ProgramNames:
|
|
31
|
+
"""Resolved and refused names, and the id each unfinished trace was sent with."""
|
|
32
|
+
|
|
33
|
+
def __init__(self) -> None:
|
|
34
|
+
self._lock = threading.Lock()
|
|
35
|
+
# Settled names: the id serve gave, or None for a name serve refused.
|
|
36
|
+
self._ids: dict[str, str | None] = {}
|
|
37
|
+
# Traces whose first body went out while their name was unsettled, keyed by
|
|
38
|
+
# trace id. They keep sending without an id until they end.
|
|
39
|
+
self._sent_without_id: set[str] = set()
|
|
40
|
+
self._warned: set[str] = set()
|
|
41
|
+
|
|
42
|
+
def after_fork(self) -> None:
|
|
43
|
+
"""Keep the answers in a forked child, with a lock the child can take.
|
|
44
|
+
|
|
45
|
+
The ids are still right in the child, so it does not ask again. A lock that
|
|
46
|
+
another thread held at the fork stays held in the child, so it is replaced.
|
|
47
|
+
"""
|
|
48
|
+
self._lock = threading.Lock()
|
|
49
|
+
|
|
50
|
+
def unsettled(self, names: Iterable[str]) -> list[str]:
|
|
51
|
+
"""The names among ``names`` that have no answer yet, each once, in order."""
|
|
52
|
+
with self._lock:
|
|
53
|
+
return list(dict.fromkeys(name for name in names if name not in self._ids))
|
|
54
|
+
|
|
55
|
+
def settle(self, name: str, program_id: str | None) -> None:
|
|
56
|
+
"""Record serve's answer for a name: its id, or None when serve refused it."""
|
|
57
|
+
with self._lock:
|
|
58
|
+
self._ids[name] = program_id
|
|
59
|
+
|
|
60
|
+
def first_warning(self, name: str) -> bool:
|
|
61
|
+
"""True the first time this is asked for a name, so each name warns once."""
|
|
62
|
+
with self._lock:
|
|
63
|
+
if name in self._warned:
|
|
64
|
+
return False
|
|
65
|
+
self._warned.add(name)
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
def stamp(self, name: str, trace_id: str, body: dict[str, Any]) -> None:
|
|
69
|
+
"""Write the name's id into a trace start or full trace body.
|
|
70
|
+
|
|
71
|
+
The id goes at the top level and in the started fact, where the builder puts
|
|
72
|
+
an id it was given. A body that already declares an id keeps it.
|
|
73
|
+
"""
|
|
74
|
+
if "program_id" in body:
|
|
75
|
+
return
|
|
76
|
+
with self._lock:
|
|
77
|
+
program_id = None if trace_id in self._sent_without_id else self._ids.get(name)
|
|
78
|
+
if program_id is None and name not in self._ids:
|
|
79
|
+
self._sent_without_id.add(trace_id)
|
|
80
|
+
if program_id is None:
|
|
81
|
+
return
|
|
82
|
+
body["program_id"] = program_id
|
|
83
|
+
started = body.get("started_fact")
|
|
84
|
+
if isinstance(started, dict):
|
|
85
|
+
started["program_id"] = program_id
|
|
86
|
+
|
|
87
|
+
def trace_ended(self, trace_id: str) -> None:
|
|
88
|
+
"""Forget a trace's decision once its last body is built."""
|
|
89
|
+
with self._lock:
|
|
90
|
+
self._sent_without_id.discard(trace_id)
|
|
@@ -49,7 +49,13 @@ class SpanRecord(Protocol):
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
class TraceRecord(Protocol):
|
|
52
|
-
"""A trace, in each of the three shapes the contract's routes take.
|
|
52
|
+
"""A trace, in each of the three shapes the contract's routes take.
|
|
53
|
+
|
|
54
|
+
A trace may also have a ``declared_name`` attribute: the name of its program, which
|
|
55
|
+
the transport resolves to a program id before it sends the trace (see
|
|
56
|
+
`program_names`). The member is optional, so it is not declared here, and the
|
|
57
|
+
transport reads it with ``getattr``.
|
|
58
|
+
"""
|
|
53
59
|
|
|
54
60
|
@property
|
|
55
61
|
def trace_id(self) -> Any: ...
|
|
@@ -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.42.
|
|
8
|
+
version = "0.42.2"
|
|
9
9
|
description = "DSPy observability and deployment SDK for cmpnd"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.11"
|
|
@@ -100,11 +100,11 @@ def test_newer_local_dspy_warns_about_the_worker_compatibility_boundary(monkeypa
|
|
|
100
100
|
monkeypatch.setattr(
|
|
101
101
|
authored,
|
|
102
102
|
"_dependency_versions",
|
|
103
|
-
lambda: {"python": "3.14.0", "dspy": "3.
|
|
103
|
+
lambda: {"python": "3.14.0", "dspy": "3.5.0", "cmpnd": "0.26.0"},
|
|
104
104
|
)
|
|
105
105
|
monkeypatch.setattr(authored, "_check_bundle", lambda *_args, **_kwargs: authored.ProgramFacts.minimal())
|
|
106
106
|
|
|
107
|
-
with pytest.warns(RuntimeWarning, match="newer than the worker's 3.
|
|
107
|
+
with pytest.warns(RuntimeWarning, match="newer than the worker's 3.4.1 compatibility baseline"):
|
|
108
108
|
authored.prepare_stdin(b"program = object()\n")
|
|
109
109
|
|
|
110
110
|
|