dslighting 1.1.8__tar.gz → 1.3.6__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 (86) hide show
  1. {dslighting-1.1.8 → dslighting-1.3.6}/PKG-INFO +1 -1
  2. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/services/llm.py +2 -1
  3. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/services/llm_single.py +2 -1
  4. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/services/sandbox.py +34 -5
  5. {dslighting-1.1.8 → dslighting-1.3.6}/dslighting/__init__.py +29 -8
  6. {dslighting-1.1.8 → dslighting-1.3.6}/dslighting/core/agent.py +122 -23
  7. {dslighting-1.1.8 → dslighting-1.3.6}/dslighting/core/config_builder.py +30 -1
  8. {dslighting-1.1.8 → dslighting-1.3.6}/dslighting/core/data_loader.py +46 -1
  9. {dslighting-1.1.8 → dslighting-1.3.6}/dslighting/utils/defaults.py +1 -1
  10. {dslighting-1.1.8 → dslighting-1.3.6}/dslighting.egg-info/PKG-INFO +1 -1
  11. {dslighting-1.1.8 → dslighting-1.3.6}/pyproject.toml +1 -1
  12. {dslighting-1.1.8 → dslighting-1.3.6}/README.md +0 -0
  13. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/__init__.py +0 -0
  14. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/benchmark/__init__.py +0 -0
  15. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/benchmark/benchmark.py +0 -0
  16. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/benchmark/datasci.py +0 -0
  17. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/benchmark/mle.py +0 -0
  18. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/benchmark/sciencebench.py +0 -0
  19. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/common/__init__.py +0 -0
  20. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/common/constants.py +0 -0
  21. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/common/exceptions.py +0 -0
  22. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/common/typing.py +0 -0
  23. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/config.py +0 -0
  24. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/models/__init__.py +0 -0
  25. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/models/candidates.py +0 -0
  26. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/models/formats.py +0 -0
  27. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/models/task.py +0 -0
  28. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/operators/__init__.py +0 -0
  29. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/operators/aflow_ops.py +0 -0
  30. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/operators/autokaggle_ops.py +0 -0
  31. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/operators/automind_ops.py +0 -0
  32. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/operators/base.py +0 -0
  33. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/operators/code.py +0 -0
  34. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/operators/dsagent_ops.py +0 -0
  35. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/operators/llm_basic.py +0 -0
  36. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/prompts/__init__.py +0 -0
  37. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/prompts/aflow_prompt.py +0 -0
  38. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/prompts/aide_prompt.py +0 -0
  39. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/prompts/autokaggle_prompt.py +0 -0
  40. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/prompts/automind_prompt.py +0 -0
  41. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/prompts/common.py +0 -0
  42. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/prompts/data_interpreter_prompt.py +0 -0
  43. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/prompts/dsagent_prompt.py +0 -0
  44. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/runner.py +0 -0
  45. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/services/__init__.py +0 -0
  46. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/services/data_analyzer.py +0 -0
  47. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/services/states/__init__.py +0 -0
  48. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/services/states/autokaggle_state.py +0 -0
  49. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/services/states/base.py +0 -0
  50. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/services/states/dsa_log.py +0 -0
  51. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/services/states/experience.py +0 -0
  52. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/services/states/journal.py +0 -0
  53. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/services/states/operator_library.py +0 -0
  54. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/services/vdb.py +0 -0
  55. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/services/workspace.py +0 -0
  56. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/tasks/__init__.py +0 -0
  57. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/tasks/handlers.py +0 -0
  58. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/templates/open_ended/grade_template.py +0 -0
  59. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/tools/__init__.py +0 -0
  60. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/utils/__init__.py +0 -0
  61. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/utils/context.py +0 -0
  62. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/utils/dynamic_import.py +0 -0
  63. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/utils/parsing.py +0 -0
  64. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/workflows/__init__.py +0 -0
  65. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/workflows/base.py +0 -0
  66. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/workflows/factory.py +0 -0
  67. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/workflows/manual/__init__.py +0 -0
  68. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/workflows/manual/autokaggle_workflow.py +0 -0
  69. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/workflows/manual/data_interpreter_workflow.py +0 -0
  70. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/workflows/manual/deepanalyze_workflow.py +0 -0
  71. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/workflows/manual/dsagent_workflow.py +0 -0
  72. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/workflows/search/__init__.py +0 -0
  73. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/workflows/search/aflow_workflow.py +0 -0
  74. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/workflows/search/aide_workflow.py +0 -0
  75. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/workflows/search/automind_workflow.py +0 -0
  76. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/workflows/templates/__init__.py +0 -0
  77. {dslighting-1.1.8 → dslighting-1.3.6}/dsat/workflows/templates/basic_kaggle_loop.py +0 -0
  78. {dslighting-1.1.8 → dslighting-1.3.6}/dslighting/core/__init__.py +0 -0
  79. {dslighting-1.1.8 → dslighting-1.3.6}/dslighting/core/task_detector.py +0 -0
  80. {dslighting-1.1.8 → dslighting-1.3.6}/dslighting/utils/__init__.py +0 -0
  81. {dslighting-1.1.8 → dslighting-1.3.6}/dslighting.egg-info/SOURCES.txt +0 -0
  82. {dslighting-1.1.8 → dslighting-1.3.6}/dslighting.egg-info/dependency_links.txt +0 -0
  83. {dslighting-1.1.8 → dslighting-1.3.6}/dslighting.egg-info/requires.txt +0 -0
  84. {dslighting-1.1.8 → dslighting-1.3.6}/dslighting.egg-info/top_level.txt +0 -0
  85. {dslighting-1.1.8 → dslighting-1.3.6}/setup.cfg +0 -0
  86. {dslighting-1.1.8 → dslighting-1.3.6}/tests/test_dslighting_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dslighting
