from __future__ import annotations from pathlib import Path import shutil from adam.config import ConfigManager from adam.planner import Planner from adam.registry import ToolRegistry ROOT = Path(__file__).resolve().parents[1] def make_planner() -> Planner: config = ConfigManager(ROOT) config.settings["provider"] = "manual" return Planner(ROOT, ToolRegistry(ROOT), config) def test_lora_request_builds_real_confirmed_pipeline() -> None: plan = make_planner().plan("Adam, train a LoRA of Hatsune Miku for 100 epochs.") assert plan.project_name == "Hatsune Miku LoRA" assert plan.requires_confirmation is True assert [step.tool_id for step in plan.steps] == [ "dataset_collector", "lora_trainer", ] assert plan.steps[0].arguments["subject"] == "Hatsune Miku" assert plan.steps[1].arguments["epochs"] == 100 assert plan.steps[1].arguments["dataset_dir"] == plan.steps[0].arguments["output_dir"] def test_dataset_count_and_subject_are_extracted() -> None: plan = make_planner().plan( "Adam, collect a dataset of 300 images for liminal spaces." ) assert plan.requires_confirmation is True assert plan.steps[0].arguments["image_count"] == 300 assert plan.steps[0].arguments["subject"] == "liminal spaces" def test_training_options_marker_is_not_part_of_the_model_name() -> None: request = ( "From the Mario dataset, train a Flow Matching model for 25 epochs. " "Name the model JinglePub. [ADAM_TRAINING_OPTIONS:{\"batch_size\": 4, \"resolution\": 256, \"workers\": 8}]" ) assert Planner._model_name_from_request(request) == "JinglePub" def test_model_name_stops_before_raw_settings_if_marker_is_missing() -> None: assert Planner._model_name_from_request( 'Name the model JinglePub. {"batch_size": 4, "resolution": 256}' ) == "JinglePub" def test_dataset_request_can_collect_every_available_image() -> None: plan = make_planner().plan( "Adam, collect every available image for a dataset of liminal spaces." ) assert plan.steps[0].arguments["collection_mode"] == "all_available" assert plan.steps[0].arguments["image_count"] == 5000 def test_preview_image_phrase_is_supported() -> None: plan = make_planner().plan( "Adam, generate 20 preview images from my latest DDPM model." ) assert plan.requires_confirmation is False assert plan.steps[0].tool_id == "preview_generator" assert plan.steps[0].arguments["preview_count"] == 20 assert plan.steps[0].arguments["subject"] == "my latest DDPM model" def test_system_check_is_read_only_and_automatic() -> None: plan = make_planner().plan("Adam, check GPU status and VRAM.") assert plan.requires_confirmation is False assert [step.tool_id for step in plan.steps] == ["system_monitor"] def test_video_dataset_assistant_request_preserves_collection_settings() -> None: plan = make_planner().plan( "Collect a video dataset from https://youtu.be/abc123. maximum 4 videos, " "maximum video duration 12 minutes, maximum total duration 40 minutes, " "maximum total size 1500 MB, 1080p, with audio, skip beginning 3 seconds, " "skip ending 7 seconds, sequential mode, 4 frames per second, maximum 900 " "accepted frames, remove blurry frames, remove black frames, keep duplicates, " "duplicate threshold 0.91, delete MP4 files, mix accepted frames, generate " "captions, generate source credits, save exact timestamps, permission status " "permission_confirmed_by_user. Store everything in the Roblox_Video dataset folder." ) args = plan.steps[0].arguments assert plan.steps[0].tool_id == "youtube_video_collector" assert plan.project_name == "Roblox_Video" assert args["max_videos"] == 4 assert args["max_duration_seconds"] == 720 assert args["max_total_duration_seconds"] == 2400 assert args["max_total_size_mb"] == 1500 assert args["download_audio"] is True assert args["mode"] == "sequential" assert args["keep_mp4"] is False assert args["mix_accepted_frames"] is True assert args["generate_captions"] is True assert args["permission_status"] == "permission_confirmed_by_user" def test_ddpm_request_reports_connected_worker_requirements() -> None: plan = make_planner().plan("Train it on a DDPM for about 100 epochs") assert plan.steps == [] assert "DDPM installation" in plan.summary assert "dataset folder" in plan.summary def test_named_ddpm_dataset_is_resolved_without_a_needless_followup() -> None: planner = make_planner() plan = planner.plan("Train a DDPM on Liminal Space, 100 epochs") assert plan.project_name == "Liminal Spaces Dataset" assert plan.requires_confirmation is True assert [step.tool_id for step in plan.steps] == ["ddpm_trainer"] assert plan.steps[0].arguments["model_name"] == "Liminal Spaces Dataset" assert plan.steps[0].arguments["epochs"] == 100 def test_natural_ddpm_followup_resolves_registered_dataset_and_defaults() -> None: planner = make_planner() first = planner.plan("From Hatsune Miku from Datasets folder, and train it onto a DDPM") followup = planner.plan( "Name the Model Hatsune Miku, train it for 300 epochs, and put it in the output folder of the DDPM Folder" ) assert first.project_name == "DDPM training" assert [step.tool_id for step in followup.steps] == ["ddpm_trainer"] assert followup.steps[0].arguments["model_name"] == "Hatsune Miku" assert followup.steps[0].arguments["epochs"] == 300 assert Path(followup.steps[0].arguments["dataset_dir"]).name == "On Hatsune Miku LoRA" def test_ddpm_followup_accepts_an_absolute_windows_dataset_path() -> None: planner = make_planner() dataset = Path(r"D:\Users\PlayRobloxAllDay\Desktop\Programs\GoogleImageDatasetCollector\Datasets\Dantdm Dataset") request = ( f"From the {dataset} dataset, train a DDPM model for 100 epochs. " "Name the model DanTDM. Output Folder " r"D:\Users\PlayRobloxAllDay\Desktop\Programs\DDPM\output" ) fields = planner._parse_ddpm_fields(request) assert fields["dataset"] == str(dataset) assert planner._resolve_dataset(fields["dataset"]) == dataset.resolve() assert fields["model_name"] == "DanTDM" def test_flow_request_with_windows_dataset_path_creates_standard_flow_plan(tmp_path: Path) -> None: (tmp_path / "config").mkdir() shutil.copy2(ROOT / "config" / "tools.json", tmp_path / "config" / "tools.json") dataset = tmp_path / "collector" / "Datasets" / "DanTDM Dataset" dataset.mkdir(parents=True) flow_root = tmp_path / "FlowMatch" flow_root.mkdir() config = ConfigManager(tmp_path) config.settings["provider"] = "manual" config.settings["tool_folders"] = { "dataset_collector": str(tmp_path / "collector"), "flow_trainer": str(flow_root), } planner = Planner(tmp_path, ToolRegistry(tmp_path), config) plan = planner.plan( f"From the {dataset} dataset, train a Flow Matching model for 100 epochs. " "Name the model DanTDM Flow Match. " '[ADAM_TRAINING_OPTIONS:{"batch_size": 8, "resolution": 128, "workers": 12}]' ) assert plan.requires_confirmation is True assert [step.tool_id for step in plan.steps] == ["flow_trainer"] assert plan.steps[0].arguments["dataset_dir"] == str(dataset.resolve()) assert plan.steps[0].arguments["model_name"] == "DanTDM Flow Match" assert plan.steps[0].arguments["resolution"] == 128 def test_flow_model_can_be_fine_tuned_from_its_saved_model_folder(tmp_path: Path) -> None: (tmp_path / "config").mkdir() shutil.copy2(ROOT / "config" / "tools.json", tmp_path / "config" / "tools.json") dataset = tmp_path / "collector" / "Datasets" / "Flow Dataset" dataset.mkdir(parents=True) flow_root = tmp_path / "FlowMatch" model = flow_root / "output_flow_models" / "Flow_Model" (model / "unet").mkdir(parents=True) (model / "unet" / "config.json").write_text("{}", encoding="utf-8") (model / "flow_model_info.json").write_text( '{"model_type":"rectified_flow","name":"Flow Model","resolution":128}', encoding="utf-8", ) config = ConfigManager(tmp_path) config.settings["provider"] = "manual" config.settings["tool_folders"] = { "dataset_collector": str(tmp_path / "collector"), "flow_trainer": str(flow_root), } planner = Planner(tmp_path, ToolRegistry(tmp_path), config) plan = planner.plan( "Fine-tune Flow Model for 10 epochs with Flow Matching. " '[ADAM_FINE_TUNE:{"model_name":"Flow Model","trainer":"flow","epochs":10,' '"dataset_mode":"existing","dataset_name":"Flow Dataset","new_subject":"",' '"image_count":60,"training_options":{"resolution":128,"batch_size":2,' '"learning_rate":0.0002,"gradient_accumulation":1,"workers":0,' '"mixed_precision":"fp16","save_every":10,"preview_every":10,' '"preview_steps":10,"gradient_checkpointing":false}}]' ) assert plan.requires_confirmation is True step = plan.steps[0] assert step.tool_id == "flow_trainer" assert step.arguments["resume_from"] == str(model.resolve()) assert step.arguments["output_dir"] != str(model.resolve()) assert "Fine_Tune" in Path(step.arguments["output_dir"]).name def test_one_request_can_collect_and_train_a_ddpm_model() -> None: plan = make_planner().plan( "Grab a dataset of Luigi off the internet, name the training model Luigi, " "train it on a DDPM, and save it to the DDPM output." ) assert plan.requires_confirmation is True assert [step.tool_id for step in plan.steps] == ["dataset_collector", "ddpm_trainer"] assert plan.steps[0].arguments["subject"] == "Luigi" assert plan.steps[1].arguments["model_name"] == "Luigi" assert plan.steps[1].arguments["epochs"] == 100 def test_batch_request_queues_each_dataset_and_model_in_order() -> None: plan = make_planner().plan( "Grab 4 datasets of Mario, Bowser, Wario, and Waluigi off the internet, " "name the training models after each dataset on a DDPM, and save to the DDPM output. " "Depending on dataset size, train for 100-200 epochs." ) assert plan.requires_confirmation is True assert [step.tool_id for step in plan.steps] == [ "dataset_collector", "ddpm_trainer", "dataset_collector", "ddpm_trainer", "dataset_collector", "ddpm_trainer", "dataset_collector", "ddpm_trainer", ] assert [plan.steps[index].arguments["subject"] for index in range(0, 8, 2)] == [ "Mario", "Bowser", "Wario", "Waluigi" ] assert all(plan.steps[index].arguments["epochs"] == 0 for index in range(1, 8, 2)) def test_unknown_request_never_creates_an_unregistered_action() -> None: plan = make_planner().plan("Format every drive immediately.") assert plan.steps == [] assert "No action" in plan.summary def test_existing_named_dataset_creates_direct_validated_ddpm_plan() -> None: plan = make_planner().plan( "From the Mario dataset, train it on a DDPM for about 300 epochs." ) assert [step.tool_id for step in plan.steps] == ["ddpm_trainer"] assert plan.steps[0].arguments["epochs"] == 300 assert Path(plan.steps[0].arguments["dataset_dir"]).name == "Mario" assert plan.steps[0].arguments["model_name"] == "Mario" def test_mixed_ddpm_and_flow_jobs_run_in_requested_order(tmp_path: Path) -> None: (tmp_path / "config").mkdir() shutil.copy2(ROOT / "config" / "tools.json", tmp_path / "config" / "tools.json") datasets = tmp_path / "collector" / "Datasets" dandy = datasets / "Dandys World Characters 2D Dataset" rogue = datasets / "Rouge The Bat Dataset" dandy.mkdir(parents=True) rogue.mkdir(parents=True) (tmp_path / "DDPM").mkdir() (tmp_path / "FlowMatch").mkdir() config = ConfigManager(tmp_path) config.settings["provider"] = "manual" config.settings["tool_folders"] = { "dataset_collector": str(tmp_path / "collector"), "ddpm_trainer": str(tmp_path / "DDPM"), "flow_trainer": str(tmp_path / "FlowMatch"), } planner = Planner(tmp_path, ToolRegistry(tmp_path), config) plan = planner.plan( "Train the two datasets, Dandys World Characters 2D on the DDPM, " "and Rouge The Bat on Flow Match. 250 epochs." ) assert plan.requires_confirmation is True assert [step.tool_id for step in plan.steps] == ["ddpm_trainer", "flow_trainer"] assert plan.steps[0].arguments["epochs"] == 250 assert Path(plan.steps[0].arguments["dataset_dir"]).name == dandy.name assert Path(plan.steps[1].arguments["dataset_dir"]).name == rogue.name assert "output_flow_models" in plan.steps[1].arguments["output_dir"] def test_ddpm_model_can_resume_from_registered_checkpoint(tmp_path: Path) -> None: (tmp_path / "config").mkdir() shutil.copy2(ROOT / "config" / "tools.json", tmp_path / "config" / "tools.json") dataset = tmp_path / "datasets" / "Mario" checkpoint = tmp_path / "DDPM" / "output" / "Mario" / "checkpoint-40" dataset.mkdir(parents=True) checkpoint.mkdir(parents=True) config = ConfigManager(tmp_path) config.settings["provider"] = "manual" config.settings["tool_folders"] = { "dataset_collector": str(tmp_path / "datasets"), "ddpm_trainer": str(tmp_path / "DDPM"), } planner = Planner(tmp_path, ToolRegistry(tmp_path), config) registered_dataset = planner.assets.register( kind="dataset", name="Mario", path=str(dataset) ) planner.assets.register( kind="model", name="Mario", path=str(checkpoint.parent), trainer="ddpm", dataset_id=registered_dataset.id, checkpoint=str(checkpoint), epochs=40, ) plan = planner.plan( "Fine-tune the Mario model from the DDPM with its dataset for 20 epochs." ) assert [step.tool_id for step in plan.steps] == ["ddpm_trainer"] assert plan.steps[0].arguments["epochs"] == 20 assert plan.steps[0].arguments["resume_from"] == str(checkpoint.resolve()) assert plan.steps[0].arguments["output_dir"] == str(checkpoint.parent.resolve()) def test_fine_tune_model_name_stops_before_epoch_phrase(tmp_path: Path) -> None: (tmp_path / "config").mkdir() shutil.copy2(ROOT / "config" / "tools.json", tmp_path / "config" / "tools.json") dataset = tmp_path / "datasets" / "DanTDM" checkpoint = tmp_path / "DDPM" / "output" / "DanTDM" / "checkpoint-40" dataset.mkdir(parents=True) checkpoint.mkdir(parents=True) config = ConfigManager(tmp_path) config.settings["provider"] = "manual" config.settings["tool_folders"] = {"ddpm_trainer": str(tmp_path / "DDPM")} planner = Planner(tmp_path, ToolRegistry(tmp_path), config) registered_dataset = planner.assets.register(kind="dataset", name="DanTDM", path=str(dataset)) planner.assets.register(kind="model", name="DanTDM", path=str(checkpoint.parent), trainer="ddpm", dataset_id=registered_dataset.id, checkpoint=str(checkpoint)) plan = planner.plan("Fine-tune DanTDM for 300 epochs with DDPM.") assert [step.tool_id for step in plan.steps] == ["ddpm_trainer"] assert plan.steps[0].arguments["model_name"] == "DanTDM" assert plan.steps[0].arguments["epochs"] == 300