classifyre-cli 0.6.9__tar.gz → 0.6.11__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.
- classifyre_cli-0.6.11/.turbo/turbo-build.log +3 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/PKG-INFO +1 -1
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/package.json +1 -1
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/pyproject.toml +21 -2
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/scripts/generate_models.py +2 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom/detector.py +1 -1
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom/runners/__init__.py +2 -0
- classifyre_cli-0.6.11/src/detectors/custom/runners/_decision.py +494 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom/runners/_factory.py +5 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom/runners/_llm.py +29 -208
- classifyre_cli-0.6.11/src/detectors/custom/runners/_provider_call.py +233 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom_detector/results.py +2 -4
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/engine_version.py +2 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/main.py +12 -2
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/models/generated_detectors.py +160 -14
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/outputs/rest.py +16 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/file_parser.py +77 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/redaction.py +8 -8
- classifyre_cli-0.6.11/tests/detectors/custom/test_decision_runner.py +665 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/custom/test_llm_runner.py +12 -12
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/test_custom_detector_examples_runtime.py +3 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_outputs.py +57 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_scan_cache.py +30 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/utils/test_file_parser.py +90 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/uv.lock +588 -579
- classifyre_cli-0.6.9/.turbo/turbo-build.log +0 -3
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/.gitignore +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/.python-version +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/README.md +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/main.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/scripts/generate_runtime_packages.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/scripts/generate_sdk_completions.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/augmentation/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/augmentation/contract.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/augmentation/preview.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/augmentation/runner.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/augmentation/sdk.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/augmentation/session.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/config.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/base.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/broken_links/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/broken_links/detector.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/config.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom/extractor.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom/runners/_base.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom/runners/_custom_detector.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom/runners/_gliner2.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom/runners/_image_classification.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom/runners/_object_detection.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom/runners/_regex.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom/runners/_tag.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom/runners/_text_classification.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom/trainer.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom_detector/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom_detector/contract.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom_detector/files.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom_detector/preview.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom_detector/runner.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom_detector/sdk.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/custom_detector/session.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/dependencies.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/errors.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/pii/detector.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/secrets/detector.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/threat/code_security_detector.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/detectors/threat/yara_detector.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/file_evaluation/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/file_evaluation/asset_fixture.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/file_evaluation/runner.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/graph/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/graph/edges.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/models/generated_input.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/models/generated_single_asset_scan_results.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/notebook/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/notebook/child.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/notebook/cli.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/notebook/cohort.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/notebook/contract.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/notebook/execute.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/notebook/files.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/notebook/groups.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/notebook/outputs.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/notebook/packages.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/notebook/protocol.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/notebook/redact.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/notebook/scaffold.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/notebook/sdk.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/notebook/serialize.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/outputs/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/outputs/base.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/outputs/console.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/outputs/factory.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/outputs/file.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/pipeline/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/pipeline/asset_payload_server.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/pipeline/content_provider.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/pipeline/detector_breaker.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/pipeline/detector_pipeline.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/pipeline/parsed_content_provider.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/pipeline/payload_window.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/pipeline/scan_cache.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/pipeline/text_artifact.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/pipeline/worker_pool.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/asset_metadata.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/atlassian_common.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/azure_blob_storage/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/azure_blob_storage/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/base.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/confluence/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/confluence/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/custom/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/custom/env.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/custom/runner.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/custom/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/databricks/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/databricks/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/delta_lake/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/delta_lake/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/dependencies.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/dremio/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/dremio/client.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/dremio/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/dropbox/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/dropbox/auth.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/dropbox/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/elasticsearch/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/elasticsearch/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/email/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/email/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/git/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/git/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/google_cloud_storage/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/google_cloud_storage/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/google_workspace/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/google_workspace/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/hive/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/hive/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/hugging_face/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/hugging_face/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/iceberg/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/iceberg/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/jira/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/jira/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/kafka/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/kafka/rest.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/kafka/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/lakehouse_base.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/local_folder/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/local_folder/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/meilisearch/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/meilisearch/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/microsoft_365/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/microsoft_365/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/mongodb/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/mongodb/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/mssql/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/mssql/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/mysql/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/mysql/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/neo4j/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/neo4j/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/notion/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/notion/client.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/notion/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/object_storage/base.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/opensearch/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/opensearch/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/oracle/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/oracle/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/postgresql/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/postgresql/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/powerbi/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/powerbi/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/recipe_normalizer.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/reddit/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/reddit/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/s3_client.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/s3_compatible_storage/README.md +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/s3_compatible_storage/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/s3_compatible_storage/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/sandbox/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/sandbox/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/search_engine_base.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/servicedesk/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/servicedesk/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/slack/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/slack/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/snowflake/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/snowflake/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/sqlite/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/sqlite/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/tableau/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/tableau/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/tabular_base.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/tabular_utils.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/wordpress/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/wordpress/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/youtube/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/sources/youtube/source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/telemetry.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/archive_extraction.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/content_extraction.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/dependency_groups.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/embedded_files.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/external_video.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/file_metadata.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/file_to_images.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/hashing.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/legacy_office.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/payload.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/range_reader.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/resources.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/sampling_cursor.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/sampling_frontier.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/source_files.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/sql_lineage.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/torch_runtime.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/transcription.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/urn.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/uv_sync.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/validation.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/src/utils/video_processing.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/_lakehouse_fakes.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/conftest.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/broken_links/test_broken_links_detector.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/conftest.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/custom/conftest.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/custom/test_gliner2_classification.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/custom/test_invoice_extraction.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/custom/test_pipeline_integration.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/custom/test_regex_runner.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/custom/test_runner_hardening.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/custom/test_transformer_runners.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/pii/conftest.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/pii/sample_invoice.pdf +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/pii/test_pii_chunking.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/pii/test_pii_detector.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/pii/test_pii_detector_extended.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/pii/test_pii_regional_entities.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/pii/test_pii_severity.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/secrets/test_secrets_detector.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/secrets/test_secrets_detector_extended.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/test_base_detector.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/test_custom_detector_session.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/test_detector_catalog_commercial.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/test_detector_pipeline_types.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/test_detector_schema_examples.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/test_detector_types.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/test_phase2_detectors.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/test_registry.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/test_tag_runner.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/threat/test_code_security_detector.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/detectors/threat/test_yara_detector.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/integration/test_wordpress_broken_links_detector.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/integration/test_wordpress_links_assets.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/pipeline/test_detector_breaker.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/pipeline/test_detector_outcomes.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/pipeline/test_detector_pipeline.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/pipeline/test_worker_pool.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_arrow_ipc_payloads.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_assets_metadata_catalog.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_augmentation_contract.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_augmentation_edges.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_augmentation_patch.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_augmentation_scan_cache.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_augmentation_sdk.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_augmentation_session.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_augmentation_tags_end_to_end.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_automatic_new_first.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_azure_blob_storage_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_base_source_attachment.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_base_source_sampling.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_byte_valued_fields.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_config.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_confluence_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_custom_detector_end_to_end.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_custom_detector_evaluation.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_custom_detector_notebook.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_custom_detector_templates.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_custom_extractor.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_custom_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_custom_source_relationships.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_custom_source_tags_end_to_end.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_databricks_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_delta_lake_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_dependency_groups.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_detector_scope.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_dremio_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_dropbox_auth_command.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_dropbox_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_elasticsearch_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_email_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_file_evaluation_runner.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_git_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_google_cloud_storage_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_google_workspace_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_graph_edges.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_hashing.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_hive_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_hugging_face_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_iceberg_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_jira_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_kafka_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_lineage_loss_reporting.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_local_folder_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_main.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_meilisearch_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_microsoft_365_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_mongodb_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_mssql_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_mysql_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_neo4j_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_notebook_asset_entity.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_notebook_asset_extract.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_notebook_asset_tags.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_notebook_cohort.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_notebook_execute.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_notebook_files.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_notebook_groups.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_notebook_log_levels.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_notebook_packages.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_notebook_query_assets.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_notebook_sdk_exports.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_notebook_serialize.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_notebook_templates.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_notion_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_object_storage_download_efficiency.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_opensearch_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_oracle_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_payload_window.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_payload_window_parquet.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_postgresql_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_powerbi_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_range_reading.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_recipe_normalizer.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_reddit_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_rest_backpressure.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_runtime_packages.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_s3_compatible_storage_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_sampling_automatic.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_sampling_cursor.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_sampling_frontier.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_sandbox_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_sdk_completions.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_secret_redaction.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_semantic_declarations.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_servicedesk_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_slack_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_snowflake_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_source_dependency_groups.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_source_strategy_conformance.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_sqlite_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_tableau_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_tabular_automatic_sampling.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_tabular_lineage.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_tabular_utils.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_torch_runtime.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_urn.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_uv_sync.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_wordpress_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_youtube_source.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/test_youtube_source_integration.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/utils/test_archive_extraction.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/utils/test_content_extraction.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/utils/test_embedded_files.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/utils/test_file_metadata.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/utils/test_file_to_images.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/utils/test_legacy_office.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/utils/test_payload.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/utils/test_transcription.py +0 -0
- {classifyre_cli-0.6.9 → classifyre_cli-0.6.11}/tests/utils/test_video_processing.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "classifyre-cli"
|
|
3
|
-
version = "0.6.
|
|
3
|
+
version = "0.6.11"
|
|
4
4
|
description = "Classifyre CLI — scan and classify unstructured data sources"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
# Upper bound is load-bearing: onnxruntime publishes no cp315 wheels (notably
|
|
@@ -127,7 +127,11 @@ lineage = [
|
|
|
127
127
|
"sqlglot>=25.0.0",
|
|
128
128
|
]
|
|
129
129
|
llm = [
|
|
130
|
-
|
|
130
|
+
# 1.104.2 is the first release with litellm.decisions(), which the DECISION
|
|
131
|
+
# detector calls: one SDK entry point for every Decisions API / System One
|
|
132
|
+
# provider (OpenAI, TypeSafe Jev, Cloudflare Clef, Perplexity, OpenRouter,
|
|
133
|
+
# Strands Decider and any self-hosted /v1/systemone server).
|
|
134
|
+
"litellm>=1.104.2",
|
|
131
135
|
# Pure-wheel PDF renderer (permissive license, no system binaries) used to
|
|
132
136
|
# rasterise PDF pages to images for vision-capable LLM detectors.
|
|
133
137
|
"pypdfium2>=4.30.0",
|
|
@@ -479,6 +483,21 @@ package = true
|
|
|
479
483
|
# distribution declared by our video and OCR groups. Installing both would
|
|
480
484
|
# overwrite the same cv2 namespace and add GUI libraries to CPU-only containers.
|
|
481
485
|
exclude-dependencies = ["opencv-python"]
|
|
486
|
+
# Two transitive dependencies whose latest release breaks the package that
|
|
487
|
+
# pulls it in, while that package's own requirement still allows it. Both
|
|
488
|
+
# failures were silent in a scan: images and scans came back with no text, and
|
|
489
|
+
# every audio and video file failed. Lift a bound when its consumer catches up.
|
|
490
|
+
constraint-dependencies = [
|
|
491
|
+
# faster-whisper 1.2.1 (its latest) decodes with
|
|
492
|
+
# av.open(..., metadata_errors="ignore"), and so does utils/transcription.py.
|
|
493
|
+
# PyAV 19 removed the argument: every transcription raised TypeError.
|
|
494
|
+
"av>=11,<19",
|
|
495
|
+
# Docling (through 2.135) imports rapidocr.utils.model_resolver.PP_OCRV6_LANGS
|
|
496
|
+
# while building its RapidOCR engine. rapidocr 3.10.0 no longer has it;
|
|
497
|
+
# Docling catches the ImportError, logs "No OCR engine found" and converts
|
|
498
|
+
# every image and scanned PDF to an empty document.
|
|
499
|
+
"rapidocr>=3.9.1,<3.10",
|
|
500
|
+
]
|
|
482
501
|
# Every platform we ship must resolve to versions with
|
|
483
502
|
# usable wheels. Without this, uv happily locks versions that are missing a
|
|
484
503
|
# platform — e.g. deltalake 1.6.1 published no win_amd64 wheel, which broke the
|
|
@@ -16,6 +16,7 @@ _PIPELINE_TYPE_DEFAULTS: dict[str, str] = {
|
|
|
16
16
|
"LLMPipelineSchema": "LLM",
|
|
17
17
|
"TagPipelineSchema": "TAG",
|
|
18
18
|
"CodeDetectorPipelineSchema": "CODE_DETECTOR",
|
|
19
|
+
"DecisionPipelineSchema": "DECISION",
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
# Pipeline schema classes whose `severity` field has a string default from JSON schema
|
|
@@ -29,6 +30,7 @@ _SEVERITY_ENUM_DEFAULT_CLASSES = {
|
|
|
29
30
|
"TextClassificationPipelineSchema": "info",
|
|
30
31
|
"TagPipelineSchema": "medium",
|
|
31
32
|
"CodeDetectorPipelineSchema": "medium",
|
|
33
|
+
"DecisionPipelineSchema": "medium",
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
|
|
@@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
|
|
|
19
19
|
class CustomDetector(BaseDetector):
|
|
20
20
|
"""Schema-driven detector backed by a pluggable runner.
|
|
21
21
|
|
|
22
|
-
GLINER2 | REGEX | LLM | transformer run over content. TAG does not: it
|
|
22
|
+
GLINER2 | REGEX | LLM | DECISION | transformer run over content. TAG does not: it
|
|
23
23
|
produces findings only from tags a CUSTOM connector attached to an asset,
|
|
24
24
|
which the pipeline applies through ``runner.tag_finding``.
|
|
25
25
|
"""
|
|
@@ -14,6 +14,7 @@ from ._base import (
|
|
|
14
14
|
_resolve_pipeline_severity,
|
|
15
15
|
)
|
|
16
16
|
from ._custom_detector import CustomDetectorRunner
|
|
17
|
+
from ._decision import DecisionRunner
|
|
17
18
|
from ._factory import create_runner
|
|
18
19
|
from ._gliner2 import (
|
|
19
20
|
GLiNER2Runner,
|
|
@@ -37,6 +38,7 @@ __all__ = [
|
|
|
37
38
|
"_TEXT_CONTENT_TYPES",
|
|
38
39
|
"BaseRunner",
|
|
39
40
|
"CustomDetectorRunner",
|
|
41
|
+
"DecisionRunner",
|
|
40
42
|
"GLiNER2Runner",
|
|
41
43
|
"ImageClassificationRunner",
|
|
42
44
|
"LLMRunner",
|
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
"""Decision pipeline runner — typed questions answered with probabilities.
|
|
2
|
+
|
|
3
|
+
A decision model (the Decisions API and System One family) does not write
|
|
4
|
+
text. It is handed the content and a list of questions and returns, for each,
|
|
5
|
+
a number: the probability that a yes/no condition holds, which option of a
|
|
6
|
+
fixed list fits, or where the content sits on a scale.
|
|
7
|
+
|
|
8
|
+
Every provider is reached through ``litellm.decisions``, which speaks both wire
|
|
9
|
+
formats and translates between them, so the model name alone picks the API.
|
|
10
|
+
This runner always sends the System One shape (``state`` plus a map of named
|
|
11
|
+
questions) because it is the one every provider accepts.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
from collections.abc import Mapping
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from datetime import UTC, datetime
|
|
21
|
+
from typing import Any, Literal
|
|
22
|
+
|
|
23
|
+
# Quiet litellm's import-time provider preload warnings (bedrock/sagemaker need
|
|
24
|
+
# botocore, which we don't install) before the library is ever imported.
|
|
25
|
+
os.environ.setdefault("LITELLM_LOG", "ERROR")
|
|
26
|
+
|
|
27
|
+
from ....models.generated_detectors import (
|
|
28
|
+
DecisionPipelineSchema,
|
|
29
|
+
DecisionQuestion,
|
|
30
|
+
DecisionQuestionOption,
|
|
31
|
+
DecisionQuestionType,
|
|
32
|
+
Severity,
|
|
33
|
+
)
|
|
34
|
+
from ....models.generated_single_asset_scan_results import (
|
|
35
|
+
DetectionResult,
|
|
36
|
+
DetectorType,
|
|
37
|
+
)
|
|
38
|
+
from ...dependencies import require_module
|
|
39
|
+
from ...errors import ProviderRefusedError
|
|
40
|
+
from ._base import _TEXT_CONTENT_TYPES, BaseRunner
|
|
41
|
+
from ._provider_call import _COMPLETION_TIMEOUT_SECONDS, call_with_backoff
|
|
42
|
+
|
|
43
|
+
logger = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
RUNNER = "DECISION"
|
|
46
|
+
|
|
47
|
+
# A yes/no answer is a finding once "yes" is more likely than not. A choice or
|
|
48
|
+
# a score always lands on exactly one answer, so its default keeps every one:
|
|
49
|
+
# the finding carries the model's confidence, and a threshold applied before
|
|
50
|
+
# recording would only hide that the model was asked.
|
|
51
|
+
_DEFAULT_YES_NO_THRESHOLD = 0.5
|
|
52
|
+
_DEFAULT_PICK_THRESHOLD = 0.0
|
|
53
|
+
|
|
54
|
+
_MIN_OPTIONS = 2
|
|
55
|
+
_MAX_CHOICE_OPTIONS = 255
|
|
56
|
+
_MAX_SCORE_LEVELS = 10
|
|
57
|
+
|
|
58
|
+
_MATCHED_CONTENT_CHARS = 320
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class DecisionCallError(RuntimeError):
|
|
62
|
+
"""Provider call failed after retries.
|
|
63
|
+
|
|
64
|
+
Carries only a string message so it survives pickling across the detector
|
|
65
|
+
worker-pool process boundary; the pipeline records the detector's outcome
|
|
66
|
+
as ERROR instead of a silent empty result.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class DecisionProviderRefusedError(DecisionCallError, ProviderRefusedError):
|
|
71
|
+
"""The provider refused in a way no retry can fix (quota, auth, billing).
|
|
72
|
+
|
|
73
|
+
Raised on the first such response, never after retries. The pipeline
|
|
74
|
+
disables the detector for the rest of the run when it sees one.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class DecisionRunner(BaseRunner):
|
|
79
|
+
"""Decision detector — asks a decision model typed questions about content."""
|
|
80
|
+
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
schema: DecisionPipelineSchema,
|
|
84
|
+
detector_key: str = "",
|
|
85
|
+
detector_name: str = "",
|
|
86
|
+
) -> None:
|
|
87
|
+
self._schema = schema
|
|
88
|
+
self._detector_key = detector_key
|
|
89
|
+
self._detector_name = detector_name
|
|
90
|
+
self._model = schema.model.strip()
|
|
91
|
+
_check_questions(detector_key, schema.questions)
|
|
92
|
+
|
|
93
|
+
self._litellm = require_module("litellm", "llm", ["llm"])
|
|
94
|
+
if not callable(getattr(self._litellm, "decisions", None)):
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f"Decision detector '{detector_key}' needs litellm 1.104.2 or newer: "
|
|
97
|
+
"the installed version has no decisions API."
|
|
98
|
+
)
|
|
99
|
+
self._litellm.suppress_debug_info = True
|
|
100
|
+
logging.getLogger("LiteLLM").setLevel(logging.ERROR)
|
|
101
|
+
self._wire_questions = {q.name: _wire_question(q) for q in schema.questions}
|
|
102
|
+
|
|
103
|
+
def run(self, text: str) -> None: # type: ignore[override] # pragma: no cover
|
|
104
|
+
raise NotImplementedError("DecisionRunner uses detect() directly")
|
|
105
|
+
|
|
106
|
+
def detect(self, content: str | bytes, content_type: str) -> list[DetectionResult]:
|
|
107
|
+
if isinstance(content, bytes) or content_type not in _TEXT_CONTENT_TYPES:
|
|
108
|
+
return []
|
|
109
|
+
text = content.strip()
|
|
110
|
+
if not text:
|
|
111
|
+
return []
|
|
112
|
+
snippet = text[: self._schema.content_limit or 8000]
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
response = self._decide(snippet)
|
|
116
|
+
answers = _answers_of(response)
|
|
117
|
+
except DecisionProviderRefusedError:
|
|
118
|
+
# Already names the detector, the model and the refusal. No stack
|
|
119
|
+
# trace: it is expected, and it will be the same on every asset.
|
|
120
|
+
raise
|
|
121
|
+
except Exception as exc:
|
|
122
|
+
logger.error(
|
|
123
|
+
"decision detector error (detector=%s, model=%s): %s",
|
|
124
|
+
self._detector_key,
|
|
125
|
+
self._model,
|
|
126
|
+
exc,
|
|
127
|
+
exc_info=True,
|
|
128
|
+
)
|
|
129
|
+
# Propagate instead of returning [] — an empty result here is
|
|
130
|
+
# indistinguishable from "genuinely found nothing" and would let
|
|
131
|
+
# the pipeline record this detector OK on an asset it never saw.
|
|
132
|
+
raise DecisionCallError(
|
|
133
|
+
f"Decision provider call failed for detector '{self._detector_key}' "
|
|
134
|
+
f"(model={self._model}): {exc}"
|
|
135
|
+
) from None
|
|
136
|
+
|
|
137
|
+
return self._results_from_answers(snippet, answers, _served_model(response))
|
|
138
|
+
|
|
139
|
+
# ── Provider call ────────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
def _decide(self, snippet: str) -> Any:
|
|
142
|
+
schema = self._schema
|
|
143
|
+
secrets = _string_map(schema.secrets)
|
|
144
|
+
headers = _string_map(schema.extra_headers)
|
|
145
|
+
|
|
146
|
+
def decide() -> Any:
|
|
147
|
+
return self._litellm.decisions(
|
|
148
|
+
model=self._model,
|
|
149
|
+
state=snippet,
|
|
150
|
+
questions=self._wire_questions,
|
|
151
|
+
# None lets litellm fall back to the provider's own environment
|
|
152
|
+
# variable, which is how a self-hosted server without a key and
|
|
153
|
+
# a deployment-wide key both work.
|
|
154
|
+
api_key=secrets.get("api_key") or None,
|
|
155
|
+
api_base=_plain(schema.api_base) or None,
|
|
156
|
+
extra_headers=headers or None,
|
|
157
|
+
timeout=_COMPLETION_TIMEOUT_SECONDS,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return call_with_backoff(
|
|
161
|
+
decide,
|
|
162
|
+
litellm=self._litellm,
|
|
163
|
+
schema=schema,
|
|
164
|
+
kind="decision",
|
|
165
|
+
detector_key=self._detector_key,
|
|
166
|
+
model=self._model,
|
|
167
|
+
refused=lambda refusal, exc: DecisionProviderRefusedError(
|
|
168
|
+
f"Decision provider refused detector '{self._detector_key}' "
|
|
169
|
+
f"(model={self._model}, {refusal}): {exc}"
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# ── Answers → findings ───────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
def _results_from_answers(
|
|
176
|
+
self,
|
|
177
|
+
snippet: str,
|
|
178
|
+
answers: Mapping[str, Mapping[str, Any]],
|
|
179
|
+
served_model: str | None,
|
|
180
|
+
) -> list[DetectionResult]:
|
|
181
|
+
schema = self._schema
|
|
182
|
+
default_severity = schema.severity if schema.severity is not None else Severity.medium
|
|
183
|
+
|
|
184
|
+
verdicts: list[_Verdict] = []
|
|
185
|
+
unanswered: list[str] = []
|
|
186
|
+
for question in schema.questions:
|
|
187
|
+
answer = answers.get(question.name)
|
|
188
|
+
verdict = _verdict(question, answer) if answer is not None else None
|
|
189
|
+
if verdict is None:
|
|
190
|
+
unanswered.append(question.name)
|
|
191
|
+
else:
|
|
192
|
+
verdicts.append(verdict)
|
|
193
|
+
|
|
194
|
+
if unanswered:
|
|
195
|
+
# A model may decline one question and answer the rest. Said out
|
|
196
|
+
# loud, because a declined question otherwise reads as "no".
|
|
197
|
+
logger.warning(
|
|
198
|
+
"%s: the model returned no usable answer for %d question(s): %s",
|
|
199
|
+
self._detector_key,
|
|
200
|
+
len(unanswered),
|
|
201
|
+
", ".join(unanswered[:10]),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Every finding carries the whole set of answers from this call, in the
|
|
205
|
+
# shape the extraction view already renders, so a reviewer sees what
|
|
206
|
+
# the model said to each question and not only the one that fired.
|
|
207
|
+
extracted = {
|
|
208
|
+
"entities": {},
|
|
209
|
+
"classification": {v.question.name: v.summary() for v in verdicts},
|
|
210
|
+
"metadata": {"runner": RUNNER, "model": served_model or self._model},
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
results: list[DetectionResult] = []
|
|
214
|
+
suppressed: list[tuple[str, float]] = []
|
|
215
|
+
for verdict in verdicts:
|
|
216
|
+
if verdict.outcome == "suppressed":
|
|
217
|
+
suppressed.append((verdict.finding_type, verdict.certainty))
|
|
218
|
+
if verdict.outcome != "finding":
|
|
219
|
+
continue
|
|
220
|
+
results.append(
|
|
221
|
+
DetectionResult(
|
|
222
|
+
detector_type=DetectorType.CUSTOM,
|
|
223
|
+
finding_type=verdict.finding_type,
|
|
224
|
+
category="CLASSIFICATION",
|
|
225
|
+
severity=verdict.severity or verdict.question.severity or default_severity,
|
|
226
|
+
confidence=verdict.certainty,
|
|
227
|
+
matched_content=snippet[:_MATCHED_CONTENT_CHARS],
|
|
228
|
+
location=None,
|
|
229
|
+
custom_detector_key=self._detector_key,
|
|
230
|
+
custom_detector_name=self._detector_name,
|
|
231
|
+
detected_at=datetime.now(UTC),
|
|
232
|
+
metadata={
|
|
233
|
+
"runner": RUNNER,
|
|
234
|
+
"model": self._model,
|
|
235
|
+
"question": verdict.question.name,
|
|
236
|
+
"question_type": verdict.question.type.value,
|
|
237
|
+
"instructions": verdict.question.instructions,
|
|
238
|
+
"label": verdict.finding_type,
|
|
239
|
+
"answer": verdict.answer,
|
|
240
|
+
**verdict.detail,
|
|
241
|
+
"input": "text",
|
|
242
|
+
},
|
|
243
|
+
extracted_data=extracted,
|
|
244
|
+
extraction_method=RUNNER,
|
|
245
|
+
)
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
if suppressed:
|
|
249
|
+
logger.warning(
|
|
250
|
+
"%s: thresholds discarded %d model answer(s): %s%s. The model answered — "
|
|
251
|
+
"lower the question's threshold (0 records every answer with its "
|
|
252
|
+
"confidence, to filter downstream) if this is not what you meant.",
|
|
253
|
+
self._detector_key,
|
|
254
|
+
len(suppressed),
|
|
255
|
+
", ".join(f"{label}={score:.2f}" for label, score in suppressed[:5]),
|
|
256
|
+
" and more" if len(suppressed) > 5 else "",
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
results.sort(key=lambda r: r.confidence, reverse=True)
|
|
260
|
+
return results
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
# ── Questions on the wire ────────────────────────────────────────────────────
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _check_questions(detector_key: str, questions: list[DecisionQuestion]) -> None:
|
|
267
|
+
"""Refuse a question set no provider would accept, before any call is paid for."""
|
|
268
|
+
seen: set[str] = set()
|
|
269
|
+
for question in questions:
|
|
270
|
+
if question.name in seen:
|
|
271
|
+
raise ValueError(
|
|
272
|
+
f"Decision detector '{detector_key}' has two questions named '{question.name}'."
|
|
273
|
+
)
|
|
274
|
+
seen.add(question.name)
|
|
275
|
+
if question.type == DecisionQuestionType.yes_no:
|
|
276
|
+
continue
|
|
277
|
+
options = question.options or []
|
|
278
|
+
limit = (
|
|
279
|
+
_MAX_CHOICE_OPTIONS
|
|
280
|
+
if question.type == DecisionQuestionType.choice
|
|
281
|
+
else _MAX_SCORE_LEVELS
|
|
282
|
+
)
|
|
283
|
+
if not _MIN_OPTIONS <= len(options) <= limit:
|
|
284
|
+
raise ValueError(
|
|
285
|
+
f"Decision detector '{detector_key}': {question.type.value} question "
|
|
286
|
+
f"'{question.name}' needs {_MIN_OPTIONS} to {limit} options, got {len(options)}."
|
|
287
|
+
)
|
|
288
|
+
labels = [option.label for option in options]
|
|
289
|
+
if len(set(labels)) != len(labels):
|
|
290
|
+
raise ValueError(
|
|
291
|
+
f"Decision detector '{detector_key}': question '{question.name}' "
|
|
292
|
+
"repeats an option label."
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _wire_question(question: DecisionQuestion) -> dict[str, Any]:
|
|
297
|
+
"""One question in the System One shape litellm forwards or translates."""
|
|
298
|
+
if question.type == DecisionQuestionType.yes_no:
|
|
299
|
+
return {"type": "noul", "instructions": question.instructions}
|
|
300
|
+
options = question.options or []
|
|
301
|
+
if question.type == DecisionQuestionType.choice:
|
|
302
|
+
return {
|
|
303
|
+
"type": "choice",
|
|
304
|
+
"instructions": question.instructions,
|
|
305
|
+
# A described option is sent with its description. An undescribed
|
|
306
|
+
# one repeats its label: null is legal for hosted providers, but a
|
|
307
|
+
# self-hosted server is not obliged to accept it.
|
|
308
|
+
"criteria": {option.label: option.description or option.label for option in options},
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
"type": "score",
|
|
312
|
+
"instructions": question.instructions,
|
|
313
|
+
"criteria": [_level_text(option) for option in options],
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _level_text(option: DecisionQuestionOption) -> str:
|
|
318
|
+
return f"{option.label}: {option.description}" if option.description else option.label
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
# ── Answers off the wire ─────────────────────────────────────────────────────
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
@dataclass(frozen=True)
|
|
325
|
+
class _Verdict:
|
|
326
|
+
"""What the model said to one question, and what that means as a finding.
|
|
327
|
+
|
|
328
|
+
``outcome`` is ``finding`` when the answer is recorded, ``quiet`` when it is
|
|
329
|
+
the expected answer nobody asked to hear about (a "no", or an option with
|
|
330
|
+
``report: false``), and ``suppressed`` when it would have been recorded but
|
|
331
|
+
fell below the question's threshold.
|
|
332
|
+
"""
|
|
333
|
+
|
|
334
|
+
question: DecisionQuestion
|
|
335
|
+
answer: str
|
|
336
|
+
certainty: float
|
|
337
|
+
finding_type: str
|
|
338
|
+
outcome: Literal["finding", "quiet", "suppressed"]
|
|
339
|
+
severity: Severity | None = None
|
|
340
|
+
detail: dict[str, Any] = field(default_factory=dict)
|
|
341
|
+
|
|
342
|
+
def summary(self) -> dict[str, Any]:
|
|
343
|
+
"""The answer as one row of the extraction view: label plus confidence."""
|
|
344
|
+
return {"label": self.answer, "confidence": self.certainty, **self.detail}
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _verdict(question: DecisionQuestion, answer: Mapping[str, Any]) -> _Verdict | None:
|
|
348
|
+
"""Read one answer; None when the model did not answer in the asked type."""
|
|
349
|
+
if question.type == DecisionQuestionType.yes_no:
|
|
350
|
+
raw = _number(answer.get("noul"))
|
|
351
|
+
if raw is None:
|
|
352
|
+
return None
|
|
353
|
+
probability = _unit(raw)
|
|
354
|
+
threshold = (
|
|
355
|
+
question.threshold if question.threshold is not None else _DEFAULT_YES_NO_THRESHOLD
|
|
356
|
+
)
|
|
357
|
+
said_yes = probability >= threshold
|
|
358
|
+
return _Verdict(
|
|
359
|
+
question,
|
|
360
|
+
answer="yes" if said_yes else "no",
|
|
361
|
+
# How sure the model is of the answer as stated, so a "no" at 0.2
|
|
362
|
+
# reads as 80% no rather than 20% of something.
|
|
363
|
+
certainty=probability if said_yes else _unit(1.0 - probability),
|
|
364
|
+
finding_type=question.name,
|
|
365
|
+
# A "no" is never the finding: the question is asked so "yes" is.
|
|
366
|
+
outcome="finding" if said_yes else "quiet",
|
|
367
|
+
detail={"probability": probability},
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
options = question.options or []
|
|
371
|
+
probabilities = _number_map(answer.get("probabilities"))
|
|
372
|
+
threshold = question.threshold if question.threshold is not None else _DEFAULT_PICK_THRESHOLD
|
|
373
|
+
|
|
374
|
+
if question.type == DecisionQuestionType.choice:
|
|
375
|
+
raw_choice = answer.get("choice")
|
|
376
|
+
if raw_choice is None:
|
|
377
|
+
return None
|
|
378
|
+
label = _choice_label(raw_choice)
|
|
379
|
+
option = next((o for o in options if o.label == label), None)
|
|
380
|
+
if option is None:
|
|
381
|
+
# The model named something that was never offered. Recording it
|
|
382
|
+
# would invent a finding type the detector does not define.
|
|
383
|
+
return None
|
|
384
|
+
confidence = _number(answer.get("confidence"))
|
|
385
|
+
certainty = _unit(confidence if confidence is not None else probabilities.get(label, 0.0))
|
|
386
|
+
return _Verdict(
|
|
387
|
+
question,
|
|
388
|
+
answer=option.label,
|
|
389
|
+
certainty=certainty,
|
|
390
|
+
finding_type=f"{question.name}:{option.label}",
|
|
391
|
+
outcome=_pick_outcome(option, certainty, threshold),
|
|
392
|
+
severity=option.severity,
|
|
393
|
+
detail={"probabilities": probabilities} if probabilities else {},
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
score = _number(answer.get("score"))
|
|
397
|
+
if score is None or not options:
|
|
398
|
+
return None
|
|
399
|
+
# The score is the probability-weighted level, so it falls between levels.
|
|
400
|
+
# The level it is nearest to is the one reported.
|
|
401
|
+
index = min(len(options) - 1, max(0, int(score + 0.5)))
|
|
402
|
+
option = options[index]
|
|
403
|
+
confidence = _number(answer.get("confidence"))
|
|
404
|
+
certainty = _unit(confidence if confidence is not None else probabilities.get(str(index), 0.0))
|
|
405
|
+
by_label = {
|
|
406
|
+
options[int(key)].label: value
|
|
407
|
+
for key, value in probabilities.items()
|
|
408
|
+
if key.isdigit() and int(key) < len(options)
|
|
409
|
+
}
|
|
410
|
+
return _Verdict(
|
|
411
|
+
question,
|
|
412
|
+
answer=option.label,
|
|
413
|
+
certainty=certainty,
|
|
414
|
+
finding_type=f"{question.name}:{option.label}",
|
|
415
|
+
outcome=_pick_outcome(option, certainty, threshold),
|
|
416
|
+
severity=option.severity,
|
|
417
|
+
detail={
|
|
418
|
+
"score": round(score, 4),
|
|
419
|
+
"level": index,
|
|
420
|
+
**({"probabilities": by_label} if by_label else {}),
|
|
421
|
+
},
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def _pick_outcome(
|
|
426
|
+
option: DecisionQuestionOption, certainty: float, threshold: float
|
|
427
|
+
) -> Literal["finding", "quiet", "suppressed"]:
|
|
428
|
+
if option.report is False:
|
|
429
|
+
return "quiet"
|
|
430
|
+
return "finding" if certainty >= threshold else "suppressed"
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def _answers_of(response: Any) -> dict[str, dict[str, Any]]:
|
|
434
|
+
"""The response's answers as plain dicts keyed by question name."""
|
|
435
|
+
raw = response.get("answers") if isinstance(response, Mapping) else None
|
|
436
|
+
if raw is None:
|
|
437
|
+
raw = getattr(response, "answers", None)
|
|
438
|
+
if not isinstance(raw, Mapping):
|
|
439
|
+
raise ValueError("the provider response carries no answers")
|
|
440
|
+
return {str(name): _as_dict(answer) for name, answer in raw.items() if answer is not None}
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _served_model(response: Any) -> str | None:
|
|
444
|
+
model = response.get("model") if isinstance(response, Mapping) else None
|
|
445
|
+
if model is None:
|
|
446
|
+
model = getattr(response, "model", None)
|
|
447
|
+
return model if isinstance(model, str) and model.strip() else None
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _as_dict(value: Any) -> dict[str, Any]:
|
|
451
|
+
if isinstance(value, Mapping):
|
|
452
|
+
return dict(value)
|
|
453
|
+
dump = getattr(value, "model_dump", None)
|
|
454
|
+
if callable(dump):
|
|
455
|
+
dumped = dump()
|
|
456
|
+
return dict(dumped) if isinstance(dumped, Mapping) else {}
|
|
457
|
+
return dict(vars(value)) if hasattr(value, "__dict__") else {}
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _choice_label(value: Any) -> str:
|
|
461
|
+
# System One keys a boolean choice by its text; mirror that.
|
|
462
|
+
if isinstance(value, bool):
|
|
463
|
+
return "true" if value else "false"
|
|
464
|
+
return str(value)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def _number(value: Any) -> float | None:
|
|
468
|
+
if isinstance(value, bool) or not isinstance(value, int | float):
|
|
469
|
+
return None
|
|
470
|
+
return float(value)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def _number_map(value: Any) -> dict[str, float]:
|
|
474
|
+
if not isinstance(value, Mapping):
|
|
475
|
+
return {}
|
|
476
|
+
numbers = {str(key): _number(item) for key, item in value.items()}
|
|
477
|
+
return {key: round(item, 4) for key, item in numbers.items() if item is not None}
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def _unit(value: float) -> float:
|
|
481
|
+
"""Clamp to [0, 1]; a finding's confidence is stored as a probability."""
|
|
482
|
+
return round(min(1.0, max(0.0, value)), 4)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _plain(value: Any) -> Any:
|
|
486
|
+
"""Unwrap a generated RootModel wrapper to the value it holds."""
|
|
487
|
+
return getattr(value, "root", value)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def _string_map(value: Any) -> dict[str, str]:
|
|
491
|
+
if not isinstance(value, Mapping):
|
|
492
|
+
return {}
|
|
493
|
+
plain = {str(key): _plain(item) for key, item in value.items()}
|
|
494
|
+
return {key: item for key, item in plain.items() if isinstance(item, str) and item}
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from ....models.generated_detectors import (
|
|
6
6
|
CodeDetectorPipelineSchema,
|
|
7
|
+
DecisionPipelineSchema,
|
|
7
8
|
GLiNER2PipelineSchema,
|
|
8
9
|
ImageClassificationPipelineSchema,
|
|
9
10
|
LLMPipelineSchema,
|
|
@@ -14,6 +15,7 @@ from ....models.generated_detectors import (
|
|
|
14
15
|
)
|
|
15
16
|
from ._base import BaseRunner
|
|
16
17
|
from ._custom_detector import CustomDetectorRunner
|
|
18
|
+
from ._decision import DecisionRunner
|
|
17
19
|
from ._gliner2 import GLiNER2Runner
|
|
18
20
|
from ._image_classification import ImageClassificationRunner
|
|
19
21
|
from ._llm import LLMRunner
|
|
@@ -33,6 +35,7 @@ def create_runner(
|
|
|
33
35
|
| ObjectDetectionPipelineSchema
|
|
34
36
|
| TagPipelineSchema
|
|
35
37
|
| CodeDetectorPipelineSchema
|
|
38
|
+
| DecisionPipelineSchema
|
|
36
39
|
),
|
|
37
40
|
detector_key: str = "",
|
|
38
41
|
detector_name: str = "",
|
|
@@ -48,6 +51,8 @@ def create_runner(
|
|
|
48
51
|
return RegexRunner(schema, detector_key, detector_name)
|
|
49
52
|
if isinstance(schema, LLMPipelineSchema):
|
|
50
53
|
return LLMRunner(schema, detector_key, detector_name)
|
|
54
|
+
if isinstance(schema, DecisionPipelineSchema):
|
|
55
|
+
return DecisionRunner(schema, detector_key, detector_name)
|
|
51
56
|
# Branched explicitly, not left to the fall-through below: a TAG schema that
|
|
52
57
|
# reached the GLiNER2 default would try to load a transformer model for a
|
|
53
58
|
# detector that is not supposed to run anything at all.
|