3
- Version: 1.1.8
3
+ Version: 1.3.6
4
4
  Summary: Simplified API for Data Science Agent Automation
5
5
  Author: DSLighting Team
6
6
  License: AGPL-3.0
@@ -43,7 +43,8 @@ def _load_custom_model_pricing():
43
43
  config = yaml.safe_load(f)
44
44
  return config.get('custom_model_pricing', {})
45
45
  else:
46
- logger.warning(f"Config file not found at {config_yaml_path}")
46
+ # Changed to debug to avoid confusing warnings for pip-installed packages
47
+ logger.debug(f"Config file not found at {config_yaml_path} (this is expected for pip-installed packages)")
47
48
  return {}
48
49
  except Exception as e:
49
50
  logger.error(f"Failed to load cost configuration: {e}")
@@ -43,7 +43,8 @@ def _load_custom_model_pricing():
43
43
  config = yaml.safe_load(f)
44
44
  return config.get('custom_model_pricing', {})
45
45
  else:
46
- logger.warning(f"Config file not found at {config_yaml_path}")
46
+ # Changed to debug to avoid confusing warnings for pip-installed packages
47
+ logger.debug(f"Config file not found at {config_yaml_path} (this is expected for pip-installed packages)")
47
48
  return {}
48
49
  except Exception as e:
49
50
  logger.error(f"Failed to load cost configuration: {e}")
@@ -245,17 +245,46 @@ class ProcessIsolatedNotebookExecutor:
245
245
  class SandboxService:
246
246
  """
247
247
  Provides unified access to isolated script and notebook code execution environments.
248
+
249
+ Args:
250
+ workspace: Workspace service for managing files
251
+ timeout: Default timeout for script execution (seconds)
252
+ auto_matplotlib: Automatically inject matplotlib backend (default: False).
253
+ Set to True for Web UI environments that need visualization.
254
+ Set to False for standalone package usage.
248
255
  """
249
- def __init__(self, workspace: WorkspaceService, timeout: int = 600):
256
+ def __init__(
257
+ self,
258
+ workspace: WorkspaceService,
259
+ timeout: int = 600,
260
+ auto_matplotlib: bool = False
261
+ ):
250
262
  self.workspace = workspace
251
263
  self.timeout = timeout
264
+ self.auto_matplotlib = auto_matplotlib # Store matplotlib injection preference
252
265
  self.execution_history: List[Dict[str, Any]] = []
253
266
 
254
267
  def run_script(self, script_code: str, timeout: Optional[int] = None) -> ExecutionResult:
