generic-ml-cache-core 0.3.0__tar.gz → 0.4.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.
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/PKG-INFO +1 -1
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/pyproject.toml +1 -1
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/persistence/in_memory_execution_repository.py +18 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/persistence/sqlite_execution_repository.py +34 -2
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/execution/artifact.py +28 -1
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/execution/ml_execution.py +5 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/run/client_run_request.py +1 -1
- generic_ml_cache_core-0.4.0/src/generic_ml_cache_core/application/domain/model/run/persistence_depth.py +36 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/inbound/run_api_execution_command.py +8 -7
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/inbound/run_managed_local_execution_command.py +6 -5
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/inbound/run_passthrough_execution_command.py +6 -5
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/execution_repository_port.py +9 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/usecase/cached_ml_execution_service.py +69 -1
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/usecase/journal_events.py +4 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/usecase/run_api_execution_service.py +11 -1
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/usecase/run_managed_local_execution_service.py +17 -1
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/usecase/run_passthrough_execution_service.py +11 -1
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_client_run_request.py +1 -1
- generic_ml_cache_core-0.4.0/tests/test_persistence_depth.py +26 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_probe_command.py +1 -1
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_run_api_execution_command.py +5 -2
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_run_api_execution_service.py +33 -2
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_run_managed_local_execution_command.py +9 -4
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_run_managed_local_execution_service.py +125 -5
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_run_passthrough_execution_command.py +4 -3
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_run_passthrough_execution_service.py +24 -3
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_sqlite_execution_repository.py +61 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/.gitignore +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/LICENSE +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/NOTICE +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/README.md +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/inbound/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/inbound/composition.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/api/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/api/stub_api_client_adapter.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/claude.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/codex.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/cursor.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/discover.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/isolation.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/local_client_runner.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/passthrough_client_runner.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/prime_directive.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/client/registry.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/clock/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/clock/system_clock.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/fingerprint/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/fingerprint/filesystem_file_fingerprint.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/metrics/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/metrics/access_registry.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/metrics/journal_metrics.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/persistence/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/persistence/call_identity_serialization.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/storage/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/adapter/out/storage/filesystem_blob_store.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/client_status.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/execution/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/execution/execution_failure.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/execution/execution_kind.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/execution/execution_state.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/identity/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/identity/api_call_identity.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/identity/call_identity.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/identity/managed_call_identity.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/identity/passthrough_call_identity.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/model_info.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/model_listing.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/parsed_output.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/probe/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/probe/probe_report.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/probe/probe_status.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/run/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/run/cache_mode.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/run/client_run_result.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/run/message.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/usage/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/usage/token_usage.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/model/usage/usage.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/service/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/service/cacheability.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/domain/service/message_fingerprinting.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/inbound/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/inbound/probe_command.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/inbound/probe_use_case.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/inbound/run_api_execution_use_case.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/inbound/run_managed_local_execution_use_case.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/inbound/run_passthrough_execution_use_case.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/api_client_port.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/base.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/blob_store_port.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/client_runner_port.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/clock_port.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/file_fingerprint_port.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/metrics_port.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/port/out/passthrough_runner_port.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/usecase/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/usecase/call_identity_building.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/application/usecase/probe_service.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/common/__init__.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/common/checksum.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/common/errors.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/src/generic_ml_cache_core/stream.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/conftest.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/fake_client.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_adapters.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_api_call_identity.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_api_client_port.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_artifact.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_blob_store_port.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_cache_mode.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_cacheability.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_call_identity_building.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_call_identity_serialization.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_checksum.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_client_run_result.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_client_runner_port.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_clock_port.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_composition.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_execution_failure.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_execution_kind.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_execution_repository.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_execution_state.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_file_content_fingerprint.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_file_fingerprint_port.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_filesystem_blob_store.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_journal_metrics.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_local_client_runner.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_managed_call_identity.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_message.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_message_fingerprinting.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_metrics_port.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_ml_execution.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_passthrough_call_identity.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_passthrough_client_runner.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_passthrough_runner_port.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_probe_report.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_probe_service.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_token_usage.py +0 -0
- {generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_usage.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: generic-ml-cache-core
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Hexagonal core library for generic-ml-cache: domain, use cases, ports, and the default outbound adapters (SQLite repo, blob store, local clients, API). Stateless; inject the data source. Zero runtime deps.
|
|
5
5
|
Project-URL: Homepage, https://github.com/danielslobozian/generic-ml-cache
|
|
6
6
|
Project-URL: Repository, https://github.com/danielslobozian/generic-ml-cache
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "generic-ml-cache-core"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Hexagonal core library for generic-ml-cache: domain, use cases, ports, and the default outbound adapters (SQLite repo, blob store, local clients, API). Stateless; inject the data source. Zero runtime deps."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -7,6 +7,10 @@ from __future__ import annotations
|
|
|
7
7
|
from dataclasses import replace
|
|
8
8
|
from typing import Dict, List, Optional, Set
|
|
9
9
|
|
|
10
|
+
from generic_ml_cache_core.application.domain.model.execution.artifact import (
|
|
11
|
+
INPUT_ARTIFACT_TYPES,
|
|
12
|
+
Artifact,
|
|
13
|
+
)
|
|
10
14
|
from generic_ml_cache_core.application.domain.model.execution.execution_state import ExecutionState
|
|
11
15
|
from generic_ml_cache_core.application.domain.model.execution.ml_execution import MlExecution
|
|
12
16
|
from generic_ml_cache_core.application.port.out.clock_port import ClockPort
|
|
@@ -63,6 +67,20 @@ class InMemoryExecutionRepository(ExecutionRepositoryPort):
|
|
|
63
67
|
return []
|
|
64
68
|
return sorted(self._tags_by_key.get(execution_key, set()))
|
|
65
69
|
|
|
70
|
+
def add_input_artifacts(self, execution_key: str, artifacts: List[Artifact]) -> None:
|
|
71
|
+
# Back-fill the input onto the key's current execution; idempotent and a
|
|
72
|
+
# no-op when there is none or it already carries input.
|
|
73
|
+
if not artifacts:
|
|
74
|
+
return
|
|
75
|
+
for execution in self._by_key.get(execution_key, []):
|
|
76
|
+
if not self._is_servable(execution):
|
|
77
|
+
continue
|
|
78
|
+
if any(a.artifact_type in INPUT_ARTIFACT_TYPES for a in execution.artifacts):
|
|
79
|
+
return
|
|
80
|
+
execution.artifacts.extend(replace(a, content=None) for a in artifacts)
|
|
81
|
+
execution.input_persisted = True
|
|
82
|
+
return
|
|
83
|
+
|
|
66
84
|
@staticmethod
|
|
67
85
|
def _is_servable(execution: MlExecution) -> bool:
|
|
68
86
|
"""A servable execution is the current cached answer: a persisted success
|
|
@@ -16,7 +16,11 @@ from generic_ml_cache_core.adapter.out.persistence.call_identity_serialization i
|
|
|
16
16
|
deserialize_identity,
|
|
17
17
|
serialize_identity,
|
|
18
18
|
)
|
|
19
|
-
from generic_ml_cache_core.application.domain.model.execution.artifact import
|
|
19
|
+
from generic_ml_cache_core.application.domain.model.execution.artifact import (
|
|
20
|
+
INPUT_ARTIFACT_TYPES,
|
|
21
|
+
Artifact,
|
|
22
|
+
ArtifactType,
|
|
23
|
+
)
|
|
20
24
|
from generic_ml_cache_core.application.domain.model.identity.call_identity import CallIdentity
|
|
21
25
|
from generic_ml_cache_core.application.domain.model.execution.execution_failure import (
|
|
22
26
|
ExecutionFailure,
|
|
@@ -33,6 +37,9 @@ from generic_ml_cache_core.application.port.out.execution_repository_port import
|
|
|
33
37
|
|
|
34
38
|
_DB_NAME = "executions.sqlite3"
|
|
35
39
|
|
|
40
|
+
#: stored string values of the input artifact types, for the idempotency check.
|
|
41
|
+
_INPUT_TYPE_VALUES = tuple(t.value for t in INPUT_ARTIFACT_TYPES)
|
|
42
|
+
|
|
36
43
|
|
|
37
44
|
@dataclass(frozen=True)
|
|
38
45
|
class ExecutionSummary:
|
|
@@ -335,6 +342,28 @@ class SqliteExecutionRepository(ExecutionRepositoryPort):
|
|
|
335
342
|
finally:
|
|
336
343
|
connection.close()
|
|
337
344
|
|
|
345
|
+
def add_input_artifacts(self, execution_key: str, artifacts: List[Artifact]) -> None:
|
|
346
|
+
if not artifacts:
|
|
347
|
+
return
|
|
348
|
+
connection = self._connect()
|
|
349
|
+
try:
|
|
350
|
+
execution_id = self._current_execution_id(connection, execution_key)
|
|
351
|
+
if execution_id is None:
|
|
352
|
+
return
|
|
353
|
+
# Idempotent: skip if this execution already carries input artifacts.
|
|
354
|
+
placeholders = ",".join("?" * len(_INPUT_TYPE_VALUES))
|
|
355
|
+
already = connection.execute(
|
|
356
|
+
f"SELECT 1 FROM artifacts WHERE execution_id = ? "
|
|
357
|
+
f"AND artifact_type IN ({placeholders}) LIMIT 1",
|
|
358
|
+
(execution_id, *_INPUT_TYPE_VALUES),
|
|
359
|
+
).fetchone()
|
|
360
|
+
if already is not None:
|
|
361
|
+
return
|
|
362
|
+
self._insert_artifacts(connection, execution_id, artifacts)
|
|
363
|
+
connection.commit()
|
|
364
|
+
finally:
|
|
365
|
+
connection.close()
|
|
366
|
+
|
|
338
367
|
# -- reconstruction ---------------------------------------------------
|
|
339
368
|
|
|
340
369
|
def _load_execution(self, connection: sqlite3.Connection, row: tuple) -> MlExecution:
|
|
@@ -349,12 +378,15 @@ class SqliteExecutionRepository(ExecutionRepositoryPort):
|
|
|
349
378
|
failure_message,
|
|
350
379
|
failure_exit_code,
|
|
351
380
|
) = row
|
|
381
|
+
artifacts = self._load_artifacts(connection, execution_id)
|
|
352
382
|
return MlExecution(
|
|
353
383
|
call_identity=self._load_identity(connection, execution_key),
|
|
354
384
|
execution_state=ExecutionState(state),
|
|
355
385
|
execution_kind=ExecutionKind(kind),
|
|
356
386
|
output_persisted=bool(output_persisted),
|
|
357
|
-
artifacts
|
|
387
|
+
# Derived, not a column: input is persisted iff INPUT_* artifacts exist.
|
|
388
|
+
input_persisted=any(a.artifact_type in INPUT_ARTIFACT_TYPES for a in artifacts),
|
|
389
|
+
artifacts=artifacts,
|
|
358
390
|
token_usage=self._load_token_usage(connection, execution_id),
|
|
359
391
|
failure=(
|
|
360
392
|
ExecutionFailure(
|
|
@@ -13,7 +13,15 @@ _BINARY = "binary"
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class ArtifactType(enum.Enum):
|
|
16
|
-
"""The kind of
|
|
16
|
+
"""The kind of document an Artifact holds.
|
|
17
|
+
|
|
18
|
+
The ``STDOUT``/``STDERR``/``OUTPUT_FILE`` types are an execution's *output*,
|
|
19
|
+
stored whenever caching is on. The ``INPUT_*`` types are the *input* sent to
|
|
20
|
+
the client — and are stored only at ``DATASET`` persistence depth, to build a
|
|
21
|
+
queryable ``(input, output)`` corpus. Each execution kind keeps its own input
|
|
22
|
+
shape: managed-local uses ``INPUT_CONTEXT``/``INPUT_PROMPT``/``INPUT_SYSTEM``,
|
|
23
|
+
the API kind a single ``INPUT_MESSAGES`` (the JSON message list), and
|
|
24
|
+
passthrough a single ``INPUT_ARGS`` (the JSON native-argument list).
|
|
17
25
|
|
|
18
26
|
RAW_USAGE is reserved for a later step (the raw client usage block stored as
|
|
19
27
|
its own artifact); today raw usage still rides on TokenUsage.
|
|
@@ -22,6 +30,11 @@ class ArtifactType(enum.Enum):
|
|
|
22
30
|
STDOUT = "stdout"
|
|
23
31
|
STDERR = "stderr"
|
|
24
32
|
OUTPUT_FILE = "output_file"
|
|
33
|
+
INPUT_CONTEXT = "input_context"
|
|
34
|
+
INPUT_PROMPT = "input_prompt"
|
|
35
|
+
INPUT_SYSTEM = "input_system"
|
|
36
|
+
INPUT_MESSAGES = "input_messages"
|
|
37
|
+
INPUT_ARGS = "input_args"
|
|
25
38
|
|
|
26
39
|
|
|
27
40
|
@dataclass(frozen=True)
|
|
@@ -76,3 +89,17 @@ class Artifact:
|
|
|
76
89
|
def is_hydrated(self) -> bool:
|
|
77
90
|
"""True when the artifact's bytes are materialised in memory."""
|
|
78
91
|
return self.content is not None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
#: The artifact types that make up an execution's persisted *input* (DATASET
|
|
95
|
+
#: depth). A single place so consumers can tell input apart from output without
|
|
96
|
+
#: re-listing the members.
|
|
97
|
+
INPUT_ARTIFACT_TYPES = frozenset(
|
|
98
|
+
{
|
|
99
|
+
ArtifactType.INPUT_CONTEXT,
|
|
100
|
+
ArtifactType.INPUT_PROMPT,
|
|
101
|
+
ArtifactType.INPUT_SYSTEM,
|
|
102
|
+
ArtifactType.INPUT_MESSAGES,
|
|
103
|
+
ArtifactType.INPUT_ARGS,
|
|
104
|
+
}
|
|
105
|
+
)
|
|
@@ -29,12 +29,17 @@ class MlExecution:
|
|
|
29
29
|
cache-currency axis (None = current, set = stale); executions are append-only
|
|
30
30
|
per call identity. ``artifacts`` may be dehydrated (refs only) or hydrated
|
|
31
31
|
(bytes materialised).
|
|
32
|
+
|
|
33
|
+
``output_persisted`` is set whenever caching is on (CACHE/DATASET);
|
|
34
|
+
``input_persisted`` is set only at DATASET depth, when the input is also kept
|
|
35
|
+
(as ``INPUT_*`` artifacts) to form a ``(input, output)`` corpus.
|
|
32
36
|
"""
|
|
33
37
|
|
|
34
38
|
call_identity: CallIdentity
|
|
35
39
|
execution_state: ExecutionState
|
|
36
40
|
execution_kind: ExecutionKind
|
|
37
41
|
output_persisted: bool
|
|
42
|
+
input_persisted: bool = False
|
|
38
43
|
artifacts: List[Artifact] = field(default_factory=list)
|
|
39
44
|
token_usage: Optional[TokenUsage] = None
|
|
40
45
|
failure: Optional[ExecutionFailure] = None
|
|
@@ -13,7 +13,7 @@ class ClientRunRequest:
|
|
|
13
13
|
"""The DTO the use case constructs and passes to ClientRunnerPort.
|
|
14
14
|
|
|
15
15
|
Carries only what the client runner needs to launch the client. The
|
|
16
|
-
command's gmlcache-specific policy fields (cache_mode,
|
|
16
|
+
command's gmlcache-specific policy fields (cache_mode, persistence_depth,
|
|
17
17
|
scan_trust) do not appear here — they are the use case's concern, not
|
|
18
18
|
the client runner's.
|
|
19
19
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Daniel Slobozian
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""PersistenceDepth."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from enum import Enum
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PersistenceDepth(Enum):
|
|
11
|
+
"""How much of an execution is kept on disk — a single ordered choice.
|
|
12
|
+
|
|
13
|
+
Each level is a superset of the one below, so the degenerate "input stored
|
|
14
|
+
without output" state is unrepresentable:
|
|
15
|
+
|
|
16
|
+
- ``METER`` -- metadata/usage only. The call runs and is recorded, but no
|
|
17
|
+
output is stored, so it is never replayed (a usage/observability mode).
|
|
18
|
+
- ``CACHE`` -- ``METER`` plus the output: stored and replayed on a hit. The
|
|
19
|
+
default, and today's behaviour.
|
|
20
|
+
- ``DATASET`` -- ``CACHE`` plus the input: replayed and retained as a labelled
|
|
21
|
+
``(input, output)`` pair.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
METER = "meter"
|
|
25
|
+
CACHE = "cache"
|
|
26
|
+
DATASET = "dataset"
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def stores_output(self) -> bool:
|
|
30
|
+
"""Whether this depth keeps the output (``CACHE`` and ``DATASET``)."""
|
|
31
|
+
return self in (PersistenceDepth.CACHE, PersistenceDepth.DATASET)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def stores_input(self) -> bool:
|
|
35
|
+
"""Whether this depth keeps the input (``DATASET`` only)."""
|
|
36
|
+
return self is PersistenceDepth.DATASET
|
|
@@ -8,6 +8,7 @@ from dataclasses import dataclass, field
|
|
|
8
8
|
from typing import List
|
|
9
9
|
|
|
10
10
|
from generic_ml_cache_core.application.domain.model.run.cache_mode import CacheMode
|
|
11
|
+
from generic_ml_cache_core.application.domain.model.run.persistence_depth import PersistenceDepth
|
|
11
12
|
from generic_ml_cache_core.application.domain.model.run.message import Message
|
|
12
13
|
|
|
13
14
|
|
|
@@ -19,8 +20,8 @@ class RunApiExecutionCommand:
|
|
|
19
20
|
files or scan folders), so there are no input-file, allow-path, grant, or
|
|
20
21
|
scan-trust fields. An API call is always cacheable.
|
|
21
22
|
|
|
22
|
-
Note (future): ``
|
|
23
|
-
execution — an async call must store its output so the caller can retrieve it
|
|
23
|
+
Note (future): the ``METER`` depth (storing no output) will be incompatible with
|
|
24
|
+
async execution — an async call must store its output so the caller can retrieve it
|
|
24
25
|
by id later. Async is not built yet, so nothing enforces it here.
|
|
25
26
|
"""
|
|
26
27
|
|
|
@@ -28,13 +29,13 @@ class RunApiExecutionCommand:
|
|
|
28
29
|
model: str
|
|
29
30
|
messages: List[Message] = field(default_factory=list)
|
|
30
31
|
cache_mode: CacheMode = CacheMode.CACHE
|
|
31
|
-
|
|
32
|
+
persistence_depth: PersistenceDepth = PersistenceDepth.CACHE
|
|
32
33
|
record_on_error: bool = False
|
|
33
34
|
|
|
34
35
|
def should_persist(self, succeeded: bool) -> bool:
|
|
35
|
-
"""Whether this command's policy stores
|
|
36
|
-
with ``succeeded``: never
|
|
37
|
-
``record_on_error``."""
|
|
38
|
-
if not self.
|
|
36
|
+
"""Whether this command's policy stores the output for a run that ended
|
|
37
|
+
with ``succeeded``: never below ``CACHE`` depth (``METER`` stores nothing);
|
|
38
|
+
a failure only with ``record_on_error``."""
|
|
39
|
+
if not self.persistence_depth.stores_output:
|
|
39
40
|
return False
|
|
40
41
|
return succeeded or self.record_on_error
|
|
@@ -8,6 +8,7 @@ from dataclasses import dataclass, field
|
|
|
8
8
|
from typing import List, Optional
|
|
9
9
|
|
|
10
10
|
from generic_ml_cache_core.application.domain.model.run.cache_mode import CacheMode
|
|
11
|
+
from generic_ml_cache_core.application.domain.model.run.persistence_depth import PersistenceDepth
|
|
11
12
|
from generic_ml_cache_core.application.domain.service.cacheability import is_call_uncacheable
|
|
12
13
|
|
|
13
14
|
|
|
@@ -32,7 +33,7 @@ class RunManagedLocalExecutionCommand:
|
|
|
32
33
|
client_args: List[str] = field(default_factory=list)
|
|
33
34
|
grants: List[str] = field(default_factory=list)
|
|
34
35
|
cache_mode: CacheMode = CacheMode.CACHE
|
|
35
|
-
|
|
36
|
+
persistence_depth: PersistenceDepth = PersistenceDepth.CACHE
|
|
36
37
|
record_on_error: bool = False
|
|
37
38
|
tags: List[str] = field(default_factory=list)
|
|
38
39
|
|
|
@@ -41,9 +42,9 @@ class RunManagedLocalExecutionCommand:
|
|
|
41
42
|
return is_call_uncacheable(self.allow_paths, self.scan_trust)
|
|
42
43
|
|
|
43
44
|
def should_persist(self, succeeded: bool) -> bool:
|
|
44
|
-
"""Whether this command's policy stores
|
|
45
|
-
with ``succeeded``: never
|
|
46
|
-
``record_on_error``."""
|
|
47
|
-
if not self.
|
|
45
|
+
"""Whether this command's policy stores the output for a run that ended
|
|
46
|
+
with ``succeeded``: never below ``CACHE`` depth (``METER`` stores nothing);
|
|
47
|
+
a failure only with ``record_on_error``."""
|
|
48
|
+
if not self.persistence_depth.stores_output:
|
|
48
49
|
return False
|
|
49
50
|
return succeeded or self.record_on_error
|
|
@@ -8,6 +8,7 @@ from dataclasses import dataclass, field
|
|
|
8
8
|
from typing import List
|
|
9
9
|
|
|
10
10
|
from generic_ml_cache_core.application.domain.model.run.cache_mode import CacheMode
|
|
11
|
+
from generic_ml_cache_core.application.domain.model.run.persistence_depth import PersistenceDepth
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
@dataclass(frozen=True)
|
|
@@ -23,13 +24,13 @@ class RunPassthroughExecutionCommand:
|
|
|
23
24
|
client: str
|
|
24
25
|
native_args: List[str] = field(default_factory=list)
|
|
25
26
|
cache_mode: CacheMode = CacheMode.CACHE
|
|
26
|
-
|
|
27
|
+
persistence_depth: PersistenceDepth = PersistenceDepth.CACHE
|
|
27
28
|
record_on_error: bool = False
|
|
28
29
|
|
|
29
30
|
def should_persist(self, succeeded: bool) -> bool:
|
|
30
|
-
"""Whether this command's policy stores
|
|
31
|
-
with ``succeeded``: never
|
|
32
|
-
``record_on_error``."""
|
|
33
|
-
if not self.
|
|
31
|
+
"""Whether this command's policy stores the output for a run that ended
|
|
32
|
+
with ``succeeded``: never below ``CACHE`` depth (``METER`` stores nothing);
|
|
33
|
+
a failure only with ``record_on_error``."""
|
|
34
|
+
if not self.persistence_depth.stores_output:
|
|
34
35
|
return False
|
|
35
36
|
return succeeded or self.record_on_error
|
|
@@ -7,6 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
8
|
from typing import List, Optional
|
|
9
9
|
|
|
10
|
+
from generic_ml_cache_core.application.domain.model.execution.artifact import Artifact
|
|
10
11
|
from generic_ml_cache_core.application.domain.model.execution.ml_execution import MlExecution
|
|
11
12
|
|
|
12
13
|
|
|
@@ -50,3 +51,11 @@ class ExecutionRepositoryPort(ABC):
|
|
|
50
51
|
def tags_for(self, execution_key: str) -> List[str]:
|
|
51
52
|
"""Return the tags on the current execution for ``execution_key``, sorted;
|
|
52
53
|
empty if none (or no current execution)."""
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
def add_input_artifacts(self, execution_key: str, artifacts: List[Artifact]) -> None:
|
|
57
|
+
"""Attach input ``artifacts`` to the current execution for ``execution_key``,
|
|
58
|
+
back-filling the input side of the corpus when a DATASET-depth call hits an
|
|
59
|
+
entry that has none yet. Idempotent — a no-op if the current execution
|
|
60
|
+
already carries input, or if there is no current execution. Like tags, this
|
|
61
|
+
enriches an existing entry without rewriting its output."""
|
|
@@ -10,6 +10,7 @@ from typing import List, Optional, Protocol, Tuple
|
|
|
10
10
|
|
|
11
11
|
from generic_ml_cache_core.application.domain.model.execution.artifact import Artifact, ArtifactType
|
|
12
12
|
from generic_ml_cache_core.application.domain.model.run.cache_mode import CacheMode
|
|
13
|
+
from generic_ml_cache_core.application.domain.model.run.persistence_depth import PersistenceDepth
|
|
13
14
|
from generic_ml_cache_core.application.domain.model.identity.call_identity import CallIdentity
|
|
14
15
|
from generic_ml_cache_core.application.domain.model.run.client_run_result import ClientRunResult
|
|
15
16
|
from generic_ml_cache_core.application.domain.model.execution.execution_kind import ExecutionKind
|
|
@@ -31,6 +32,7 @@ class CacheableExecutionCommand(Protocol):
|
|
|
31
32
|
persistence policy. The kind-specific fields are read through hooks."""
|
|
32
33
|
|
|
33
34
|
cache_mode: CacheMode
|
|
35
|
+
persistence_depth: PersistenceDepth
|
|
34
36
|
|
|
35
37
|
def should_persist(self, succeeded: bool) -> bool: ...
|
|
36
38
|
|
|
@@ -63,6 +65,11 @@ class CachedMlExecutionService(ABC):
|
|
|
63
65
|
if command.cache_mode is CacheMode.OFFLINE:
|
|
64
66
|
return self._serve_offline(command, execution_key)
|
|
65
67
|
|
|
68
|
+
if not command.persistence_depth.stores_output:
|
|
69
|
+
# METER: never replays — always run, store nothing, but record whether
|
|
70
|
+
# the call *would* have hit a stored entry (would-be hit/miss).
|
|
71
|
+
return self._run_metered(command, call_identity, execution_key)
|
|
72
|
+
|
|
66
73
|
if command.cache_mode is CacheMode.CACHE:
|
|
67
74
|
current_execution = self._repository.find_current(execution_key)
|
|
68
75
|
if current_execution is not None:
|
|
@@ -121,8 +128,22 @@ class CachedMlExecutionService(ABC):
|
|
|
121
128
|
hydrated_execution = self._hydrate(current_execution)
|
|
122
129
|
self._record_event(journal_events.HIT, execution_key, command)
|
|
123
130
|
self._apply_tags(execution_key, command)
|
|
131
|
+
self._accumulate_input(command, execution_key, current_execution)
|
|
124
132
|
return hydrated_execution
|
|
125
133
|
|
|
134
|
+
def _accumulate_input(
|
|
135
|
+
self, command: CacheableExecutionCommand, execution_key: str, current_execution: MlExecution
|
|
136
|
+
) -> None:
|
|
137
|
+
"""If the user now wants the input kept (DATASET) and this entry doesn't yet
|
|
138
|
+
carry it, back-fill it onto the existing entry — the input is in the command,
|
|
139
|
+
so no re-run is needed. Mirrors how tags accumulate on a hit; the user
|
|
140
|
+
changing their mind to enrich the stored data is their decision."""
|
|
141
|
+
if not command.persistence_depth.stores_input or current_execution.input_persisted:
|
|
142
|
+
return
|
|
143
|
+
input_artifacts = self._build_input_artifacts(command, store=True)
|
|
144
|
+
if input_artifacts:
|
|
145
|
+
self._repository.add_input_artifacts(execution_key, input_artifacts)
|
|
146
|
+
|
|
126
147
|
def _run_uncacheable(
|
|
127
148
|
self, command: CacheableExecutionCommand, call_identity: CallIdentity, execution_key: str
|
|
128
149
|
) -> MlExecution:
|
|
@@ -141,12 +162,17 @@ class CachedMlExecutionService(ABC):
|
|
|
141
162
|
client_run_result = self._run_client(command)
|
|
142
163
|
should_store = allow_store and command.should_persist(client_run_result.succeeded)
|
|
143
164
|
artifacts = self._build_artifacts(client_run_result, store=should_store)
|
|
165
|
+
# Input rides on a stored output (DATASET is a superset of CACHE): only
|
|
166
|
+
# capture it when the output is being stored and the depth keeps input.
|
|
167
|
+
store_input = should_store and command.persistence_depth.stores_input
|
|
168
|
+
input_artifacts = self._build_input_artifacts(command, store=store_input)
|
|
144
169
|
execution = MlExecution(
|
|
145
170
|
call_identity=call_identity,
|
|
146
171
|
execution_state=client_run_result.outcome(),
|
|
147
172
|
execution_kind=self._execution_kind(),
|
|
148
173
|
output_persisted=should_store,
|
|
149
|
-
|
|
174
|
+
input_persisted=bool(input_artifacts),
|
|
175
|
+
artifacts=artifacts + input_artifacts,
|
|
150
176
|
token_usage=client_run_result.token_usage,
|
|
151
177
|
failure=client_run_result.failure(),
|
|
152
178
|
)
|
|
@@ -158,6 +184,28 @@ class CachedMlExecutionService(ABC):
|
|
|
158
184
|
self._record_event(journal_events.RUN, execution_key, command)
|
|
159
185
|
return execution
|
|
160
186
|
|
|
187
|
+
def _run_metered(
|
|
188
|
+
self, command: CacheableExecutionCommand, call_identity: CallIdentity, execution_key: str
|
|
189
|
+
) -> MlExecution:
|
|
190
|
+
"""METER depth: always run and store nothing, but journal whether a stored
|
|
191
|
+
entry existed — so usage analytics can report would-be hit/miss ("you'd
|
|
192
|
+
have saved N runs") without the cache ever serving or storing anything."""
|
|
193
|
+
would_hit = self._repository.find_current(execution_key) is not None
|
|
194
|
+
client_run_result = self._run_client(command)
|
|
195
|
+
execution = MlExecution(
|
|
196
|
+
call_identity=call_identity,
|
|
197
|
+
execution_state=client_run_result.outcome(),
|
|
198
|
+
execution_kind=self._execution_kind(),
|
|
199
|
+
output_persisted=False,
|
|
200
|
+
input_persisted=False,
|
|
201
|
+
artifacts=self._build_artifacts(client_run_result, store=False),
|
|
202
|
+
token_usage=client_run_result.token_usage,
|
|
203
|
+
failure=client_run_result.failure(),
|
|
204
|
+
)
|
|
205
|
+
event = journal_events.WOULD_HIT if would_hit else journal_events.WOULD_MISS
|
|
206
|
+
self._record_event(event, execution_key, command)
|
|
207
|
+
return execution
|
|
208
|
+
|
|
161
209
|
# -- artifacts --------------------------------------------------------
|
|
162
210
|
|
|
163
211
|
def _build_artifacts(self, client_run_result: ClientRunResult, store: bool) -> List[Artifact]:
|
|
@@ -189,6 +237,26 @@ class CachedMlExecutionService(ABC):
|
|
|
189
237
|
self._blob_store.put(blob_key, content_bytes)
|
|
190
238
|
return Artifact.from_content(artifact_type, blob_key, content_bytes, name=artifact_name)
|
|
191
239
|
|
|
240
|
+
def _build_input_artifacts(
|
|
241
|
+
self, command: CacheableExecutionCommand, store: bool
|
|
242
|
+
) -> List[Artifact]:
|
|
243
|
+
"""The input documents to keep at DATASET depth, content-addressed like
|
|
244
|
+
any artifact. Empty when ``store`` is false (below DATASET, or nothing was
|
|
245
|
+
stored) or when the kind has no recordable input."""
|
|
246
|
+
if not store:
|
|
247
|
+
return []
|
|
248
|
+
return [
|
|
249
|
+
self._store_artifact(artifact_type, name, content_bytes, store=True)
|
|
250
|
+
for (artifact_type, name, content_bytes) in self._input_parts(command)
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
def _input_parts(
|
|
254
|
+
self, command: CacheableExecutionCommand
|
|
255
|
+
) -> List[Tuple[ArtifactType, Optional[str], bytes]]:
|
|
256
|
+
"""The ``(type, name, bytes)`` input documents this kind would persist at
|
|
257
|
+
DATASET depth. Default: none — a kind whose input is not recorded."""
|
|
258
|
+
return []
|
|
259
|
+
|
|
192
260
|
def _hydrate(self, execution: MlExecution) -> MlExecution:
|
|
193
261
|
hydrated_artifacts = [self._hydrate_artifact(artifact) for artifact in execution.artifacts]
|
|
194
262
|
return replace(execution, artifacts=hydrated_artifacts)
|
|
@@ -17,3 +17,7 @@ RECORD = "record"
|
|
|
17
17
|
MISS = "miss"
|
|
18
18
|
#: a fresh real call ran but was not stored (uncacheable, or a non-persisted/failed run)
|
|
19
19
|
RUN = "run"
|
|
20
|
+
#: a METER call ran (never replays) and a stored entry existed — it *would* have hit
|
|
21
|
+
WOULD_HIT = "would_hit"
|
|
22
|
+
#: a METER call ran (never replays) and no stored entry existed — it *would* have missed
|
|
23
|
+
WOULD_MISS = "would_miss"
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
import json
|
|
8
|
+
from typing import List, Optional, Tuple
|
|
8
9
|
|
|
10
|
+
from generic_ml_cache_core.application.domain.model.execution.artifact import ArtifactType
|
|
9
11
|
from generic_ml_cache_core.application.domain.model.identity.api_call_identity import (
|
|
10
12
|
ApiCallIdentity,
|
|
11
13
|
)
|
|
@@ -67,3 +69,11 @@ class RunApiExecutionService(CachedMlExecutionService, RunApiExecutionUseCase):
|
|
|
67
69
|
def _journal_fields(self, command: RunApiExecutionCommand) -> Tuple[str, str, str]:
|
|
68
70
|
# The provider plays the role of "client" in the journal; no effort concept.
|
|
69
71
|
return command.provider, command.model, ""
|
|
72
|
+
|
|
73
|
+
def _input_parts(
|
|
74
|
+
self, command: RunApiExecutionCommand
|
|
75
|
+
) -> List[Tuple[ArtifactType, Optional[str], bytes]]:
|
|
76
|
+
# The API call's input is its message list; keep it as one JSON artifact so
|
|
77
|
+
# the (role, content) structure survives into the exported corpus.
|
|
78
|
+
payload = json.dumps([{"role": m.role, "content": m.content} for m in command.messages])
|
|
79
|
+
return [(ArtifactType.INPUT_MESSAGES, None, payload.encode("utf-8"))]
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
from typing import List, Tuple
|
|
7
|
+
from typing import List, Optional, Tuple
|
|
8
8
|
|
|
9
|
+
from generic_ml_cache_core.application.domain.model.execution.artifact import ArtifactType
|
|
9
10
|
from generic_ml_cache_core.application.domain.model.execution.ml_execution import normalize_tags
|
|
10
11
|
from generic_ml_cache_core.application.domain.model.identity.call_identity import CallIdentity
|
|
11
12
|
from generic_ml_cache_core.application.domain.model.run.client_run_request import ClientRunRequest
|
|
@@ -70,6 +71,21 @@ class RunManagedLocalExecutionService(CachedMlExecutionService, RunManagedLocalE
|
|
|
70
71
|
def _execution_tags(self, command: RunManagedLocalExecutionCommand) -> List[str]:
|
|
71
72
|
return normalize_tags(command.tags)
|
|
72
73
|
|
|
74
|
+
def _input_parts(
|
|
75
|
+
self, command: RunManagedLocalExecutionCommand
|
|
76
|
+
) -> List[Tuple[ArtifactType, Optional[str], bytes]]:
|
|
77
|
+
# The text sent to the client, as the input side of the corpus. The prompt
|
|
78
|
+
# is always present; context and system prompt only when non-empty.
|
|
79
|
+
parts: List[Tuple[ArtifactType, Optional[str], bytes]] = []
|
|
80
|
+
if command.context:
|
|
81
|
+
parts.append((ArtifactType.INPUT_CONTEXT, None, command.context.encode("utf-8")))
|
|
82
|
+
parts.append((ArtifactType.INPUT_PROMPT, None, command.prompt.encode("utf-8")))
|
|
83
|
+
if command.user_system_prompt:
|
|
84
|
+
parts.append(
|
|
85
|
+
(ArtifactType.INPUT_SYSTEM, None, command.user_system_prompt.encode("utf-8"))
|
|
86
|
+
)
|
|
87
|
+
return parts
|
|
88
|
+
|
|
73
89
|
@staticmethod
|
|
74
90
|
def _build_client_run_request(command: RunManagedLocalExecutionCommand) -> ClientRunRequest:
|
|
75
91
|
# The one allowed self-less method (AGENTS §6): the inbound-command ->
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
import json
|
|
8
|
+
from typing import List, Optional, Tuple
|
|
8
9
|
|
|
10
|
+
from generic_ml_cache_core.application.domain.model.execution.artifact import ArtifactType
|
|
9
11
|
from generic_ml_cache_core.application.domain.model.identity.call_identity import CallIdentity
|
|
10
12
|
from generic_ml_cache_core.application.domain.model.run.client_run_result import ClientRunResult
|
|
11
13
|
from generic_ml_cache_core.application.domain.model.execution.execution_kind import ExecutionKind
|
|
@@ -65,3 +67,11 @@ class RunPassthroughExecutionService(CachedMlExecutionService, RunPassthroughExe
|
|
|
65
67
|
def _journal_fields(self, command: RunPassthroughExecutionCommand) -> Tuple[str, str, str]:
|
|
66
68
|
# A passthrough has no modelled model/effort — only the client is known.
|
|
67
69
|
return command.client, "", ""
|
|
70
|
+
|
|
71
|
+
def _input_parts(
|
|
72
|
+
self, command: RunPassthroughExecutionCommand
|
|
73
|
+
) -> List[Tuple[ArtifactType, Optional[str], bytes]]:
|
|
74
|
+
# The passthrough's input is its opaque native-argument list; keep it as one
|
|
75
|
+
# JSON artifact so the argument vector survives into the exported corpus.
|
|
76
|
+
payload = json.dumps(list(command.native_args))
|
|
77
|
+
return [(ArtifactType.INPUT_ARGS, None, payload.encode("utf-8"))]
|
{generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_client_run_request.py
RENAMED
|
@@ -74,7 +74,7 @@ def test_grants_are_a_frozenset():
|
|
|
74
74
|
|
|
75
75
|
def test_cache_policy_fields_are_absent():
|
|
76
76
|
assert not hasattr(ClientRunRequest, "cache_mode")
|
|
77
|
-
assert not hasattr(ClientRunRequest, "
|
|
77
|
+
assert not hasattr(ClientRunRequest, "persistence_depth")
|
|
78
78
|
assert not hasattr(ClientRunRequest, "scan_trust")
|
|
79
79
|
|
|
80
80
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Daniel Slobozian
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Tests for PersistenceDepth."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from generic_ml_cache_core.application.domain.model.run.persistence_depth import PersistenceDepth
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_meter_stores_neither_output_nor_input():
|
|
11
|
+
assert PersistenceDepth.METER.stores_output is False
|
|
12
|
+
assert PersistenceDepth.METER.stores_input is False
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_cache_stores_output_but_not_input():
|
|
16
|
+
assert PersistenceDepth.CACHE.stores_output is True
|
|
17
|
+
assert PersistenceDepth.CACHE.stores_input is False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_dataset_stores_both_output_and_input():
|
|
21
|
+
assert PersistenceDepth.DATASET.stores_output is True
|
|
22
|
+
assert PersistenceDepth.DATASET.stores_input is True
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_values_are_the_stable_wire_strings():
|
|
26
|
+
assert [depth.value for depth in PersistenceDepth] == ["meter", "cache", "dataset"]
|
|
@@ -38,7 +38,7 @@ def test_scan_trust_makes_it_cacheable_again():
|
|
|
38
38
|
|
|
39
39
|
def test_carries_no_run_policy():
|
|
40
40
|
assert not hasattr(ProbeCommand, "cache_mode")
|
|
41
|
-
assert not hasattr(ProbeCommand, "
|
|
41
|
+
assert not hasattr(ProbeCommand, "persistence_depth")
|
|
42
42
|
assert not hasattr(ProbeCommand, "record_on_error")
|
|
43
43
|
|
|
44
44
|
|
{generic_ml_cache_core-0.3.0 → generic_ml_cache_core-0.4.0}/tests/test_run_api_execution_command.py
RENAMED
|
@@ -7,6 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
9
9
|
from generic_ml_cache_core.application.domain.model.run.cache_mode import CacheMode
|
|
10
|
+
from generic_ml_cache_core.application.domain.model.run.persistence_depth import PersistenceDepth
|
|
10
11
|
from generic_ml_cache_core.application.domain.model.run.message import Message
|
|
11
12
|
from generic_ml_cache_core.application.port.inbound.run_api_execution_command import (
|
|
12
13
|
RunApiExecutionCommand,
|
|
@@ -17,7 +18,7 @@ def test_defaults():
|
|
|
17
18
|
command = RunApiExecutionCommand(provider="openai", model="gpt-x")
|
|
18
19
|
assert command.messages == []
|
|
19
20
|
assert command.cache_mode is CacheMode.CACHE
|
|
20
|
-
assert command.
|
|
21
|
+
assert command.persistence_depth is PersistenceDepth.CACHE
|
|
21
22
|
assert command.record_on_error is False
|
|
22
23
|
|
|
23
24
|
|
|
@@ -36,7 +37,9 @@ def test_should_persist_policy():
|
|
|
36
37
|
is True
|
|
37
38
|
)
|
|
38
39
|
assert (
|
|
39
|
-
RunApiExecutionCommand(
|
|
40
|
+
RunApiExecutionCommand(
|
|
41
|
+
provider="p", model="m", persistence_depth=PersistenceDepth.METER
|
|
42
|
+
).should_persist(True)
|
|
40
43
|
is False
|
|
41
44
|
)
|
|
42
45
|
|