dslighting 1.3.9__py3-none-any.whl

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 (80) hide show
  1. dsat/__init__.py +3 -0
  2. dsat/benchmark/__init__.py +1 -0
  3. dsat/benchmark/benchmark.py +168 -0
  4. dsat/benchmark/datasci.py +291 -0
  5. dsat/benchmark/mle.py +777 -0
  6. dsat/benchmark/sciencebench.py +304 -0
  7. dsat/common/__init__.py +0 -0
  8. dsat/common/constants.py +11 -0
  9. dsat/common/exceptions.py +48 -0
  10. dsat/common/typing.py +19 -0
  11. dsat/config.py +79 -0
  12. dsat/models/__init__.py +3 -0
  13. dsat/models/candidates.py +16 -0
  14. dsat/models/formats.py +52 -0
  15. dsat/models/task.py +64 -0
  16. dsat/operators/__init__.py +0 -0
  17. dsat/operators/aflow_ops.py +90 -0
  18. dsat/operators/autokaggle_ops.py +170 -0
  19. dsat/operators/automind_ops.py +38 -0
  20. dsat/operators/base.py +22 -0
  21. dsat/operators/code.py +45 -0
  22. dsat/operators/dsagent_ops.py +123 -0
  23. dsat/operators/llm_basic.py +84 -0
  24. dsat/prompts/__init__.py +0 -0
  25. dsat/prompts/aflow_prompt.py +76 -0
  26. dsat/prompts/aide_prompt.py +52 -0
  27. dsat/prompts/autokaggle_prompt.py +290 -0
  28. dsat/prompts/automind_prompt.py +29 -0
  29. dsat/prompts/common.py +51 -0
  30. dsat/prompts/data_interpreter_prompt.py +82 -0
  31. dsat/prompts/dsagent_prompt.py +88 -0
  32. dsat/runner.py +554 -0
  33. dsat/services/__init__.py +0 -0
  34. dsat/services/data_analyzer.py +387 -0
  35. dsat/services/llm.py +486 -0
  36. dsat/services/llm_single.py +421 -0
  37. dsat/services/sandbox.py +386 -0
  38. dsat/services/states/__init__.py +0 -0
  39. dsat/services/states/autokaggle_state.py +43 -0
  40. dsat/services/states/base.py +14 -0
  41. dsat/services/states/dsa_log.py +13 -0
  42. dsat/services/states/experience.py +237 -0
  43. dsat/services/states/journal.py +153 -0
  44. dsat/services/states/operator_library.py +290 -0
  45. dsat/services/vdb.py +76 -0
  46. dsat/services/workspace.py +178 -0
  47. dsat/tasks/__init__.py +3 -0
  48. dsat/tasks/handlers.py +376 -0
  49. dsat/templates/open_ended/grade_template.py +107 -0
  50. dsat/tools/__init__.py +4 -0
  51. dsat/utils/__init__.py +0 -0
  52. dsat/utils/context.py +172 -0
  53. dsat/utils/dynamic_import.py +71 -0
  54. dsat/utils/parsing.py +33 -0
  55. dsat/workflows/__init__.py +12 -0
  56. dsat/workflows/base.py +53 -0
  57. dsat/workflows/factory.py +439 -0
  58. dsat/workflows/manual/__init__.py +0 -0
  59. dsat/workflows/manual/autokaggle_workflow.py +148 -0
  60. dsat/workflows/manual/data_interpreter_workflow.py +153 -0
  61. dsat/workflows/manual/deepanalyze_workflow.py +484 -0
  62. dsat/workflows/manual/dsagent_workflow.py +76 -0
  63. dsat/workflows/search/__init__.py +0 -0
  64. dsat/workflows/search/aflow_workflow.py +344 -0
  65. dsat/workflows/search/aide_workflow.py +283 -0
  66. dsat/workflows/search/automind_workflow.py +237 -0
  67. dsat/workflows/templates/__init__.py +0 -0
  68. dsat/workflows/templates/basic_kaggle_loop.py +71 -0
  69. dslighting/__init__.py +170 -0
  70. dslighting/core/__init__.py +13 -0
  71. dslighting/core/agent.py +646 -0
  72. dslighting/core/config_builder.py +318 -0
  73. dslighting/core/data_loader.py +422 -0
  74. dslighting/core/task_detector.py +422 -0
  75. dslighting/utils/__init__.py +19 -0
  76. dslighting/utils/defaults.py +151 -0
  77. dslighting-1.3.9.dist-info/METADATA +554 -0
  78. dslighting-1.3.9.dist-info/RECORD +80 -0
  79. dslighting-1.3.9.dist-info/WHEEL +5 -0
  80. dslighting-1.3.9.dist-info/top_level.txt +2 -0
