cmpnd 0.60.0__tar.gz → 0.62.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cmpnd-0.60.0 → cmpnd-0.62.0}/PKG-INFO +1 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/_traces.py +7 -2
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/callback.py +2 -2
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/commands/programs.py +214 -51
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/commands/top.py +17 -4
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/commands/traces.py +40 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/commands/valsets.py +29 -4
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/shell.py +6 -4
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/trace_render.py +29 -2
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/models.py +9 -3
- {cmpnd-0.60.0 → cmpnd-0.62.0}/docs/cli.md +71 -14
- {cmpnd-0.60.0 → cmpnd-0.62.0}/docs/reading-traces.md +3 -2
- {cmpnd-0.60.0 → cmpnd-0.62.0}/docs/sdk-instrumentation.md +7 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/ingest/cmpnd_ingest/trace_facts.py +8 -3
- {cmpnd-0.60.0 → cmpnd-0.62.0}/pyproject.toml +1 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/scripts/capture_review_run.py +5 -5
- cmpnd-0.62.0/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/scripts/seed_review_run.py +5 -5
- cmpnd-0.62.0/tests/e2e/target_capabilities.py +58 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_composite_module.py +1 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_deploy_execute.py +0 -3
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_deploy_pandas.py +6 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_deploy_programs.py +5 -0
- cmpnd-0.62.0/tests/e2e/test_optimize_authored_metric.py +78 -0
- cmpnd-0.62.0/tests/e2e/test_optimize_custom_proposer.py +158 -0
- cmpnd-0.62.0/tests/e2e/test_optimize_failures.py +69 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_optimize_server_owned_completion.py +7 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_parallel_export.py +2 -2
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_project_attribution.py +1 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/load/rlm_shape.py +1 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/parity/test_trace_streaming.py +1 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/parity/test_traces.py +4 -4
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/span_reference_fixture.py +1 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/stress/_provider.py +19 -25
- cmpnd-0.62.0/tests/stress/test_bedrock_converse.py +127 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/stress/test_multi_client_stress.py +15 -5
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_callback.py +4 -4
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_programs.py +247 -38
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_shell.py +3 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_traces.py +2 -2
- cmpnd-0.62.0/tests/test_cli/test_traces_program_and_module.py +274 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_traces_search_wire.py +4 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_valsets.py +66 -3
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_client.py +2 -2
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_client_program_names.py +2 -2
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_context_propagation.py +1 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_eval_example_attribution.py +6 -6
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_eval_trace_grouping.py +3 -3
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_exporter.py +3 -3
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_exporter_program_names.py +1 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_exporter_queue.py +1 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_exporter_real.py +3 -3
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_ingest_conformance.py +1 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_integration.py +1 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_models.py +6 -6
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_multi_client_stress_readiness.py +6 -5
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_program_identity.py +1 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_project_payload.py +7 -7
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_repro_reactv2_bugs.py +1 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_run_attribution_paths.py +1 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_schemas.py +1 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_trace_fact_wire_contract.py +1 -1
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_trace_facts.py +8 -8
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_trace_render.py +3 -2
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_truncation.py +2 -2
- {cmpnd-0.60.0 → cmpnd-0.62.0}/uv.lock +1 -1
- cmpnd-0.60.0/scripts/fixtures/review_run.json.gz +0 -0
- cmpnd-0.60.0/tests/e2e/target_capabilities.py +0 -70
- cmpnd-0.60.0/tests/e2e/test_optimize_failures.py +0 -136
- {cmpnd-0.60.0 → cmpnd-0.62.0}/.gitignore +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/AGENTS.md +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/CLAUDE.md +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/README.md +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/__init__.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/_assessments.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/_authored_import.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/_deployments.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/_gepa_patch.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/_http.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/_runtime_tools.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/_valsets.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/authored.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/__init__.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/commands/admin.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/commands/assessments.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/commands/deployments.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/commands/evals.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/commands/org.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/commands/signatures.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/tree_help.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/cli/verbs.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/client.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/configuration.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/context.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/credentials.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/dataset_sync.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/datasets.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/decorators.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/deployment.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/deployment_metadata.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/deployment_runs.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/execution.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/exporter.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/helpers.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/identity.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/imaging.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/ir/reflection.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/lm_configuration.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/mcp_servers.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/named_programs.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/optimization.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/optimizers/gepa.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/optimizers/gepa_callback.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/optimizers/tracker.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/packaging.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/programs.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/references.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/retry.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/cmpnd/traces.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/docs/2026-04-02-code-review.md +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/docs/client.md +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/docs/custom-proposer.md +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/examples/local_ollama.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/ingest/README.md +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/ingest/cmpnd_ingest/bodies.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/ingest/cmpnd_ingest/client_floor.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/ingest/cmpnd_ingest/program_names.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/ingest/cmpnd_ingest/protocols.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/ingest/pyproject.toml +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/scripts/check-pip-install.sh +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/scripts/install-test-env.sh +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/scripts/release-stress.sh +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/scripts/require_version_bump.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/scripts/selfhost_compat.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/scripts/test-in-linux.sh +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/stubs/dspy/utils/dummies.pyi +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/__init__.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/_credential_route.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/_deployment_signature_helpers.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/_fake_clock.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/_resolve_refs.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/_traced_run.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/conftest.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/committee_task.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/helpers.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_assessments_cli.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_traces_read.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_user_attribution.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/e2e/test_valsets_cli.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/integration/__init__.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/integration/authored_optimize/judge.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/integration/conftest.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/integration/hosted_optimize_probe.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/integration/test_logs_renders_traces.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/integration/test_unhappy_path.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/load/__init__.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/load/__main__.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/load/drain.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/load/ingest.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/load/rlm_size_report.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/load/sweep.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/load/test_rlm_size_report.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/parity/__init__.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/parity/conftest.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/parity/test_health.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/stress/__init__.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/stress/_readiness.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/stress/_reliability.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/stress/_result.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/stress/mint_bedrock_token.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/stress/test_deploy_programs.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/stress/test_optimize_client_committee.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/stress/test_optimize_loop.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/stress/test_optimize_stress.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/stress/test_provider_roles.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/stress/test_readiness.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/stress/test_real_provider_twins.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/stress/test_reliability.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/stress/test_reliability_classify.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/stress/test_rlm_wasm.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/stress/test_trace_search.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_adapter_reentrancy.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_assessments_api.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_authored.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_authored_binding_credentials.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_authored_bundle_collisions.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_authored_metric_contract.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_authored_named_models.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_authored_optimize.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_admin.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_api_client.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_assessments.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_deployment_runs.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_deployments.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_evals.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_main.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_org.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_parity.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_provider_credentials.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_schema_sample.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_signatures.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_top.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cli/test_tree_help.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_client_floor.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_configuration.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_context.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_credential_kwargs.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_credential_selection.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_credential_values.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_dataset_sync.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_datasets.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_decision_client_spans.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_decorators.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_deploy.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_deploy_program_id.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_deployment_metadata.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_deployment_runs.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_deployments_read.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_eval.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_eval_start.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_execute.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_execute_run_identity.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_gepa_output_normalization.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_identity.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_imaging.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_import_weight.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_interpreter_callback.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_lm15_native_calls.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_lm_configuration.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_lm_usage_shapes.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_mcp_tools.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_optimize.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_optimize_lms.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_optimize_reflection_lm.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_packaging.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_packaging_mcp_tools.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_packaging_program_source.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_poll_until.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_program_id_deprecation.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_program_lineage_persist.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_program_names_in_deploy.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_programs_create.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_real_provider_twins_readiness.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_reflector_properties.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_reflector_smoke.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_release_stress_script.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_replaced_names.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_retry.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_rlm_patch.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_runtime_tools.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_seal_lm_credential.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_signature_inputs.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_span_references.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_span_sent_once.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_system_one.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_trace_context_concurrency.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_traces_read.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_tracked_gepa.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_upload_auth_headers.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_valsets_api.py +0 -0
- {cmpnd-0.60.0 → cmpnd-0.62.0}/tests/test_xxh64.py +0 -0
|
@@ -4,7 +4,7 @@ The implementation of ``list`` and ``show`` over the shared ``_http`` transport.
|
|
|
4
4
|
:class:`cmpnd.Client`'s ``list_traces`` / ``get_trace`` call these directly; the
|
|
5
5
|
deprecated :mod:`cmpnd.traces` shim wraps them with a warning. The filter and
|
|
6
6
|
verb names match the ``cmpnd traces`` CLI vocabulary — ``list`` filtered by
|
|
7
|
-
``query`` / ``program`` / ``hash`` / ``deployment`` / ``since`` / ``until`` /
|
|
7
|
+
``query`` / ``program`` / ``module`` / ``hash`` / ``deployment`` / ``since`` / ``until`` /
|
|
8
8
|
``all_history``, and ``show`` for one trace — so the surfaces do not drift.
|
|
9
9
|
"""
|
|
10
10
|
|
|
@@ -28,6 +28,7 @@ def list(
|
|
|
28
28
|
query: str | None = None,
|
|
29
29
|
model: str | None = None,
|
|
30
30
|
program: str | None = None,
|
|
31
|
+
module: str | None = None,
|
|
31
32
|
hash: str | None = None,
|
|
32
33
|
deployment: str | None = None,
|
|
33
34
|
metadata: dict[str, str] | None = None,
|
|
@@ -49,7 +50,10 @@ def list(
|
|
|
49
50
|
|
|
50
51
|
The filters mirror ``cmpnd traces list``: ``query`` searches indexed span text;
|
|
51
52
|
``program`` names the program by name, and ``hash`` by its program id (``p_…``)
|
|
52
|
-
or a legacy signature hash;
|
|
53
|
+
or a legacy signature hash; ``module`` is the root module's class, matched exactly.
|
|
54
|
+
No index serves the class, so without ``program`` or a program id in ``hash`` it
|
|
55
|
+
reads every trace in the time range and slows as the range grows. Pair it with a program or a shorter
|
|
56
|
+
``since``;
|
|
53
57
|
``deployment`` is a deployment id; ``metadata`` is matched by containment (every
|
|
54
58
|
pair must be present); ``tags`` matches any of the given tags. ``since`` /
|
|
55
59
|
``until`` accept an ISO 8601 timestamp, a bare date (``YYYY-MM-DD``), or a
|
|
@@ -68,6 +72,7 @@ def list(
|
|
|
68
72
|
("q", query),
|
|
69
73
|
("model", model),
|
|
70
74
|
("program_name", program),
|
|
75
|
+
("module_class", module),
|
|
71
76
|
("deployment_id", deployment),
|
|
72
77
|
("tags", tags),
|
|
73
78
|
):
|
|
@@ -1190,7 +1190,7 @@ class _ModuleMixin(_CallbackBase):
|
|
|
1190
1190
|
trace = models.TraceBuilder(
|
|
1191
1191
|
trace_id=uuid4(),
|
|
1192
1192
|
start_time=datetime.now(timezone.utc),
|
|
1193
|
-
|
|
1193
|
+
module_class=instance.__class__.__name__,
|
|
1194
1194
|
)
|
|
1195
1195
|
self._stamp(trace)
|
|
1196
1196
|
|
|
@@ -1633,7 +1633,7 @@ class _LMMixin(_CallbackBase):
|
|
|
1633
1633
|
trace_id=span.trace_id or uuid4(),
|
|
1634
1634
|
start_time=span.start_time,
|
|
1635
1635
|
end_time=span.end_time,
|
|
1636
|
-
|
|
1636
|
+
module_class="GEPA",
|
|
1637
1637
|
)
|
|
1638
1638
|
self._stamp(trace)
|
|
1639
1639
|
# A metric-phase call can start while DSPy's Evaluate span is still on the
|
|
@@ -13,12 +13,12 @@ from .. import api_client, pagination
|
|
|
13
13
|
from . import provider_credentials
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# The
|
|
20
|
-
#
|
|
21
|
-
#
|
|
16
|
+
# `show`, `traces`, `stats` and `usage` take a program name or a p_ id, as the
|
|
17
|
+
# Trash commands do. The `programs optimized` group and `select` take a compiled
|
|
18
|
+
# program hash, the value `programs optimized list` prints as `program_hash`.
|
|
19
|
+
# The routes resolve that hash against optimization runs, so a program
|
|
20
|
+
# signature hash 404s there. A compiled hash and a program name have the same
|
|
21
|
+
# shape, so no command takes both in one slot.
|
|
22
22
|
def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
23
23
|
parser = subparsers.add_parser("programs", help="Create and browse programs")
|
|
24
24
|
sub = parser.add_subparsers(dest="action")
|
|
@@ -83,7 +83,8 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
83
83
|
help="List optimized programs by compiled program hash",
|
|
84
84
|
description=(
|
|
85
85
|
"List the compiled programs your optimization runs produced, one row per compiled program hash, with "
|
|
86
|
-
"their run counts and scores. `programs show`, `traces`, `stats` and `usage` take the hash
|
|
86
|
+
"their run counts and scores. `programs optimized show`, `traces`, `stats` and `usage` take the hash "
|
|
87
|
+
"from the "
|
|
87
88
|
"program_hash column."
|
|
88
89
|
),
|
|
89
90
|
)
|
|
@@ -94,37 +95,54 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
94
95
|
pagination.add_args(opt_list, with_page_aliases=True, default_limit=50)
|
|
95
96
|
opt_list.set_defaults(func=handle_optimized_list)
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
opt_show = optimized_sub.add_parser("show", help="Show the optimization runs that produced a compiled program")
|
|
99
|
+
opt_show.add_argument("hash", help=_COMPILED_HASH_HELP)
|
|
100
|
+
opt_show.set_defaults(func=handle_optimized_show)
|
|
101
|
+
|
|
102
|
+
opt_traces = optimized_sub.add_parser("traces", help="List the traces of a compiled program's program")
|
|
103
|
+
opt_traces.add_argument("hash", help=_COMPILED_HASH_HELP)
|
|
104
|
+
pagination.add_args(opt_traces, with_page_aliases=False, default_limit=50)
|
|
105
|
+
opt_traces.set_defaults(func=handle_optimized_traces)
|
|
106
|
+
|
|
107
|
+
opt_stats = optimized_sub.add_parser("stats", help="Show the execution statistics of a compiled program's program")
|
|
108
|
+
opt_stats.add_argument("hash", help=_COMPILED_HASH_HELP)
|
|
109
|
+
opt_stats.set_defaults(func=handle_optimized_stats)
|
|
110
|
+
|
|
111
|
+
opt_usage = optimized_sub.add_parser("usage", help="Show the usage over time of a compiled program's program")
|
|
112
|
+
opt_usage.add_argument("hash", help=_COMPILED_HASH_HELP)
|
|
113
|
+
_add_usage_window(opt_usage)
|
|
114
|
+
opt_usage.set_defaults(func=handle_optimized_usage)
|
|
115
|
+
|
|
116
|
+
show = sub.add_parser(
|
|
117
|
+
"show",
|
|
118
|
+
help="Show a program",
|
|
119
|
+
description=(
|
|
120
|
+
"Show PROGRAM: its name, p_ id, whether it is in the Trash, its trace count and latest trace, and "
|
|
121
|
+
"the optimized programs its optimization runs produced."
|
|
122
|
+
),
|
|
100
123
|
)
|
|
124
|
+
show.add_argument("program", metavar="PROGRAM", help="Program name or p_ id")
|
|
101
125
|
show.set_defaults(func=handle_show)
|
|
102
126
|
|
|
103
|
-
tr = sub.add_parser(
|
|
104
|
-
|
|
105
|
-
|
|
127
|
+
tr = sub.add_parser(
|
|
128
|
+
"traces",
|
|
129
|
+
help="List a program's traces, newest first, over the widest window trace search reads",
|
|
130
|
+
description=(
|
|
131
|
+
"List PROGRAM's traces, newest first, through trace search over its widest window, 365 days. "
|
|
132
|
+
"`cmpnd traces list --hash ID --since DATE` sets the window itself. --limit is at most 500."
|
|
133
|
+
),
|
|
106
134
|
)
|
|
135
|
+
tr.add_argument("program", metavar="PROGRAM", help="Program name or p_ id")
|
|
107
136
|
pagination.add_args(tr, with_page_aliases=False, default_limit=50)
|
|
108
137
|
tr.set_defaults(func=handle_traces)
|
|
109
138
|
|
|
110
|
-
stats = sub.add_parser("stats", help="Show a program's execution statistics")
|
|
111
|
-
stats.add_argument(
|
|
112
|
-
"id", help="Compiled program hash, as `programs optimized list` prints it in the program_hash column"
|
|
113
|
-
)
|
|
139
|
+
stats = sub.add_parser("stats", help="Show a program's execution statistics over all history")
|
|
140
|
+
stats.add_argument("program", metavar="PROGRAM", help="Program name or p_ id")
|
|
114
141
|
stats.set_defaults(func=handle_stats)
|
|
115
142
|
|
|
116
143
|
usage = sub.add_parser("usage", help="Show a program's trace volume, tokens and cost over time")
|
|
117
|
-
usage.add_argument(
|
|
118
|
-
|
|
119
|
-
)
|
|
120
|
-
usage.add_argument("--hours", type=int, default=24, help="Rolling window in hours (default %(default)s)")
|
|
121
|
-
# Constrained on purpose: an unrecognised period buckets hourly server-side
|
|
122
|
-
# rather than failing, so a typo would answer a different question silently.
|
|
123
|
-
usage.add_argument(
|
|
124
|
-
"--period",
|
|
125
|
-
choices=["minute", "5m", "10m", "30m", "hour", "day"],
|
|
126
|
-
help="Bucket width (default: hour)",
|
|
127
|
-
)
|
|
144
|
+
usage.add_argument("program", metavar="PROGRAM", help="Program name or p_ id")
|
|
145
|
+
_add_usage_window(usage)
|
|
128
146
|
usage.set_defaults(func=handle_usage)
|
|
129
147
|
|
|
130
148
|
# The registry is the programs an SDK client registered, as distinct from
|
|
@@ -173,13 +191,32 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
173
191
|
purge.set_defaults(func=handle_purge)
|
|
174
192
|
|
|
175
193
|
select = sub.add_parser("select", help="Pin an optimization run as this program's chosen version")
|
|
176
|
-
select.add_argument(
|
|
177
|
-
"id", help="Compiled program hash, as `programs optimized list` prints it in the program_hash column"
|
|
178
|
-
)
|
|
194
|
+
select.add_argument("id", help=_COMPILED_HASH_HELP)
|
|
179
195
|
select.add_argument("--run", dest="run_id", required=True, help="Optimization run ID (op_...) to pin")
|
|
180
196
|
select.set_defaults(func=handle_select)
|
|
181
197
|
|
|
182
198
|
|
|
199
|
+
_COMPILED_HASH_HELP = "Compiled program hash, as `programs optimized list` prints it in the program_hash column"
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _add_usage_window(parser: ArgumentParser) -> None:
|
|
203
|
+
parser.add_argument("--hours", type=int, default=24, help="Rolling window in hours (default %(default)s)")
|
|
204
|
+
# Constrained on purpose: an unrecognised period buckets hourly server-side
|
|
205
|
+
# rather than failing, so a typo would answer a different question silently.
|
|
206
|
+
parser.add_argument(
|
|
207
|
+
"--period",
|
|
208
|
+
choices=["minute", "5m", "10m", "30m", "hour", "day"],
|
|
209
|
+
help="Bucket width (default: hour)",
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _usage_params(args: Any) -> dict[str, str]:
|
|
214
|
+
params = {"hours": str(args.hours)}
|
|
215
|
+
if args.period:
|
|
216
|
+
params["period"] = args.period
|
|
217
|
+
return params
|
|
218
|
+
|
|
219
|
+
|
|
183
220
|
def handle_create(client: api_client.ApiClient, args: Any) -> Any:
|
|
184
221
|
"""Create the program named ``args.name``, and refuse a name that already has one.
|
|
185
222
|
|
|
@@ -246,7 +283,7 @@ def handle_list(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
246
283
|
]:
|
|
247
284
|
if getattr(args, attr, default) != default:
|
|
248
285
|
raise api_client.CliError(f"{flag} does not apply to --trashed, which lists the whole Trash")
|
|
249
|
-
return client
|
|
286
|
+
return _trash_listing(client)
|
|
250
287
|
params = pagination.wire(args, "limit_offset", as_str=True)
|
|
251
288
|
# Read as text: a server from before the program list, such as self-host release 2026-001, redirects this
|
|
252
289
|
# path to its dispatcher, and the redirect's body is HTML.
|
|
@@ -310,24 +347,125 @@ def handle_optimized_list(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
310
347
|
return client.get("/api/v1/programs/v2", params=params)
|
|
311
348
|
|
|
312
349
|
|
|
350
|
+
# A server from before program names, p_ ids and the Trash API, such as self-host release 2026-001, reads every
|
|
351
|
+
# path under /api/v1/programs/ as a compiled program hash. A GET there answers 404 and a POST answers 405, and its
|
|
352
|
+
# trace search ignores program_id. A current server answers the Trash listing with 200, so that read tells them
|
|
353
|
+
# apart once a command has already failed.
|
|
354
|
+
_OLD_SERVER = "this cmpnd server predates program names, p_ ids and the Trash API, as self-host release 2026-001 does."
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _predates_program_ids(client: api_client.ApiClient) -> bool:
|
|
358
|
+
try:
|
|
359
|
+
client.get("/api/v1/programs/trash")
|
|
360
|
+
except api_client.CliError as exc:
|
|
361
|
+
return exc.status_code == 404
|
|
362
|
+
return False
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _old_server_refusal(remedy: str) -> api_client.CliError:
|
|
366
|
+
return api_client.CliError(f"{_OLD_SERVER} {remedy}")
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _refuse_if_old(client: api_client.ApiClient, exc: api_client.CliError, remedy: str) -> None:
|
|
370
|
+
"""Raise the old-server refusal in place of ``exc`` when the server predates program ids."""
|
|
371
|
+
if exc.status_code in (404, 405) and _predates_program_ids(client):
|
|
372
|
+
raise _old_server_refusal(remedy) from exc
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _trash_remedy() -> str:
|
|
376
|
+
return "Manage its Trash in the web app, at /ui/account/trash, or upgrade the server."
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _read_remedy(verb: str) -> str:
|
|
380
|
+
from . import top
|
|
381
|
+
|
|
382
|
+
return (
|
|
383
|
+
f"`{top.hint_programs('optimized', verb, 'HASH')}` reads a compiled program hash there, and "
|
|
384
|
+
f"`{top.hint_programs('list')}` lists the programs it recorded traces for."
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _resolve_for_read(client: api_client.ApiClient, ref: str, verb: str, *, trashed_ok: bool = False) -> str:
|
|
389
|
+
"""Return the p_ id of the program ``ref`` names, for a read of its activity.
|
|
390
|
+
|
|
391
|
+
A program in the Trash has no activity to read, so it is refused unless
|
|
392
|
+
``trashed_ok``. An unknown name may be a compiled program hash, which the
|
|
393
|
+
``programs optimized`` command of the same verb takes.
|
|
394
|
+
"""
|
|
395
|
+
from . import top
|
|
396
|
+
|
|
397
|
+
program_id, trashed = resolve(
|
|
398
|
+
client,
|
|
399
|
+
ref,
|
|
400
|
+
unknown_hint=f"A compiled program hash goes to `{top.hint_programs('optimized', verb, ref)}`.",
|
|
401
|
+
old_server_remedy=_read_remedy(verb),
|
|
402
|
+
)
|
|
403
|
+
if trashed and not trashed_ok:
|
|
404
|
+
raise api_client.CliError(
|
|
405
|
+
f"{ref} is in the Trash, so it has no activity to read. "
|
|
406
|
+
f"Restore it with `{top.hint_programs('restore', ref)}`."
|
|
407
|
+
)
|
|
408
|
+
return program_id
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def _read(client: api_client.ApiClient, verb: str, path: str, params: dict[str, str] | None = None) -> Any:
|
|
412
|
+
try:
|
|
413
|
+
return client.get(path) if params is None else client.get(path, params=params)
|
|
414
|
+
except api_client.CliError as exc:
|
|
415
|
+
_refuse_if_old(client, exc, _read_remedy(verb))
|
|
416
|
+
raise
|
|
417
|
+
|
|
418
|
+
|
|
313
419
|
def handle_show(client: api_client.ApiClient, args: Any) -> Any:
|
|
314
|
-
|
|
420
|
+
program_id = _resolve_for_read(client, args.program, "show", trashed_ok=True)
|
|
421
|
+
return _read(client, "show", f"/api/v1/programs/{program_id}")
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
# Trace search reads a page_size above this as its default of 50, which would answer other rows than the ones asked
|
|
425
|
+
# for.
|
|
426
|
+
_MAX_TRACE_PAGE = 500
|
|
315
427
|
|
|
316
428
|
|
|
317
429
|
def handle_traces(client: api_client.ApiClient, args: Any) -> Any:
|
|
318
|
-
|
|
319
|
-
|
|
430
|
+
"""List the program's traces through trace search, which takes a program id, over its widest window."""
|
|
431
|
+
if args.limit > _MAX_TRACE_PAGE:
|
|
432
|
+
raise api_client.CliError(
|
|
433
|
+
f"--limit is at most {_MAX_TRACE_PAGE}, the largest page trace search returns. Page with --offset."
|
|
434
|
+
)
|
|
435
|
+
program_id = _resolve_for_read(client, args.program, "traces")
|
|
436
|
+
body: dict[str, Any] = {"program_id": program_id, **pagination.wire(args, "page_size"), "max_window": True}
|
|
437
|
+
answer = client.post("/api/v1/traces/search", json=body)
|
|
438
|
+
# A server that ignores program_id answers every trace in the org, which must not pass for the program's.
|
|
439
|
+
if "program_id" in (answer.get("ignored_fields") or []):
|
|
440
|
+
raise _old_server_refusal(_read_remedy("traces"))
|
|
441
|
+
return answer
|
|
320
442
|
|
|
321
443
|
|
|
322
444
|
def handle_stats(client: api_client.ApiClient, args: Any) -> Any:
|
|
323
|
-
|
|
445
|
+
program_id = _resolve_for_read(client, args.program, "stats")
|
|
446
|
+
return _read(client, "stats", f"/api/v1/programs/{program_id}/execution-stats")
|
|
324
447
|
|
|
325
448
|
|
|
326
449
|
def handle_usage(client: api_client.ApiClient, args: Any) -> Any:
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
450
|
+
program_id = _resolve_for_read(client, args.program, "usage")
|
|
451
|
+
return _read(client, "usage", f"/api/v1/programs/{program_id}/usage-history", _usage_params(args))
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def handle_optimized_show(client: api_client.ApiClient, args: Any) -> Any:
|
|
455
|
+
return client.get(f"/api/v1/programs/{args.hash}")
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def handle_optimized_traces(client: api_client.ApiClient, args: Any) -> Any:
|
|
459
|
+
params = pagination.wire(args, "limit_offset", as_str=True)
|
|
460
|
+
return client.get(f"/api/v1/programs/{args.hash}/traces", params=params)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def handle_optimized_stats(client: api_client.ApiClient, args: Any) -> Any:
|
|
464
|
+
return client.get(f"/api/v1/programs/{args.hash}/execution-stats")
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def handle_optimized_usage(client: api_client.ApiClient, args: Any) -> Any:
|
|
468
|
+
return client.get(f"/api/v1/programs/{args.hash}/usage-history", params=_usage_params(args))
|
|
331
469
|
|
|
332
470
|
|
|
333
471
|
def handle_registry_list(client: api_client.ApiClient, args: Any) -> Any:
|
|
@@ -351,12 +489,17 @@ def handle_select(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
351
489
|
return client.post(f"/api/v1/programs/{args.id}/select", json={"optimization_run_id": args.run_id})
|
|
352
490
|
|
|
353
491
|
|
|
354
|
-
def
|
|
492
|
+
def resolve(
|
|
493
|
+
client: api_client.ApiClient, ref: str, *, unknown_hint: str = "", old_server_remedy: str = ""
|
|
494
|
+
) -> tuple[str, bool | None]:
|
|
355
495
|
"""Return the program id ``ref`` names, and whether it is in the Trash when known.
|
|
356
496
|
|
|
357
497
|
A ``p_`` id is used as given, because no program name starts with ``p_``, and its
|
|
358
498
|
Trash state is unknown until the route answers. A name is looked up, which never
|
|
359
|
-
makes a program, and the answer says whether it is in the Trash.
|
|
499
|
+
makes a program, and the answer says whether it is in the Trash. ``unknown_hint``
|
|
500
|
+
follows the error for a name no program has. On a server from before program
|
|
501
|
+
names the lookup fails too, and the error is the old-server refusal with
|
|
502
|
+
``old_server_remedy``.
|
|
360
503
|
"""
|
|
361
504
|
from ... import programs
|
|
362
505
|
|
|
@@ -370,7 +513,9 @@ def _resolve(client: api_client.ApiClient, ref: str) -> tuple[str, bool | None]:
|
|
|
370
513
|
answer = client.get("/api/v1/programs/by-name/" + urllib.parse.quote(ref, safe=""))
|
|
371
514
|
except api_client.CliError as exc:
|
|
372
515
|
if exc.status_code == 404:
|
|
373
|
-
|
|
516
|
+
_refuse_if_old(client, exc, old_server_remedy)
|
|
517
|
+
message = f"no program in your organization is named {ref!r}"
|
|
518
|
+
raise api_client.CliError(f"{message}. {unknown_hint}" if unknown_hint else message) from exc
|
|
374
519
|
if exc.status_code == 410:
|
|
375
520
|
raise api_client.CliError(f"{ref!r} belongs to a purged program, which nothing restores") from exc
|
|
376
521
|
raise
|
|
@@ -381,21 +526,21 @@ def handle_trash(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
381
526
|
"""Move a program to the Trash, which frees its place under the plan's program limit."""
|
|
382
527
|
from . import top
|
|
383
528
|
|
|
384
|
-
program_id, trashed =
|
|
529
|
+
program_id, trashed = resolve(client, args.program, old_server_remedy=_trash_remedy())
|
|
385
530
|
if trashed:
|
|
386
531
|
print(
|
|
387
532
|
f"{args.program} is already in the Trash. Restore it with `{top.hint_programs('restore', args.program)}`.",
|
|
388
533
|
file=sys.stderr,
|
|
389
534
|
)
|
|
390
535
|
return {"program_id": program_id, "trashed": True}
|
|
391
|
-
return client
|
|
536
|
+
return _trash_write(client, f"/api/v1/programs/{program_id}/trash")
|
|
392
537
|
|
|
393
538
|
|
|
394
539
|
def handle_restore(client: api_client.ApiClient, args: Any) -> Any:
|
|
395
540
|
"""Return a program from the Trash. The server refuses it when the plan's program limit is full."""
|
|
396
541
|
from . import top
|
|
397
542
|
|
|
398
|
-
program_id, trashed =
|
|
543
|
+
program_id, trashed = resolve(client, args.program, old_server_remedy=_trash_remedy())
|
|
399
544
|
if trashed is False:
|
|
400
545
|
# A name another program took from a Trashed one reaches the new, live program. The Trashed one is
|
|
401
546
|
# restored by its id.
|
|
@@ -403,7 +548,7 @@ def handle_restore(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
403
548
|
f"{args.program} is not in the Trash. If its name now belongs to another program, restore the one "
|
|
404
549
|
f"in the Trash by the p_ id that `{top.hint_programs('list', '--trashed')}` prints."
|
|
405
550
|
)
|
|
406
|
-
return client
|
|
551
|
+
return _trash_write(client, f"/api/v1/programs/{program_id}/restore")
|
|
407
552
|
|
|
408
553
|
|
|
409
554
|
def handle_purge(client: api_client.ApiClient, args: Any) -> Any:
|
|
@@ -417,20 +562,38 @@ def handle_purge(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
417
562
|
# The count is for the person confirming. --force skips the listing, which needs traces:read, so a key
|
|
418
563
|
# that may only write can still empty the Trash.
|
|
419
564
|
if not args.force:
|
|
420
|
-
listed = client
|
|
565
|
+
listed = _trash_listing(client)
|
|
421
566
|
count = len(listed.get("programs") or [])
|
|
422
567
|
if count == 0:
|
|
423
568
|
return {"purged": 0, "kept": 0}
|
|
424
569
|
noun = "program" if count == 1 else "programs"
|
|
425
570
|
provider_credentials.confirm(args, f"the {count} {noun} in the Trash for good", consequence)
|
|
426
|
-
return client
|
|
571
|
+
return _trash_write(client, "/api/v1/programs/trash/empty")
|
|
427
572
|
if args.program is None:
|
|
428
573
|
raise api_client.CliError("name the PROGRAM to purge, or pass --all to empty the Trash")
|
|
429
|
-
program_id, trashed =
|
|
574
|
+
program_id, trashed = resolve(client, args.program, old_server_remedy=_trash_remedy())
|
|
430
575
|
if trashed is False:
|
|
431
576
|
raise api_client.CliError(
|
|
432
577
|
f"{args.program} is live, and only a program in the Trash can be purged. "
|
|
433
578
|
f"Move it there first with `{top.hint_programs('trash', args.program)}`."
|
|
434
579
|
)
|
|
435
580
|
provider_credentials.confirm(args, f"the program {args.program} for good", consequence)
|
|
436
|
-
return client
|
|
581
|
+
return _trash_write(client, f"/api/v1/programs/{program_id}/purge")
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def _trash_listing(client: api_client.ApiClient) -> Any:
|
|
585
|
+
"""GET the Trash. On a server from before the Trash API it answers 404, which is the old-server signal."""
|
|
586
|
+
try:
|
|
587
|
+
return client.get("/api/v1/programs/trash")
|
|
588
|
+
except api_client.CliError as exc:
|
|
589
|
+
if exc.status_code == 404:
|
|
590
|
+
raise _old_server_refusal(_trash_remedy()) from exc
|
|
591
|
+
raise
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def _trash_write(client: api_client.ApiClient, path: str) -> Any:
|
|
595
|
+
try:
|
|
596
|
+
return client.post(path)
|
|
597
|
+
except api_client.CliError as exc:
|
|
598
|
+
_refuse_if_old(client, exc, _trash_remedy())
|
|
599
|
+
raise
|
|
@@ -18,7 +18,7 @@ from typing import Any, NoReturn
|
|
|
18
18
|
|
|
19
19
|
from ... import _deployments, deployment_runs
|
|
20
20
|
from ...deployment_metadata import DeploymentMetadataError
|
|
21
|
-
from .. import api_client, pagination
|
|
21
|
+
from .. import api_client, pagination, trace_render
|
|
22
22
|
from . import deployments as deployment_commands
|
|
23
23
|
from . import signatures
|
|
24
24
|
|
|
@@ -338,8 +338,20 @@ def render_deployments_table(deployments: list[dict[str, Any]]) -> str:
|
|
|
338
338
|
return "\n".join(lines)
|
|
339
339
|
|
|
340
340
|
|
|
341
|
+
def trace_program_cells(trace: dict[str, Any]) -> tuple[str, str]:
|
|
342
|
+
"""The PROGRAM and MODULE cells for one trace record.
|
|
343
|
+
|
|
344
|
+
PROGRAM is the name the user gave the program, or a dash when it has none.
|
|
345
|
+
MODULE is the root module's class, or a dash when the trace carries no
|
|
346
|
+
class. `trace_render.program_and_module` reads an older server's shape,
|
|
347
|
+
where `program_name` holds the class.
|
|
348
|
+
"""
|
|
349
|
+
name, module = trace_render.program_and_module(trace)
|
|
350
|
+
return name or "—", module or "—"
|
|
351
|
+
|
|
352
|
+
|
|
341
353
|
def render_traces_table(traces: list[dict[str, Any]]) -> str:
|
|
342
|
-
"""Return a colored TRACE / STATUS / PROGRAM / AGE table as a string.
|
|
354
|
+
"""Return a colored TRACE / STATUS / PROGRAM / MODULE / AGE table as a string.
|
|
343
355
|
|
|
344
356
|
Reused by the shell when typing `d.traces` at the REPL (via
|
|
345
357
|
`Trace._render_table`). Mirrors `render_deployments_table` so the
|
|
@@ -353,13 +365,13 @@ def render_traces_table(traces: list[dict[str, Any]]) -> str:
|
|
|
353
365
|
(
|
|
354
366
|
id_render(t["trace_id"]),
|
|
355
367
|
t.get("status", "—"),
|
|
356
|
-
_truncate(
|
|
368
|
+
*(_truncate(cell, 28) for cell in trace_program_cells(t)),
|
|
357
369
|
_age(t.get("start_time")),
|
|
358
370
|
)
|
|
359
371
|
for t in traces
|
|
360
372
|
]
|
|
361
373
|
|
|
362
|
-
headers = ("TRACE", "STATUS", "PROGRAM", "AGE")
|
|
374
|
+
headers = ("TRACE", "STATUS", "PROGRAM", "MODULE", "AGE")
|
|
363
375
|
widths = [max(len(h), max((len(r[i]) for r in rows), default=0)) for i, h in enumerate(headers)]
|
|
364
376
|
|
|
365
377
|
header_line = " ".join(h.ljust(widths[i]) for i, h in enumerate(headers))
|
|
@@ -370,6 +382,7 @@ def render_traces_table(traces: list[dict[str, Any]]) -> str:
|
|
|
370
382
|
_trace_status(r[1]).ljust(widths[1] + (len(_trace_status(r[1])) - len(r[1]))),
|
|
371
383
|
r[2].ljust(widths[2]),
|
|
372
384
|
r[3].ljust(widths[3]),
|
|
385
|
+
r[4].ljust(widths[4]),
|
|
373
386
|
]
|
|
374
387
|
lines.append(" ".join(cells))
|
|
375
388
|
return "\n".join(lines)
|
|
@@ -28,6 +28,16 @@ def _metadata_pair(value: str) -> tuple[str, str]:
|
|
|
28
28
|
return key, val
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
# The wait after which a class search with no program filter gets a hint.
|
|
32
|
+
MODULE_SLOW_HINT_SECONDS = 5.0
|
|
33
|
+
|
|
34
|
+
# The search-body keys that name a program, whichever spelling the flag took.
|
|
35
|
+
_PROGRAM_FILTER_KEYS = ("program_name", "program_id", "program_signature_hash")
|
|
36
|
+
|
|
37
|
+
# The clock the hint measures with, a seam for tests.
|
|
38
|
+
_monotonic = time.monotonic
|
|
39
|
+
|
|
40
|
+
|
|
31
41
|
def _add_list_args(p: ArgumentParser) -> None:
|
|
32
42
|
p.add_argument("--status", help="Filter by status (ok, error, incomplete, in_progress)")
|
|
33
43
|
p.add_argument(
|
|
@@ -49,7 +59,20 @@ def _add_list_args(p: ArgumentParser) -> None:
|
|
|
49
59
|
help="Filter by a metadata pair your program recorded; repeat for all-of. "
|
|
50
60
|
"This is how you find a trace by your own identifier",
|
|
51
61
|
)
|
|
52
|
-
p.add_argument(
|
|
62
|
+
p.add_argument(
|
|
63
|
+
"--program",
|
|
64
|
+
dest="program_name",
|
|
65
|
+
metavar="NAME",
|
|
66
|
+
help="Filter by the program's name, the one given with cmpnd.Program or `cmpnd programs create`",
|
|
67
|
+
)
|
|
68
|
+
p.add_argument(
|
|
69
|
+
"--module",
|
|
70
|
+
dest="module_class",
|
|
71
|
+
metavar="CLASS",
|
|
72
|
+
help="Filter by the root module's class (exact, e.g. ReAct). No index serves the class: "
|
|
73
|
+
"without --program or --hash it reads every trace in the time range, so it gets slower "
|
|
74
|
+
"as the range grows. Narrow it with --program, --hash or --since",
|
|
75
|
+
)
|
|
53
76
|
p.add_argument(
|
|
54
77
|
"--hash",
|
|
55
78
|
dest="program_signature_hash",
|
|
@@ -189,6 +212,7 @@ def handle_list(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
189
212
|
("q", "q"),
|
|
190
213
|
("model", "model"),
|
|
191
214
|
("program_name", "program_name"),
|
|
215
|
+
("module_class", "module_class"),
|
|
192
216
|
("deployment_id", "deployment_id"),
|
|
193
217
|
("tags", "tags"),
|
|
194
218
|
("start_time_from", "start_time_from"),
|
|
@@ -211,7 +235,22 @@ def handle_list(client: api_client.ApiClient, args: Any) -> Any:
|
|
|
211
235
|
body["start_time_from"] = _timespec.resolve_search_time(str(body["start_time_from"]))
|
|
212
236
|
if "start_time_to" in body:
|
|
213
237
|
body["start_time_to"] = _timespec.resolve_search_time(str(body["start_time_to"]), end_of_day=True)
|
|
238
|
+
started = _monotonic()
|
|
214
239
|
resp = client.post("/api/v1/traces/search", json=body)
|
|
240
|
+
# No index serves the class, so a class search with no program filter reads
|
|
241
|
+
# every trace in the range. Say what narrows it once the wait is long enough
|
|
242
|
+
# to notice, on stderr so stdout stays the response.
|
|
243
|
+
if (
|
|
244
|
+
"module_class" in body
|
|
245
|
+
and not any(key in body for key in _PROGRAM_FILTER_KEYS)
|
|
246
|
+
and _monotonic() - started > MODULE_SLOW_HINT_SECONDS
|
|
247
|
+
):
|
|
248
|
+
print(
|
|
249
|
+
"note: --module reads every trace in the time range when no program is given. "
|
|
250
|
+
"Consider filtering by program (--program or --hash) or a shorter range (--since) "
|
|
251
|
+
"for faster results.",
|
|
252
|
+
file=sys.stderr,
|
|
253
|
+
)
|
|
215
254
|
|
|
216
255
|
# A window the caller did not ask for has to be visible: read as an org
|
|
217
256
|
# figure, a windowed total is simply wrong, and nothing in the numbers says
|
|
@@ -8,6 +8,7 @@ inputs and expected outputs of one validation example.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import json
|
|
11
|
+
import re
|
|
11
12
|
from argparse import ArgumentParser, _SubParsersAction
|
|
12
13
|
from typing import Any
|
|
13
14
|
|
|
@@ -23,7 +24,7 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
23
24
|
create.add_argument(
|
|
24
25
|
"program_hash",
|
|
25
26
|
metavar="program",
|
|
26
|
-
help="Owning program: a program id (p_…), or a legacy signature hash "
|
|
27
|
+
help="Owning program: a program name, a program id (p_…), or a legacy signature hash "
|
|
27
28
|
"(16-hex, as `cmpnd signatures list` shows)",
|
|
28
29
|
)
|
|
29
30
|
create.add_argument(
|
|
@@ -33,7 +34,7 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
33
34
|
create.set_defaults(func=handle_create)
|
|
34
35
|
|
|
35
36
|
ls = sub.add_parser("list", help="List a program's validation sets")
|
|
36
|
-
ls.add_argument("program", help="Program id (p_…), or a legacy signature hash (16-hex)")
|
|
37
|
+
ls.add_argument("program", help="Program name, program id (p_…), or a legacy signature hash (16-hex)")
|
|
37
38
|
pagination.add_args(ls, with_page_aliases=False, default_limit=50)
|
|
38
39
|
ls.set_defaults(func=handle_list)
|
|
39
40
|
|
|
@@ -75,15 +76,39 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
75
76
|
edelete.set_defaults(func=handle_entries_delete)
|
|
76
77
|
|
|
77
78
|
|
|
79
|
+
# A legacy program signature hash in its display form: 16 hex digits.
|
|
80
|
+
_LEGACY_HASH = re.compile(r"[0-9a-fA-F]{16}")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _program(client: api_client.ApiClient, ref: str) -> str:
|
|
84
|
+
"""The value the valsets routes take for ``ref``.
|
|
85
|
+
|
|
86
|
+
A p_ id and a legacy 16-hex hash pass unchanged. Anything else is a program
|
|
87
|
+
name, which resolves to its p_ id without making a program. A program whose
|
|
88
|
+
name is 16 hex digits is reached by its p_ id.
|
|
89
|
+
"""
|
|
90
|
+
from . import programs
|
|
91
|
+
|
|
92
|
+
if _LEGACY_HASH.fullmatch(ref):
|
|
93
|
+
return ref
|
|
94
|
+
program_id, _ = programs.resolve(
|
|
95
|
+
client,
|
|
96
|
+
ref,
|
|
97
|
+
old_server_remedy="Name the program by its 16-hex signature hash, as `cmpnd signatures list` shows, "
|
|
98
|
+
"or upgrade the server.",
|
|
99
|
+
)
|
|
100
|
+
return program_id
|
|
101
|
+
|
|
102
|
+
|
|
78
103
|
def handle_create(client: api_client.ApiClient, args: Any) -> Any:
|
|
79
104
|
# One generator (cmpnd._valsets.default_name) for both the CLI and the
|
|
80
105
|
# Client, so the collision-avoiding suffix can't drift between them.
|
|
81
106
|
name = args.name or _valsets.default_name()
|
|
82
|
-
return client.post("/api/v1/valsets", json={"program": args.program_hash, "name": name})
|
|
107
|
+
return client.post("/api/v1/valsets", json={"program": _program(client, args.program_hash), "name": name})
|
|
83
108
|
|
|
84
109
|
|
|
85
110
|
def handle_list(client: api_client.ApiClient, args: Any) -> Any:
|
|
86
|
-
params = {"program": args.program, **pagination.wire(args, "limit_offset", as_str=True)}
|
|
111
|
+
params = {"program": _program(client, args.program), **pagination.wire(args, "limit_offset", as_str=True)}
|
|
87
112
|
return client.get("/api/v1/valsets", params=params)
|
|
88
113
|
|
|
89
114
|
|