data-designer 0.9.2__tar.gz → 0.9.3__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.
- {data_designer-0.9.2 → data_designer-0.9.3}/PKG-INFO +3 -3
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/integrations/huggingface/client.py +8 -8
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/interface/__init__.py +22 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/interface/composite_workflow.py +41 -4
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/interface/data_designer.py +6 -3
- data_designer-0.9.3/src/data_designer/interface/workflow_metadata.py +97 -0
- data_designer-0.9.3/tests/docs/test_chart_qa_recipe.py +136 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/interface/test_composite_workflow.py +107 -1
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/interface/test_data_designer.py +115 -0
- data_designer-0.9.3/tests/interface/test_workflow_metadata.py +191 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/.gitignore +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/README.md +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/dev-tools/hatch_build.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/pyproject.toml +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/README.md +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/__init__.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/agent_command_defs.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/commands/__init__.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/commands/agent.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/commands/check_models.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/commands/create.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/commands/download.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/commands/list.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/commands/mcp.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/commands/models.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/commands/plugin.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/commands/preview.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/commands/providers.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/commands/reset.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/commands/tools.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/commands/validate.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/controllers/__init__.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/controllers/download_controller.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/controllers/generation_controller.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/controllers/mcp_provider_controller.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/controllers/model_controller.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/controllers/plugin_catalog_controller.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/controllers/provider_controller.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/controllers/tool_controller.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/forms/__init__.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/forms/builder.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/forms/field.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/forms/form.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/forms/mcp_provider_builder.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/forms/model_builder.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/forms/provider_builder.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/forms/tool_builder.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/lazy_group.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/main.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/plugin_catalog.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/repositories/__init__.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/repositories/base.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/repositories/mcp_provider_repository.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/repositories/model_repository.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/repositories/persona_repository.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/repositories/plugin_catalog_repository.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/repositories/provider_repository.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/repositories/tool_repository.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/runtime.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/services/__init__.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/services/download_service.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/services/mcp_provider_service.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/services/model_service.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/services/plugin_catalog_service.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/services/plugin_install_service.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/services/provider_service.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/services/tool_service.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/ui.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/utils/__init__.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/utils/agent_introspection.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/utils/agent_text_formatter.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/utils/config_loader.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/utils/sample_records_pager.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/cli/version_notice.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/integrations/huggingface/__init__.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/integrations/huggingface/dataset_card.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/integrations/huggingface/dataset_card_template.md +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/integrations/opentelemetry.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/interface/errors.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/interface/results.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/commands/test_agent_command.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/commands/test_check_models_command.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/commands/test_create_command.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/commands/test_download_command.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/commands/test_list_command.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/commands/test_models_command.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/commands/test_plugin_command.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/commands/test_preview_command.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/commands/test_providers_command.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/commands/test_reset_command.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/commands/test_validate_command.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/conftest.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/controllers/test_download_controller.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/controllers/test_generation_controller.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/controllers/test_mcp_provider_controller.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/controllers/test_model_controller.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/controllers/test_plugin_catalog_controller.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/controllers/test_provider_controller.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/controllers/test_tool_controller.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/fixtures/upstream-catalogs/catalog-invalid-install.json +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/fixtures/upstream-catalogs/catalog-unsupported-version.json +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/fixtures/upstream-catalogs/catalog-valid.json +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/forms/test_field.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/forms/test_form.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/forms/test_mcp_provider_builder.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/forms/test_model_builder.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/forms/test_provider_builder.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/forms/test_tool_builder.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/repositories/test_mcp_provider_repository.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/repositories/test_model_repository.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/repositories/test_persona_repository.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/repositories/test_plugin_catalog_repository.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/repositories/test_provider_repository.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/repositories/test_tool_repository.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/services/test_download_service.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/services/test_mcp_provider_service.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/services/test_model_service.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/services/test_plugin_catalog_service.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/services/test_plugin_install_service.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/services/test_provider_service.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/services/test_tool_service.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/test_cli_utils.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/test_main.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/test_plugin_catalog.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/test_runtime.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/test_version_notice.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/utils/test_agent_introspection.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/utils/test_agent_introspection_integration.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/utils/test_agent_text_formatter.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/utils/test_config_loader.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/cli/utils/test_sample_records_pager.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/conftest.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/docs/test_build_notebooks_cached.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/docs/test_docs_workflows.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/docs/test_document_review_gate_recipe.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/docs/test_fern_notebook_snapshot.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/docs/test_fern_published_branch.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/docs/test_image_generation_recipes.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/integrations/huggingface/__init__.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/integrations/huggingface/test_client.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/integrations/huggingface/test_dataset_card.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/integrations/test_opentelemetry.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/interface/test_acreate.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/interface/test_results.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/test_agentic_ci_attempts.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/test_dependency_audit.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/test_import_perf.py +0 -0
- {data_designer-0.9.2 → data_designer-0.9.3}/tests/test_lazy_imports.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: data-designer
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.3
|
|
4
4
|
Summary: General framework for synthetic data generation
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -15,8 +15,8 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
15
15
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
16
16
|
Classifier: Topic :: Software Development
|
|
17
17
|
Requires-Python: >=3.10
|
|
18
|
-
Requires-Dist: data-designer-config==0.9.
|
|
19
|
-
Requires-Dist: data-designer-engine==0.9.
|
|
18
|
+
Requires-Dist: data-designer-config==0.9.3
|
|
19
|
+
Requires-Dist: data-designer-engine==0.9.3
|
|
20
20
|
Requires-Dist: huggingface-hub<2,>=1.0.1
|
|
21
21
|
Requires-Dist: opentelemetry-api<1.44,>=1.43
|
|
22
22
|
Requires-Dist: opentelemetry-exporter-prometheus<0.65,>=0.64b0
|
{data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/integrations/huggingface/client.py
RENAMED
|
@@ -119,7 +119,7 @@ class HuggingFaceHubClient:
|
|
|
119
119
|
URL to the uploaded dataset
|
|
120
120
|
|
|
121
121
|
Raises:
|
|
122
|
-
|
|
122
|
+
HuggingFaceHubClientUploadError: If validation fails or upload encounters errors
|
|
123
123
|
"""
|
|
124
124
|
logger.info(f"🤗 Uploading dataset to Hugging Face Hub: {repo_id}")
|
|
125
125
|
|
|
@@ -162,7 +162,7 @@ class HuggingFaceHubClient:
|
|
|
162
162
|
private: Whether to create private repo
|
|
163
163
|
|
|
164
164
|
Raises:
|
|
165
|
-
|
|
165
|
+
HuggingFaceHubClientUploadError: If repository creation fails
|
|
166
166
|
"""
|
|
167
167
|
logger.info(f"{LOG_INDENT}{RandomEmoji.working()} Checking if repository exists...")
|
|
168
168
|
try:
|
|
@@ -203,7 +203,7 @@ class HuggingFaceHubClient:
|
|
|
203
203
|
parquet_folder: Path to folder containing parquet files
|
|
204
204
|
|
|
205
205
|
Raises:
|
|
206
|
-
|
|
206
|
+
HuggingFaceHubClientUploadError: If upload fails
|
|
207
207
|
"""
|
|
208
208
|
logger.info(f"{LOG_INDENT}{RandomEmoji.loading()} Uploading main dataset files...")
|
|
209
209
|
try:
|
|
@@ -225,7 +225,7 @@ class HuggingFaceHubClient:
|
|
|
225
225
|
images_folder: Path to images folder
|
|
226
226
|
|
|
227
227
|
Raises:
|
|
228
|
-
|
|
228
|
+
HuggingFaceHubClientUploadError: If upload fails
|
|
229
229
|
"""
|
|
230
230
|
if not images_folder.exists():
|
|
231
231
|
return
|
|
@@ -255,7 +255,7 @@ class HuggingFaceHubClient:
|
|
|
255
255
|
processors_folder: Path to folder containing processor output directories
|
|
256
256
|
|
|
257
257
|
Raises:
|
|
258
|
-
|
|
258
|
+
HuggingFaceHubClientUploadError: If upload fails
|
|
259
259
|
"""
|
|
260
260
|
if not processors_folder.exists():
|
|
261
261
|
return
|
|
@@ -290,7 +290,7 @@ class HuggingFaceHubClient:
|
|
|
290
290
|
builder_config_path: Path to builder_config.json file
|
|
291
291
|
|
|
292
292
|
Raises:
|
|
293
|
-
|
|
293
|
+
HuggingFaceHubClientUploadError: If upload fails
|
|
294
294
|
"""
|
|
295
295
|
logger.info(f"{LOG_INDENT}{RandomEmoji.loading()} Uploading configuration files...")
|
|
296
296
|
|
|
@@ -345,7 +345,7 @@ class HuggingFaceHubClient:
|
|
|
345
345
|
tags: Additional custom tags for the dataset
|
|
346
346
|
|
|
347
347
|
Raises:
|
|
348
|
-
|
|
348
|
+
HuggingFaceHubClientUploadError: If dataset card generation or upload fails
|
|
349
349
|
"""
|
|
350
350
|
try:
|
|
351
351
|
with open(metadata_path) as f:
|
|
@@ -455,7 +455,7 @@ class HuggingFaceHubClient:
|
|
|
455
455
|
base_dataset_path: Path to dataset directory
|
|
456
456
|
|
|
457
457
|
Raises:
|
|
458
|
-
|
|
458
|
+
HuggingFaceHubClientUploadError: If directory structure is invalid
|
|
459
459
|
"""
|
|
460
460
|
if not base_dataset_path.exists():
|
|
461
461
|
raise HuggingFaceHubClientUploadError(f"Dataset path does not exist: {base_dataset_path}")
|
|
@@ -22,19 +22,41 @@ if TYPE_CHECKING:
|
|
|
22
22
|
DataDesignerWorkflowError,
|
|
23
23
|
)
|
|
24
24
|
from data_designer.interface.results import DatasetCreationResults # noqa: F401
|
|
25
|
+
from data_designer.interface.workflow_metadata import ( # noqa: F401
|
|
26
|
+
CompletedWorkflowStageMetadata,
|
|
27
|
+
FailedWorkflowStageMetadata,
|
|
28
|
+
RunningWorkflowStageMetadata,
|
|
29
|
+
SkippedWorkflowStageMetadata,
|
|
30
|
+
WorkflowMetadata,
|
|
31
|
+
WorkflowStageMetadata,
|
|
32
|
+
validate_workflow_stage_metadata,
|
|
33
|
+
)
|
|
25
34
|
|
|
26
35
|
_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
|
|
27
36
|
"CompositeWorkflow": ("data_designer.interface.composite_workflow", "CompositeWorkflow"),
|
|
28
37
|
"CompositeWorkflowResults": ("data_designer.interface.composite_workflow", "CompositeWorkflowResults"),
|
|
38
|
+
"CompletedWorkflowStageMetadata": (
|
|
39
|
+
"data_designer.interface.workflow_metadata",
|
|
40
|
+
"CompletedWorkflowStageMetadata",
|
|
41
|
+
),
|
|
29
42
|
"DataDesigner": ("data_designer.interface.data_designer", "DataDesigner"),
|
|
30
43
|
"DataDesignerEarlyShutdownError": ("data_designer.interface.errors", "DataDesignerEarlyShutdownError"),
|
|
31
44
|
"DataDesignerGenerationError": ("data_designer.interface.errors", "DataDesignerGenerationError"),
|
|
32
45
|
"DataDesignerProfilingError": ("data_designer.interface.errors", "DataDesignerProfilingError"),
|
|
33
46
|
"DataDesignerWorkflowError": ("data_designer.interface.errors", "DataDesignerWorkflowError"),
|
|
34
47
|
"DatasetCreationResults": ("data_designer.interface.results", "DatasetCreationResults"),
|
|
48
|
+
"FailedWorkflowStageMetadata": ("data_designer.interface.workflow_metadata", "FailedWorkflowStageMetadata"),
|
|
35
49
|
"ResumeMode": ("data_designer.config.run_config", "ResumeMode"),
|
|
50
|
+
"RunningWorkflowStageMetadata": ("data_designer.interface.workflow_metadata", "RunningWorkflowStageMetadata"),
|
|
36
51
|
"SkippedStageResult": ("data_designer.interface.composite_workflow", "SkippedStageResult"),
|
|
37
52
|
"SkippedStageStatus": ("data_designer.interface.composite_workflow", "SkippedStageStatus"),
|
|
53
|
+
"SkippedWorkflowStageMetadata": ("data_designer.interface.workflow_metadata", "SkippedWorkflowStageMetadata"),
|
|
54
|
+
"WorkflowMetadata": ("data_designer.interface.workflow_metadata", "WorkflowMetadata"),
|
|
55
|
+
"WorkflowStageMetadata": ("data_designer.interface.workflow_metadata", "WorkflowStageMetadata"),
|
|
56
|
+
"validate_workflow_stage_metadata": (
|
|
57
|
+
"data_designer.interface.workflow_metadata",
|
|
58
|
+
"validate_workflow_stage_metadata",
|
|
59
|
+
),
|
|
38
60
|
}
|
|
39
61
|
|
|
40
62
|
__all__ = list(_LAZY_IMPORTS.keys())
|
{data_designer-0.9.2 → data_designer-0.9.3}/src/data_designer/interface/composite_workflow.py
RENAMED
|
@@ -40,6 +40,7 @@ from data_designer.interface.results import (
|
|
|
40
40
|
_export_jsonl,
|
|
41
41
|
_export_parquet,
|
|
42
42
|
)
|
|
43
|
+
from data_designer.interface.workflow_metadata import WorkflowMetadata, validate_workflow_stage_metadata
|
|
43
44
|
|
|
44
45
|
if TYPE_CHECKING:
|
|
45
46
|
import pandas as pd
|
|
@@ -219,8 +220,19 @@ class CompositeWorkflow:
|
|
|
219
220
|
_validate_dir_name(name, "stage name")
|
|
220
221
|
if any(stage.name == name for stage in self._stages):
|
|
221
222
|
raise DataDesignerWorkflowError(f"Stage name {name!r} is already used in workflow {self.name!r}.")
|
|
222
|
-
if num_records is not None
|
|
223
|
-
|
|
223
|
+
if num_records is not None:
|
|
224
|
+
if not isinstance(num_records, int) or isinstance(num_records, bool):
|
|
225
|
+
raise DataDesignerWorkflowError("Stage num_records must be an integer.")
|
|
226
|
+
if num_records < 1:
|
|
227
|
+
raise DataDesignerWorkflowError("Stage num_records must be at least 1.")
|
|
228
|
+
if on_success_version is not None and not isinstance(on_success_version, str):
|
|
229
|
+
raise DataDesignerWorkflowError("Stage on_success_version must be a string.")
|
|
230
|
+
if not isinstance(allow_empty, bool):
|
|
231
|
+
raise DataDesignerWorkflowError("Stage allow_empty must be a boolean.")
|
|
232
|
+
if not isinstance(sampling_strategy, SamplingStrategy):
|
|
233
|
+
raise DataDesignerWorkflowError("Stage sampling_strategy must be a SamplingStrategy.")
|
|
234
|
+
if selection_strategy is not None and not isinstance(selection_strategy, (IndexRange, PartitionBlock)):
|
|
235
|
+
raise DataDesignerWorkflowError("Stage selection_strategy must be an IndexRange or PartitionBlock.")
|
|
224
236
|
_validate_stage_output(output)
|
|
225
237
|
output_processors = output_processors or []
|
|
226
238
|
_validate_distinct_output_processors(config_builder, output_processors)
|
|
@@ -276,6 +288,7 @@ class CompositeWorkflow:
|
|
|
276
288
|
workflow_path.mkdir(parents=True, exist_ok=True)
|
|
277
289
|
prior_metadata = _read_prior_workflow_metadata(workflow_path, self.name, resume)
|
|
278
290
|
metadata: dict[str, Any] = {
|
|
291
|
+
**(prior_metadata or {}),
|
|
279
292
|
"name": self.name,
|
|
280
293
|
"library_version": get_library_version(),
|
|
281
294
|
"stages": [],
|
|
@@ -388,6 +401,10 @@ class CompositeWorkflow:
|
|
|
388
401
|
f"Cannot resume workflow {self.name!r}: stage {stage.name!r} is not reusable."
|
|
389
402
|
)
|
|
390
403
|
|
|
404
|
+
if stage_resume == ResumeMode.ALWAYS and prior_stage_metadata is not None:
|
|
405
|
+
prior_stage = validate_workflow_stage_metadata(prior_stage_metadata)
|
|
406
|
+
stage_metadata.update(prior_stage.model_extra or {})
|
|
407
|
+
|
|
391
408
|
if stage_resume == ResumeMode.NEVER and stage_path.exists():
|
|
392
409
|
shutil.rmtree(stage_path)
|
|
393
410
|
|
|
@@ -598,6 +615,22 @@ def _read_prior_workflow_metadata(
|
|
|
598
615
|
raise DataDesignerWorkflowError(
|
|
599
616
|
f"Cannot resume workflow {workflow_name!r}: workflow metadata has invalid shape."
|
|
600
617
|
)
|
|
618
|
+
try:
|
|
619
|
+
validated_metadata = WorkflowMetadata.model_validate(metadata)
|
|
620
|
+
except ValidationError as exc:
|
|
621
|
+
if resume != ResumeMode.ALWAYS:
|
|
622
|
+
error = exc.errors(include_url=False, include_input=False)[0]
|
|
623
|
+
location = ".".join(str(part) for part in error["loc"])
|
|
624
|
+
logger.warning(
|
|
625
|
+
"Workflow metadata for %r has invalid field %s (%s); starting fresh.",
|
|
626
|
+
workflow_name,
|
|
627
|
+
location,
|
|
628
|
+
error["msg"],
|
|
629
|
+
)
|
|
630
|
+
return None
|
|
631
|
+
raise DataDesignerWorkflowError(
|
|
632
|
+
f"Cannot resume workflow {workflow_name!r}: workflow metadata has invalid shape."
|
|
633
|
+
) from exc
|
|
601
634
|
if metadata.get("name") != workflow_name:
|
|
602
635
|
if resume != ResumeMode.ALWAYS:
|
|
603
636
|
logger.warning("Workflow metadata for %r has a different name; starting fresh.", workflow_name)
|
|
@@ -605,7 +638,7 @@ def _read_prior_workflow_metadata(
|
|
|
605
638
|
raise DataDesignerWorkflowError(
|
|
606
639
|
f"Cannot resume workflow {workflow_name!r}: workflow metadata name does not match."
|
|
607
640
|
)
|
|
608
|
-
return
|
|
641
|
+
return validated_metadata.model_dump(mode="json", exclude_unset=True)
|
|
609
642
|
|
|
610
643
|
|
|
611
644
|
def _get_prior_stage_metadata(
|
|
@@ -870,9 +903,13 @@ def _parquet_files(path: Path) -> list[Path]:
|
|
|
870
903
|
def _write_workflow_metadata(workflow_path: Path, metadata: dict[str, Any]) -> None:
|
|
871
904
|
path = workflow_path / WORKFLOW_METADATA_FILENAME
|
|
872
905
|
tmp_path = path.with_name(f"{path.name}.tmp.{os.getpid()}.{uuid.uuid4().hex}")
|
|
906
|
+
try:
|
|
907
|
+
validated_metadata = WorkflowMetadata.model_validate(metadata)
|
|
908
|
+
except ValidationError as exc:
|
|
909
|
+
raise DataDesignerWorkflowError("Workflow metadata has invalid shape.") from exc
|
|
873
910
|
try:
|
|
874
911
|
with tmp_path.open("w", encoding="utf-8") as f:
|
|
875
|
-
json.dump(
|
|
912
|
+
json.dump(validated_metadata.model_dump(mode="json", exclude_unset=True), f, indent=2, sort_keys=True)
|
|
876
913
|
f.flush()
|
|
877
914
|
os.fsync(f.fileno())
|
|
878
915
|
os.replace(tmp_path, path)
|
|
@@ -354,11 +354,10 @@ class DataDesigner(DataDesignerInterface[DatasetCreationResults]):
|
|
|
354
354
|
)
|
|
355
355
|
|
|
356
356
|
try:
|
|
357
|
-
profiler_column_configs = config_builder.get_column_configs() or builder.data_designer_config.columns
|
|
358
357
|
profiler = self._create_dataset_profiler(
|
|
359
358
|
config_builder,
|
|
360
359
|
resource_provider,
|
|
361
|
-
column_configs=
|
|
360
|
+
column_configs=builder.data_designer_config.columns,
|
|
362
361
|
)
|
|
363
362
|
analysis = profiler.profile_dataset(num_records, dataset_for_profiler)
|
|
364
363
|
except Exception as e:
|
|
@@ -457,7 +456,11 @@ class DataDesigner(DataDesignerInterface[DatasetCreationResults]):
|
|
|
457
456
|
dataset_for_profiler = processed_dataset
|
|
458
457
|
|
|
459
458
|
try:
|
|
460
|
-
profiler = self._create_dataset_profiler(
|
|
459
|
+
profiler = self._create_dataset_profiler(
|
|
460
|
+
config_builder,
|
|
461
|
+
resource_provider,
|
|
462
|
+
column_configs=builder.data_designer_config.columns,
|
|
463
|
+
)
|
|
461
464
|
analysis = profiler.profile_dataset(num_records, dataset_for_profiler)
|
|
462
465
|
except Exception as e:
|
|
463
466
|
raise DataDesignerProfilingError(f"🛑 Error profiling preview dataset: {e}") from e
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Annotated, Any, Literal, TypeAlias
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field, TypeAdapter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class _WorkflowStageMetadataBase(BaseModel):
|
|
12
|
+
"""Common metadata for a composite workflow stage."""
|
|
13
|
+
|
|
14
|
+
model_config = ConfigDict(extra="allow")
|
|
15
|
+
|
|
16
|
+
status: Literal["running", "failed", "completed", "completed_empty", "skipped_empty_upstream"]
|
|
17
|
+
index: int
|
|
18
|
+
name: str
|
|
19
|
+
stage_dir: str
|
|
20
|
+
depends_on: list[str]
|
|
21
|
+
allow_empty: bool
|
|
22
|
+
on_success_version: str | None
|
|
23
|
+
output_processors: list[dict[str, Any]]
|
|
24
|
+
output: str
|
|
25
|
+
sampling_strategy: str
|
|
26
|
+
selection_strategy: dict[str, Any] | None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class _StartedWorkflowStageMetadata(_WorkflowStageMetadataBase):
|
|
30
|
+
fingerprint: str
|
|
31
|
+
num_records_requested: int
|
|
32
|
+
seeded_from_stage: str | None
|
|
33
|
+
seed_path: str | None
|
|
34
|
+
config: dict[str, Any]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class RunningWorkflowStageMetadata(_StartedWorkflowStageMetadata):
|
|
38
|
+
"""Metadata for a running workflow stage."""
|
|
39
|
+
|
|
40
|
+
status: Literal["running"]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class FailedWorkflowStageMetadata(_StartedWorkflowStageMetadata):
|
|
44
|
+
"""Metadata for a failed workflow stage."""
|
|
45
|
+
|
|
46
|
+
status: Literal["failed"]
|
|
47
|
+
duration_sec: float | None = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class CompletedWorkflowStageMetadata(_StartedWorkflowStageMetadata):
|
|
51
|
+
"""Metadata for a completed workflow stage."""
|
|
52
|
+
|
|
53
|
+
status: Literal["completed", "completed_empty"]
|
|
54
|
+
num_records_actual: int
|
|
55
|
+
output_records: int
|
|
56
|
+
output_seed_path: str
|
|
57
|
+
callback_output_path: str | None
|
|
58
|
+
stage_output_override_path: str | None = None
|
|
59
|
+
output_processor_output_path: str | None
|
|
60
|
+
duration_sec: float
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class SkippedWorkflowStageMetadata(_WorkflowStageMetadataBase):
|
|
64
|
+
"""Metadata for a stage skipped after an empty upstream stage."""
|
|
65
|
+
|
|
66
|
+
status: Literal["skipped_empty_upstream"]
|
|
67
|
+
upstream_stage: str
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
WorkflowStageMetadata: TypeAlias = Annotated[
|
|
71
|
+
RunningWorkflowStageMetadata
|
|
72
|
+
| FailedWorkflowStageMetadata
|
|
73
|
+
| CompletedWorkflowStageMetadata
|
|
74
|
+
| SkippedWorkflowStageMetadata,
|
|
75
|
+
Field(discriminator="status"),
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
_WORKFLOW_STAGE_METADATA_ADAPTER = TypeAdapter(WorkflowStageMetadata)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def validate_workflow_stage_metadata(value: object) -> WorkflowStageMetadata:
|
|
82
|
+
"""Validate the stage union and return its concrete status model.
|
|
83
|
+
|
|
84
|
+
Use the concrete status models for runtime type checks.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
return _WORKFLOW_STAGE_METADATA_ADAPTER.validate_python(value)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class WorkflowMetadata(BaseModel):
|
|
91
|
+
"""Metadata persisted for a composite workflow run."""
|
|
92
|
+
|
|
93
|
+
model_config = ConfigDict(extra="allow")
|
|
94
|
+
|
|
95
|
+
name: str
|
|
96
|
+
library_version: str
|
|
97
|
+
stages: list[WorkflowStageMetadata]
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import importlib.util
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from types import ModuleType
|
|
9
|
+
from unittest.mock import MagicMock
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
REPO_ROOT = Path(__file__).resolve().parents[4]
|
|
14
|
+
RECIPE_PATH = REPO_ROOT / "docs" / "assets" / "recipes" / "chart_qa" / "chart_qa.py"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _load_recipe() -> ModuleType:
|
|
18
|
+
spec = importlib.util.spec_from_file_location("chart_qa_recipe", RECIPE_PATH)
|
|
19
|
+
assert spec is not None
|
|
20
|
+
assert spec.loader is not None
|
|
21
|
+
module = importlib.util.module_from_spec(spec)
|
|
22
|
+
spec.loader.exec_module(module)
|
|
23
|
+
return module
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.fixture(scope="module")
|
|
27
|
+
def recipe() -> ModuleType:
|
|
28
|
+
return _load_recipe()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def seed_path(tmp_path: Path) -> Path:
|
|
33
|
+
path = tmp_path / "chart_seed.jsonl"
|
|
34
|
+
path.write_text('{"image_url": "https://example.com/chart.png"}\n', encoding="utf-8")
|
|
35
|
+
return path
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.mark.parametrize("prompt_version", ["level1", "level2"])
|
|
39
|
+
def test_chart_prompt_versions_match_pipeline_config(recipe: ModuleType, seed_path: Path, prompt_version: str) -> None:
|
|
40
|
+
config = recipe.build_config(seed_path=str(seed_path), prompt_version=prompt_version).build()
|
|
41
|
+
columns = {column.name: column for column in config.columns}
|
|
42
|
+
models = {model.alias: model for model in config.model_configs}
|
|
43
|
+
|
|
44
|
+
assert list(columns) == ["question_type", "question", "answer", "quality_check"]
|
|
45
|
+
assert columns["question_type"].params.values == list(recipe.QUESTION_TYPES)
|
|
46
|
+
assert columns["question_type"].params.weights == list(recipe.QUESTION_TYPE_WEIGHTS)
|
|
47
|
+
assert columns["question"].model_alias == "chart-vlm-question"
|
|
48
|
+
assert columns["answer"].model_alias == "chart-vlm"
|
|
49
|
+
assert columns["quality_check"].model_alias == "chart-vlm-quality"
|
|
50
|
+
assert columns["answer"].extract_reasoning_content is True
|
|
51
|
+
assert columns["question"].multi_modal_context[0].column_name == "image_url"
|
|
52
|
+
assert columns["question"].multi_modal_context[0].data_type == "url"
|
|
53
|
+
assert columns["question"].multi_modal_context[0].image_format is None
|
|
54
|
+
assert columns["answer"].multi_modal_context == columns["question"].multi_modal_context
|
|
55
|
+
assert columns["quality_check"].multi_modal_context == columns["question"].multi_modal_context
|
|
56
|
+
assert "{{ answer__reasoning_content }}" in columns["quality_check"].prompt
|
|
57
|
+
assert config.seed_config.sampling_strategy == recipe.dd.SamplingStrategy.ORDERED
|
|
58
|
+
assert list(models) == ["chart-vlm", "chart-vlm-question", "chart-vlm-quality"]
|
|
59
|
+
|
|
60
|
+
for model in models.values():
|
|
61
|
+
inference = model.inference_parameters
|
|
62
|
+
assert model.model == recipe.DEFAULT_VLM_MODEL
|
|
63
|
+
assert model.provider == recipe.VLLM_PROVIDER_NAME
|
|
64
|
+
assert model.skip_health_check is False
|
|
65
|
+
assert inference.max_tokens == 50000
|
|
66
|
+
assert inference.timeout == 1200
|
|
67
|
+
assert inference.temperature == 0.6
|
|
68
|
+
assert inference.top_p == 0.95
|
|
69
|
+
assert inference.max_parallel_requests == 20
|
|
70
|
+
assert inference.extra_body == recipe.PRODUCTION_SAMPLING
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@pytest.mark.parametrize(
|
|
74
|
+
("prompt_version", "raw_prompts"),
|
|
75
|
+
[
|
|
76
|
+
(
|
|
77
|
+
"level1",
|
|
78
|
+
("LEVEL1_QUESTION_PROMPT", "LEVEL1_ANSWER_PROMPT", "LEVEL1_QUALITY_PROMPT"),
|
|
79
|
+
),
|
|
80
|
+
(
|
|
81
|
+
"level2",
|
|
82
|
+
(
|
|
83
|
+
"LEVEL2_QUESTION_PROMPT",
|
|
84
|
+
"LEVEL2_ANSWER_PROMPT",
|
|
85
|
+
"LEVEL2_QUALITY_PROMPT",
|
|
86
|
+
),
|
|
87
|
+
),
|
|
88
|
+
],
|
|
89
|
+
)
|
|
90
|
+
def test_prompt_pack_matches_runtime_whitespace_handling(
|
|
91
|
+
recipe: ModuleType, prompt_version: str, raw_prompts: tuple[str, str, str]
|
|
92
|
+
) -> None:
|
|
93
|
+
prompt_pack = recipe.get_prompt_pack(prompt_version)
|
|
94
|
+
|
|
95
|
+
assert tuple(prompt_pack) == tuple(getattr(recipe, name).rstrip() for name in raw_prompts)
|
|
96
|
+
assert all(not prompt.endswith(("\n", "\r", " ", "\t")) for prompt in prompt_pack)
|
|
97
|
+
|
|
98
|
+
if prompt_version == "level2":
|
|
99
|
+
assert "DIFFICULTY FLOOR (required)" in prompt_pack.question
|
|
100
|
+
assert "NO-ATTEMPT HANDLING" in prompt_pack.answer
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_unknown_prompt_version_is_rejected(recipe: ModuleType) -> None:
|
|
104
|
+
with pytest.raises(ValueError, match="Unknown prompt version"):
|
|
105
|
+
recipe.get_prompt_pack("level3")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_create_dataset_uses_production_provider_settings(
|
|
109
|
+
recipe: ModuleType, seed_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
110
|
+
) -> None:
|
|
111
|
+
config_builder = recipe.build_config(seed_path=str(seed_path))
|
|
112
|
+
dataset_results = MagicMock()
|
|
113
|
+
data_designer = MagicMock()
|
|
114
|
+
data_designer.create.return_value = dataset_results
|
|
115
|
+
data_designer_class = MagicMock(return_value=data_designer)
|
|
116
|
+
monkeypatch.setattr(recipe, "DataDesigner", data_designer_class)
|
|
117
|
+
|
|
118
|
+
result = recipe.create_dataset(
|
|
119
|
+
config_builder,
|
|
120
|
+
num_records=7,
|
|
121
|
+
vllm_endpoint="http://localhost:8000/v1",
|
|
122
|
+
prompt_version="level1",
|
|
123
|
+
artifact_path="artifacts",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
assert result is dataset_results
|
|
127
|
+
provider = data_designer_class.call_args.kwargs["model_providers"][0]
|
|
128
|
+
assert provider.name == "vllm-vl"
|
|
129
|
+
assert provider.endpoint == "http://localhost:8000/v1"
|
|
130
|
+
assert provider.provider_type == "openai"
|
|
131
|
+
assert provider.api_key is None
|
|
132
|
+
data_designer.create.assert_called_once_with(
|
|
133
|
+
config_builder,
|
|
134
|
+
num_records=7,
|
|
135
|
+
dataset_name="chart_qa_level1",
|
|
136
|
+
)
|
|
@@ -6,6 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
import json
|
|
7
7
|
import shutil
|
|
8
8
|
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
9
10
|
from unittest.mock import MagicMock
|
|
10
11
|
|
|
11
12
|
import pytest
|
|
@@ -22,6 +23,7 @@ from data_designer.config.seed_source import LocalFileSeedSource
|
|
|
22
23
|
from data_designer.config.seed_source_dataframe import DataFrameSeedSource
|
|
23
24
|
from data_designer.engine.secret_resolver import PlaintextResolver
|
|
24
25
|
from data_designer.engine.storage.artifact_storage import ArtifactStorage, BatchStage, ResumeMode
|
|
26
|
+
from data_designer.interface import WorkflowMetadata
|
|
25
27
|
from data_designer.interface.composite_workflow import SkippedStageResult, SkippedStageStatus
|
|
26
28
|
from data_designer.interface.data_designer import DataDesigner
|
|
27
29
|
from data_designer.interface.errors import DataDesignerWorkflowError
|
|
@@ -194,6 +196,9 @@ def test_composite_workflow_runs_linear_stages_with_disk_handoff(
|
|
|
194
196
|
assert data_designer.artifact_path == stub_artifact_path
|
|
195
197
|
|
|
196
198
|
metadata = _load_workflow_metadata(stub_artifact_path, "linear-chain")
|
|
199
|
+
metadata_path = stub_artifact_path / "linear-chain" / "workflow-metadata.json"
|
|
200
|
+
validated_metadata = WorkflowMetadata.model_validate_json(metadata_path.read_text())
|
|
201
|
+
assert validated_metadata.model_dump(mode="json", exclude_unset=True) == metadata
|
|
197
202
|
assert [stage["status"] for stage in metadata["stages"]] == ["completed", "completed"]
|
|
198
203
|
assert metadata["stages"][1]["seeded_from_stage"] == "base"
|
|
199
204
|
assert metadata["stages"][1]["depends_on"] == ["base"]
|
|
@@ -364,6 +369,33 @@ def test_composite_workflow_rejects_invalid_stage_outputs(
|
|
|
364
369
|
workflow.add_stage("base", _category_builder(stub_model_configs), output=output)
|
|
365
370
|
|
|
366
371
|
|
|
372
|
+
@pytest.mark.parametrize(
|
|
373
|
+
("kwargs", "match"),
|
|
374
|
+
[
|
|
375
|
+
({"num_records": "3"}, "num_records must be an integer"),
|
|
376
|
+
({"num_records": True}, "num_records must be an integer"),
|
|
377
|
+
({"num_records": 0}, "num_records must be at least 1"),
|
|
378
|
+
({"on_success_version": 1}, "on_success_version must be a string"),
|
|
379
|
+
({"allow_empty": "yes"}, "allow_empty must be a boolean"),
|
|
380
|
+
({"sampling_strategy": "ordered"}, "sampling_strategy must be a SamplingStrategy"),
|
|
381
|
+
({"selection_strategy": {}}, "selection_strategy must be an IndexRange or PartitionBlock"),
|
|
382
|
+
],
|
|
383
|
+
)
|
|
384
|
+
def test_composite_workflow_rejects_invalid_stage_metadata_before_artifacts(
|
|
385
|
+
stub_artifact_path: Path,
|
|
386
|
+
stub_model_providers: list[ModelProvider],
|
|
387
|
+
stub_model_configs: list[ModelConfig],
|
|
388
|
+
kwargs: dict[str, Any],
|
|
389
|
+
match: str,
|
|
390
|
+
) -> None:
|
|
391
|
+
workflow = _data_designer(stub_artifact_path, stub_model_providers).compose_workflow(name="invalid-version")
|
|
392
|
+
|
|
393
|
+
with pytest.raises(DataDesignerWorkflowError, match=match):
|
|
394
|
+
workflow.add_stage("base", _category_builder(stub_model_configs), **kwargs)
|
|
395
|
+
|
|
396
|
+
assert not stub_artifact_path.exists()
|
|
397
|
+
|
|
398
|
+
|
|
367
399
|
def test_composite_workflow_rejects_unknown_processor_stage_output(
|
|
368
400
|
stub_artifact_path: Path,
|
|
369
401
|
stub_model_providers: list[ModelProvider],
|
|
@@ -568,7 +600,7 @@ def test_composite_workflow_stage_output_override_path_must_exist(
|
|
|
568
600
|
assert create_mock.call_count == 0
|
|
569
601
|
|
|
570
602
|
|
|
571
|
-
def
|
|
603
|
+
def test_composite_workflow_resume_if_possible_skips_legacy_completed_stages(
|
|
572
604
|
stub_artifact_path: Path,
|
|
573
605
|
stub_model_providers: list[ModelProvider],
|
|
574
606
|
stub_model_configs: list[ModelConfig],
|
|
@@ -580,6 +612,13 @@ def test_composite_workflow_resume_if_possible_skips_completed_stages(
|
|
|
580
612
|
workflow.add_stage("base", _category_builder(stub_model_configs), num_records=3)
|
|
581
613
|
workflow.add_stage("copy", _copy_builder(stub_model_configs))
|
|
582
614
|
workflow.run()
|
|
615
|
+
metadata_path = stub_artifact_path / "resume-skip" / "workflow-metadata.json"
|
|
616
|
+
metadata = json.loads(metadata_path.read_text(encoding="utf-8"))
|
|
617
|
+
metadata["workflow_extension"] = True
|
|
618
|
+
for stage in metadata["stages"]:
|
|
619
|
+
stage.pop("stage_output_override_path")
|
|
620
|
+
stage["stage_extension"] = {"value": stage["index"]}
|
|
621
|
+
metadata_path.write_text(json.dumps(metadata), encoding="utf-8")
|
|
583
622
|
create_mock.reset_mock()
|
|
584
623
|
|
|
585
624
|
resumed = data_designer.compose_workflow(name="resume-skip")
|
|
@@ -590,6 +629,10 @@ def test_composite_workflow_resume_if_possible_skips_completed_stages(
|
|
|
590
629
|
assert create_mock.call_count == 0
|
|
591
630
|
assert results.count_records() == 3
|
|
592
631
|
assert results.load_dataset()["category"].tolist() == ["alpha", "alpha", "alpha"]
|
|
632
|
+
resumed_metadata = json.loads(metadata_path.read_text(encoding="utf-8"))
|
|
633
|
+
assert resumed_metadata["workflow_extension"] is True
|
|
634
|
+
assert [stage["stage_extension"] for stage in resumed_metadata["stages"]] == [{"value": 0}, {"value": 1}]
|
|
635
|
+
assert all("stage_output_override_path" not in stage for stage in resumed_metadata["stages"])
|
|
593
636
|
|
|
594
637
|
|
|
595
638
|
def test_composite_workflow_resume_if_possible_skips_stage_with_output_processors(
|
|
@@ -825,6 +868,30 @@ def test_composite_workflow_resume_if_possible_invalid_metadata_shape_starts_fre
|
|
|
825
868
|
assert [call.kwargs["dataset_name"] for call in create_mock.call_args_list] == ["stage-0-base", "stage-1-copy"]
|
|
826
869
|
|
|
827
870
|
|
|
871
|
+
def test_composite_workflow_resume_if_possible_invalid_stage_metadata_starts_fresh(
|
|
872
|
+
stub_artifact_path: Path,
|
|
873
|
+
stub_model_providers: list[ModelProvider],
|
|
874
|
+
stub_model_configs: list[ModelConfig],
|
|
875
|
+
stub_dataset_profiler_results,
|
|
876
|
+
) -> None:
|
|
877
|
+
data_designer = _data_designer(stub_artifact_path, stub_model_providers)
|
|
878
|
+
create_mock = _patch_create(data_designer, stub_dataset_profiler_results)
|
|
879
|
+
workflow = data_designer.compose_workflow(name="resume-invalid-stage")
|
|
880
|
+
workflow.add_stage("base", _category_builder(stub_model_configs), num_records=2)
|
|
881
|
+
workflow.run()
|
|
882
|
+
metadata_path = stub_artifact_path / "resume-invalid-stage" / "workflow-metadata.json"
|
|
883
|
+
metadata = json.loads(metadata_path.read_text(encoding="utf-8"))
|
|
884
|
+
metadata["stages"][0]["status"] = "unknown"
|
|
885
|
+
metadata_path.write_text(json.dumps(metadata), encoding="utf-8")
|
|
886
|
+
create_mock.reset_mock()
|
|
887
|
+
|
|
888
|
+
resumed = data_designer.compose_workflow(name="resume-invalid-stage")
|
|
889
|
+
resumed.add_stage("base", _category_builder(stub_model_configs), num_records=2)
|
|
890
|
+
resumed.run(resume=ResumeMode.IF_POSSIBLE)
|
|
891
|
+
|
|
892
|
+
assert [call.kwargs["dataset_name"] for call in create_mock.call_args_list] == ["stage-0-base"]
|
|
893
|
+
|
|
894
|
+
|
|
828
895
|
@pytest.mark.parametrize("status", ["running", "failed"])
|
|
829
896
|
def test_composite_workflow_resume_if_possible_delegates_matching_resumable_stage(
|
|
830
897
|
stub_artifact_path: Path,
|
|
@@ -842,8 +909,20 @@ def test_composite_workflow_resume_if_possible_delegates_matching_resumable_stag
|
|
|
842
909
|
metadata_path = stub_artifact_path / "resume-partial" / "workflow-metadata.json"
|
|
843
910
|
metadata = json.loads(metadata_path.read_text(encoding="utf-8"))
|
|
844
911
|
_mark_stage_resumable(metadata, 0, status)
|
|
912
|
+
# duration_sec is schema-owned by failed metadata, but is an extension on running metadata.
|
|
913
|
+
if status == "failed":
|
|
914
|
+
metadata["stages"][0]["duration_sec"] = 123.0
|
|
915
|
+
metadata["stages"][0]["stage_extension"] = {"value": status}
|
|
845
916
|
metadata_path.write_text(json.dumps(metadata), encoding="utf-8")
|
|
846
917
|
create_mock.reset_mock()
|
|
918
|
+
create_side_effect = create_mock.side_effect
|
|
919
|
+
metadata_at_create: list[dict[str, Any]] = []
|
|
920
|
+
|
|
921
|
+
def capture_create(*args: Any, **kwargs: Any) -> DatasetCreationResults:
|
|
922
|
+
metadata_at_create.append(json.loads(metadata_path.read_text(encoding="utf-8")))
|
|
923
|
+
return create_side_effect(*args, **kwargs)
|
|
924
|
+
|
|
925
|
+
create_mock.side_effect = capture_create
|
|
847
926
|
|
|
848
927
|
resumed = data_designer.compose_workflow(name="resume-partial")
|
|
849
928
|
resumed.add_stage("base", _category_builder(stub_model_configs), num_records=2)
|
|
@@ -852,6 +931,10 @@ def test_composite_workflow_resume_if_possible_delegates_matching_resumable_stag
|
|
|
852
931
|
|
|
853
932
|
assert [call.kwargs["dataset_name"] for call in create_mock.call_args_list] == ["stage-0-base", "stage-1-copy"]
|
|
854
933
|
assert [call.kwargs["resume"] for call in create_mock.call_args_list] == [ResumeMode.ALWAYS, ResumeMode.NEVER]
|
|
934
|
+
assert metadata_at_create[0]["stages"][0]["stage_extension"] == {"value": status}
|
|
935
|
+
assert "duration_sec" not in metadata_at_create[0]["stages"][0]
|
|
936
|
+
resumed_metadata = json.loads(metadata_path.read_text(encoding="utf-8"))
|
|
937
|
+
assert resumed_metadata["stages"][0]["stage_extension"] == {"value": status}
|
|
855
938
|
|
|
856
939
|
|
|
857
940
|
def test_composite_workflow_resume_always_reruns_descendants_after_partial_stage(
|
|
@@ -936,6 +1019,29 @@ def test_composite_workflow_resume_always_rejects_invalid_metadata_shape(
|
|
|
936
1019
|
resumed.run(resume=ResumeMode.ALWAYS)
|
|
937
1020
|
|
|
938
1021
|
|
|
1022
|
+
def test_composite_workflow_resume_always_rejects_invalid_stage_metadata(
|
|
1023
|
+
stub_artifact_path: Path,
|
|
1024
|
+
stub_model_providers: list[ModelProvider],
|
|
1025
|
+
stub_model_configs: list[ModelConfig],
|
|
1026
|
+
stub_dataset_profiler_results,
|
|
1027
|
+
) -> None:
|
|
1028
|
+
data_designer = _data_designer(stub_artifact_path, stub_model_providers)
|
|
1029
|
+
_patch_create(data_designer, stub_dataset_profiler_results)
|
|
1030
|
+
workflow = data_designer.compose_workflow(name="resume-invalid-stage-always")
|
|
1031
|
+
workflow.add_stage("base", _category_builder(stub_model_configs), num_records=2)
|
|
1032
|
+
workflow.run()
|
|
1033
|
+
metadata_path = stub_artifact_path / "resume-invalid-stage-always" / "workflow-metadata.json"
|
|
1034
|
+
metadata = json.loads(metadata_path.read_text(encoding="utf-8"))
|
|
1035
|
+
metadata["stages"][0]["status"] = "unknown"
|
|
1036
|
+
metadata_path.write_text(json.dumps(metadata), encoding="utf-8")
|
|
1037
|
+
|
|
1038
|
+
resumed = data_designer.compose_workflow(name="resume-invalid-stage-always")
|
|
1039
|
+
resumed.add_stage("base", _category_builder(stub_model_configs), num_records=2)
|
|
1040
|
+
|
|
1041
|
+
with pytest.raises(DataDesignerWorkflowError, match="workflow metadata has invalid shape"):
|
|
1042
|
+
resumed.run(resume=ResumeMode.ALWAYS)
|
|
1043
|
+
|
|
1044
|
+
|
|
939
1045
|
def test_composite_workflow_resume_always_rejects_changed_stage(
|
|
940
1046
|
stub_artifact_path: Path,
|
|
941
1047
|
stub_model_providers: list[ModelProvider],
|