data-designer 0.9.2__tar.gz → 0.9.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. {data_designer-0.9.2 → data_designer-0.9.4}/PKG-INFO +5 -5
  2. {data_designer-0.9.2 → data_designer-0.9.4}/README.md +1 -1
  3. {data_designer-0.9.2 → data_designer-0.9.4}/pyproject.toml +1 -1
  4. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/integrations/huggingface/client.py +8 -8
  5. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/interface/__init__.py +22 -0
  6. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/interface/composite_workflow.py +41 -4
  7. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/interface/data_designer.py +7 -8
  8. data_designer-0.9.4/src/data_designer/interface/workflow_metadata.py +97 -0
  9. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/commands/test_create_command.py +54 -0
  10. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/controllers/test_generation_controller.py +3 -2
  11. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/utils/test_config_loader.py +13 -3
  12. data_designer-0.9.4/tests/docs/test_chart_qa_recipe.py +136 -0
  13. {data_designer-0.9.2 → data_designer-0.9.4}/tests/interface/test_composite_workflow.py +107 -1
  14. {data_designer-0.9.2 → data_designer-0.9.4}/tests/interface/test_data_designer.py +161 -5
  15. data_designer-0.9.4/tests/interface/test_workflow_metadata.py +191 -0
  16. {data_designer-0.9.2 → data_designer-0.9.4}/tests/test_lazy_imports.py +12 -0
  17. data_designer-0.9.4/tests/test_pr_linked_issue_workflow.py +215 -0
  18. {data_designer-0.9.2 → data_designer-0.9.4}/.gitignore +0 -0
  19. {data_designer-0.9.2 → data_designer-0.9.4}/dev-tools/hatch_build.py +0 -0
  20. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/README.md +0 -0
  21. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/__init__.py +0 -0
  22. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/agent_command_defs.py +0 -0
  23. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/commands/__init__.py +0 -0
  24. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/commands/agent.py +0 -0
  25. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/commands/check_models.py +0 -0
  26. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/commands/create.py +0 -0
  27. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/commands/download.py +0 -0
  28. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/commands/list.py +0 -0
  29. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/commands/mcp.py +0 -0
  30. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/commands/models.py +0 -0
  31. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/commands/plugin.py +0 -0
  32. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/commands/preview.py +0 -0
  33. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/commands/providers.py +0 -0
  34. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/commands/reset.py +0 -0
  35. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/commands/tools.py +0 -0
  36. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/commands/validate.py +0 -0
  37. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/controllers/__init__.py +0 -0
  38. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/controllers/download_controller.py +0 -0
  39. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/controllers/generation_controller.py +0 -0
  40. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/controllers/mcp_provider_controller.py +0 -0
  41. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/controllers/model_controller.py +0 -0
  42. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/controllers/plugin_catalog_controller.py +0 -0
  43. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/controllers/provider_controller.py +0 -0
  44. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/controllers/tool_controller.py +0 -0
  45. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/forms/__init__.py +0 -0
  46. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/forms/builder.py +0 -0
  47. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/forms/field.py +0 -0
  48. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/forms/form.py +0 -0
  49. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/forms/mcp_provider_builder.py +0 -0
  50. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/forms/model_builder.py +0 -0
  51. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/forms/provider_builder.py +0 -0
  52. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/forms/tool_builder.py +0 -0
  53. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/lazy_group.py +0 -0
  54. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/main.py +0 -0
  55. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/plugin_catalog.py +0 -0
  56. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/repositories/__init__.py +0 -0
  57. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/repositories/base.py +0 -0
  58. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/repositories/mcp_provider_repository.py +0 -0
  59. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/repositories/model_repository.py +0 -0
  60. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/repositories/persona_repository.py +0 -0
  61. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/repositories/plugin_catalog_repository.py +0 -0
  62. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/repositories/provider_repository.py +0 -0
  63. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/repositories/tool_repository.py +0 -0
  64. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/runtime.py +0 -0
  65. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/services/__init__.py +0 -0
  66. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/services/download_service.py +0 -0
  67. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/services/mcp_provider_service.py +0 -0
  68. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/services/model_service.py +0 -0
  69. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/services/plugin_catalog_service.py +0 -0
  70. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/services/plugin_install_service.py +0 -0
  71. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/services/provider_service.py +0 -0
  72. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/services/tool_service.py +0 -0
  73. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/ui.py +0 -0
  74. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/utils/__init__.py +0 -0
  75. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/utils/agent_introspection.py +0 -0
  76. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/utils/agent_text_formatter.py +0 -0
  77. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/utils/config_loader.py +0 -0
  78. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/utils/sample_records_pager.py +0 -0
  79. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/cli/version_notice.py +0 -0
  80. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/integrations/huggingface/__init__.py +0 -0
  81. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/integrations/huggingface/dataset_card.py +0 -0
  82. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/integrations/huggingface/dataset_card_template.md +0 -0
  83. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/integrations/opentelemetry.py +0 -0
  84. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/interface/errors.py +0 -0
  85. {data_designer-0.9.2 → data_designer-0.9.4}/src/data_designer/interface/results.py +0 -0
  86. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/commands/test_agent_command.py +0 -0
  87. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/commands/test_check_models_command.py +0 -0
  88. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/commands/test_download_command.py +0 -0
  89. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/commands/test_list_command.py +0 -0
  90. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/commands/test_models_command.py +0 -0
  91. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/commands/test_plugin_command.py +0 -0
  92. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/commands/test_preview_command.py +0 -0
  93. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/commands/test_providers_command.py +0 -0
  94. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/commands/test_reset_command.py +0 -0
  95. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/commands/test_validate_command.py +0 -0
  96. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/conftest.py +0 -0
  97. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/controllers/test_download_controller.py +0 -0
  98. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/controllers/test_mcp_provider_controller.py +0 -0
  99. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/controllers/test_model_controller.py +0 -0
  100. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/controllers/test_plugin_catalog_controller.py +0 -0
  101. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/controllers/test_provider_controller.py +0 -0
  102. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/controllers/test_tool_controller.py +0 -0
  103. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/fixtures/upstream-catalogs/catalog-invalid-install.json +0 -0
  104. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/fixtures/upstream-catalogs/catalog-unsupported-version.json +0 -0
  105. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/fixtures/upstream-catalogs/catalog-valid.json +0 -0
  106. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/forms/test_field.py +0 -0
  107. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/forms/test_form.py +0 -0
  108. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/forms/test_mcp_provider_builder.py +0 -0
  109. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/forms/test_model_builder.py +0 -0
  110. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/forms/test_provider_builder.py +0 -0
  111. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/forms/test_tool_builder.py +0 -0
  112. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/repositories/test_mcp_provider_repository.py +0 -0
  113. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/repositories/test_model_repository.py +0 -0
  114. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/repositories/test_persona_repository.py +0 -0
  115. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/repositories/test_plugin_catalog_repository.py +0 -0
  116. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/repositories/test_provider_repository.py +0 -0
  117. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/repositories/test_tool_repository.py +0 -0
  118. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/services/test_download_service.py +0 -0
  119. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/services/test_mcp_provider_service.py +0 -0
  120. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/services/test_model_service.py +0 -0
  121. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/services/test_plugin_catalog_service.py +0 -0
  122. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/services/test_plugin_install_service.py +0 -0
  123. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/services/test_provider_service.py +0 -0
  124. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/services/test_tool_service.py +0 -0
  125. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/test_cli_utils.py +0 -0
  126. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/test_main.py +0 -0
  127. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/test_plugin_catalog.py +0 -0
  128. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/test_runtime.py +0 -0
  129. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/test_version_notice.py +0 -0
  130. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/utils/test_agent_introspection.py +0 -0
  131. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/utils/test_agent_introspection_integration.py +0 -0
  132. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/utils/test_agent_text_formatter.py +0 -0
  133. {data_designer-0.9.2 → data_designer-0.9.4}/tests/cli/utils/test_sample_records_pager.py +0 -0
  134. {data_designer-0.9.2 → data_designer-0.9.4}/tests/conftest.py +0 -0
  135. {data_designer-0.9.2 → data_designer-0.9.4}/tests/docs/test_build_notebooks_cached.py +0 -0
  136. {data_designer-0.9.2 → data_designer-0.9.4}/tests/docs/test_docs_workflows.py +0 -0
  137. {data_designer-0.9.2 → data_designer-0.9.4}/tests/docs/test_document_review_gate_recipe.py +0 -0
  138. {data_designer-0.9.2 → data_designer-0.9.4}/tests/docs/test_fern_notebook_snapshot.py +0 -0
  139. {data_designer-0.9.2 → data_designer-0.9.4}/tests/docs/test_fern_published_branch.py +0 -0
  140. {data_designer-0.9.2 → data_designer-0.9.4}/tests/docs/test_image_generation_recipes.py +0 -0
  141. {data_designer-0.9.2 → data_designer-0.9.4}/tests/integrations/huggingface/__init__.py +0 -0
  142. {data_designer-0.9.2 → data_designer-0.9.4}/tests/integrations/huggingface/test_client.py +0 -0
  143. {data_designer-0.9.2 → data_designer-0.9.4}/tests/integrations/huggingface/test_dataset_card.py +0 -0
  144. {data_designer-0.9.2 → data_designer-0.9.4}/tests/integrations/test_opentelemetry.py +0 -0
  145. {data_designer-0.9.2 → data_designer-0.9.4}/tests/interface/test_acreate.py +0 -0
  146. {data_designer-0.9.2 → data_designer-0.9.4}/tests/interface/test_results.py +0 -0
  147. {data_designer-0.9.2 → data_designer-0.9.4}/tests/test_agentic_ci_attempts.py +0 -0
  148. {data_designer-0.9.2 → data_designer-0.9.4}/tests/test_dependency_audit.py +0 -0
  149. {data_designer-0.9.2 → data_designer-0.9.4}/tests/test_import_perf.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: data-designer
