cmpnd 0.27.3__tar.gz → 0.27.4__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.27.3 → cmpnd-0.27.4}/PKG-INFO +1 -1
- cmpnd-0.27.4/cmpnd/_runtime_tools.py +39 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/ir/reflection.py +12 -3
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/packaging.py +46 -16
- {cmpnd-0.27.3 → cmpnd-0.27.4}/docs/cli.md +8 -6
- {cmpnd-0.27.3 → cmpnd-0.27.4}/docs/sdk-instrumentation.md +15 -1
- {cmpnd-0.27.3 → cmpnd-0.27.4}/pyproject.toml +1 -1
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_exporter.py +5 -3
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_packaging_mcp_tools.py +103 -1
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_reflector_properties.py +55 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_reflector_smoke.py +14 -1
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_retry.py +1 -0
- cmpnd-0.27.4/tests/test_runtime_tools.py +47 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/uv.lock +1 -1
- {cmpnd-0.27.3 → cmpnd-0.27.4}/.gitignore +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/AGENTS.md +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/CLAUDE.md +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/README.md +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/__init__.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/_assessments.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/_authored_import.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/_gepa_patch.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/_http.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/_traces.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/_valsets.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/authored.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/callback.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/__init__.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/commands/admin.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/commands/assessments.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/commands/deployments.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/commands/evals.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/commands/org.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/commands/programs.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/commands/provider_credentials.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/commands/signatures.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/commands/top.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/commands/traces.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/commands/valsets.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/shell.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/trace_render.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/tree_help.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/cli/verbs.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/client.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/configuration.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/context.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/dataset_sync.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/datasets.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/decorators.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/deployment.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/execution.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/exporter.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/helpers.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/identity.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/imaging.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/lm_configuration.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/mcp_servers.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/models.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/optimization.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/optimizers/gepa.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/optimizers/gepa_callback.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/optimizers/tracker.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/retry.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/cmpnd/traces.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/docs/2026-04-02-code-review.md +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/docs/client.md +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/docs/custom-proposer.md +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/docs/reading-traces.md +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/examples/local_ollama.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/ingest/README.md +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/ingest/cmpnd_ingest/bodies.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/ingest/cmpnd_ingest/exporter_http.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/ingest/cmpnd_ingest/protocols.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/ingest/pyproject.toml +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/scripts/capture_review_run.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/scripts/release-stress.sh +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/scripts/require_version_bump.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/scripts/seed_review_run.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/scripts/test-in-linux.sh +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/__init__.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/_fake_clock.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/conftest.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/committee_task.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/helpers.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/target_capabilities.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_assessments_cli.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_composite_module.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_deploy_execute.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_deploy_programs.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_optimize_failures.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_parallel_export.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_project_attribution.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_traces_read.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_user_attribution.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/e2e/test_valsets_cli.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/integration/__init__.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/integration/authored_optimize/judge.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/integration/conftest.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/integration/hosted_optimize_probe.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/integration/test_logs_renders_traces.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/integration/test_unhappy_path.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/load/__init__.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/load/__main__.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/load/drain.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/load/ingest.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/load/rlm_shape.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/load/sweep.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/parity/__init__.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/parity/conftest.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/parity/test_health.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/parity/test_trace_streaming.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/parity/test_traces.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/stress/__init__.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/stress/_provider.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/stress/_readiness.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/stress/_reliability.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/stress/_result.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/stress/mint_bedrock_token.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/stress/test_deploy_programs.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/stress/test_multi_client_stress.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/stress/test_optimize_client_committee.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/stress/test_optimize_loop.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/stress/test_optimize_loop_wasm.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/stress/test_optimize_stress.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/stress/test_provider_roles.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/stress/test_readiness.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/stress/test_real_provider_twins.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/stress/test_reliability.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/stress/test_reliability_classify.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/stress/test_rlm_wasm.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/stress/test_trace_search.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_assessments_api.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_authored.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_authored_bundle_collisions.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_authored_metric_contract.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_authored_named_models.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_authored_optimize.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_callback.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_admin.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_api_client.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_assessments.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_deployments.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_evals.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_main.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_org.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_parity.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_programs.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_provider_credentials.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_schema_sample.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_shell.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_signatures.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_top.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_traces.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_traces_search_wire.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_tree_help.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cli/test_valsets.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_client.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_configuration.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_context.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_context_propagation.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_dataset_sync.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_datasets.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_decorators.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_deploy.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_eval.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_eval_example_attribution.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_eval_start.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_eval_trace_grouping.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_execute.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_exporter_queue.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_exporter_real.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_identity.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_imaging.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_import_weight.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_ingest_conformance.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_integration.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_interpreter_callback.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_lm_configuration.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_lm_usage_shapes.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_mcp_tools.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_models.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_optimize.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_optimize_lms.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_optimize_reflection_lm.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_packaging.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_packaging_program_source.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_poll_until.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_program_lineage_persist.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_project_payload.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_release_stress_script.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_repro_reactv2_bugs.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_rlm_patch.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_schemas.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_span_sent_once.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_trace_render.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_traces_read.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_tracked_gepa.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_truncation.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_valsets_api.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/tests/test_xxh64.py +0 -0
- {cmpnd-0.27.3 → cmpnd-0.27.4}/todo.md +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Private provenance for tools that CMPND adds while a program runs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, TypeVar
|
|
6
|
+
|
|
7
|
+
_RUNTIME_TOOL_MARKER = "_cmpnd_runtime_tool"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _RuntimeToolProvenance:
|
|
11
|
+
"""An in-process token that keeps its identity through DSPy module copies."""
|
|
12
|
+
|
|
13
|
+
def __copy__(self) -> _RuntimeToolProvenance:
|
|
14
|
+
return self
|
|
15
|
+
|
|
16
|
+
def __deepcopy__(self, memo: dict[int, Any]) -> _RuntimeToolProvenance:
|
|
17
|
+
del memo
|
|
18
|
+
return self
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
_RUNTIME_TOOL_PROVENANCE = _RuntimeToolProvenance()
|
|
22
|
+
_T = TypeVar("_T")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def mark_runtime_tool(tool: _T) -> _T:
|
|
26
|
+
"""Mark a platform-created tool without trusting a caller-chosen boolean."""
|
|
27
|
+
setattr(tool, _RUNTIME_TOOL_MARKER, _RUNTIME_TOOL_PROVENANCE)
|
|
28
|
+
function = getattr(tool, "func", None)
|
|
29
|
+
if function is not None:
|
|
30
|
+
setattr(function, _RUNTIME_TOOL_MARKER, _RUNTIME_TOOL_PROVENANCE)
|
|
31
|
+
return tool
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def is_runtime_tool(tool: object) -> bool:
|
|
35
|
+
"""Return whether this process marked the tool or its wrapped callable."""
|
|
36
|
+
if getattr(tool, _RUNTIME_TOOL_MARKER, None) is _RUNTIME_TOOL_PROVENANCE:
|
|
37
|
+
return True
|
|
38
|
+
function = getattr(tool, "func", None)
|
|
39
|
+
return getattr(function, _RUNTIME_TOOL_MARKER, None) is _RUNTIME_TOOL_PROVENANCE
|
|
@@ -14,6 +14,7 @@ from typing import Any
|
|
|
14
14
|
|
|
15
15
|
import dspy
|
|
16
16
|
|
|
17
|
+
from .._runtime_tools import is_runtime_tool
|
|
17
18
|
from . import program, types
|
|
18
19
|
|
|
19
20
|
_PRIM_BY_PYTYPE: dict[Any, types.PrimKind] = {
|
|
@@ -123,9 +124,17 @@ def reflect(m: Any) -> program.Module:
|
|
|
123
124
|
react_cls = _opt("ReAct")
|
|
124
125
|
if react_cls is not None and isinstance(m, react_cls):
|
|
125
126
|
tools = getattr(m, "tools", None) or {}
|
|
126
|
-
# Exclude DSPy
|
|
127
|
-
#
|
|
128
|
-
|
|
127
|
+
# Exclude runtime control tools. DSPy supplies `finish`; a hosted worker may
|
|
128
|
+
# add marked platform tools that are absent from the authored program. Check
|
|
129
|
+
# the explicit marker rather than a name, so a user's similarly named tool
|
|
130
|
+
# remains part of the program's identity.
|
|
131
|
+
names = tuple(
|
|
132
|
+
sorted(
|
|
133
|
+
name
|
|
134
|
+
for name, tool in tools.items()
|
|
135
|
+
if name != "finish" and not (name.startswith("cmpnd_") and is_runtime_tool(tool))
|
|
136
|
+
)
|
|
137
|
+
)
|
|
129
138
|
return program.ReAct(signature=project_sig(m.signature), tools=names)
|
|
130
139
|
|
|
131
140
|
bestofn_cls = _opt("BestOfN")
|
|
@@ -30,6 +30,8 @@ from typing import TYPE_CHECKING, Any
|
|
|
30
30
|
|
|
31
31
|
import typing_extensions
|
|
32
32
|
|
|
33
|
+
from ._runtime_tools import is_runtime_tool
|
|
34
|
+
|
|
33
35
|
if TYPE_CHECKING:
|
|
34
36
|
import dspy
|
|
35
37
|
|
|
@@ -802,6 +804,12 @@ def declared_module_tools(module: dspy.Module) -> dict[str, typing.Any]:
|
|
|
802
804
|
continue
|
|
803
805
|
pending.extend(vars(member).values())
|
|
804
806
|
for name, tool in module_tools(member).items():
|
|
807
|
+
if name.startswith("cmpnd_") and is_runtime_tool(tool):
|
|
808
|
+
continue
|
|
809
|
+
if name.startswith("cmpnd_"):
|
|
810
|
+
raise ValueError(
|
|
811
|
+
f"tool name {name!r} uses the reserved cmpnd_ prefix; rename the tool before deploying"
|
|
812
|
+
)
|
|
805
813
|
if is_bare_mcp_tool(tool):
|
|
806
814
|
_resolve_an_mcp_tool(name, tool, {})
|
|
807
815
|
previous = collected.get(name)
|
|
@@ -818,6 +826,8 @@ def declared_module_tools(module: dspy.Module) -> dict[str, typing.Any]:
|
|
|
818
826
|
def generate_program_source(
|
|
819
827
|
module: dspy.Module,
|
|
820
828
|
mcp_tools: Mapping[tuple[str, str], typing.Any] | None = None,
|
|
829
|
+
*,
|
|
830
|
+
_declarations: list[dict[str, typing.Any]] | None = None,
|
|
821
831
|
) -> str:
|
|
822
832
|
"""Return raw Python source for module.
|
|
823
833
|
|
|
@@ -968,6 +978,12 @@ def generate_program_source(
|
|
|
968
978
|
from . import mcp_servers
|
|
969
979
|
|
|
970
980
|
mcp_bindings: list[tuple[Any, str]] = []
|
|
981
|
+
|
|
982
|
+
def mcp_tool_expression(route: str, alias: str, *, wrap: bool = False) -> str:
|
|
983
|
+
if wrap or alias != route:
|
|
984
|
+
return f"dspy.Tool({route}, name={alias!r})"
|
|
985
|
+
return route
|
|
986
|
+
|
|
971
987
|
for name, tool in tools_dict.items():
|
|
972
988
|
if name == "finish":
|
|
973
989
|
continue
|
|
@@ -977,18 +993,22 @@ def generate_program_source(
|
|
|
977
993
|
# instead of a body — see [_generate_mcp_tool_stub]. The `def`'s name is
|
|
978
994
|
# the MCP tool's own, because the declaration names the tool and the
|
|
979
995
|
# worker maps that name to the token it holds.
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
996
|
+
reference = mcp_servers.mcp_tool_reference(tool)
|
|
997
|
+
route = next((route for declared, route in mcp_bindings if declared == reference), None)
|
|
998
|
+
if route is None:
|
|
999
|
+
signature = mcp_servers.synthesized_signature(pinned)
|
|
1000
|
+
route = signature.name
|
|
1001
|
+
lines.append(_generate_mcp_tool_stub(pinned, signature))
|
|
1002
|
+
mcp_bindings.append((reference, route))
|
|
1003
|
+
mcp_declarations.append(
|
|
1004
|
+
{
|
|
1005
|
+
"tool": route,
|
|
1006
|
+
"server": pinned.server_url,
|
|
1007
|
+
"schema_hash": pinned.schema_hash,
|
|
1008
|
+
"parameters": signature.parameter_names,
|
|
1009
|
+
}
|
|
1010
|
+
)
|
|
1011
|
+
tool_names.append(mcp_tool_expression(route, name))
|
|
992
1012
|
continue
|
|
993
1013
|
func = getattr(tool, "func", None)
|
|
994
1014
|
if func is not None:
|
|
@@ -1024,9 +1044,10 @@ def generate_program_source(
|
|
|
1024
1044
|
ancestors = ancestors | {id(value)}
|
|
1025
1045
|
reference = mcp_servers.mcp_tool_reference(value)
|
|
1026
1046
|
if reference is not None:
|
|
1027
|
-
for declared,
|
|
1047
|
+
for declared, route in mcp_bindings:
|
|
1028
1048
|
if reference == declared:
|
|
1029
|
-
|
|
1049
|
+
alias = str(getattr(value, "name", route))
|
|
1050
|
+
return mcp_tool_expression(route, alias, wrap=True)
|
|
1030
1051
|
raise ValueError(
|
|
1031
1052
|
"MCP tool binding is not declared by this program; deploy the authored source file"
|
|
1032
1053
|
)
|
|
@@ -1178,6 +1199,8 @@ def generate_program_source(
|
|
|
1178
1199
|
# decide whether an empty list means "none" or "not resolved".
|
|
1179
1200
|
if mcp_declarations:
|
|
1180
1201
|
lines.append(f"{MCP_TOOLS_DECLARATION} = {mcp_declarations!r}")
|
|
1202
|
+
if _declarations is not None:
|
|
1203
|
+
_declarations.extend(mcp_declarations)
|
|
1181
1204
|
|
|
1182
1205
|
return "\n\n".join(lines)
|
|
1183
1206
|
|
|
@@ -1346,7 +1369,12 @@ def generate_source_zip(
|
|
|
1346
1369
|
"""
|
|
1347
1370
|
import dspy
|
|
1348
1371
|
|
|
1349
|
-
|
|
1372
|
+
tool_declarations: list[dict[str, typing.Any]] = []
|
|
1373
|
+
program_source = generate_program_source(
|
|
1374
|
+
module,
|
|
1375
|
+
mcp_tools=mcp_tools,
|
|
1376
|
+
_declarations=tool_declarations,
|
|
1377
|
+
)
|
|
1350
1378
|
|
|
1351
1379
|
metric_filename = None
|
|
1352
1380
|
metric_source = None
|
|
@@ -1359,7 +1387,7 @@ def generate_source_zip(
|
|
|
1359
1387
|
|
|
1360
1388
|
lm = _extract_lm_object(module)
|
|
1361
1389
|
declarations = lm_configuration.resolve({"default-lm": lm} if lm is not None else {}, require_all=False)
|
|
1362
|
-
manifest = {
|
|
1390
|
+
manifest: dict[str, typing.Any] = {
|
|
1363
1391
|
"lm_capabilities": declarations,
|
|
1364
1392
|
"dependency_versions": {
|
|
1365
1393
|
"python": platform.python_version(),
|
|
@@ -1368,6 +1396,8 @@ def generate_source_zip(
|
|
|
1368
1396
|
"program": "program_source.py",
|
|
1369
1397
|
"metric": metric_filename,
|
|
1370
1398
|
}
|
|
1399
|
+
if tool_declarations:
|
|
1400
|
+
manifest["tools"] = tool_declarations
|
|
1371
1401
|
|
|
1372
1402
|
buf = io.BytesIO()
|
|
1373
1403
|
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
@@ -282,12 +282,14 @@ immediately, because the credential goes with it. The confirmation says so.
|
|
|
282
282
|
A program built with `cmpnd.mcp_tools` deploys against exactly this registry.
|
|
283
283
|
`cmpnd deploy` resolves each of the program's tool references against it before
|
|
284
284
|
packaging anything — one listing read plus one read per server, and none at all
|
|
285
|
-
for a program with no MCP tools — so the bundle
|
|
286
|
-
|
|
287
|
-
registry
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
285
|
+
for a program with no MCP tools — so the bundle records declarations built from the
|
|
286
|
+
current reduced schema and its hash. Serve checks that hash against the current
|
|
287
|
+
registry at deploy time. mcp-tools validates each call against the current stored
|
|
288
|
+
schema, so a refresh can change call validation after a program was built. An
|
|
289
|
+
unregistered server and a tool the registry does not list are both refused at the
|
|
290
|
+
terminal, naming the tool, the server and the command that fixes it; the platform
|
|
291
|
+
refuses the same two authoritatively, plus a tool that is registered but not
|
|
292
|
+
allow-listed and a schema that has changed since the program was built. See
|
|
291
293
|
[`sdk-instrumentation.md`](sdk-instrumentation.md) for what the bundle carries in
|
|
292
294
|
place of a tool's body.
|
|
293
295
|
|
|
@@ -401,7 +401,8 @@ def web_search_exa(query: str, numResults: float | None = None) -> dict:
|
|
|
401
401
|
|
|
402
402
|
|
|
403
403
|
__cmpnd_mcp_tools__ = [{'tool': 'web_search_exa', 'server': 'https://mcp.exa.ai/mcp',
|
|
404
|
-
'schema_hash': '…',
|
|
404
|
+
'schema_hash': '…',
|
|
405
|
+
'parameters': ['query', 'numResults']}]
|
|
405
406
|
```
|
|
406
407
|
|
|
407
408
|
The body is a refusal because the deployed program runs in the kernel, which has no MCP
|
|
@@ -425,6 +426,13 @@ annotation at all for a type name the table does not carry. A property name that
|
|
|
425
426
|
Python identifier, or is a keyword, refuses the deploy — it is never mangled, because the
|
|
426
427
|
name has to reach the MCP server unchanged.
|
|
427
428
|
|
|
429
|
+
The SDK uses the registry's pinned, secret-free `input_schema` to construct the generated
|
|
430
|
+
function signature. The source marker and generated bundle manifest retain `schema_hash`
|
|
431
|
+
and `parameters`. Serve checks the hash against the registry at deploy time, and the
|
|
432
|
+
worker uses the parameters for positional binding. Runtime schema inspection reads the
|
|
433
|
+
current stored schema through credentialed-egress and mcp-tools, so it does not rely on a
|
|
434
|
+
schema copy in the program bundle.
|
|
435
|
+
|
|
428
436
|
### Direct local MCP discovery supports two client generations
|
|
429
437
|
|
|
430
438
|
When registry lookup falls back to a direct local connection, the SDK lists tools
|
|
@@ -622,3 +630,9 @@ The WASM kernel calls tools through a typed worker import. The worker holds the
|
|
|
622
630
|
scoped tokens and returns the MCP result document. Optional arguments omitted by
|
|
623
631
|
the program remain omitted. ReAct's tool callbacks produce the application span;
|
|
624
632
|
the worker adds transport measurements to that span using its correlation ID.
|
|
633
|
+
|
|
634
|
+
The worker reserves the `cmpnd_` prefix for platform tools. Deployment refuses an
|
|
635
|
+
authored tool that uses this prefix and asks the author to rename it. Platform tools
|
|
636
|
+
that the worker adds while a program runs carry a private, copy-stable provenance token.
|
|
637
|
+
Only tools carrying that token stay out of the program's identity and generated bundle.
|
|
638
|
+
Setting a similarly named attribute does not grant platform provenance.
|
|
@@ -5,7 +5,7 @@ name = "cmpnd"
|
|
|
5
5
|
# it's new) — no git tags. Bump with `uv version --bump {minor,patch}` or by
|
|
6
6
|
# hand. Runtime `cmpnd.__version__` reads installed metadata, so it tracks
|
|
7
7
|
# this automatically. See docs/plans/2026-07-02-sdk-pypi-tag-free-publish.md.
|
|
8
|
-
version = "0.27.
|
|
8
|
+
version = "0.27.4"
|
|
9
9
|
description = "DSPy observability and deployment SDK for cmpnd"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.10"
|
|
@@ -13,6 +13,7 @@ from typing import Any
|
|
|
13
13
|
from uuid import uuid4
|
|
14
14
|
|
|
15
15
|
import httpx
|
|
16
|
+
import pytest
|
|
16
17
|
|
|
17
18
|
from cmpnd import retry
|
|
18
19
|
from cmpnd.configuration import Config
|
|
@@ -1007,9 +1008,10 @@ def test_data_send_retry_exhausted_counts_error():
|
|
|
1007
1008
|
assert exporter._dropped_count == 0
|
|
1008
1009
|
|
|
1009
1010
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1011
|
+
@pytest.mark.parametrize("status", [422, 500])
|
|
1012
|
+
def test_data_send_does_not_retry_permanent_error(status: int):
|
|
1013
|
+
"""The exporter counts a permanent error once and does not retry it."""
|
|
1014
|
+
capture = SequenceCapture([status])
|
|
1013
1015
|
exporter = _make_seq_exporter(capture)
|
|
1014
1016
|
|
|
1015
1017
|
exporter._flush_batch([ExportItem(item_type="trace", data=_make_trace(), timestamp=0.0)])
|
|
@@ -21,6 +21,9 @@ from __future__ import annotations
|
|
|
21
21
|
|
|
22
22
|
import ast
|
|
23
23
|
import functools
|
|
24
|
+
import io
|
|
25
|
+
import json
|
|
26
|
+
import zipfile
|
|
24
27
|
from typing import Any
|
|
25
28
|
|
|
26
29
|
import pytest
|
|
@@ -28,7 +31,8 @@ import pytest
|
|
|
28
31
|
dspy = pytest.importorskip("dspy")
|
|
29
32
|
|
|
30
33
|
from cmpnd import mcp_servers # noqa: E402
|
|
31
|
-
from cmpnd.
|
|
34
|
+
from cmpnd._runtime_tools import mark_runtime_tool # noqa: E402
|
|
35
|
+
from cmpnd.packaging import declared_module_tools, generate_program_source, generate_source_zip # noqa: E402
|
|
32
36
|
|
|
33
37
|
global_search_tool: Any = None
|
|
34
38
|
global_search_tools: Any = None
|
|
@@ -132,6 +136,7 @@ class TestTheGeneratedStub:
|
|
|
132
136
|
assert "'tool': 'web_search_exa'" in source
|
|
133
137
|
assert f"'server': {_EXA_SERVER!r}" in source
|
|
134
138
|
assert f"'schema_hash': {'c' * 64!r}" in source
|
|
139
|
+
assert "'input_schema'" not in source
|
|
135
140
|
assert "'parameters': ['query', 'numResults']" in source
|
|
136
141
|
|
|
137
142
|
def test_the_declared_parameters_are_the_defs_own_in_order(self, monkeypatch):
|
|
@@ -145,6 +150,65 @@ class TestTheGeneratedStub:
|
|
|
145
150
|
declared = namespace["__cmpnd_mcp_tools__"][0]
|
|
146
151
|
stub = namespace[declared["tool"]]
|
|
147
152
|
assert list(inspect.signature(stub).parameters) == declared["parameters"]
|
|
153
|
+
assert "input_schema" not in declared
|
|
154
|
+
|
|
155
|
+
def test_the_zip_manifest_carries_the_same_pinned_declaration(self, monkeypatch):
|
|
156
|
+
tools = _exa_tools(monkeypatch)
|
|
157
|
+
archive_bytes = generate_source_zip(
|
|
158
|
+
_react_with(tools[0]),
|
|
159
|
+
mcp_tools={(_EXA_SERVER, "web_search_exa"): _EXA_SEARCH},
|
|
160
|
+
)
|
|
161
|
+
with zipfile.ZipFile(io.BytesIO(archive_bytes)) as archive:
|
|
162
|
+
manifest = json.loads(archive.read("manifest.json"))
|
|
163
|
+
namespace: dict[str, Any] = {}
|
|
164
|
+
exec(compile(archive.read("program_source.py"), "<generated>", "exec"), namespace) # noqa: S102
|
|
165
|
+
|
|
166
|
+
assert manifest["tools"] == namespace["__cmpnd_mcp_tools__"]
|
|
167
|
+
|
|
168
|
+
def test_the_zip_preserves_an_alias_while_the_declaration_keeps_the_route(self, monkeypatch):
|
|
169
|
+
tool = _exa_tools(monkeypatch)[0]
|
|
170
|
+
tool.name = "fetch_pages"
|
|
171
|
+
archive_bytes = generate_source_zip(
|
|
172
|
+
_react_with(tool),
|
|
173
|
+
mcp_tools={(_EXA_SERVER, "web_search_exa"): _EXA_SEARCH},
|
|
174
|
+
)
|
|
175
|
+
with zipfile.ZipFile(io.BytesIO(archive_bytes)) as archive:
|
|
176
|
+
manifest = json.loads(archive.read("manifest.json"))
|
|
177
|
+
namespace: dict[str, Any] = {}
|
|
178
|
+
exec(compile(archive.read("program_source.py"), "<generated>", "exec"), namespace) # noqa: S102
|
|
179
|
+
|
|
180
|
+
rebuilt = namespace["module"]
|
|
181
|
+
assert set(rebuilt.tools) == {"fetch_pages", "finish"}
|
|
182
|
+
assert rebuilt.tools["fetch_pages"].func.__name__ == "web_search_exa"
|
|
183
|
+
assert manifest["tools"] == [
|
|
184
|
+
{
|
|
185
|
+
"tool": "web_search_exa",
|
|
186
|
+
"server": _EXA_SERVER,
|
|
187
|
+
"schema_hash": "c" * 64,
|
|
188
|
+
"parameters": ["query", "numResults"],
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
def test_two_aliases_share_one_canonical_declaration(self, monkeypatch):
|
|
193
|
+
first = _exa_tools(monkeypatch)[0]
|
|
194
|
+
second = _exa_tools(monkeypatch)[0]
|
|
195
|
+
first.name = "fetch_pages"
|
|
196
|
+
second.name = "search_pages"
|
|
197
|
+
archive_bytes = generate_source_zip(
|
|
198
|
+
dspy.ReAct("question -> answer", tools=[first, second]),
|
|
199
|
+
mcp_tools={(_EXA_SERVER, "web_search_exa"): _EXA_SEARCH},
|
|
200
|
+
)
|
|
201
|
+
with zipfile.ZipFile(io.BytesIO(archive_bytes)) as archive:
|
|
202
|
+
manifest = json.loads(archive.read("manifest.json"))
|
|
203
|
+
namespace: dict[str, Any] = {}
|
|
204
|
+
exec(compile(archive.read("program_source.py"), "<generated>", "exec"), namespace) # noqa: S102
|
|
205
|
+
|
|
206
|
+
rebuilt = namespace["module"]
|
|
207
|
+
assert set(rebuilt.tools) == {"fetch_pages", "search_pages", "finish"}
|
|
208
|
+
assert {rebuilt.tools[name].func.__name__ for name in ("fetch_pages", "search_pages")} == {"web_search_exa"}
|
|
209
|
+
assert len(manifest["tools"]) == 1
|
|
210
|
+
assert manifest["tools"][0]["tool"] == "web_search_exa"
|
|
211
|
+
assert "input_schema" not in manifest["tools"][0]
|
|
148
212
|
|
|
149
213
|
def test_the_generated_source_names_the_stub_in_the_tools_kwarg(self, monkeypatch):
|
|
150
214
|
source = self._source(monkeypatch)
|
|
@@ -360,7 +424,45 @@ class TestPlainToolsAreUnaffected:
|
|
|
360
424
|
assert "'tool': 'web_search_exa'" in source
|
|
361
425
|
assert "'_add_at_module_scope'" not in source, "a source tool is not an MCP declaration"
|
|
362
426
|
|
|
427
|
+
def test_user_tool_in_the_platform_namespace_is_refused(self):
|
|
428
|
+
module = dspy.ReAct(
|
|
429
|
+
"question -> answer",
|
|
430
|
+
tools=[dspy.Tool(_add_at_module_scope, name="cmpnd_inspect_tool_schema")],
|
|
431
|
+
)
|
|
432
|
+
with pytest.raises(ValueError, match="cmpnd_inspect_tool_schema.*rename"):
|
|
433
|
+
generate_program_source(module)
|
|
434
|
+
|
|
435
|
+
def test_user_cannot_claim_platform_provenance_with_a_boolean_attribute(self):
|
|
436
|
+
tool = dspy.Tool(_add_at_module_scope, name="ordinary_tool")
|
|
437
|
+
tool._cmpnd_runtime_tool = True
|
|
438
|
+
module = _react_with(tool)
|
|
439
|
+
del module.tools[tool.name]
|
|
440
|
+
module.tools["cmpnd_inspect_tool_schema"] = tool
|
|
441
|
+
|
|
442
|
+
with pytest.raises(ValueError, match="cmpnd_inspect_tool_schema.*rename"):
|
|
443
|
+
generate_program_source(module)
|
|
444
|
+
|
|
445
|
+
def test_runtime_platform_tool_is_not_packaged(self):
|
|
446
|
+
module = _react_with(dspy.Tool(_add_at_module_scope))
|
|
447
|
+
ambient = mark_runtime_tool(dspy.Tool(_runtime_inspector_at_module_scope, name="cmpnd_inspect_tool_schema"))
|
|
448
|
+
module.tools[ambient.name] = ambient
|
|
449
|
+
|
|
450
|
+
assert "cmpnd_inspect_tool_schema" not in declared_module_tools(module)
|
|
451
|
+
|
|
452
|
+
def test_marked_non_platform_tool_is_still_packaged(self):
|
|
453
|
+
module = _react_with(dspy.Tool(_add_at_module_scope))
|
|
454
|
+
marked = dspy.Tool(_add_at_module_scope, name="inspect_tool_schema")
|
|
455
|
+
marked._cmpnd_runtime_tool = True
|
|
456
|
+
module.tools[marked.name] = marked
|
|
457
|
+
|
|
458
|
+
assert "inspect_tool_schema" in declared_module_tools(module)
|
|
459
|
+
|
|
363
460
|
|
|
364
461
|
def _add_at_module_scope(x: int, y: int) -> int:
|
|
365
462
|
"""Add two numbers."""
|
|
366
463
|
return x + y
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _runtime_inspector_at_module_scope(tool: str) -> str:
|
|
467
|
+
"""Test-only stand-in for the worker's ambient schema inspector."""
|
|
468
|
+
return tool
|
|
@@ -12,6 +12,7 @@ import pytest
|
|
|
12
12
|
from hypothesis import HealthCheck, assume, given, settings
|
|
13
13
|
from hypothesis import strategies as st
|
|
14
14
|
|
|
15
|
+
from cmpnd._runtime_tools import mark_runtime_tool
|
|
15
16
|
from cmpnd.ir import (
|
|
16
17
|
ChainOfThought as IR_ChainOfThought,
|
|
17
18
|
)
|
|
@@ -155,6 +156,60 @@ def test_react_tool_set_changes_hash() -> None:
|
|
|
155
156
|
assert program_signature_hash(reflect(r1)) != program_signature_hash(reflect(r2))
|
|
156
157
|
|
|
157
158
|
|
|
159
|
+
def test_react_platform_tool_does_not_change_hash() -> None:
|
|
160
|
+
def search(query: str) -> str:
|
|
161
|
+
return query
|
|
162
|
+
|
|
163
|
+
def inspect_schema(tool: str) -> str:
|
|
164
|
+
return tool
|
|
165
|
+
|
|
166
|
+
react = dspy.ReAct("q -> a", tools=[search])
|
|
167
|
+
before = program_signature_hash(reflect(react))
|
|
168
|
+
ambient = mark_runtime_tool(dspy.Tool(inspect_schema, name="cmpnd_inspect_tool_schema"))
|
|
169
|
+
react.tools[ambient.name] = ambient
|
|
170
|
+
|
|
171
|
+
assert program_signature_hash(reflect(react)) == before
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_react_user_tool_with_platform_prefix_remains_part_of_identity() -> None:
|
|
175
|
+
def inspect_schema(tool: str) -> str:
|
|
176
|
+
return tool
|
|
177
|
+
|
|
178
|
+
plain = dspy.ReAct("q -> a", tools=[])
|
|
179
|
+
user_tool = dspy.ReAct(
|
|
180
|
+
"q -> a",
|
|
181
|
+
tools=[dspy.Tool(inspect_schema, name="cmpnd_inspect_tool_schema")],
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
assert program_signature_hash(reflect(user_tool)) != program_signature_hash(reflect(plain))
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def test_react_boolean_marker_cannot_remove_an_exposed_alias_from_identity() -> None:
|
|
188
|
+
def inspect_schema(tool: str) -> str:
|
|
189
|
+
return tool
|
|
190
|
+
|
|
191
|
+
plain = dspy.ReAct("q -> a", tools=[])
|
|
192
|
+
user_tool = dspy.Tool(inspect_schema, name="ordinary_tool")
|
|
193
|
+
user_tool._cmpnd_runtime_tool = True
|
|
194
|
+
forged = dspy.ReAct("q -> a", tools=[user_tool])
|
|
195
|
+
del forged.tools[user_tool.name]
|
|
196
|
+
forged.tools["cmpnd_inspect_tool_schema"] = user_tool
|
|
197
|
+
|
|
198
|
+
assert program_signature_hash(reflect(forged)) != program_signature_hash(reflect(plain))
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def test_react_marked_non_platform_tool_remains_part_of_identity() -> None:
|
|
202
|
+
def inspect_schema(tool: str) -> str:
|
|
203
|
+
return tool
|
|
204
|
+
|
|
205
|
+
plain = dspy.ReAct("q -> a", tools=[])
|
|
206
|
+
user_tool = dspy.Tool(inspect_schema, name="inspect_tool_schema")
|
|
207
|
+
user_tool._cmpnd_runtime_tool = True
|
|
208
|
+
marked = dspy.ReAct("q -> a", tools=[user_tool])
|
|
209
|
+
|
|
210
|
+
assert program_signature_hash(reflect(marked)) != program_signature_hash(reflect(plain))
|
|
211
|
+
|
|
212
|
+
|
|
158
213
|
def test_refine_distinct_from_inner() -> None:
|
|
159
214
|
import cmpnd.ir
|
|
160
215
|
|
|
@@ -54,6 +54,7 @@ import dspy
|
|
|
54
54
|
import numpy as np
|
|
55
55
|
import pytest
|
|
56
56
|
|
|
57
|
+
from cmpnd._runtime_tools import mark_runtime_tool
|
|
57
58
|
from cmpnd.ir import (
|
|
58
59
|
KNN as IR_KNN,
|
|
59
60
|
)
|
|
@@ -121,6 +122,17 @@ def _sample_tool(query: str) -> str:
|
|
|
121
122
|
return query
|
|
122
123
|
|
|
123
124
|
|
|
125
|
+
def _inspect_tool_schema(tool: str) -> str:
|
|
126
|
+
return tool
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _build_react() -> dspy.ReAct:
|
|
130
|
+
react = dspy.ReAct("q -> a", tools=[_sample_tool])
|
|
131
|
+
inspector = mark_runtime_tool(dspy.Tool(_inspect_tool_schema, name="cmpnd_inspect_tool_schema"))
|
|
132
|
+
react.tools[inspector.name] = inspector
|
|
133
|
+
return react
|
|
134
|
+
|
|
135
|
+
|
|
124
136
|
def _build_user_composite() -> dspy.Module:
|
|
125
137
|
class _TwoPredictApp(dspy.Module):
|
|
126
138
|
def __init__(self) -> None:
|
|
@@ -202,6 +214,7 @@ def _check_react(ir: Any) -> None:
|
|
|
202
214
|
f"expected user tool in {ir.tools}"
|
|
203
215
|
)
|
|
204
216
|
assert "finish" not in ir.tools, "auto-injected finish sentinel leaked into identity"
|
|
217
|
+
assert "cmpnd_inspect_tool_schema" not in ir.tools, "ambient platform tool leaked into identity"
|
|
205
218
|
|
|
206
219
|
|
|
207
220
|
def _check_best_of_n(ir: Any) -> None:
|
|
@@ -285,7 +298,7 @@ KIND_FIXTURES: tuple[KindFixture, ...] = (
|
|
|
285
298
|
name="ReAct",
|
|
286
299
|
dspy_attr="ReAct",
|
|
287
300
|
ir_cls=IR_ReAct,
|
|
288
|
-
build=
|
|
301
|
+
build=_build_react,
|
|
289
302
|
required_attrs=("signature", "tools"),
|
|
290
303
|
extra=_check_react,
|
|
291
304
|
),
|
|
@@ -29,6 +29,7 @@ class TestClassification:
|
|
|
29
29
|
assert not retry.is_retryable(_resp(500, body={"error": "boom"}))
|
|
30
30
|
assert not retry.is_retryable(_resp(400))
|
|
31
31
|
assert not retry.is_retryable(_resp(404))
|
|
32
|
+
assert not retry.is_retryable(_resp(422))
|
|
32
33
|
assert not retry.is_retryable(_resp(429))
|
|
33
34
|
|
|
34
35
|
def test_explicit_false_is_not_retryable(self):
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
|
|
5
|
+
import dspy
|
|
6
|
+
|
|
7
|
+
from cmpnd._runtime_tools import is_runtime_tool, mark_runtime_tool
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_an_arbitrary_boolean_on_a_tool_is_not_runtime_provenance() -> None:
|
|
11
|
+
def inspect_schema(tool: str) -> str:
|
|
12
|
+
return tool
|
|
13
|
+
|
|
14
|
+
tool = dspy.Tool(inspect_schema, name="cmpnd_inspect_tool_schema")
|
|
15
|
+
tool._cmpnd_runtime_tool = True
|
|
16
|
+
|
|
17
|
+
assert not is_runtime_tool(tool)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_an_arbitrary_boolean_on_a_function_is_not_runtime_provenance() -> None:
|
|
21
|
+
def inspect_schema(tool: str) -> str:
|
|
22
|
+
return tool
|
|
23
|
+
|
|
24
|
+
inspect_schema._cmpnd_runtime_tool = True
|
|
25
|
+
|
|
26
|
+
assert not is_runtime_tool(dspy.Tool(inspect_schema, name="cmpnd_inspect_tool_schema"))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_runtime_provenance_survives_tool_and_module_copies() -> None:
|
|
30
|
+
def inspect_schema(tool: str) -> str:
|
|
31
|
+
return tool
|
|
32
|
+
|
|
33
|
+
tool = mark_runtime_tool(dspy.Tool(inspect_schema, name="cmpnd_inspect_tool_schema"))
|
|
34
|
+
react = dspy.ReAct("q -> a", tools=[tool])
|
|
35
|
+
|
|
36
|
+
assert is_runtime_tool(copy.copy(tool))
|
|
37
|
+
assert is_runtime_tool(copy.deepcopy(tool))
|
|
38
|
+
assert is_runtime_tool(react.deepcopy().tools[tool.name])
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_runtime_provenance_survives_callable_normalization() -> None:
|
|
42
|
+
def inspect_schema(tool: str) -> str:
|
|
43
|
+
return tool
|
|
44
|
+
|
|
45
|
+
marked = mark_runtime_tool(inspect_schema)
|
|
46
|
+
|
|
47
|
+
assert is_runtime_tool(dspy.Tool(marked, name="cmpnd_inspect_tool_schema"))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|