cmpnd 0.32.0__tar.gz → 0.33.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.32.0 → cmpnd-0.33.0}/PKG-INFO +1 -1
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/callback.py +8 -4
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/commands/traces.py +7 -1
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/models.py +149 -79
- {cmpnd-0.32.0 → cmpnd-0.33.0}/docs/sdk-instrumentation.md +18 -2
- {cmpnd-0.32.0 → cmpnd-0.33.0}/ingest/README.md +15 -1
- {cmpnd-0.32.0 → cmpnd-0.33.0}/ingest/cmpnd_ingest/bodies.py +24 -34
- {cmpnd-0.32.0 → cmpnd-0.33.0}/ingest/cmpnd_ingest/exporter_http.py +20 -6
- cmpnd-0.33.0/ingest/cmpnd_ingest/trace_facts.py +144 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/pyproject.toml +1 -1
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_user_attribution.py +15 -28
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_traces.py +4 -1
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_import_weight.py +1 -0
- cmpnd-0.33.0/tests/test_trace_fact_wire_contract.py +69 -0
- cmpnd-0.33.0/tests/test_trace_facts.py +363 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/uv.lock +1 -1
- {cmpnd-0.32.0 → cmpnd-0.33.0}/.gitignore +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/AGENTS.md +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/CLAUDE.md +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/README.md +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/__init__.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/_assessments.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/_authored_import.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/_deployments.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/_gepa_patch.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/_http.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/_runtime_tools.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/_traces.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/_valsets.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/authored.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/__init__.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/commands/admin.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/commands/assessments.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/commands/deployments.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/commands/evals.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/commands/org.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/commands/programs.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/commands/signatures.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/commands/top.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/commands/valsets.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/shell.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/trace_render.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/tree_help.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/cli/verbs.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/client.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/configuration.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/context.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/dataset_sync.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/datasets.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/decorators.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/deployment.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/deployment_metadata.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/execution.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/exporter.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/helpers.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/identity.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/imaging.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/ir/reflection.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/lm_configuration.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/mcp_servers.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/optimization.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/optimizers/gepa.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/optimizers/gepa_callback.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/optimizers/tracker.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/packaging.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/references.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/retry.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/cmpnd/traces.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/docs/2026-04-02-code-review.md +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/docs/cli.md +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/docs/client.md +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/docs/custom-proposer.md +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/docs/reading-traces.md +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/examples/local_ollama.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/ingest/cmpnd_ingest/protocols.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/ingest/pyproject.toml +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/scripts/capture_review_run.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/scripts/release-stress.sh +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/scripts/require_version_bump.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/scripts/seed_review_run.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/scripts/test-in-linux.sh +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/stubs/dspy/utils/dummies.pyi +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/__init__.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/_deployment_signature_helpers.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/_fake_clock.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/_resolve_refs.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/_traced_run.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/conftest.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/committee_task.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/helpers.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/target_capabilities.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_assessments_cli.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_composite_module.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_deploy_execute.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_deploy_programs.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_optimize_failures.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_parallel_export.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_project_attribution.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_traces_read.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/e2e/test_valsets_cli.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/integration/__init__.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/integration/authored_optimize/judge.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/integration/conftest.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/integration/hosted_optimize_probe.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/integration/test_logs_renders_traces.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/integration/test_unhappy_path.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/load/__init__.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/load/__main__.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/load/drain.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/load/ingest.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/load/rlm_shape.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/load/rlm_size_report.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/load/sweep.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/load/test_rlm_size_report.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/parity/__init__.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/parity/conftest.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/parity/test_health.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/parity/test_trace_streaming.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/parity/test_traces.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/span_reference_fixture.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/stress/__init__.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/stress/_provider.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/stress/_readiness.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/stress/_reliability.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/stress/_result.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/stress/mint_bedrock_token.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/stress/test_deploy_programs.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/stress/test_multi_client_stress.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/stress/test_optimize_client_committee.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/stress/test_optimize_loop.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/stress/test_optimize_stress.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/stress/test_provider_roles.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/stress/test_readiness.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/stress/test_real_provider_twins.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/stress/test_reliability.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/stress/test_reliability_classify.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/stress/test_rlm_wasm.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/stress/test_trace_search.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_adapter_reentrancy.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_assessments_api.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_authored.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_authored_bundle_collisions.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_authored_metric_contract.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_authored_named_models.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_authored_optimize.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_callback.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_admin.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_api_client.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_assessments.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_deployments.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_evals.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_main.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_org.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_parity.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_programs.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_provider_credentials.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_schema_sample.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_shell.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_signatures.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_top.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_traces_search_wire.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_tree_help.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cli/test_valsets.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_client.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_configuration.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_context.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_context_propagation.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_dataset_sync.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_datasets.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_decorators.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_deploy.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_deployment_metadata.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_deployments_read.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_eval.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_eval_example_attribution.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_eval_start.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_eval_trace_grouping.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_execute.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_execute_run_identity.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_exporter.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_exporter_queue.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_exporter_real.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_gepa_output_normalization.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_identity.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_imaging.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_ingest_conformance.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_integration.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_interpreter_callback.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_lm_configuration.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_lm_usage_shapes.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_mcp_tools.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_models.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_optimize.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_optimize_lms.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_optimize_reflection_lm.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_packaging.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_packaging_mcp_tools.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_packaging_program_source.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_poll_until.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_program_lineage_persist.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_project_payload.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_reflector_properties.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_reflector_smoke.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_release_stress_script.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_repro_reactv2_bugs.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_retry.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_rlm_patch.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_run_attribution_paths.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_runtime_tools.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_schemas.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_span_references.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_span_sent_once.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_trace_render.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_traces_read.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_tracked_gepa.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_truncation.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_valsets_api.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/tests/test_xxh64.py +0 -0
- {cmpnd-0.32.0 → cmpnd-0.33.0}/todo.md +0 -0
|
@@ -708,16 +708,16 @@ class _ModuleMixin(_CallbackBase):
|
|
|
708
708
|
if span_type == models.SpanType.RLM:
|
|
709
709
|
trace.streaming = True
|
|
710
710
|
self._streaming_traces[str(trace.trace_id)] = trace.streaming
|
|
711
|
+
trace.seal_start()
|
|
711
712
|
|
|
712
713
|
# Emit an in_progress start row for EVERY root trace, not
|
|
713
714
|
# just streaming/RLM ones, so a non-RLM program is visible the moment
|
|
714
715
|
# it starts (`traces list --status in_progress`, a lookupable id)
|
|
715
716
|
# instead of only appearing at finalize. `trace.streaming` still
|
|
716
717
|
# governs per-span vs batch export below — only the start emission is
|
|
717
|
-
# generalized. The
|
|
718
|
-
#
|
|
719
|
-
# exporter coalesces
|
|
720
|
-
# fast trace pays a single insert, not two.
|
|
718
|
+
# generalized. The start and full body identify the same logical
|
|
719
|
+
# trace, with independent completion and annotation reports. The
|
|
720
|
+
# exporter coalesces them when the full body preserves every fact.
|
|
721
721
|
if self._stream_trace_starts:
|
|
722
722
|
exp = self._current_exporter()
|
|
723
723
|
if exp:
|
|
@@ -953,6 +953,7 @@ class _ModuleMixin(_CallbackBase):
|
|
|
953
953
|
f"Trace collected for eval: tokens={trace.total_tokens}, cost={trace.total_cost:.4f}"
|
|
954
954
|
)
|
|
955
955
|
|
|
956
|
+
trace.seal_completion()
|
|
956
957
|
exp = self._current_exporter()
|
|
957
958
|
if exp:
|
|
958
959
|
if trace.streaming:
|
|
@@ -1542,6 +1543,9 @@ class _LMMixin(_CallbackBase):
|
|
|
1542
1543
|
trace.total_tokens = span.total_tokens or 0
|
|
1543
1544
|
trace.total_cost = span.cost or 0.0
|
|
1544
1545
|
trace.span_count = 1
|
|
1546
|
+
trace.root_span_type = span.span_type.value
|
|
1547
|
+
trace.seal_start()
|
|
1548
|
+
trace.seal_completion()
|
|
1545
1549
|
|
|
1546
1550
|
exp = self._current_exporter()
|
|
1547
1551
|
if exp:
|
|
@@ -8,6 +8,8 @@ import time
|
|
|
8
8
|
from argparse import ArgumentParser, ArgumentTypeError, _SubParsersAction
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
|
+
from cmpnd_ingest.trace_facts import MetadataFact, StringMapChange
|
|
12
|
+
|
|
11
13
|
from ... import _timespec
|
|
12
14
|
from .. import api_client, deprecation, pagination, trace_render
|
|
13
15
|
|
|
@@ -288,7 +290,11 @@ def handle_update(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
288
290
|
it exists for: stamping a correlation id that only became known after the
|
|
289
291
|
run, without disturbing whatever the program recorded at the time.
|
|
290
292
|
"""
|
|
291
|
-
|
|
293
|
+
metadata = dict(args.metadata)
|
|
294
|
+
fact = MetadataFact(metadata=StringMapChange(set_items=tuple(sorted(metadata.items()))))
|
|
295
|
+
return client.patch(
|
|
296
|
+
f"/api/v1/traces/{args.id}/metadata", json={"metadata": metadata, "metadata_fact": fact.to_dict()}
|
|
297
|
+
)
|
|
292
298
|
|
|
293
299
|
|
|
294
300
|
def handle_delete(client: api_client.ApiClient, args: Any) -> Any:
|
|
@@ -11,6 +11,8 @@ from enum import Enum
|
|
|
11
11
|
from typing import Any
|
|
12
12
|
from uuid import UUID, uuid4
|
|
13
13
|
|
|
14
|
+
from cmpnd_ingest.trace_facts import CompletionFact, MetadataFact, StartedFact, StringMapChange
|
|
15
|
+
|
|
14
16
|
from . import identity, imaging
|
|
15
17
|
|
|
16
18
|
# U+0000 (NUL) and lone UTF-16 surrogates are valid JSON, and valid in a Python
|
|
@@ -910,93 +912,161 @@ class TraceBuilder:
|
|
|
910
912
|
# Snapshot project_tags at construction time to avoid reconfigure races in async export
|
|
911
913
|
project_tags: list[str] | None = field(default_factory=configuration._current_project_tags)
|
|
912
914
|
|
|
915
|
+
_started_fact: StartedFact | None = field(default=None, init=False, repr=False)
|
|
916
|
+
_completion_fact: CompletionFact | None = field(default=None, init=False, repr=False)
|
|
917
|
+
_metadata_facts: list[MetadataFact] = field(default_factory=list, init=False, repr=False)
|
|
918
|
+
_sealed_tags: dict[str, str] = field(default_factory=dict, init=False, repr=False)
|
|
919
|
+
_sealed_metadata: dict[str, str] = field(default_factory=dict, init=False, repr=False)
|
|
920
|
+
_sealed_project_tags: tuple[str, ...] = field(default=(), init=False, repr=False)
|
|
921
|
+
|
|
922
|
+
def seal_start(self) -> None:
|
|
923
|
+
"""Retain the first creation snapshot and its independent annotations."""
|
|
924
|
+
if self._started_fact is None:
|
|
925
|
+
self._started_fact = StartedFact.from_payload(self.to_start_dict())
|
|
926
|
+
self.seal_metadata()
|
|
927
|
+
|
|
928
|
+
def seal_metadata(self) -> None:
|
|
929
|
+
"""Seal changes since the previous annotation report, including removals."""
|
|
930
|
+
tags: dict[str, str] = _safe_serialize(self.tags)
|
|
931
|
+
metadata: dict[str, str] = _safe_serialize(self.metadata)
|
|
932
|
+
projects = tuple(self.project_tags or ())
|
|
933
|
+
tag_change = StringMapChange.between(self._sealed_tags, tags)
|
|
934
|
+
metadata_change = StringMapChange.between(self._sealed_metadata, metadata)
|
|
935
|
+
project_change = projects if projects != self._sealed_project_tags else None
|
|
936
|
+
if tag_change is not None or metadata_change is not None or project_change is not None:
|
|
937
|
+
self._metadata_facts.append(MetadataFact(tag_change, metadata_change, project_change))
|
|
938
|
+
self._sealed_tags, self._sealed_metadata, self._sealed_project_tags = tags, metadata, projects
|
|
939
|
+
|
|
940
|
+
def seal_completion(self) -> None:
|
|
941
|
+
"""Seal one new completion report; serialization reuses it unchanged."""
|
|
942
|
+
if self.end_time is None:
|
|
943
|
+
raise ValueError("a completion report requires end_time")
|
|
944
|
+
self.seal_start()
|
|
945
|
+
assert self._started_fact is not None
|
|
946
|
+
start = datetime.fromisoformat(self._started_fact.start_time) if self._started_fact.start_time else None
|
|
947
|
+
duration = self.end_time - start if start else None
|
|
948
|
+
duration_ns = (
|
|
949
|
+
(duration.days * 86400 + duration.seconds) * 1_000_000_000 + duration.microseconds * 1000
|
|
950
|
+
if duration is not None
|
|
951
|
+
else 0
|
|
952
|
+
)
|
|
953
|
+
self._completion_fact = CompletionFact(
|
|
954
|
+
producer_sequence=self._completion_fact.producer_sequence + 1 if self._completion_fact else 1,
|
|
955
|
+
end_time=self.end_time.isoformat(),
|
|
956
|
+
status=self.status.value,
|
|
957
|
+
error_message=_strip_unstorable(self.error_message or ""),
|
|
958
|
+
duration_ns=duration_ns,
|
|
959
|
+
)
|
|
960
|
+
self.seal_metadata()
|
|
961
|
+
|
|
962
|
+
def _with_facts(self, payload: dict[str, Any], *, completion: bool = False, started: bool = True) -> dict[str, Any]:
|
|
963
|
+
if self._started_fact is not None:
|
|
964
|
+
creation = self._started_fact.creation_dict()
|
|
965
|
+
payload.update(creation if started else {key: value for key, value in creation.items() if key in payload})
|
|
966
|
+
if started:
|
|
967
|
+
payload["started_fact"] = self._started_fact.to_dict()
|
|
968
|
+
if completion and self._completion_fact is not None:
|
|
969
|
+
payload["completion_fact"] = self._completion_fact.to_dict()
|
|
970
|
+
if self._started_fact is not None or self._completion_fact is not None or self._metadata_facts:
|
|
971
|
+
payload["metadata_facts"] = [fact.to_dict() for fact in self._metadata_facts]
|
|
972
|
+
return payload
|
|
973
|
+
|
|
913
974
|
def to_api_dict(self) -> dict[str, Any]:
|
|
914
975
|
"""Convert to API-compatible dictionary."""
|
|
915
|
-
return
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
976
|
+
return self._with_facts(
|
|
977
|
+
{
|
|
978
|
+
"trace_id": str(self.trace_id),
|
|
979
|
+
"start_time": self.start_time.isoformat() if self.start_time else None,
|
|
980
|
+
"end_time": self.end_time.isoformat() if self.end_time else None,
|
|
981
|
+
"status": self.status.value,
|
|
982
|
+
"error_message": _strip_unstorable_opt(self.error_message),
|
|
983
|
+
"program_name": self.program_name,
|
|
984
|
+
"program_version": self.program_version,
|
|
985
|
+
"signature_name": self.signature_name,
|
|
986
|
+
"signature_input_fields": self.signature_input_fields,
|
|
987
|
+
"signature_output_fields": self.signature_output_fields,
|
|
988
|
+
"signature_input_field_types": self.signature_input_field_types,
|
|
989
|
+
"signature_output_field_types": self.signature_output_field_types,
|
|
990
|
+
"signature_input_field_schemas": self.signature_input_field_schemas,
|
|
991
|
+
"signature_output_field_schemas": self.signature_output_field_schemas,
|
|
992
|
+
"program_signature_hash": identity.format_signature_hash(self.program_signature_hash),
|
|
993
|
+
"signature_summary": self.signature_summary,
|
|
994
|
+
# The server forwards this blob verbatim into a jsonb column, and its
|
|
995
|
+
# program registration is best-effort (a rejection is only logged), so
|
|
996
|
+
# an un-storable code point here would silently lose the whole
|
|
997
|
+
# component list. Scrub at the source — the only place that can.
|
|
998
|
+
"component_signatures": _safe_serialize(self.component_signatures),
|
|
999
|
+
"compiled_from_run_id": self.compiled_from_run_id,
|
|
1000
|
+
"demo_ids": self.demo_ids,
|
|
1001
|
+
"request_preview": (self.request_preview or "")[:500],
|
|
1002
|
+
"response_preview": (self.response_preview or "")[:500],
|
|
1003
|
+
"eval_run_id": str(self.eval_run_id) if self.eval_run_id else None,
|
|
1004
|
+
"optimization_run_id": (str(self.optimization_run_id) if self.optimization_run_id else None),
|
|
1005
|
+
"adapter_type": self.adapter_type,
|
|
1006
|
+
"tags": _safe_serialize(self.tags),
|
|
1007
|
+
"metadata": _safe_serialize(self.metadata),
|
|
1008
|
+
"project": self.project_tags[0] if self.project_tags else None,
|
|
1009
|
+
"project_tags": self.project_tags if self.project_tags else [],
|
|
1010
|
+
"spans": [s.to_api_dict() for s in self._batch_spans] if self._batch_spans else [],
|
|
1011
|
+
},
|
|
1012
|
+
completion=True,
|
|
1013
|
+
)
|
|
950
1014
|
|
|
951
1015
|
def to_start_dict(self) -> dict[str, Any]:
|
|
952
1016
|
"""Lightweight payload for trace identity, signature and run attribution."""
|
|
953
|
-
return
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1017
|
+
return self._with_facts(
|
|
1018
|
+
{
|
|
1019
|
+
"start_time": self.start_time.isoformat() if self.start_time else None,
|
|
1020
|
+
"program_name": self.program_name,
|
|
1021
|
+
"program_version": self.program_version,
|
|
1022
|
+
"signature_name": self.signature_name,
|
|
1023
|
+
"signature_input_fields": self.signature_input_fields,
|
|
1024
|
+
"signature_output_fields": self.signature_output_fields,
|
|
1025
|
+
"signature_input_field_types": self.signature_input_field_types,
|
|
1026
|
+
"signature_output_field_types": self.signature_output_field_types,
|
|
1027
|
+
"program_signature_hash": identity.format_signature_hash(self.program_signature_hash),
|
|
1028
|
+
"signature_summary": self.signature_summary,
|
|
1029
|
+
"component_signatures": _safe_serialize(self.component_signatures),
|
|
1030
|
+
"root_span_type": self.root_span_type,
|
|
1031
|
+
"compiled_from_run_id": self.compiled_from_run_id,
|
|
1032
|
+
"project": self.project_tags[0] if self.project_tags else None,
|
|
1033
|
+
"project_tags": self.project_tags if self.project_tags else [],
|
|
1034
|
+
# Known at start (the callback attaches them in _create_trace), so
|
|
1035
|
+
# the start row carries them from the first send instead of the
|
|
1036
|
+
# finalize row re-attributing them later.
|
|
1037
|
+
"eval_run_id": str(self.eval_run_id) if self.eval_run_id else None,
|
|
1038
|
+
"optimization_run_id": (str(self.optimization_run_id) if self.optimization_run_id else None),
|
|
1039
|
+
}
|
|
1040
|
+
)
|
|
975
1041
|
|
|
976
1042
|
def to_finalize_dict(self) -> dict[str, Any]:
|
|
977
1043
|
"""Payload for trace finalization — timing, metrics, previews."""
|
|
978
|
-
return
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1044
|
+
return self._with_facts(
|
|
1045
|
+
{
|
|
1046
|
+
"start_time": self.start_time.isoformat() if self.start_time else None,
|
|
1047
|
+
"end_time": self.end_time.isoformat() if self.end_time else None,
|
|
1048
|
+
"status": self.status.value,
|
|
1049
|
+
"error_message": _strip_unstorable_opt(self.error_message),
|
|
1050
|
+
"request_preview": (self.request_preview or "")[:500],
|
|
1051
|
+
"response_preview": (self.response_preview or "")[:500],
|
|
1052
|
+
"total_tokens": self.total_tokens,
|
|
1053
|
+
"prompt_tokens": self.prompt_tokens,
|
|
1054
|
+
"completion_tokens": self.completion_tokens,
|
|
1055
|
+
"total_cost": self.total_cost,
|
|
1056
|
+
"span_count": self.span_count,
|
|
1057
|
+
"adapter_type": self.adapter_type,
|
|
1058
|
+
"compiled_from_run_id": self.compiled_from_run_id,
|
|
1059
|
+
"demo_ids": self.demo_ids,
|
|
1060
|
+
"eval_run_id": str(self.eval_run_id) if self.eval_run_id else None,
|
|
1061
|
+
"optimization_run_id": (str(self.optimization_run_id) if self.optimization_run_id else None),
|
|
1062
|
+
"tags": _safe_serialize(self.tags),
|
|
1063
|
+
"metadata": _safe_serialize(self.metadata),
|
|
1064
|
+
"project": self.project_tags[0] if self.project_tags else None,
|
|
1065
|
+
"project_tags": self.project_tags if self.project_tags else [],
|
|
1066
|
+
},
|
|
1067
|
+
completion=True,
|
|
1068
|
+
started=False,
|
|
1069
|
+
)
|
|
1000
1070
|
|
|
1001
1071
|
|
|
1002
1072
|
class EvalRunStatus(str, Enum):
|
|
@@ -583,10 +583,26 @@ Its first payload carries the optimization id, plus the active eval id for a
|
|
|
583
583
|
metric call. A reflection call outside evaluation has no eval id. Manual
|
|
584
584
|
decorators and `start_span` create spans rather than another automatic trace.
|
|
585
585
|
|
|
586
|
+
The callback seals `started_fact` after it sets the root type, before the
|
|
587
|
+
module executes. It keeps that snapshot even when start emission is disabled.
|
|
588
|
+
The completion callback seals one independently identified `completion_fact`
|
|
589
|
+
with a positive producer sequence. Repeated serialization and HTTP retries
|
|
590
|
+
reuse both objects. A standalone optimizer LM trace seals its creation and
|
|
591
|
+
completion before its first export. The late adapter type stays in the flat
|
|
592
|
+
compatibility fields and ended adapter spans.
|
|
593
|
+
|
|
594
|
+
Each metadata change gets its own `metadata_facts` entry with explicit map
|
|
595
|
+
assignments and removals. The callback seals initial annotations at creation
|
|
596
|
+
and later changes at completion. Every modern body includes the array, even
|
|
597
|
+
when it is empty, so a retry cannot reapply its flat metadata view as a legacy
|
|
598
|
+
mutation. A streaming finalize carries completion and metadata facts. A full
|
|
599
|
+
body also carries its original started fact. `cmpnd traces update` sends one
|
|
600
|
+
independent `metadata_fact` alongside its existing metadata PATCH envelope.
|
|
601
|
+
|
|
586
602
|
Two things keep the extra start cheap and correct:
|
|
587
603
|
|
|
588
|
-
- **
|
|
589
|
-
- **Coalescing.** When a trace's
|
|
604
|
+
- **Shared identity.** The start and full body identify the same logical trace and retain the same creation fields. Independent completion reports update its lifecycle without changing the started fact. A trace without a completion report keeps its open execution state until the server infers abandonment.
|
|
605
|
+
- **Coalescing.** When a trace's start and full body land in the same flush, the exporter suppresses the start only if the full body preserves its sealed start and every independent metadata fact. A start in an earlier flush still ships. Set `stream_trace_starts=False` to emit the full body only at completion.
|
|
590
606
|
|
|
591
607
|
---
|
|
592
608
|
|
|
@@ -44,12 +44,26 @@ Nothing nominal. `BatchExporter` takes a config and record objects described by
|
|
|
44
44
|
satisfy them by shape, and so may any other producer's records. That is the
|
|
45
45
|
import boundary: the transport knows how to ship a record, not how one is built.
|
|
46
46
|
|
|
47
|
-
A producer whose records are **already in wire shape** — plain dicts from a
|
|
47
|
+
A producer whose records are **already in wire shape** — plain dicts from a kernel, or
|
|
48
48
|
from its own record writer — wraps them in `bodies.py`: `TraceBody`, `SpanBody`,
|
|
49
49
|
`OptimizationRunBody` satisfy those same protocols over a dict it hands over. One place
|
|
50
50
|
holds that mapping, so a third producer does not re-derive it from the transport's
|
|
51
51
|
internals.
|
|
52
52
|
|
|
53
|
+
## Sealing trace facts before delivery
|
|
54
|
+
|
|
55
|
+
The exporter snapshots a trace when it enters the queue. `TraceBody` also isolates
|
|
56
|
+
nested values at construction and returns fresh copies, so a caller's later edits
|
|
57
|
+
cannot change a pending request or its replay identity. Existing structural records
|
|
58
|
+
and flat legacy payloads remain accepted.
|
|
59
|
+
|
|
60
|
+
Producers can use `trace_facts.py` without DSPy or HTTP imports. `StartedFact` seals
|
|
61
|
+
creation fields. `CompletionFact` identifies one report with a positive producer
|
|
62
|
+
sequence and a duration in nanoseconds. `MetadataFact` carries independent string-map
|
|
63
|
+
assignments and removals or a project-tag replacement. Serializers reuse these values
|
|
64
|
+
without minting another ID. A start POST is coalesced into a full body only when that
|
|
65
|
+
body retains its identified started and metadata evidence.
|
|
66
|
+
|
|
53
67
|
## Writing a run to an id you were handed
|
|
54
68
|
|
|
55
69
|
`export_optimization_start` reserves an id and then writes the record to it, which is
|
|
@@ -1,45 +1,35 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
**These are adapters, not a second protocol.** Widening `protocols.py` to `dict | Record`
|
|
12
|
-
was the alternative, and it was rejected: every member the transport reads would grow a
|
|
13
|
-
branch, the SDK's typed path would start paying for a shape it never has, and a producer
|
|
14
|
-
whose records are neither — the third one the contract anticipates — would still be
|
|
15
|
-
outside. A structural `Protocol` is already the widest possible door; what was missing was
|
|
16
|
-
a body-shaped key for it, in one place, so a third producer does not re-derive the mapping
|
|
17
|
-
from the transport's internals.
|
|
18
|
-
|
|
19
|
-
Each class wraps **one** body and answers every serialization the matching protocol
|
|
20
|
-
declares with it. That is deliberate rather than lax: a producer holding a dict knows
|
|
21
|
-
which of the contract's routes that dict is a body for, and pairs it with the matching
|
|
22
|
-
`export_*` call. The transport asks for exactly one serialization per call, so the pairing
|
|
23
|
-
is what decides which — the same freedom, and the same responsibility, a producer has when
|
|
24
|
-
it writes its own record class.
|
|
25
|
-
|
|
26
|
-
Every serialization returns a **shallow copy**, because the transport writes into what it
|
|
27
|
-
is given: it stamps the run id into a run body, and `_flush_batch` splices buffered spans
|
|
28
|
-
into a trace body. A producer's dict is its own.
|
|
1
|
+
"""Adapters expose already serialized bodies through the producer protocols.
|
|
2
|
+
|
|
3
|
+
Each adapter answers the serialization methods for one body. The producer pairs it
|
|
4
|
+
with the matching export call, so independent workers can send wire records without
|
|
5
|
+
importing DSPy. The transport does not create another protocol for dictionary bodies.
|
|
6
|
+
|
|
7
|
+
TraceBody seals nested values at construction and returns independent copies. A queued
|
|
8
|
+
trace and its replay identities survive later changes to the producer's input or a
|
|
9
|
+
returned serialization. Other adapters copy their outer dictionary before transport
|
|
10
|
+
stamping, and optimization bodies keep the producer's delivery acknowledgement callback.
|
|
29
11
|
"""
|
|
30
12
|
|
|
31
13
|
from __future__ import annotations
|
|
32
14
|
|
|
33
15
|
from collections.abc import Callable
|
|
16
|
+
from copy import deepcopy
|
|
34
17
|
from dataclasses import dataclass, field
|
|
35
18
|
from typing import TYPE_CHECKING, Any
|
|
36
19
|
|
|
37
20
|
|
|
38
|
-
@dataclass(frozen=True)
|
|
21
|
+
@dataclass(frozen=True, init=False)
|
|
39
22
|
class TraceBody:
|
|
40
23
|
"""A trace a producer already holds as the body of one of the trace routes."""
|
|
41
24
|
|
|
42
|
-
|
|
25
|
+
_payload: dict[str, Any]
|
|
26
|
+
|
|
27
|
+
def __init__(self, payload: dict[str, Any]) -> None:
|
|
28
|
+
object.__setattr__(self, "_payload", deepcopy(payload))
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def payload(self) -> dict[str, Any]:
|
|
32
|
+
return deepcopy(self._payload)
|
|
43
33
|
|
|
44
34
|
@property
|
|
45
35
|
def trace_id(self) -> str:
|
|
@@ -50,16 +40,16 @@ class TraceBody:
|
|
|
50
40
|
post needs no such member: it addresses `/traces`, and the id it is keyed on is
|
|
51
41
|
the one in the body.)
|
|
52
42
|
"""
|
|
53
|
-
return str(self.
|
|
43
|
+
return str(self._payload["trace_id"])
|
|
54
44
|
|
|
55
45
|
def to_api_dict(self) -> dict[str, Any]:
|
|
56
|
-
return
|
|
46
|
+
return deepcopy(self._payload)
|
|
57
47
|
|
|
58
48
|
def to_start_dict(self) -> dict[str, Any]:
|
|
59
|
-
return
|
|
49
|
+
return deepcopy(self._payload)
|
|
60
50
|
|
|
61
51
|
def to_finalize_dict(self) -> dict[str, Any]:
|
|
62
|
-
return
|
|
52
|
+
return deepcopy(self._payload)
|
|
63
53
|
|
|
64
54
|
|
|
65
55
|
@dataclass(frozen=True)
|
|
@@ -33,7 +33,7 @@ from typing import Any, cast
|
|
|
33
33
|
import httpx
|
|
34
34
|
|
|
35
35
|
from . import request_log, retry
|
|
36
|
-
from .bodies import EncodedTraceBody
|
|
36
|
+
from .bodies import EncodedTraceBody, TraceBody
|
|
37
37
|
from .protocols import (
|
|
38
38
|
EvalRunRecord,
|
|
39
39
|
ExporterConfig,
|
|
@@ -557,7 +557,7 @@ class BatchExporter:
|
|
|
557
557
|
|
|
558
558
|
def export_trace(self, trace: TraceRecord) -> None:
|
|
559
559
|
"""Queue a trace for export (non-blocking)."""
|
|
560
|
-
self._enqueue(ExportItem("trace", trace, time.time()))
|
|
560
|
+
self._enqueue(ExportItem("trace", TraceBody(trace.to_api_dict()), time.time()))
|
|
561
561
|
|
|
562
562
|
def export_encoded_trace(self, trace: EncodedTraceBody) -> None:
|
|
563
563
|
"""Queue a compressed finished trace for export (non-blocking).
|
|
@@ -594,11 +594,13 @@ class BatchExporter:
|
|
|
594
594
|
|
|
595
595
|
def export_trace_start(self, trace: TraceRecord) -> None:
|
|
596
596
|
"""Queue a trace start for export (non-blocking)."""
|
|
597
|
-
|
|
597
|
+
body = {**trace.to_start_dict(), "trace_id": str(trace.trace_id)}
|
|
598
|
+
self._enqueue(ExportItem("trace_start", TraceBody(body), time.time()))
|
|
598
599
|
|
|
599
600
|
def export_trace_finalize(self, trace: TraceRecord) -> None:
|
|
600
601
|
"""Queue a trace finalization for export (non-blocking)."""
|
|
601
|
-
|
|
602
|
+
body = {**trace.to_finalize_dict(), "trace_id": str(trace.trace_id)}
|
|
603
|
+
self._enqueue(ExportItem("trace_finalize", TraceBody(body), time.time()))
|
|
602
604
|
|
|
603
605
|
def export_optimization_start(self, optimization_run: OptimizationRunRecord) -> str:
|
|
604
606
|
"""Create the optimization record server-side and return the
|
|
@@ -1197,8 +1199,20 @@ class BatchExporter:
|
|
|
1197
1199
|
# whose finalize lands in a LATER flush still ships, which is the whole
|
|
1198
1200
|
# point: the trace is visible while it runs.
|
|
1199
1201
|
if trace_starts and traces_to_send:
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
+
|
|
1203
|
+
def start_is_in_body(trace_id: str, start: dict[str, Any]) -> bool:
|
|
1204
|
+
for full in traces_to_send:
|
|
1205
|
+
if str(full.get("trace_id")) != trace_id:
|
|
1206
|
+
continue
|
|
1207
|
+
if not all(fact in full.get("metadata_facts", []) for fact in start.get("metadata_facts", [])):
|
|
1208
|
+
continue
|
|
1209
|
+
if "started_fact" not in start:
|
|
1210
|
+
return True
|
|
1211
|
+
if full.get("started_fact") == start["started_fact"]:
|
|
1212
|
+
return True
|
|
1213
|
+
return False
|
|
1214
|
+
|
|
1215
|
+
trace_starts = [pair for pair in trace_starts if not start_is_in_body(*pair)]
|
|
1202
1216
|
|
|
1203
1217
|
# A span is sent exactly once. When its trace ships in this same flush it
|
|
1204
1218
|
# rides that trace's body and is taken off the spans route; otherwise the
|