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