255
- """Runs a Python script within the sandbox workspace."""
256
- # Force non-interactive backend for matplotlib to prevent blocking plt.show()
257
- fixed_code = "import matplotlib\nmatplotlib.use('Agg')\n" + script_code
258
-
268
+ """
269
+ Runs a Python script within the sandbox workspace.
270
+
271
+ Args:
272
+ script_code: Python code to execute
273
+ timeout: Optional timeout override (uses self.timeout if None)
274
+
275
+ Returns:
276
+ ExecutionResult with stdout, stderr, success status, etc.
277
+ """
278
+ # Optionally inject matplotlib non-interactive backend
279
+ # This is only done if auto_matplotlib=True (used by Web UI for visualization)
280
+ if self.auto_matplotlib:
281
+ # Force non-interactive backend for matplotlib to prevent blocking plt.show()
282
+ fixed_code = "import matplotlib\nmatplotlib.use('Agg')\n" + script_code
283
+ logger.debug("Auto-injected matplotlib non-interactive backend")
284
+ else:
285
+ # Use code as-is without modification (default for DSLighting package)
286
+ fixed_code = script_code
287
+
259
288
  script_name = f"_sandbox_script_{uuid.uuid4().hex}.py"
260
289
  script_path = self.workspace.run_dir / script_name
261
290
  execution_id = uuid.uuid4().hex
@@ -26,7 +26,7 @@ Advanced Usage:
26
26
  For more information, see: https://github.com/usail-hkust/dslighting
27
27
  """
28
28
 
29
- __version__ = "1.1.8"
29
+ __version__ = "1.3.6"
30
30
  __author__ = "DSLighting Team"
31
31
 
32
32
  # Core API classes
@@ -60,29 +60,40 @@ def load_data(source, **kwargs):
60
60
  return loader.load(source, **kwargs)
61
61
 
62
62
 
63
- def run_agent(data, **kwargs):
63
+ def run_agent(data=None, task_id=None, data_dir=None, keep_workspace=False, keep_workspace_on_failure=True, **kwargs):
64
64
  """
65
65
  Quick one-liner: load data and run with defaults.
66
66
 
67
67
  This function creates an Agent with the specified parameters and runs it on the data.
68
68
 
69
69
  Args:
70
- data: Data source (path, DataFrame, dict, etc.)
70
+ data: Optional data source (path, DataFrame, dict, etc.)
71
+ task_id: Task/Competition identifier (e.g., "bike-sharing-demand")
72
+ data_dir: Base data directory (default: "data/competitions")
73
+ keep_workspace: Keep workspace after completion (default: False)
74
+ keep_workspace_on_failure: Keep workspace on failure (default: True)
71
75
  **kwargs: Parameters passed to Agent.__init__ and Agent.run
72
76
 
73
77
  Returns:
74
78
  AgentResult with output, metrics, and metadata
75
79
 
76
80
  Examples:
77
- >>> # Simplest usage - all defaults
78
- >>> result = dslighting.run_agent("data/titanic")
81
+ >>> # Recommended: using task_id
82
+ >>> result = dslighting.run_agent(
83
+ ... task_id="bike-sharing-demand",
84
+ ... data_dir="data/competitions"
85
+ ... )
79
86
  >>> print(f"Score: {result.score}, Cost: ${result.cost}")
80
87
 
88
+ >>> # Legacy: using data path
89
+ >>> result = dslighting.run_agent("data/titanic")
90
+
81
91
  >>> # With custom parameters
82
92
  >>> result = dslighting.run_agent(
83
- ... "data/titanic",
93
+ ... task_id="bike-sharing-demand",
84
94
  ... workflow="autokaggle",
85
- ... model="gpt-4o"
95
+ ... model="gpt-4o",
96
+ ... keep_workspace=True # Keep workspace for debugging
86
97
  ... )