3
- Version: 0.9.2
3
+ Version: 0.9.4
4
4
  Summary: General framework for synthetic data generation
5
5
  License-Expression: Apache-2.0
6
6
  Classifier: Development Status :: 4 - Beta
@@ -15,14 +15,14 @@ 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.2
19
- Requires-Dist: data-designer-engine==0.9.2
18
+ Requires-Dist: data-designer-config==0.9.4
19
+ Requires-Dist: data-designer-engine==0.9.4
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
23
23
  Requires-Dist: opentelemetry-sdk<1.44,>=1.43
24
24
  Requires-Dist: packaging<27,>=25
25
- Requires-Dist: pandas<3,>=2.3.3
25
+ Requires-Dist: pandas<4,>=2.3.3
26
26
  Requires-Dist: prometheus-client<1,>=0.24
27
27
  Requires-Dist: prompt-toolkit<4,>=3.0.0
28
28
  Requires-Dist: pyarrow<25,>=24
@@ -173,7 +173,7 @@ After installation, invoke the `data-designer` skill or describe the dataset you
173
173
 
174
174
  This repository supports agent-assisted development — see [CONTRIBUTING.md](CONTRIBUTING.md) for the recommended workflow.
175
175
 
176
- - **[Contributing Guide](CONTRIBUTING.md)** – How to contribute, including agent-assisted workflows
176
+ - **[Contributing Guide](CONTRIBUTING.md)** – How to contribute, including issue triage before external PRs and agent-assisted workflows
177
177
  - **[GitHub Issues](https://github.com/NVIDIA-NeMo/DataDesigner/issues)** – Report bugs or make a feature request
178
178
 
179
179
  ---
@@ -139,7 +139,7 @@ After installation, invoke the `data-designer` skill or describe the dataset you
139
139
 
140
140
  This repository supports agent-assisted development — see [CONTRIBUTING.md](CONTRIBUTING.md) for the recommended workflow.
141
141
 
142
- - **[Contributing Guide](CONTRIBUTING.md)** – How to contribute, including agent-assisted workflows
142
+ - **[Contributing Guide](CONTRIBUTING.md)** – How to contribute, including issue triage before external PRs and agent-assisted workflows
143
143
  - **[GitHub Issues](https://github.com/NVIDIA-NeMo/DataDesigner/issues)** – Report bugs or make a feature request
144
144
 
145
145
  ---
@@ -47,7 +47,7 @@ dependencies = [
47
47
  "opentelemetry-exporter-prometheus>=0.64b0,<0.65",
48
48
  "opentelemetry-sdk>=1.43,<1.44",
49
49
  "packaging>=25,<27",
50
- "pandas>=2.3.3,<3",
50
+ "pandas>=2.3.3,<4",
51
51
  "prometheus-client>=0.24,<1",
52
52
  "prompt-toolkit>=3.0.0,<4",
53
53
  "pyarrow>=24,<25",
@@ -119,7 +119,7 @@ class HuggingFaceHubClient:
119
119
  URL to the uploaded dataset
120
120
 
121
121
  Raises:
122
- HuggingFaceUploadError: If validation fails or upload encounters errors
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
- HuggingFaceUploadError: If repository creation fails
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
- HuggingFaceUploadError: If upload fails
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
- HuggingFaceUploadError: If upload fails
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
- HuggingFaceUploadError: If upload fails
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
- HuggingFaceUploadError: If upload fails
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
- HuggingFaceUploadError: If dataset card generation or upload fails
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
- HuggingFaceUploadError: If directory structure is invalid
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())
@@ -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 and num_records < 1:
223
- raise DataDesignerWorkflowError("Stage num_records must be at least 1.")
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 metadata
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(metadata, f, indent=2, sort_keys=True)
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=profiler_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(config_builder, resource_provider)
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
@@ -623,11 +626,7 @@ class DataDesigner(DataDesignerInterface[DatasetCreationResults]):
623
626
  """Get the runtime configuration applied to dataset generation.
624
627
 
625
628
  Returns:
626
- The active RunConfig instance. Note that ``RunConfig`` normalizes
627
- some fields on construction (e.g., ``shutdown_error_rate`` becomes
628
- ``1.0`` when ``disable_early_shutdown=True``), so the returned
629
- object may not exactly equal the one originally passed to
630
- ``set_run_config``.
629
+ The active RunConfig instance.
631
630
  """
632
631
  return self._run_config
633
632
 
@@ -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]
@@ -3,11 +3,20 @@
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
+ from pathlib import Path
6
7
  from unittest.mock import MagicMock, patch
7
8
 
9
+ import pytest
10
+ from typer.testing import CliRunner
11
+
8
12
  from data_designer.cli.commands.create import create_command
13
+ from data_designer.cli.main import app
14
+ from data_designer.config.run_config import RunConfig
9
15
  from data_designer.engine.storage.artifact_storage import ResumeMode
10
16
 
17
+ runner = CliRunner()
18
+ _CTRL = "data_designer.cli.controllers.generation_controller"
19
+
11
20
  # ---------------------------------------------------------------------------
12
21
  # create_command delegation tests
13
22
  # ---------------------------------------------------------------------------
@@ -229,3 +238,48 @@ def test_create_command_passes_tui_override(mock_ctrl_cls: MagicMock) -> None:
229
238
  tui=False,
230
239
  script_args=None,
231
240
  )
