From 896ab79a5209d5a2ae93255043b4e6870b81062c Mon Sep 17 00:00:00 2001 From: Charlie Date: Thu, 12 Jun 2025 16:03:57 -0400 Subject: [PATCH 01/15] Refine AI diagnostics message --- sunbeam/scripts/run.py | 49 ++++++++++++++++++++++++++++++++++- tests/e2e/test_sunbeam_run.py | 24 +++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/sunbeam/scripts/run.py b/sunbeam/scripts/run.py index 117dd527..eccf760b 100644 --- a/sunbeam/scripts/run.py +++ b/sunbeam/scripts/run.py @@ -6,6 +6,42 @@ from sunbeam import __version__ +def analyze_failure(log: str) -> None: + """Use OpenAI to analyze failure logs.""" + try: + import openai + except Exception: + sys.stderr.write( + "AI analysis requested, but the 'openai' package is not installed.\n" + ) + return + + api_key = os.getenv("OPENAI_API_KEY") + if not api_key: + sys.stderr.write("OPENAI_API_KEY not set; skipping AI analysis.\n") + return + + try: + openai.api_key = api_key + resp = openai.ChatCompletion.create( + model="gpt-4.1-nano", + messages=[ + { + "role": "system", + "content": "You diagnose errors from Sunbeam pipeline runs.", + }, + { + "role": "user", + "content": f"Sunbeam run failed with the following output:\n{log}\n", + }, + ], + max_tokens=150, + ) + sys.stderr.write("AI diagnosis:\n" + resp.choices[0].message.content + "\n") + except Exception as exc: # pragma: no cover - network errors are non-deterministic + sys.stderr.write(f"AI analysis failed: {exc}\n") + + def main(argv: list[str] = sys.argv): """CLI entry point for running Sunbeam.""" parser = main_parser() @@ -67,7 +103,13 @@ def main(argv: list[str] = sys.argv): ] + remaining sys.stderr.write("Running: " + " ".join(snakemake_args) + "\n") - cmd = subprocess.run(snakemake_args) + cmd = subprocess.run(snakemake_args, capture_output=True, text=True) + if cmd.stdout: + sys.stdout.write(cmd.stdout) + if cmd.stderr: + sys.stderr.write(cmd.stderr) + if cmd.returncode != 0 and args.ai: + analyze_failure(cmd.stdout + "\n" + cmd.stderr) sys.exit(cmd.returncode) @@ -116,5 +158,10 @@ def main_parser(): default=__version__, help="The tag to use when pulling docker images for the core pipeline environments, defaults to sunbeam's current version, a good alternative is 'latest' for the latest stable release", ) + parser.add_argument( + "--ai", + action="store_true", + help="Use OpenAI to diagnose failures after the run", + ) return parser diff --git a/tests/e2e/test_sunbeam_run.py b/tests/e2e/test_sunbeam_run.py index 5abb084d..3c8fd9dd 100755 --- a/tests/e2e/test_sunbeam_run.py +++ b/tests/e2e/test_sunbeam_run.py @@ -113,3 +113,27 @@ def test_sunbeam_run_with_target_after_exclude(tmp_path, DATA_DIR, capsys): assert "clean_qc" in captured.err assert "filter_reads" not in captured.err + + +def test_sunbeam_run_ai_option(tmp_path, monkeypatch): + project_dir = tmp_path / "empty" + project_dir.mkdir() + + called = {"flag": False} + + def fake_analyze(log): + called["flag"] = True + + monkeypatch.setattr("sunbeam.scripts.run.analyze_failure", fake_analyze) + + with pytest.raises(SystemExit): + Run( + [ + "--profile", + str(project_dir), + "--ai", + "-n", + ] + ) + + assert called["flag"] From 7dbdf0eab5444db3d5691ea52a7b8b290a9879fe Mon Sep 17 00:00:00 2001 From: Charlie Date: Tue, 17 Jun 2025 13:22:10 -0400 Subject: [PATCH 02/15] Update sunbeam/scripts/run.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- sunbeam/scripts/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sunbeam/scripts/run.py b/sunbeam/scripts/run.py index eccf760b..fd128b27 100644 --- a/sunbeam/scripts/run.py +++ b/sunbeam/scripts/run.py @@ -10,7 +10,7 @@ def analyze_failure(log: str) -> None: """Use OpenAI to analyze failure logs.""" try: import openai - except Exception: + except ImportError: sys.stderr.write( "AI analysis requested, but the 'openai' package is not installed.\n" ) From ad9f88c6bc9c0bc8bd943c2ece2ff4cda9801898 Mon Sep 17 00:00:00 2001 From: Charlie Date: Tue, 17 Jun 2025 15:10:16 -0400 Subject: [PATCH 03/15] Update AI option test --- sunbeam/bfx/decontam.py | 2 +- tests/e2e/test_sunbeam_run.py | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/sunbeam/bfx/decontam.py b/sunbeam/bfx/decontam.py index 8498ac28..0a9a938c 100755 --- a/sunbeam/bfx/decontam.py +++ b/sunbeam/bfx/decontam.py @@ -17,7 +17,7 @@ def get_mapped_reads(fp: str, min_pct_id: float, min_len_frac: float) -> Iterato def _get_pct_identity( - read: Dict[str, Union[int, float, str, Tuple[int, str]]] + read: Dict[str, Union[int, float, str, Tuple[int, str]]], ) -> float: edit_dist = read.get("NM", 0) pct_mm = float(edit_dist) / len(read["SEQ"]) diff --git a/tests/e2e/test_sunbeam_run.py b/tests/e2e/test_sunbeam_run.py index 3c8fd9dd..7461c0ba 100755 --- a/tests/e2e/test_sunbeam_run.py +++ b/tests/e2e/test_sunbeam_run.py @@ -1,3 +1,4 @@ +import sys import pytest from sunbeam import CONFIGS_DIR, EXTENSIONS_DIR from sunbeam.project import SampleList, SunbeamConfig, SunbeamProfile @@ -121,10 +122,22 @@ def test_sunbeam_run_ai_option(tmp_path, monkeypatch): called = {"flag": False} - def fake_analyze(log): + import types + + fake_openai = types.SimpleNamespace() + + def dummy_create(**kwargs): called["flag"] = True + return types.SimpleNamespace( + choices=[ + types.SimpleNamespace(message=types.SimpleNamespace(content="analysis")) + ] + ) + + fake_openai.ChatCompletion = types.SimpleNamespace(create=dummy_create) - monkeypatch.setattr("sunbeam.scripts.run.analyze_failure", fake_analyze) + monkeypatch.setitem(sys.modules, "openai", fake_openai) + monkeypatch.setenv("OPENAI_API_KEY", "token") with pytest.raises(SystemExit): Run( From 01e43a41121cc863d4d8e0ff15f32d475680b085 Mon Sep 17 00:00:00 2001 From: Charlie Date: Tue, 17 Jun 2025 15:10:20 -0400 Subject: [PATCH 04/15] Update OpenAI call for v1 API --- sunbeam/scripts/run.py | 2 +- tests/e2e/test_sunbeam_run.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/sunbeam/scripts/run.py b/sunbeam/scripts/run.py index fd128b27..d44cd191 100644 --- a/sunbeam/scripts/run.py +++ b/sunbeam/scripts/run.py @@ -10,7 +10,7 @@ def analyze_failure(log: str) -> None: """Use OpenAI to analyze failure logs.""" try: import openai - except ImportError: + resp = openai.chat.completions.create( sys.stderr.write( "AI analysis requested, but the 'openai' package is not installed.\n" ) diff --git a/tests/e2e/test_sunbeam_run.py b/tests/e2e/test_sunbeam_run.py index 7461c0ba..f4e2637c 100755 --- a/tests/e2e/test_sunbeam_run.py +++ b/tests/e2e/test_sunbeam_run.py @@ -134,7 +134,9 @@ def dummy_create(**kwargs): ] ) - fake_openai.ChatCompletion = types.SimpleNamespace(create=dummy_create) + fake_openai.chat = types.SimpleNamespace( + completions=types.SimpleNamespace(create=dummy_create) + ) monkeypatch.setitem(sys.modules, "openai", fake_openai) monkeypatch.setenv("OPENAI_API_KEY", "token") From a2f06278abe7515b304f5c7d3821720aecc381a2 Mon Sep 17 00:00:00 2001 From: Ulthran Date: Tue, 17 Jun 2025 15:15:13 -0400 Subject: [PATCH 05/15] Working analysis --- sunbeam/scripts/run.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sunbeam/scripts/run.py b/sunbeam/scripts/run.py index d44cd191..73c6173d 100644 --- a/sunbeam/scripts/run.py +++ b/sunbeam/scripts/run.py @@ -9,8 +9,8 @@ def analyze_failure(log: str) -> None: """Use OpenAI to analyze failure logs.""" try: - import openai - resp = openai.chat.completions.create( + from openai import OpenAI + except ImportError: # pragma: no cover - this is a soft dependency sys.stderr.write( "AI analysis requested, but the 'openai' package is not installed.\n" ) @@ -22,8 +22,8 @@ def analyze_failure(log: str) -> None: return try: - openai.api_key = api_key - resp = openai.ChatCompletion.create( + client = OpenAI(api_key=api_key) + resp = client.chat.completions.create( model="gpt-4.1-nano", messages=[ { From e7ab237684f7ed34e417660c00bb73b6c0e48a49 Mon Sep 17 00:00:00 2001 From: Ulthran Date: Tue, 17 Jun 2025 18:03:04 -0400 Subject: [PATCH 06/15] Working but not streaming --- sunbeam/logging.py | 20 ++++++++++++++++++++ sunbeam/scripts/run.py | 17 +++++++++++++---- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/sunbeam/logging.py b/sunbeam/logging.py index 57d72623..0f891efc 100644 --- a/sunbeam/logging.py +++ b/sunbeam/logging.py @@ -2,6 +2,26 @@ from pathlib import Path +class StreamToLogger: + def __init__(self, logger, level=logging.INFO): + self.logger = logger + self.level = level + self.buffer = "" + + def write(self, message): + if message != "\n": + self.buffer += message + if "\n" in self.buffer: + for line in self.buffer.splitlines(): + self.logger.log(self.level, line.strip()) + self.buffer = "" + + def flush(self): + if self.buffer: + self.logger.log(self.level, self.buffer.strip()) + self.buffer = "" + + class ConditionalLevelFormatter(logging.Formatter): def format(self, record): # For WARNING and above, include "LEVELNAME: message" diff --git a/sunbeam/scripts/run.py b/sunbeam/scripts/run.py index e434584f..3bc38e65 100644 --- a/sunbeam/scripts/run.py +++ b/sunbeam/scripts/run.py @@ -1,12 +1,14 @@ import argparse +import contextlib import datetime +import io import logging import os import sys from pathlib import Path from snakemake.cli import main as snakemake_main from sunbeam import __version__ -from sunbeam.logging import get_pipeline_logger +from sunbeam.logging import get_pipeline_logger, StreamToLogger def analyze_run(log: str, logger: logging.Logger) -> None: @@ -31,11 +33,11 @@ def analyze_run(log: str, logger: logging.Logger) -> None: messages=[ { "role": "system", - "content": "You diagnose errors from Sunbeam pipeline runs.", + "content": "You diagnose errors from Sunbeam pipeline runs. If there are problems, suggest possible causes and solutions. You should always include a link to the Sunbeam documentation (https://sunbeam.readthedocs.io/en/stable/) and the GitHub issues page (https://github.com/sunbeam-labs/sunbeam/issues).", }, { "role": "user", - "content": f"Sunbeam run failed with the following output:\n{log}\n", + "content": f"Sunbeam ran with the following output:\n{log}\n", }, ], max_tokens=150, @@ -110,8 +112,15 @@ def main(argv: list[str] = sys.argv): logger.info("Running: " + " ".join(snakemake_args)) try: - snakemake_main(snakemake_args) + stderr_buffer = io.StringIO() + + with contextlib.redirect_stderr(stderr_buffer): + snakemake_main(snakemake_args) + + logger.info("Snakemake run completed successfully.\n") finally: + logger.info(stderr_buffer.getvalue()) + if args.ai: with open(log_file, "r") as f: analyze_run(f.read(), logger) From 7cb3389e368cb5db7d47171f966421fc8014474b Mon Sep 17 00:00:00 2001 From: Ulthran Date: Tue, 17 Jun 2025 18:10:59 -0400 Subject: [PATCH 07/15] Working pretty well! --- sunbeam/scripts/run.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sunbeam/scripts/run.py b/sunbeam/scripts/run.py index 3bc38e65..abadc781 100644 --- a/sunbeam/scripts/run.py +++ b/sunbeam/scripts/run.py @@ -33,16 +33,20 @@ def analyze_run(log: str, logger: logging.Logger) -> None: messages=[ { "role": "system", - "content": "You diagnose errors from Sunbeam pipeline runs. If there are problems, suggest possible causes and solutions. You should always include a link to the Sunbeam documentation (https://sunbeam.readthedocs.io/en/stable/) and the GitHub issues page (https://github.com/sunbeam-labs/sunbeam/issues).", + "content": "You diagnose errors from Sunbeam pipeline runs. If there are problems, suggest possible causes and solutions. Keep the answer short and sweet. If there are relevant file paths for debugging (like log files), mention them.", }, { "role": "user", "content": f"Sunbeam ran with the following output:\n{log}\n", }, ], - max_tokens=150, + max_tokens=1500, + ) + logger.info( + "AI diagnosis:\n" + + resp.choices[0].message.content + + "\nCheck out the Sunbeam documentation (https://sunbeam.readthedocs.io/en/stable/) and the GitHub issues page (https://github.com/sunbeam-labs/sunbeam/issues) for more information or to open a new issue.\n" ) - logger.info("AI diagnosis:\n" + resp.choices[0].message.content + "\n") except Exception as exc: # pragma: no cover - network errors are non-deterministic logger.error(f"AI analysis failed: {exc}\n") @@ -112,15 +116,11 @@ def main(argv: list[str] = sys.argv): logger.info("Running: " + " ".join(snakemake_args)) try: - stderr_buffer = io.StringIO() + stream_logger = StreamToLogger(logger, level=logging.INFO) - with contextlib.redirect_stderr(stderr_buffer): + with contextlib.redirect_stderr(stream_logger): snakemake_main(snakemake_args) - - logger.info("Snakemake run completed successfully.\n") finally: - logger.info(stderr_buffer.getvalue()) - if args.ai: with open(log_file, "r") as f: analyze_run(f.read(), logger) From b5b9f5221f5ab24e765c875b5397e7eb4ccea9cc Mon Sep 17 00:00:00 2001 From: Ulthran Date: Tue, 17 Jun 2025 18:19:13 -0400 Subject: [PATCH 08/15] Reformat answers --- sunbeam/scripts/run.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sunbeam/scripts/run.py b/sunbeam/scripts/run.py index abadc781..dd24a85a 100644 --- a/sunbeam/scripts/run.py +++ b/sunbeam/scripts/run.py @@ -43,7 +43,7 @@ def analyze_run(log: str, logger: logging.Logger) -> None: max_tokens=1500, ) logger.info( - "AI diagnosis:\n" + "\n\nAI diagnosis:\n" + resp.choices[0].message.content + "\nCheck out the Sunbeam documentation (https://sunbeam.readthedocs.io/en/stable/) and the GitHub issues page (https://github.com/sunbeam-labs/sunbeam/issues) for more information or to open a new issue.\n" ) @@ -125,8 +125,6 @@ def main(argv: list[str] = sys.argv): with open(log_file, "r") as f: analyze_run(f.read(), logger) - logger.info("Sunbeam run completed.") - def main_parser(): epilog_str = ( From 4737ed97cca50e08ce084dfbed09cddacd7aebcc Mon Sep 17 00:00:00 2001 From: Ulthran Date: Tue, 17 Jun 2025 23:26:44 -0400 Subject: [PATCH 09/15] Stick to AI analysis for now --- sunbeam/scripts/run.py | 86 +++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/sunbeam/scripts/run.py b/sunbeam/scripts/run.py index dd24a85a..b335d528 100644 --- a/sunbeam/scripts/run.py +++ b/sunbeam/scripts/run.py @@ -1,7 +1,6 @@ import argparse import contextlib import datetime -import io import logging import os import sys @@ -11,44 +10,48 @@ from sunbeam.logging import get_pipeline_logger, StreamToLogger -def analyze_run(log: str, logger: logging.Logger) -> None: - """Use OpenAI to analyze failure logs.""" - try: - from openai import OpenAI - except ImportError: # pragma: no cover - this is a soft dependency - logger.error( - "AI analysis requested, but the 'openai' package is not installed.\n" - ) - return - - api_key = os.getenv("OPENAI_API_KEY") - if not api_key: - logger.error("OPENAI_API_KEY not set; skipping AI analysis.\n") - return - - try: - client = OpenAI(api_key=api_key) - resp = client.chat.completions.create( - model="gpt-4.1-nano", - messages=[ - { - "role": "system", - "content": "You diagnose errors from Sunbeam pipeline runs. If there are problems, suggest possible causes and solutions. Keep the answer short and sweet. If there are relevant file paths for debugging (like log files), mention them.", - }, - { - "role": "user", - "content": f"Sunbeam ran with the following output:\n{log}\n", - }, - ], - max_tokens=1500, - ) - logger.info( - "\n\nAI diagnosis:\n" - + resp.choices[0].message.content - + "\nCheck out the Sunbeam documentation (https://sunbeam.readthedocs.io/en/stable/) and the GitHub issues page (https://github.com/sunbeam-labs/sunbeam/issues) for more information or to open a new issue.\n" - ) - except Exception as exc: # pragma: no cover - network errors are non-deterministic - logger.error(f"AI analysis failed: {exc}\n") +def analyze_run(log: str, logger: logging.Logger, ai: bool) -> None: + """Analyze the run log and provide insights or suggestions.""" + # We could do some rule-based analysis here but I'd rather lean into the AI features and see how far they can take us + if ai: + try: + from openai import OpenAI + except ImportError: # pragma: no cover - this is a soft dependency + logger.error( + "AI analysis requested, but the 'openai' package is not installed.\n" + ) + return + + api_key = os.getenv("OPENAI_API_KEY") + if not api_key: + logger.error("OPENAI_API_KEY not set; skipping AI analysis.\n") + return + + try: + client = OpenAI(api_key=api_key) + resp = client.chat.completions.create( + model="gpt-4.1-nano", + messages=[ + { + "role": "system", + "content": "You diagnose errors from Sunbeam pipeline runs. If there are problems, suggest possible causes and solutions. Keep the answer short and sweet. If there are relevant file paths for debugging (like log files), mention them.", + }, + { + "role": "user", + "content": f"Sunbeam ran with the following output:\n{log}\n", + }, + ], + max_tokens=1500, + ) + logger.info( + "\n\nAI diagnosis:\n" + + resp.choices[0].message.content + + "\nCheck out the Sunbeam documentation (https://sunbeam.readthedocs.io/en/stable/) and the GitHub issues page (https://github.com/sunbeam-labs/sunbeam/issues) for more information or to open a new issue.\n" + ) + except ( + Exception + ) as exc: # pragma: no cover - network errors are non-deterministic + logger.error(f"AI analysis failed: {exc}\n") def main(argv: list[str] = sys.argv): @@ -121,9 +124,8 @@ def main(argv: list[str] = sys.argv): with contextlib.redirect_stderr(stream_logger): snakemake_main(snakemake_args) finally: - if args.ai: - with open(log_file, "r") as f: - analyze_run(f.read(), logger) + with open(log_file, "r") as f: + analyze_run(f.read(), logger, args.ai) def main_parser(): From 42288e421d2a05d88517a5d4083b58dda3ef75ac Mon Sep 17 00:00:00 2001 From: Ulthran Date: Tue, 17 Jun 2025 23:35:40 -0400 Subject: [PATCH 10/15] Make sure the log file exists --- sunbeam/scripts/run.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sunbeam/scripts/run.py b/sunbeam/scripts/run.py index b335d528..f1ddcf4c 100644 --- a/sunbeam/scripts/run.py +++ b/sunbeam/scripts/run.py @@ -73,6 +73,7 @@ def main(argv: list[str] = sys.argv): # You could argue it would make more sense to start this at the actual snakemake call # but this way we can log some relevant setup information that might be useful on post-mortem analysis logger = get_pipeline_logger(log_file) + logger.debug("Sunbeam pipeline logger initialized.") snakefile = Path(__file__).parent.parent / "workflow" / "Snakefile" if not snakefile.exists(): From 9d50f3625e81e762b67a4442776ad669c468b358 Mon Sep 17 00:00:00 2001 From: Ulthran Date: Wed, 18 Jun 2025 14:07:16 -0400 Subject: [PATCH 11/15] DEBUG --- sunbeam/scripts/run.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sunbeam/scripts/run.py b/sunbeam/scripts/run.py index f1ddcf4c..c9fa60b1 100644 --- a/sunbeam/scripts/run.py +++ b/sunbeam/scripts/run.py @@ -125,6 +125,8 @@ def main(argv: list[str] = sys.argv): with contextlib.redirect_stderr(stream_logger): snakemake_main(snakemake_args) finally: + print(log_file) + print(log_file.exists()) with open(log_file, "r") as f: analyze_run(f.read(), logger, args.ai) From 725ede9200ab0a38f39c3831bc1a2057a7667d7c Mon Sep 17 00:00:00 2001 From: Ulthran Date: Wed, 18 Jun 2025 14:14:48 -0400 Subject: [PATCH 12/15] DEBUG --- sunbeam/scripts/run.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sunbeam/scripts/run.py b/sunbeam/scripts/run.py index c9fa60b1..5c2e349f 100644 --- a/sunbeam/scripts/run.py +++ b/sunbeam/scripts/run.py @@ -74,6 +74,7 @@ def main(argv: list[str] = sys.argv): # but this way we can log some relevant setup information that might be useful on post-mortem analysis logger = get_pipeline_logger(log_file) logger.debug("Sunbeam pipeline logger initialized.") + print(log_file.exists()) snakefile = Path(__file__).parent.parent / "workflow" / "Snakefile" if not snakefile.exists(): From 10b3e244f8bf5ae28e5406d3f75cd9fd2d270a64 Mon Sep 17 00:00:00 2001 From: Ulthran Date: Wed, 18 Jun 2025 14:41:07 -0400 Subject: [PATCH 13/15] DEBUG --- pyproject.toml | 3 +++ sunbeam/scripts/run.py | 4 +++- sunbeam/workflow/Snakefile | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bfc02358..0db0bc08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,9 @@ dev = [ "snakefmt==0.11.0", "pytest==8.4.0", ] +ai = [ + "openai==1.88.0", +] [project.urls] "Homepage" = "https://github.com/sunbeam-labs/sunbeam" diff --git a/sunbeam/scripts/run.py b/sunbeam/scripts/run.py index 5c2e349f..a5a4fbf2 100644 --- a/sunbeam/scripts/run.py +++ b/sunbeam/scripts/run.py @@ -18,7 +18,7 @@ def analyze_run(log: str, logger: logging.Logger, ai: bool) -> None: from openai import OpenAI except ImportError: # pragma: no cover - this is a soft dependency logger.error( - "AI analysis requested, but the 'openai' package is not installed.\n" + "AI analysis requested, but the 'openai' package is not installed. Try `pip install -e sunbeamlib[ai]`.\n" ) return @@ -126,6 +126,8 @@ def main(argv: list[str] = sys.argv): with contextlib.redirect_stderr(stream_logger): snakemake_main(snakemake_args) finally: + # Show all files in log_file directory + print(list(log_file.parent.glob("*"))) print(log_file) print(log_file.exists()) with open(log_file, "r") as f: diff --git a/sunbeam/workflow/Snakefile b/sunbeam/workflow/Snakefile index 58510bc4..651b8f67 100644 --- a/sunbeam/workflow/Snakefile +++ b/sunbeam/workflow/Snakefile @@ -17,6 +17,7 @@ from sunbeam.project.post import compile_benchmarks logger = get_pipeline_logger() +logger.debug("Sunbeam pipeline starting...") MIN_MEM_MB = int(os.getenv("SUNBEAM_MIN_MEM_MB", 8000)) MIN_RUNTIME = int(os.getenv("SUNBEAM_MIN_RUNTIME", 15)) From 41976aa4bab2ee1ed9b3343227ffffe0e2722d93 Mon Sep 17 00:00:00 2001 From: Ulthran Date: Wed, 18 Jun 2025 14:48:37 -0400 Subject: [PATCH 14/15] Fix local test issues --- sunbeam/scripts/run.py | 4 ++-- tests/e2e/test_sunbeam_run.py | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/sunbeam/scripts/run.py b/sunbeam/scripts/run.py index a5a4fbf2..f8b9a98f 100644 --- a/sunbeam/scripts/run.py +++ b/sunbeam/scripts/run.py @@ -15,7 +15,7 @@ def analyze_run(log: str, logger: logging.Logger, ai: bool) -> None: # We could do some rule-based analysis here but I'd rather lean into the AI features and see how far they can take us if ai: try: - from openai import OpenAI + import openai except ImportError: # pragma: no cover - this is a soft dependency logger.error( "AI analysis requested, but the 'openai' package is not installed. Try `pip install -e sunbeamlib[ai]`.\n" @@ -28,7 +28,7 @@ def analyze_run(log: str, logger: logging.Logger, ai: bool) -> None: return try: - client = OpenAI(api_key=api_key) + client = openai.OpenAI(api_key=api_key) resp = client.chat.completions.create( model="gpt-4.1-nano", messages=[ diff --git a/tests/e2e/test_sunbeam_run.py b/tests/e2e/test_sunbeam_run.py index 5f4f605e..a549aa21 100755 --- a/tests/e2e/test_sunbeam_run.py +++ b/tests/e2e/test_sunbeam_run.py @@ -1,6 +1,7 @@ -import sys import pytest import subprocess as sp +import sys +import types from sunbeam.scripts.init import main as Init from sunbeam.scripts.run import main as Run @@ -122,10 +123,6 @@ def test_sunbeam_run_ai_option(tmp_path, monkeypatch): called = {"flag": False} - import types - - fake_openai = types.SimpleNamespace() - def dummy_create(**kwargs): called["flag"] = True return types.SimpleNamespace( @@ -134,8 +131,12 @@ def dummy_create(**kwargs): ] ) - fake_openai.chat = types.SimpleNamespace( - completions=types.SimpleNamespace(create=dummy_create) + fake_openai = types.SimpleNamespace() + + fake_openai.OpenAI = lambda *args, **kwargs: types.SimpleNamespace( + chat=types.SimpleNamespace( + completions=types.SimpleNamespace(create=dummy_create) + ) ) monkeypatch.setitem(sys.modules, "openai", fake_openai) From 3a9b770264f0db71ff1733a1a099af432ef09618 Mon Sep 17 00:00:00 2001 From: Ulthran Date: Wed, 18 Jun 2025 14:50:54 -0400 Subject: [PATCH 15/15] Include init in test --- tests/e2e/test_sunbeam_run.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/e2e/test_sunbeam_run.py b/tests/e2e/test_sunbeam_run.py index a549aa21..1b28ef48 100755 --- a/tests/e2e/test_sunbeam_run.py +++ b/tests/e2e/test_sunbeam_run.py @@ -117,9 +117,8 @@ def test_sunbeam_run_with_target_after_exclude(tmp_path, DATA_DIR, capsys): assert "filter_reads" not in ret.stderr.decode("utf-8") -def test_sunbeam_run_ai_option(tmp_path, monkeypatch): - project_dir = tmp_path / "empty" - project_dir.mkdir() +def test_sunbeam_run_ai_option(tmp_path, monkeypatch, DATA_DIR): + project_dir = tmp_path / "test" called = {"flag": False} @@ -142,6 +141,14 @@ def dummy_create(**kwargs): monkeypatch.setitem(sys.modules, "openai", fake_openai) monkeypatch.setenv("OPENAI_API_KEY", "token") + Init( + [ + str(project_dir), + "--data_fp", + str(DATA_DIR / "reads"), + ] + ) + with pytest.raises(SystemExit): Run( [