87
98
  """
88
99
  # Extract run-specific parameters if present
@@ -90,7 +101,7 @@ def run_agent(data, **kwargs):
90
101
  agent_params = {}
91
102
 
92
103
  # Parameters that should go to run(), not __init__
93
- run_only_params = {'task_id', 'output_path', 'description'}
104
+ run_only_params = {'task_id', 'data_dir', 'output_path', 'description'}
94
105
 
95
106
  for key, value in kwargs.items():
96
107
  if key in run_only_params:
@@ -98,6 +109,16 @@ def run_agent(data, **kwargs):
98
109
  else:
99
110
  agent_params[key] = value
100
111
 
112
+ # Add explicit parameters to run_kwargs
113
+ if task_id is not None:
114
+ run_kwargs['task_id'] = task_id
115
+ if data_dir is not None:
116
+ run_kwargs['data_dir'] = data_dir
117
+
118
+ # Add workspace preservation parameters to agent
119
+ agent_params['keep_workspace'] = keep_workspace
120
+ agent_params['keep_workspace_on_failure'] = keep_workspace_on_failure
121
+
101
122
  # Create agent and run
102
123
  agent = Agent(**agent_params)
103
124
  return agent.run(data, **run_kwargs)
@@ -108,6 +108,8 @@ class Agent:
108
108
  num_drafts: int = None,
109
109
  workspace_dir: str = None,
110
110
  run_name: str = None,
111
+ keep_workspace: bool = False,
112
+ keep_workspace_on_failure: bool = True,
111
113
  verbose: bool = True,
112
114
  **kwargs
113
115
  ):
@@ -129,6 +131,8 @@ class Agent:
129
131
  num_drafts: Number of drafts to generate
130
132
  workspace_dir: Custom workspace directory
131
133
  run_name: Name for this run
134
+ keep_workspace: Keep workspace after completion (default: False)
135
+ keep_workspace_on_failure: Keep workspace on failure (default: True)
132
136
  verbose: Enable verbose logging
133
137
  **kwargs: Additional parameters passed to DSATConfig
134
138
  """
@@ -148,6 +152,8 @@ class Agent:
148
152
  num_drafts=num_drafts,
149
153
  workspace_dir=workspace_dir,
150
154
  run_name=run_name,
155
+ keep_workspace=keep_workspace,
156
+ keep_workspace_on_failure=keep_workspace_on_failure,
151
157
  **kwargs
152
158
  )
153
159
 
@@ -161,8 +167,9 @@ class Agent:
161
167
 