@@ -0,0 +1,386 @@
1
+ # dsat/services/sandbox.py
2
+
3
+ import logging
4
+ import re
5
+ import subprocess
6
+ import sys
7
+ import uuid
8
+ import asyncio
9
+ import shutil
10
+ import time
11
+ from datetime import datetime
12
+ from contextlib import asynccontextmanager
13
+ from pathlib import Path
14
+ from typing import AsyncGenerator, List, Dict, Any, Optional
15
+ from multiprocessing import Process, Queue
16
+
17
+ import nbformat
18
+ from nbclient import NotebookClient
19
+ from nbclient.exceptions import CellExecutionError, CellTimeoutError, DeadKernelError
20
+
21
+ from dsat.common.typing import ExecutionResult
22
+ from dsat.services.workspace import WorkspaceService
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ NOTEBOOK_INIT_CODE = """
27
+ import warnings
28
+ import pandas as pd
29
+ import numpy as np
30
+ import matplotlib.pyplot as plt
31
+ import os
32
+
33
+ try:
34
+ import seaborn as sns
35
+ sns.set_theme(style="whitegrid")
36
+ except Exception:
37
+ pass
38
+
39
+ warnings.filterwarnings('ignore')
40
+ print("DSAT Notebook environment initialized.")
41
+ """
42
+
43
+ class NotebookExecutor:
44
+ """
45
+ Manages a persistent Jupyter kernel for cell-by-cell code execution.
46
+ This class is intended to be used as an async context manager.
47
+ """
48
+ def __init__(self, workspace: WorkspaceService, timeout: int):
49
+ self.workspace = workspace
50
+ self.timeout = timeout
51
+ self.nb = nbformat.v4.new_notebook()
52
+ self.client = NotebookClient(
53
+ self.nb,
54
+ timeout=self.timeout,
55
+ cwd=str(self.workspace.get_path("sandbox_workdir"))
56
+ )
57
+ self._initialized = False
58
+ self._kernel_cm = None
59
+
60
+ async def start(self):
61
+ if self._initialized:
62
+ return
63
+ logger.info("Starting new Jupyter kernel for notebook execution...")
64
+ self._kernel_cm = self.client.async_setup_kernel()
65
+ await self._kernel_cm.__aenter__()
66
+ logger.info("Kernel started. Initializing environment...")
67
+ self._initialized = True
68
+ sandbox_workdir_path = self.workspace.get_path("sandbox_workdir").resolve()
69
+ full_init_code = (
70
+ f"import os\n"
71
+ f"os.chdir(r'{sandbox_workdir_path}')\n"
72
+ f"print(f'CWD set to: {{os.getcwd()}}')\n"
73
+ f"\n"
74
+ f"{NOTEBOOK_INIT_CODE}"
75
+ )
76
+ init_result = await self.execute_cell(full_init_code)
77
+ if not init_result.success:
78
+ await self.stop()
79
+ raise RuntimeError(f"Could not initialize notebook environment. Error: {init_result.stderr}")
80
+ if self.nb.cells:
81
+ self.nb.cells.pop(0)
82
+ logger.info(f"Notebook environment ready. CWD is now {sandbox_workdir_path}")
83
+
84
+ async def stop(self):
85
+ logger.info("Shutting down Jupyter kernel.")
86
+ if self._kernel_cm is not None:
87
+ try:
88
+ await self._kernel_cm.__aexit__(None, None, None)
89
+ finally:
90
+ self._kernel_cm = None
91
+ self._initialized = False
92
+
93
+ def _parse_cell_outputs(self, outputs: list) -> ExecutionResult:
94
+ stdout_lines, stderr_lines, artifacts = [], [], []
95
+ success = True
96
+ exc_type = None
97
+ for out in outputs:
98
+ if out.output_type == 'stream':
99
+ if out.name == 'stdout': stdout_lines.append(out.text)
100
+ else: stderr_lines.append(out.text)
101
+ elif out.output_type in ['execute_result', 'display_data']:
102
+ if 'text/plain' in out.data: stdout_lines.append(out.data['text/plain'])
103
+ if 'image/png' in out.data: artifacts.append("[Image data generated in notebook]")
104
+ elif out.output_type == 'error':
105
+ success = False
106
+ exc_type = out.ename
107
+ ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
108
+ traceback_text = ansi_escape.sub('', "\n".join(out.traceback))
109
+ stderr_lines.append(f"ERROR: {out.ename}\n{traceback_text}")
110
+ return ExecutionResult(
111
+ success=success, stdout="\n".join(stdout_lines).strip(),
112
+ stderr="\n".join(stderr_lines).strip(), artifacts=artifacts, exc_type=exc_type
113
+ )
114
+
115
+ async def execute_cell(self, code: str) -> ExecutionResult:
116
+ if not self._initialized:
117
+ raise RuntimeError("NotebookExecutor not started. Use 'async with' context manager.")
118
+ cell_index = len(self.nb.cells)
119
+ self.nb.cells.append(nbformat.v4.new_code_cell(source=code))
120
+ try:
121
+ await self.client.async_execute_cell(self.nb.cells[cell_index], cell_index)
122
+ result = self._parse_cell_outputs(self.nb.cells[cell_index].outputs)
123
+ except CellExecutionError:
124
+ result = self._parse_cell_outputs(self.nb.cells[cell_index].outputs)
125
+ except (CellTimeoutError, DeadKernelError) as e:
126
+ result = ExecutionResult(success=False, stderr=str(e), exc_type=e.__class__.__name__)
127
+
128
+ notebook_path = self.workspace.get_path("artifacts") / "session.ipynb"
129
+ with open(notebook_path, "w", encoding='utf-8') as f:
130
+ nbformat.write(self.nb, f)
131
+ return result
132
+
133
+ # ==============================================================================
134
+ # == NEW: WORKER FUNCTION FOR SUBPROCESS ==
135
+ # ==============================================================================
136
+
137
+ def notebook_worker(
138
+ task_queue: Queue,
139
+ result_queue: Queue,
140
+ workspace: WorkspaceService,
141
+ timeout: int
142
+ ):
143
+ """
144
+ This function runs in a separate process. It creates an asyncio event loop,
145
+ manages a NotebookExecutor, and processes tasks from the queue.
146
+ """
147
+ async def main_loop():
148
+ # The executor's lifecycle is tied to this async function
149
+ executor = NotebookExecutor(workspace, timeout)
150
+ await executor.start()
151
+
152
+ while True:
153
+ # Get code from the main process. A `None` object is the shutdown signal.
154
+ code = task_queue.get()
155
+ if code is None:
156
+ break
157
+
158
+ # Execute the code and put the result back
159
+ result = await executor.execute_cell(code)
160
+ result_queue.put(result)
161
+
162
+ await executor.stop()
163
+
164
+ try:
165
+ asyncio.run(main_loop())
166
+ except Exception as e:
167
+ # If something catastrophic happens, report it back
168
+ logger.error(f"Notebook worker process failed: {e}", exc_info=True)
169
+ result_queue.put(ExecutionResult(
170
+ success=False, stderr=f"Worker process failed: {e}", exc_type=e.__class__.__name__
171
+ ))
172
+
173
+
174
+ class ProcessIsolatedNotebookExecutor:
175
+ """
176
+ Manages the lifecycle of a notebook worker process, providing a clean
177
+ interface to the main application and ensuring robust cleanup.
178
+ """
179
+ def __init__(self, workspace: WorkspaceService, timeout: int):
180
+ self.task_queue = Queue()
181
+ self.result_queue = Queue()
182
+ self.worker_process = Process(
183
+ target=notebook_worker,
184
+ args=(self.task_queue, self.result_queue, workspace, timeout),
185
+ daemon=True # Set as daemon process to prevent main process from hanging
186
+ )
187
+
188
+ def start(self):
189
+ """Starts the worker process."""
190
+ logger.info("Starting process-isolated notebook worker...")
191
+ self.worker_process.start()
192
+
193
+ def stop(self, timeout: int = 10):
194
+ """
195
+ Stops the worker process gracefully, with a forceful termination fallback.
196
+ Note: Since this is a daemon process, it will be automatically terminated
197
+ when the parent process exits, providing an additional safety mechanism.
198
+ """
199
+ if not self.worker_process.is_alive():
200
+ return
201
+
202
+ logger.info("Stopping process-isolated notebook worker...")
203
+ try:
204
+ # Send shutdown signal
205
+ self.task_queue.put(None)
206
+ # Wait for graceful shutdown
207
+ self.worker_process.join(timeout=timeout)
208
+
209
+ # If still alive, it's hung. Terminate it.
210
+ if self.worker_process.is_alive():
211
+ logger.warning(
212
+ f"Notebook worker did not exit gracefully within {timeout}s. Terminating."
213
+ )
214
+ self.worker_process.terminate()
215
+ self.worker_process.join() # Wait for termination to complete
216
+ finally:
217
+ self.task_queue.close()
218
+ self.result_queue.close()
219
+ logger.info("Notebook worker stopped.")
220
+
221
+ async def execute_cell(self, code: str) -> ExecutionResult:
222
+ """
223
+ Sends code to the worker process and waits for the result.
224
+ This is async to fit into the main application's event loop, but the
225
+ underlying queue.get() is blocking. We use run_in_executor to avoid
226
+ blocking the main event loop.
227
+ """
228
+ if not self.worker_process.is_alive():
229
+ raise RuntimeError("Worker process is not running.")
230
+
231
+ loop = asyncio.get_running_loop()
232
+
233
+ # Send the task asynchronously
234
+ await loop.run_in_executor(None, self.task_queue.put, code)
235
+
236
+ # Wait for the result asynchronously
237
+ result = await loop.run_in_executor(None, self.result_queue.get)
238
+
239
+ return result
240
+
241
+
242
+ # ==============================================================================
243
+ # == MODIFIED: SANDBOX SERVICE INTEGRATION ==
244
+ # ==============================================================================
245
+ class SandboxService:
246
+ """
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.
255
+ """
256
+ def __init__(
257
+ self,
258
+ workspace: WorkspaceService,
259
+ timeout: int = 600,
260
+ auto_matplotlib: bool = False
261
+ ):
262
+ self.workspace = workspace
263
+ self.timeout = timeout
264
+ self.auto_matplotlib = auto_matplotlib # Store matplotlib injection preference
265
+ self.execution_history: List[Dict[str, Any]] = []
266
+
267
+ def run_script(self, script_code: str, timeout: Optional[int] = None) -> ExecutionResult:
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
+
288
+ script_name = f"_sandbox_script_{uuid.uuid4().hex}.py"
289
+ script_path = self.workspace.run_dir / script_name
290
+ execution_id = uuid.uuid4().hex
291
+ started_at = datetime.utcnow()
292
+ perf_start = time.perf_counter()
293
+ execution_result: ExecutionResult = ExecutionResult(success=False, stdout="", stderr="", exc_type=None)
294
+
295
+ try:
296
+ script_path.write_text(fixed_code, encoding="utf-8")
297
+ logger.info(f"Executing script '{script_name}' in sandbox (timeout: {self.timeout}s)...")
298
+ completed_process = subprocess.run(
299
+ [sys.executable, str(script_path)],
300
+ capture_output=True, text=True, timeout=self.timeout,
301
+ cwd=self.workspace.get_path("sandbox_workdir"),
302
+ encoding='utf-8', errors='replace'
303
+ )
304
+ success = completed_process.returncode == 0
305
+ exc_type = None
306
+ if not success:
307
+ stderr_lines = completed_process.stderr.strip().split('\n')
308
+ if stderr_lines:
309
+ match = re.search(r"^(\w+(?:Error|Exception)):", stderr_lines[-1])
310
+ if match: exc_type = match.group(1)
311
+ status = "succeeded" if success else f"failed (exit code {completed_process.returncode})"
312
+ logger.info(f"Script execution finished: {status}.")
313
+ if not success:
314
+ logger.error(f"=== SCRIPT EXECUTION FAILED ===")
315
+ logger.error(f"Exit Code: {completed_process.returncode}")
316
+ logger.error(f"Exception Type: {exc_type}")
317
+ if completed_process.stdout: logger.error(f"STDOUT:\n{completed_process.stdout}")
318
+ if completed_process.stderr: logger.error(f"STDERR:\n{completed_process.stderr}")
319
+ logger.error(f"=== END ERROR LOG ===")
320
+ execution_result = ExecutionResult(
321
+ success=success, stdout=completed_process.stdout,
322
+ stderr=completed_process.stderr, exc_type=exc_type
323
+ )
324
+ except subprocess.TimeoutExpired as e:
325
+ logger.warning("Script execution timed out. Process was terminated.")
326
+ execution_result = ExecutionResult(
327
+ success=False, stdout=e.stdout or "",
328
+ stderr=e.stderr or f"TimeoutError: Execution exceeded {self.timeout} seconds.",
329
+ exc_type="TimeoutError"
330
+ )
331
+ except Exception as e:
332
+ logger.error(f"An unexpected error occurred during sandbox setup: {e}", exc_info=True)
333
+ execution_result = ExecutionResult(success=False, stderr=str(e), exc_type=e.__class__.__name__)
334
+ finally:
335
+ ended_at = datetime.utcnow()
336
+ duration = round(time.perf_counter() - perf_start, 4)
337
+
338
+ copied_script_path = None
339
+ scripts_dir = self.workspace.get_path("artifacts") / "sandbox_scripts"
340
+ scripts_dir.mkdir(parents=True, exist_ok=True)
341
+ if script_path.exists():
342
+ try:
343
+ copied_script_path = scripts_dir / script_name
344
+ shutil.copy2(script_path, copied_script_path)
345
+ except Exception as copy_error:
346
+ logger.error(f"Failed to copy sandbox script '{script_name}' to artifacts: {copy_error}", exc_info=True)
347
+
348
+ execution_metadata = {
349
+ "execution_id": execution_id,
350
+ "script_filename": script_name,
351
+ "original_script_path": str(script_path) if script_path.exists() else None,
352
+ "copied_script_path": str(copied_script_path) if copied_script_path else None,
353
+ "sandbox_cwd": str(self.workspace.get_path("sandbox_workdir")),
354
+ "started_at_utc": started_at.isoformat() + "Z",
355
+ "ended_at_utc": ended_at.isoformat() + "Z",
356
+ "duration_seconds": duration,
357
+ }
358
+ execution_result.metadata = execution_metadata
359
+
360
+ history_entry = {
361
+ **execution_metadata,
362
+ "success": execution_result.success,
363
+ "exc_type": execution_result.exc_type,
364
+ "stdout": execution_result.stdout,
365
+ "stderr": execution_result.stderr,
366
+ "code": fixed_code,
367
+ }
368
+ self.execution_history.append(history_entry)
369
+ return execution_result
370
+
371
+ @asynccontextmanager
372
+ async def notebook_executor(self) -> AsyncGenerator[ProcessIsolatedNotebookExecutor, None]:
373
+ """
374
+ Provides a process-isolated notebook executor as an asynchronous context
375
+ manager to ensure proper startup and cleanup of the worker process.
376
+ """
377
+ executor = ProcessIsolatedNotebookExecutor(self.workspace, self.timeout)
378
+ executor.start()
379
+ try:
380
+ yield executor
381
+ finally:
382
+ executor.stop()
383
+
384
+ def get_execution_history(self) -> List[Dict[str, Any]]:
385
+ """Return a copy of sandbox execution history for telemetry persistence."""
386
+ return list(self.execution_history)
File without changes
@@ -0,0 +1,43 @@
1
+ # dsat/services/states/autokaggle_state.py
2
+
3
+ from pydantic import BaseModel, Field
4
+ from typing import List, Dict, Any, Optional
5
+
6
+ from dsat.models.formats import TaskContract
7
+
8
+
9
+ class AttemptMemory(BaseModel):
10
+ """Stores the memory of a single attempt within a phase."""
11
+ attempt_number: int
12
+ plan: str
13
+ code: str
14
+ execution_output: str
15
+ execution_error: Optional[str]
16
+ validation_result: Dict[str, Any]
17
+ review_score: float
18
+ review_suggestion: str
19
+
20
+
21
+ class PhaseMemory(BaseModel):
22
+ """Stores the history of all attempts for a single successful phase."""
23
+ phase_goal: str
24
+ attempts: List[AttemptMemory] = Field(default_factory=list)
25
+ final_report: str = ""
26
+ is_successful: bool = False
27
+ # Keep track of artifacts produced *in this phase*. Paths are relative to the sandbox workdir.
28
+ output_artifacts: Dict[str, str] = Field(default_factory=dict, description="Mapping of artifact filenames to their descriptions.")
29
+
30
+
31
+ class AutoKaggleState(BaseModel):
32
+ """Manages the entire state of a dynamic AutoKaggle workflow run."""
33
+ contract: TaskContract
34
+ dynamic_phases: List[str] = Field(default_factory=list)
35
+ phase_history: List[PhaseMemory] = Field(default_factory=list)
36
+ io_instructions: str = Field(default="", description="Standardized I/O instructions for the agents.")
37
+ full_task_description: str = Field(
38
+ description="The original, complete task description including the data analysis report from DataAnalyzer."
39
+ )
40
+ # The central registry for all artifacts created during the run.
41
+ # This provides state continuity between phases.
42
+ # Maps artifact filename to a description.
43
+ global_artifacts: Dict[str, str] = Field(default_factory=dict)
@@ -0,0 +1,14 @@
1
+ """
2
+ Defines the abstract base class for all state management services in DSAT.
3
+ """
4
+ from abc import ABC
5
+
6
+ class State(ABC):
7
+ """
8
+ Abstract base class for state managers.
9
+
10
+ This class serves as a common interface and type hint for different state
11
+ management strategies used by various agent paradigms, such as journaling for
12
+ tree-based search or linear phase logs for sequential workflows.
13
+ """
14
+ pass
@@ -0,0 +1,13 @@
1
+ """
2
+ Implements DSAgentState, which manages the running log for the DS-Agent workflow.
3
+ """
4
+ from pydantic import BaseModel
5
+ from dsat.services.states.base import State
6
+
7
+ class DSAgentState(State, BaseModel):
8
+ """
9
+ Holds the state for a DS-Agent workflow execution, primarily the running log
10
+ which accumulates summaries of each experimental step.
11
+ """
12
+ running_log: str = ""
13
+ final_code: str = ""