241
+
242
+
243
+ # ---------------------------------------------------------------------------
244
+ # create --run-config overlay tests
245
+ # ---------------------------------------------------------------------------
246
+
247
+
248
+ @pytest.mark.parametrize(
249
+ ("yaml_text", "expected_rate", "expected_effective_rate"),
250
+ [
251
+ ("disable_early_shutdown: true\n", 0.3, 1.0),
252
+ ("disable_early_shutdown: true\nshutdown_error_rate: 0.45\n", 0.45, 1.0),
253
+ ("shutdown_error_rate: 0.45\n", 0.45, 0.45),
254
+ ],
255
+ ids=["flag-only-keeps-baseline-rate", "flag-with-explicit-rate", "rate-only"],
256
+ )
257
+ @patch(f"{_CTRL}.DataDesigner")
258
+ @patch(f"{_CTRL}.load_config_builder")
259
+ def test_create_run_config_overlay_preserves_shutdown_error_rate(
260
+ mock_load_config: MagicMock,
261
+ mock_dd_cls: MagicMock,
262
+ tmp_path: Path,
263
+ yaml_text: str,
264
+ expected_rate: float,
265
+ expected_effective_rate: float,
266
+ ) -> None:
267
+ """`create --run-config` overlays the YAML file without discarding the baseline error rate."""
268
+ run_config_file = tmp_path / "run-config.yaml"
269
+ run_config_file.write_text(yaml_text)
270
+
271
+ mock_load_config.return_value = MagicMock()
272
+ mock_results = MagicMock()
273
+ mock_results.count_records.return_value = 10
274
+ mock_results.load_analysis.return_value = None
275
+ mock_dd = MagicMock()
276
+ mock_dd.run_config = RunConfig(shutdown_error_rate=0.3)
277
+ mock_dd.create.return_value = mock_results
278
+ mock_dd_cls.return_value = mock_dd
279
+
280
+ result = runner.invoke(app, ["create", "dataset.yaml", "--run-config", str(run_config_file)])
281
+
282
+ assert result.exit_code == 0, result.output
283
+ effective = mock_dd.set_run_config.call_args.args[0]
284
+ assert effective.shutdown_error_rate == expected_rate
285
+ assert effective.effective_shutdown_error_rate == expected_effective_rate
@@ -866,7 +866,7 @@ def test_run_create_applies_run_config_precedence(
866
866
  }