162
168
  def run(
163
169
  self,
164
- data: Union[str, Path, dict, pd.DataFrame, LoadedData],
170
+ data: Union[str, Path, dict, pd.DataFrame, LoadedData] = None,
165
171
  task_id: str = None,
172
+ data_dir: str = None,
166
173
  output_path: str = None,
167
174
  description: str = None,
168
175
  **kwargs
@@ -175,8 +182,12 @@ class Agent:
175
182
  result collection.
176
183
 
177
184
  Args:
178
- data: Data source (path, DataFrame, dict, or LoadedData)
179
- task_id: Optional task identifier
185
+ data: Optional data source (path, DataFrame, dict, or LoadedData).
186
+ If not provided, use task_id + data_dir pattern.
187
+ task_id: Task/Competition identifier (e.g., "bike-sharing-demand").
188
+ Required when using MLE benchmark format.
189
+ data_dir: Base data directory containing competition data.
190
+ Default: "data/competitions"
180
191
  output_path: Custom output path for results
181
192
  description: Optional task description (overrides detected)
182
193
  **kwargs: Additional task parameters
@@ -185,22 +196,77 @@ class Agent:
185
196
  AgentResult with output, metrics, and metadata
186
197
 
187
198
  Examples:
188
- >>> result = agent.run("data/titanic")
189
- >>> print(f"Score: {result.score}, Cost: ${result.cost}")
199
+ >>> # Method 1: Recommended - using task_id + data_dir
200
+ >>> result = agent.run(
201
+ ... task_id="bike-sharing-demand",
202
+ ... data_dir="data/competitions"
203
+ ... )
204
+
205
+ >>> # Method 2: Using data path directly
206
+ >>> result = agent.run("path/to/competition")
190
207
 
208
+ >>> # Method 3: Using DataFrame
191
209
  >>> result = agent.run(df, description="Predict price")
192
- >>> predictions = result.output
193
210
  """
194
211
  # Start timing
195
212
  start_time = time.time()
196
213
 
197
214
  try:
198
- # Load data if not already loaded
199
- if not isinstance(data, LoadedData):
215
+ # ========== New simplified API: task_id + data_dir ==========
216
+ if task_id:
217
+ # Set default data_dir if not provided
218
+ if data_dir is None:
219
+ data_dir = "data/competitions"
220
+
221
+ self.logger.info(f"Using MLE benchmark format")
222
+ self.logger.info(f" task_id: {task_id}")
223
+ self.logger.info(f" data_dir: {data_dir}")
224
+
225
+ # Resolve paths
226
+ data_dir_path = Path(data_dir).resolve()
227
+ competition_dir = data_dir_path / task_id
228
+
229
+ # Check if task exists in benchmarks registry
230
+ benchmark_dir = self._get_default_benchmark_dir()
231
+ task_registry = benchmark_dir / task_id
232
+
233
+ if not task_registry.exists():
234
+ self.logger.warning(
235
+ f"Task '{task_id}' not found in benchmark registry: {benchmark_dir}"
236
+ )
237
+ self.logger.warning(
238
+ f"This means the task cannot be auto-graded. "
239
+ f"To enable grading, register the task at: {task_registry}"
240
+ )
241
+ else:
242
+ self.logger.info(f" ✓ Task registered: {task_registry}")
243
+
244
+ # Check if data exists
245
+ if not competition_dir.exists():
246
+ raise FileNotFoundError(
247
+ f"Data directory not found: {competition_dir}\n"
248
+ f"Please ensure data is prepared at: {competition_dir}/prepared/"
249
+ )
250
+
251
+ self.logger.info(f" Data directory: {competition_dir}")
252
+
253
+ # Load data
200
254
  loader = DataLoader()
201
- loaded_data = loader.load(data)
255
+ loaded_data = loader.load(competition_dir)
256
+
257
+ # ========== Legacy API: direct data path ==========
258
+ elif data is not None:
259
+ # Load data if not already loaded
260
+ if not isinstance(data, LoadedData):
261
+ loader = DataLoader()
262
+ loaded_data = loader.load(data)
263
+ else:
264
+ loaded_data = data
202
265
  else:
203
- loaded_data = data
266
+ raise ValueError(
267
+ "Either 'task_id' or 'data' must be provided. "
268
+ "Example: agent.run(task_id='bike-sharing-demand', data_dir='data/competitions')"
269
+ )
204
270
 
205
271
  # Get task information
206
272
  task_detection = loaded_data.task_detection
@@ -365,30 +431,33 @@ class Agent:
365
431
 
366
432
  if task_type == "kaggle":
367
433
  # MLE/Kaggle format: needs public_data_dir and output_submission_path
434
+ # Follow MLEBenchmark pattern: {data_dir}/prepared/public
368
435
  prepared_dir = data_dir / "prepared"
369
- if prepared_dir.exists():
370
- public_dir = prepared_dir / "public"
371
- if public_dir.exists():
372
- payload["public_data_dir"] = str(public_dir)
373
- else:
374
- # Fallback: use data_dir as public_data_dir
375
- payload["public_data_dir"] = str(data_dir)
436
+ public_dir = prepared_dir / "public"
437
+
438
+ # Check if prepared/public exists (MLE format)
439
+ if public_dir.exists():
440
+ payload["public_data_dir"] = str(public_dir.resolve())
441
+ self.logger.info(f"Using MLE prepared data: {public_dir.resolve()}")
376
442
  else:
377
- # No prepared dir, use data_dir directly
378
- payload["public_data_dir"] = str(data_dir)
443
+ # Fallback: use data_dir directly
444
+ payload["public_data_dir"] = str(data_dir.resolve())
445
+ self.logger.warning(
446
+ f"Prepared data not found at {public_dir}, using data_dir instead"
447
+ )
379
448
 
380
- # Set output path to workspace with unique ID (like MLEBenchmark does)
449
+ # Set output path - use simple filename, will be saved in workspace/sandbox
381
450
  if output_path is None:
382
451
  # Extract competition_id from data_dir path if possible
383
452
  competition_id = data_dir.name
384
453
  unique_id = str(uuid.uuid4())[:8]
385
454
  output_filename = f"submission_{competition_id}_{unique_id}.csv"
386
455
 
387
- # Save to workspace directory (not sandbox, workspace is preserved)
388
- workspace_dir = self._get_workspace_dir()
389
- output_path = workspace_dir / output_filename
456
+ # Use just the filename - DSAT will save it in workspace/sandbox
457
+ output_path = Path(output_filename)
390
458
 
391
459
  payload["output_submission_path"] = str(output_path)
460
+ self.logger.info(f"Output submission file: {output_path}")
392
461
  else:
393
462
  # Other task types: use data_dir
394
463
  payload["data_dir"] = str(data_dir)
@@ -427,6 +496,36 @@ class Agent:
427
496
 
428
497
  return workspace_path
429
498
 
499
+ def _get_default_benchmark_dir(self) -> Path:
500
+ """
501
+ Get the default benchmark registry directory.
502
+
503
+ This is where task registration files (grade.py, description.md, etc.) are stored.
504
+ Default: benchmarks/mlebench/competitions/
505
+
506
+ Returns:
507
+ Path to benchmark registry directory
508
+ """
509
+ # Try to get from config
510
+ benchmark_dir = None
511
+
512
+ if hasattr(self, 'config') and hasattr(self.config, 'run'):
513
+ run_config = self.config.run
514
+ if hasattr(run_config, 'parameters') and run_config.parameters:
515
+ benchmark_dir = run_config.parameters.get('benchmark_dir')
516
+
517
+ # Fallback to default benchmark directory
518
+ if benchmark_dir is None:
519
+ # Use relative path from current working directory
520
+ # Default: benchmarks/mlebench/competitions/
521
+ benchmark_dir = "benchmarks/mlebench/competitions"
522
+
523
+ benchmark_path = Path(benchmark_dir).resolve()
524
+
525
+ self.logger.debug(f"Benchmark registry directory: {benchmark_path}")
526
+
527
+ return benchmark_path
528
+
430
529
  async def _execute_task(
431
530
  self,
432
531
  task: TaskDefinition,
@@ -60,6 +60,8 @@ class ConfigBuilder:
60
60
  num_drafts: int = None,
61
61
  workspace_dir: str = None,
62
62
  run_name: str = None,
63
+ keep_workspace: bool = None,
64
+ keep_workspace_on_failure: bool = None,
63
65
  **kwargs
64
66
  ) -> DSATConfig:
65
67
  """
@@ -76,6 +78,8 @@ class ConfigBuilder:
76
78
  num_drafts: Number of drafts to generate
77
79
  workspace_dir: Workspace directory
78
80
  run_name: Name for this run
81
+ keep_workspace: Keep workspace after completion
82
+ keep_workspace_on_failure: Keep workspace on failure
79
83
  **kwargs: Additional parameters
80
84
 
81
85
  Returns:
@@ -100,6 +104,8 @@ class ConfigBuilder:
100
104
  num_drafts=num_drafts,
101
105
  workspace_dir=workspace_dir,
102
106
  run_name=run_name,
107
+ keep_workspace=keep_workspace,
108
+ keep_workspace_on_failure=keep_workspace_on_failure,
103
109
  **kwargs
104
110
  )
105
111
  config = self._deep_merge(config, user_config)
@@ -180,7 +186,22 @@ class ConfigBuilder:
180
186
  logger.warning("LLM_MODEL_CONFIGS must be a JSON object")
181
187
  return {}
182
188
 
183
- return {k: v for k, v in parsed.items() if isinstance(k, str) and isinstance(v, dict)}
189
+ # Process each model config
190
+ result = {}
191
+ for k, v in parsed.items():
192
+ if not isinstance(k, str) or not isinstance(v, dict):
193
+ continue
194
+
195
+ # Handle api_key as list (take the first one)
196
+ if "api_key" in v and isinstance(v["api_key"], list):
197
+ if len(v["api_key"]) > 0:
198
+ v = v.copy() # Shallow copy to avoid mutating original
199
+ v["api_key"] = v["api_key"][0]
200
+ logger.debug(f"Model '{k}': using first API key from list of {len(v['api_key'])}")
201
+
202
+ result[k] = v
203
+
204
+ return result
184
205
 
185
206
  def _build_user_config(
186
207
  self,
@@ -194,6 +215,8 @@ class ConfigBuilder:
194
215
  num_drafts: int = None,
195
216
  workspace_dir: str = None,
196
217
  run_name: str = None,
218
+ keep_workspace: bool = None,
219
+ keep_workspace_on_failure: bool = None,
197
220
  **kwargs
198
221
  ) -> Dict[str, Any]:
199
222
  """Build user configuration from parameters."""
@@ -230,6 +253,12 @@ class ConfigBuilder:
230
253
  if workspace_dir is not None:
231
254
  config.setdefault("run", {}).setdefault("parameters", {})["workspace_dir"] = workspace_dir
232
255
 
256
+ if keep_workspace is not None:
257
+ config.setdefault("run", {})["keep_all_workspaces"] = keep_workspace
258
+
259
+ if keep_workspace_on_failure is not None:
260
+ config.setdefault("run", {})["keep_workspace_on_failure"] = keep_workspace_on_failure
261
+
233
262
  # Additional kwargs are added to run.parameters