867
867
  )
868
868
  mock_dd = MagicMock()
869
- mock_dd.run_config = RunConfig(buffer_size=64, progress_interval=11.0, display_tui=True)
869
+ mock_dd.run_config = RunConfig(buffer_size=64, progress_interval=11.0, display_tui=True, shutdown_error_rate=0.3)
870
870
  mock_dd.create.return_value = _make_mock_create_results()
871
871
  mock_dd_cls.return_value = mock_dd
872
872
 
@@ -886,7 +886,8 @@ def test_run_create_applies_run_config_precedence(
886
886
  assert effective.buffer_size == 1000
887
887
  assert effective.progress_interval == 11.0
888
888
  assert effective.preserve_dropped_columns is False
889
- assert effective.shutdown_error_rate == 1.0
889
+ assert effective.shutdown_error_rate == 0.3
890
+ assert effective.effective_shutdown_error_rate == 1.0
890
891
  assert effective.display_tui is False
891
892
  assert [item[0] for item in mock_dd.method_calls] == ["set_run_config", "create"]
892
893
  assert "Run config: run.yaml" in capsys.readouterr().out
@@ -68,13 +68,23 @@ def test_load_run_config_accepts_empty_mapping(tmp_path: Path) -> None:
68
68
  assert loaded.model_dump(exclude_unset=True) == {}
69
69
 
70
70
 
71
- def test_load_run_config_preserves_explicit_partial_fields(tmp_path: Path) -> None:
71
+ @pytest.mark.parametrize(
72
+ ("yaml_text", "expected"),
73
+ [
74
+ ("buffer_size: 250\ndisplay_tui: true\n", {"buffer_size": 250, "display_tui": True}),
75
+ ("disable_early_shutdown: true\n", {"disable_early_shutdown": True}),
76
+ ],
77
+ ids=["buffer-size-and-tui", "disable-early-shutdown-only"],
78
+ )
79
+ def test_load_run_config_preserves_explicit_partial_fields(
80
+ tmp_path: Path, yaml_text: str, expected: dict[str, object]
81
+ ) -> None:
72
82
  run_config_file = tmp_path / "run-config.yaml"
73
- run_config_file.write_text("buffer_size: 250\ndisplay_tui: true\n")
83
+ run_config_file.write_text(yaml_text)
74
84
 
75
85
  loaded = load_run_config(str(run_config_file))
76
86
 
77
- assert loaded.model_dump(exclude_unset=True) == {"buffer_size": 250, "display_tui": True}
87
+ assert loaded.model_dump(exclude_unset=True) == expected
78
88
 
79
89
 
80
90
  def test_load_run_config_preserves_partial_nested_fields(tmp_path: Path) -> None:
@@ -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
+ )