234
263
  if kwargs:
235
264
  config.setdefault("run", {}).setdefault("parameters", {}).update(kwargs)
@@ -269,10 +269,13 @@ class DataLoader:
269
269
  description = "MLE competition task"
270
270
 
271
271
  if isinstance(source, (str, Path)):
272
- path = Path(source)
272
+ path = Path(source).resolve() # Convert to absolute path
273
+ self.logger.info(f"Resolved path: {path}")
274
+
273
275
  if path.exists():
274
276
  if path.is_dir():
275
277
  data_dir = path
278
+ self.logger.info(f"Data directory found: {data_dir}")
276
279
  # Try to load description
277
280
  desc_file = path / "description.md"
278
281
  if desc_file.exists():
@@ -283,6 +286,7 @@ class DataLoader:
283
286
  pass
284
287
  elif path.is_file():
285
288
  data_dir = path.parent
289
+ self.logger.info(f"Data directory (from file parent): {data_dir}")
286
290
  # Try to load description from parent directory
287
291
  desc_file = path.parent / "description.md"
288
292
  if desc_file.exists():
@@ -291,6 +295,47 @@ class DataLoader:
291
295
  self.logger.info(f"Loaded description from {desc_file}")
292
296
  except Exception:
293
297
  pass
298
+ else:
299
+ self.logger.warning(f"Path does not exist: {path}")
300
+
301
+ # Try to find the data in common locations
302
+ competition_id = path.name
303
+
304
+ # Common search locations for data
305
+ search_locations = [
306
+ # Current project: ./data/competitions/
307
+ Path.cwd() / "data" / "competitions" / competition_id,
308
+ # Parent dslighting: ../dslighting/data/competitions/
309
+ Path.cwd().parent / "dslighting" / "data" / "competitions" / competition_id,
310
+ # Parent data: ../data/competitions/
311
+ Path.cwd().parent / "data" / "competitions" / competition_id,
312
+ # From package location: ../../data/competitions/
313
+ Path(__file__).parent.parent.parent / "data" / "competitions" / competition_id,
314
+ # Absolute path fallback
315
+ Path("/Users/liufan/Applications/Github/dslighting/data/competitions") / competition_id,
316
+ ]
317
+
318
+ for location in search_locations:
319
+ self.logger.info(f" Trying: {location}")
320
+ if location.exists() and location.is_dir():
321
+ data_dir = location
322
+ self.logger.info(f" ✓ Found data at: {data_dir}")
323
+ break
324
+
325
+ if data_dir is None:
326
+ # Last resort: use the original resolved path
327
+ self.logger.warning(f" Could not find data, using original path: {path}")
328
+ data_dir = path
329
+
330
+ # Try to load description (if data_dir was found)
331
+ if data_dir and data_dir.exists():
332
+ desc_file = data_dir / "description.md"
333
+ if desc_file.exists():
334
+ try:
335
+ description = desc_file.read_text(encoding='utf-8')
336
+ self.logger.info(f"Loaded description from {desc_file}")
337
+ except Exception:
338
+ pass
294
339
 
295
340
  # Create MLE-style detection
296
341
  from dslighting.utils.defaults import WORKFLOW_RECOMMENDATIONS
@@ -105,7 +105,7 @@ DEFAULT_CONFIG: Dict[str, Any] = {
105
105
  "params": {}
106
106
  },
107
107
  "run": {
108
- "name": "dslighting", # Fixed name without UID to avoid UUID suffix
108
+ "name": "dsat_run", # Use "dsat_run" to let DSATRunner auto-generate: dsat_run_{task_id}_{uid}
109
109
  "total_steps": DEFAULT_MAX_ITERATIONS,
110
110
  "keep_all_workspaces": DEFAULT_KEEP_ALL_WORKSPACES,
111
111
  "keep_workspace_on_failure": DEFAULT_KEEP_WORKSPACE_ON_FAILURE,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dslighting
3
- Version: 1.1.8
3
+ Version: 1.3.6
4
4
  Summary: Simplified API for Data Science Agent Automation
5
5
  Author: DSLighting Team
6
6
  License: AGPL-3.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dslighting"
7
- version = "1.1.8"
7
+ version = "1.3.6"
8
8
  description = "Simplified API for Data Science Agent Automation"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
File without changes
File without changes
File without changes
File without changes
File without changes