Download augmentoolkit/generation_functions/one_to_many_step.py from Leon4gr45/agent-0: direct link, hf CLI and curl.
- Browser
- Download file 31.9 kB
-
https://huggingface.co/spaces/Leon4gr45/agent-0/resolve/main/augmentoolkit/generation_functions/one_to_many_step.py
- Command line
-
hf download hf://spaces/Leon4gr45/agent-0/augmentoolkit/generation_functions/one_to_many_step.py
-
curl -L -o one_to_many_step.py https://huggingface.co/spaces/Leon4gr45/agent-0/resolve/main/augmentoolkit/generation_functions/one_to_many_step.py
31.9 kB
| import hashlib | |
| import json | |
| import traceback | |
| from tqdm import asyncio as tqdmasyncio | |
| import os | |
| import signal | |
| import sys | |
| import logging | |
| import time # Added for timestamping logs | |
| import asyncio # Add asyncio import | |
| from augmentoolkit.generation_functions.pipeline_step_class import PipelineStep | |
| from filelock import FileLock | |
| # Try importing pyfiglet for banners, provide fallback | |
| try: | |
| import pyfiglet | |
| def _print_banner(text): # Use underscore to indicate internal helper | |
| print(pyfiglet.figlet_format(text, font="standard")) | |
| except ImportError: | |
| print( | |
| "Warning: pyfiglet not installed. Install with 'pip install pyfiglet' for formatted banners." | |
| ) | |
| def _print_banner(text): | |
| print(f"\n{'='*10} {text} {'='*10}\n") | |
| # Helper function similar to the one in PipelineStep | |
| def filter_out_nonpresent_keys(item_dict, key_to_check="judgement"): | |
| # print(f"[DEBUG] filter_out_nonpresent_keys: Starting filter for key '{key_to_check}' on dict with {len(item_dict)} items.") # DEBUG | |
| keys_to_remove = [] | |
| for key, value in item_dict.items(): | |
| # Ensure value is a dictionary before checking for the key | |
| if isinstance(value, dict) and key_to_check not in value: | |
| # print(f"[DEBUG] filter_out_nonpresent_keys: Marking key '{key}' for removal (missing '{key_to_check}').") # DEBUG | |
| keys_to_remove.append(key) | |
| elif not isinstance( | |
| value, dict | |
| ): # Handle cases where value might not be a dict unexpectedly | |
| print( | |
| f"Warning: Expected dictionary for key '{key}' in filter_out_nonpresent_keys, but got {type(value)}. Skipping." | |
| ) | |
| # Optionally remove if malformed data should be cleaned | |
| # keys_to_remove.append(key) | |
| if keys_to_remove: | |
| print( | |
| f"Filtering out {len(keys_to_remove)} items missing the key '{key_to_check}'" | |
| ) | |
| for key in keys_to_remove: | |
| del item_dict[key] | |
| # print(f"[DEBUG] filter_out_nonpresent_keys: Finished filtering. {len(item_dict)} items remain.") # DEBUG | |
| class OneToManyStep( | |
| PipelineStep | |
| ): # A way of switching from one list of items to a new list of many items (each part of a group generated from the input items). Like going from a small list of source paragraphs to a larger list of question/answer pairs to iterate over. | |
| # fundamentally, it is easier if we carry over all previous information. Like, all previous keys. Yeah. Let's start with that they can subclass it if they want. | |
| def __init__( | |
| self, | |
| prompt_path, | |
| sampling_params, | |
| output_file, | |
| output_processor, | |
| result_key, | |
| max_retries, | |
| details_key, | |
| log_full_outputs, | |
| input_file, | |
| validation_function=lambda x, y: {"result": True, "message": "default message"}, | |
| regex=r"(.*)", | |
| input_processor=lambda x: (x, {}), | |
| **kwargs, | |
| ): | |
| # print(f"[DEBUG] OneToManyStep.__init__: Initializing step for output file '{output_file}'.") # DEBUG | |
| super().__init__( | |
| prompt_path=prompt_path, | |
| regex=regex, | |
| sampling_params=sampling_params, | |
| output_file=output_file, | |
| output_processor=output_processor, | |
| result_key=result_key, | |
| max_retries=max_retries, | |
| log_full_outputs=log_full_outputs, | |
| validation_function=validation_function, | |
| details_key=details_key, | |
| input_processor=input_processor, | |
| **kwargs, | |
| ) | |
| self.input_file = input_file | |
| # print(f"[DEBUG] OneToManyStep.__init__: Initialization complete for '{output_file}'.") # DEBUG | |
| async def read_previous_output(self, key, output_dict): | |
| # print(f"[DEBUG {time.time():.2f}] read_previous_output: Checking for key '{key}' or prefix '{key}-' in output_dict (size {len(output_dict)}).") # DEBUG | |
| # Check if any keys start with this key followed by separator in the in-memory dict | |
| key_prefix = str(key) + "-" | |
| matching_entries = [k for k in output_dict.keys() if k.startswith(key_prefix)] | |
| if matching_entries: | |
| # print(f"[DEBUG {time.time():.2f}] read_previous_output: Found {len(matching_entries)} existing entries for key prefix '{key}-'. Skipping generation.") # DEBUG | |
| # No need to load from file, data should already be in output_dict if loaded | |
| # print(f"Found existing entries for key {key} in memory: {matching_entries}") | |
| return True # Indicate that output for this key exists | |
| # print(f"[DEBUG {time.time():.2f}] read_previous_output: No existing entries found for key prefix '{key}-'. Proceeding with generation.") # DEBUG | |
| return False | |
| def save( | |
| self, | |
| result=None, | |
| key=None, | |
| output_dict=None, | |
| input_data=None, | |
| full_output=None, | |
| full_response=None, | |
| full_input=None, | |
| include_details=False, | |
| completion_mode=False, | |
| ): | |
| # print(f"[DEBUG {time.time():.2f}] save: Starting save for key '{key}'. Result type: {type(result)}, Result length: {len(result) if isinstance(result, list) else 'N/A'}.") # DEBUG | |
| # Removed output_dir, input_dict args as they are handled by execute_pipeline | |
| # print("\n=== Starting save() function (In-Memory Update) ===") | |
| # print(f"Key: {key}") | |
| # print(f"Result type: {type(result)}") | |
| # print(f"Result length: {len(result)}") | |
| # print(f"Include details: {include_details}") | |
| assert isinstance( | |
| result, list | |
| ), f"Result must be a list, got {type(result)}" # needed for one-to-many | |
| # print(f"[DEBUG {time.time():.2f}] save: Assertion passed, result is a list.") # DEBUG | |
| # Removed output_path and related file operations | |
| if self.log_full_outputs: | |
| # print(f"[DEBUG {time.time():.2f}] save: log_full_outputs is True. Logging full outputs (logic omitted for brevity).") # DEBUG | |
| # print("\nLogging full outputs:") # Keep logging if needed | |
| if full_output: | |
| # ... (logging logic remains the same) ... | |
| pass # Pass for brevity, assume logging logic is unchanged | |
| # print("\n=== Processing result items (In-Memory Update) ===") | |
| details_payload = None | |
| if include_details: | |
| # print(f"[DEBUG {time.time():.2f}] save: include_details is True. Preparing details payload.") # DEBUG | |
| # Prepare details payload once, assign it to each subitem | |
| details_payload = [ | |
| { | |
| "full_response": full_response, | |
| "full_input": full_input, | |
| "completion_mode": completion_mode, | |
| "result": result, | |
| } | |
| ] | |
| # print("Details payload prepared:") | |
| # print(details_payload) | |
| for idx, subitem in enumerate(result): | |
| # print(f"[DEBUG {time.time():.2f}] save: Processing item {idx} for original key '{key}'. Subitem: {str(subitem)[:50]}...") # DEBUG | |
| # print(f"\nProcessing item {idx}:") | |
| # print(f"Subitem: {str(subitem)[:100]}...") | |
| # Hash the idx and subitem using hashlib | |
| hash_input = f"{str(subitem)}" | |
| item_hash = hashlib.sha256(hash_input.encode()).hexdigest() | |
| item_key = f"{key}-{idx}-{item_hash}" | |
| # print(f"[DEBUG {time.time():.2f}] save: Generated item_key: {item_key}") # DEBUG | |
| # print(f"Generated item_key: {item_key}") | |
| # Create new entry with ALL original data plus the new result | |
| output_dict[str(item_key)] = ( | |
| input_data.copy() | |
| ) # Start with a copy of the original input | |
| # print(f"[DEBUG {time.time():.2f}] save: Copied input_data to new key '{item_key}'.") # DEBUG | |
| output_dict[str(item_key)][ | |
| self.result_key | |
| ] = subitem # Add the specific subitem result | |
| # print(f"[DEBUG {time.time():.2f}] save: Added subitem result under key '{self.result_key}' for item_key '{item_key}'.") # DEBUG | |
| # print(f"Added result to output_dict with key: {self.result_key}") | |
| if include_details and details_payload: | |
| output_dict[str(item_key)][ | |
| self.details_key | |
| ] = details_payload # Add details payload | |
| # print(f"[DEBUG {time.time():.2f}] save: Added details payload under key '{self.details_key}' for item_key '{item_key}'.") # DEBUG | |
| # print(f"Added details to output_dict with key: {self.details_key}") | |
| # Removed file writing logic for each item | |
| # Removed logic that modified the input_file/input_dict for details | |
| # print(f"[DEBUG {time.time():.2f}] save: Finished processing {len(result)} items for key '{key}'. output_dict size now {len(output_dict)}.") # DEBUG | |
| # print("\n=== save() function completed (In-Memory Update) ===\n") | |
| # Save now returns None as it modifies output_dict in place | |
| return ( | |
| None # Or return result if needed elsewhere, but primary effect is mutation | |
| ) | |
| def load_dataset(self, output_dict, output_dir): | |
| output_path = self.make_output_path(output_dir) | |
| # print(f"[DEBUG {time.time():.2f}] load_dataset: Attempting to load dataset for '{self.output_file}' from '{output_path}'.") # DEBUG | |
| if os.path.exists(output_path): | |
| # Use file lock for reading, although less critical than writing | |
| with open(output_path, "r", encoding="utf-8") as f: | |
| try: | |
| print( | |
| f"Loading existing output file for {self.output_file} at {output_path}" | |
| ) | |
| file_contents = json.load(f) | |
| except json.JSONDecodeError: | |
| print("\n" + "!" * 80) | |
| print(f"WARNING: JSON DECODE ERROR in file {output_path}") | |
| print( | |
| "The file exists but contains invalid JSON. Starting with an empty dictionary instead." | |
| ) | |
| print( | |
| "This may indicate data corruption or an incomplete write operation." | |
| ) | |
| print("!" * 80 + "\n") | |
| file_contents = {} # Start fresh if decode fails | |
| assert isinstance( | |
| file_contents, dict | |
| ), f"Expected file_contents to be a dictionary, but got {type(file_contents).__name__}." | |
| # print(f"[DEBUG {time.time():.2f}] load_dataset: File read successfully. Loaded {len(file_contents)} items. Updating output_dict.") # DEBUG | |
| output_dict.update(file_contents) # Load into the provided dict | |
| print(f"Loaded {len(file_contents)} items from {output_path}") | |
| else: | |
| # print(f"[DEBUG {time.time():.2f}] load_dataset: Output file '{output_path}' not found. Starting with an empty dataset.") # DEBUG | |
| print( | |
| f"Output file {output_path} not found. Starting with an empty dataset." | |
| ) | |
| # Ensure directory exists even if file doesn't | |
| os.makedirs(os.path.dirname(output_path), exist_ok=True) | |
| # print(f"[DEBUG {time.time():.2f}] load_dataset: Ensured directory '{os.path.dirname(output_path)}' exists.") # DEBUG | |
| def save_dataset(self, output_dict, output_dir): | |
| output_path = self.make_output_path(output_dir=output_dir) | |
| # print(f"\n[DEBUG {time.time():.2f}] save_dataset: Attempting to save dataset for '{self.output_file}' ({len(output_dict)} items) to '{output_path}'.") # DEBUG | |
| print(f"\nAttempting to save dataset... {output_path}") | |
| # Robust save with interrupt handling (adapted from PipelineStep) | |
| interrupt_count = 0 | |
| max_interrupts = 5 | |
| original_sigint_handler = signal.getsignal(signal.SIGINT) | |
| def _robust_save_interrupt_handler(sig, frame): | |
| nonlocal interrupt_count | |
| interrupt_count += 1 | |
| if interrupt_count == 1: | |
| _print_banner("SAVE WARNING") | |
| # print(f"[DEBUG {time.time():.2f}] _robust_save_interrupt_handler: First interrupt during save for '{self.output_file}'.") # DEBUG | |
| print( | |
| f"\nWARNING: Interrupt detected during critical dataset save for step '{self.output_file}'!", | |
| file=sys.stderr, | |
| ) | |
| print( | |
| f"Press Ctrl+C {max_interrupts - interrupt_count} more times consecutively to force exit (DATA LOSS RISK!).", | |
| file=sys.stderr, | |
| ) | |
| elif interrupt_count < max_interrupts: | |
| print( | |
| f"\nInterrupt {interrupt_count}/{max_interrupts}. Press Ctrl+C {max_interrupts - interrupt_count} more times to force exit.", | |
| file=sys.stderr, | |
| ) | |
| # print(f"[DEBUG {time.time():.2f}] _robust_save_interrupt_handler: Interrupt {interrupt_count}/{max_interrupts} during save for '{self.output_file}'.") # DEBUG | |
| else: | |
| print( | |
| f"\n{max_interrupts} consecutive interrupts detected during save. Forcing exit.", | |
| file=sys.stderr, | |
| ) | |
| # print(f"[DEBUG {time.time():.2f}] _robust_save_interrupt_handler: Max interrupts reached for '{self.output_file}'. Forcing exit.") # DEBUG | |
| signal.signal( | |
| signal.SIGINT, original_sigint_handler | |
| ) # Restore before exit | |
| sys.exit(130) # Exit code 130 for SIGINT | |
| save_successful = False | |
| try: | |
| # print(f"[DEBUG {time.time():.2f}] save_dataset: Setting SIGINT handler for robust save.") # DEBUG | |
| signal.signal( | |
| signal.SIGINT, _robust_save_interrupt_handler | |
| ) # Set custom handler | |
| # Ensure directory exists | |
| # print(f"[DEBUG {time.time():.2f}] save_dataset: Ensuring directory '{os.path.dirname(output_path)}' exists.") # DEBUG | |
| os.makedirs(os.path.dirname(output_path), exist_ok=True) | |
| # Use file lock for writing | |
| # print(f"[DEBUG {time.time():.2f}] save_dataset: Lock acquired. Starting atomic write.") # DEBUG | |
| # Atomic write using temp file | |
| temp_output_path = ( | |
| f"{output_path}.temp_{os.getpid()}" # Add PID for extra safety | |
| ) | |
| # print(f"[DEBUG {time.time():.2f}] save_dataset: Writing to temp file: '{temp_output_path}'.") # DEBUG | |
| try: | |
| with open(temp_output_path, "w", encoding="utf-8") as f: | |
| json.dump( | |
| output_dict, f, indent=None, ensure_ascii=True | |
| ) # Use ensure_ascii=True for cross-platform compatibility | |
| # print(f"[DEBUG {time.time():.2f}] save_dataset: Temp file write complete. Replacing original file.") # DEBUG | |
| os.replace(temp_output_path, output_path) # Atomic rename/replace | |
| # print(f"[DEBUG {time.time():.2f}] save_dataset: Atomic replace successful.") # DEBUG | |
| save_successful = True | |
| except Exception as write_error: | |
| # print(f"[DEBUG {time.time():.2f}] save_dataset: ERROR during file write/replace: {write_error}") # DEBUG | |
| print( | |
| f"\nERROR during file write/replace operation: {write_error}", | |
| file=sys.stderr, | |
| ) | |
| traceback.print_exc() | |
| # Clean up temp file if replace failed | |
| if os.path.exists(temp_output_path): | |
| # print(f"[DEBUG {time.time():.2f}] save_dataset: Attempting to remove failed temp file '{temp_output_path}'.") # DEBUG | |
| try: | |
| os.remove(temp_output_path) | |
| except OSError as remove_error: | |
| print( | |
| f"Error removing temp file {temp_output_path}: {remove_error}", | |
| file=sys.stderr, | |
| ) | |
| raise # Re-raise the error after attempting cleanup | |
| finally: | |
| # Ensure temp file is removed if it somehow still exists after a successful replace (unlikely but safe) | |
| if save_successful and os.path.exists(temp_output_path): | |
| # print(f"[DEBUG {time.time():.2f}] save_dataset: Ensuring temp file '{temp_output_path}' is removed after successful save.") # DEBUG | |
| try: | |
| os.remove(temp_output_path) | |
| except OSError: | |
| pass # Ignore error if removal fails post-success | |
| # print(f"[DEBUG {time.time():.2f}] save_dataset: Lock released.") # DEBUG | |
| except Exception as e: | |
| # print(f"[DEBUG {time.time():.2f}] save_dataset: Exception during save process: {e}") # DEBUG | |
| print(f"\nAn error occurred during the save process: {e}", file=sys.stderr) | |
| # The finally block handles handler restoration. | |
| # Consider re-raising if the pipeline should halt on save failure. | |
| # raise e | |
| finally: | |
| # print(f"[DEBUG {time.time():.2f}] save_dataset: Restoring original SIGINT handler.") # DEBUG | |
| signal.signal( | |
| signal.SIGINT, original_sigint_handler | |
| ) # ALWAYS restore handler | |
| if save_successful: | |
| # print(f"[DEBUG {time.time():.2f}] save_dataset: Save successful for '{output_path}'.") # DEBUG | |
| print( | |
| f"Dataset for '{self.output_file}' saved successfully to {output_path}." | |
| ) | |
| elif interrupt_count < max_interrupts: | |
| # print(f"[DEBUG {time.time():.2f}] save_dataset: Save may be incomplete for '{output_path}' due to error or interrupt.") # DEBUG | |
| print( | |
| f"Dataset save for '{self.output_file}' may be incomplete due to an error or interruption.", | |
| file=sys.stderr, | |
| ) | |
| # Reset counter is implicit as it's local to the next call | |
| # print(f"[DEBUG {time.time():.2f}] save_dataset: Save process finished.") # DEBUG | |
| async def run( | |
| self, | |
| key=None, | |
| input_data=None, | |
| engine_wrapper=None, | |
| default_prompt_folder=None, | |
| prompt_folder=None, | |
| completion_mode=False, | |
| use_stop=True, | |
| output_dict=None, | |
| include_details=False, | |
| **kwargs, | |
| ): | |
| # print(f"[DEBUG {time.time():.2f}] run: Starting for key '{key}'. Completion mode: {completion_mode}.") # DEBUG | |
| full_prompt_path = ( | |
| self.prompt_path + ".yaml" | |
| if not completion_mode | |
| else self.prompt_path + ".txt" | |
| ) | |
| # print(f"[DEBUG {time.time():.2f}] run: Prompt path: '{full_prompt_path}'.") # DEBUG | |
| # Check existing entries IN MEMORY | |
| # print(f"[DEBUG {time.time():.2f}] run: Checking previous output for key '{key}'.") # DEBUG | |
| if await self.read_previous_output(key, output_dict): # Pass output_dict | |
| # print(f"[DEBUG {time.time():.2f}] run: Previous output found for key '{key}'. Skipping.") # DEBUG | |
| return # Skip generation if already processed | |
| # print(f"[DEBUG {time.time():.2f}] run: No previous output found for key '{key}'. Proceeding.") # DEBUG | |
| # print(f"[DEBUG {time.time():.2f}] run: Processing input data for key '{key}'.") # DEBUG | |
| if asyncio.iscoroutinefunction(self.process_input_data): | |
| processed_data, additional_kwargs = await self.process_input_data(input_data) | |
| else: | |
| processed_data, additional_kwargs = self.process_input_data(input_data) | |
| # print(f"[DEBUG {time.time():.2f}] run: Input data processed for key '{key}'.") # DEBUG | |
| error_message = "" | |
| complete = False | |
| retries_left = self.max_retries | |
| # print(f"[DEBUG {time.time():.2f}] run: Starting generation loop for key '{key}'. Max retries: {retries_left}.") # DEBUG | |
| while not complete and retries_left > 0: | |
| # print(f"[DEBUG {time.time():.2f}] run: Attempting generation for key '{key}'. Retries left: {retries_left}.") # DEBUG | |
| try: | |
| # print(f"[DEBUG {time.time():.2f}] run: Calling generate_data for key '{key}'.") # DEBUG | |
| start_time = time.time() # DEBUG TIME | |
| result, full_output, full_response, full_input = ( | |
| await self.generate_data( | |
| processed_data=processed_data, | |
| engine_wrapper=engine_wrapper, | |
| prompt_path=full_prompt_path, | |
| prompt_folder=prompt_folder, | |
| default_prompt_folder=default_prompt_folder, | |
| completion_mode=completion_mode, | |
| use_stop=use_stop, | |
| error_message=error_message, | |
| **kwargs, | |
| **additional_kwargs, | |
| ) | |
| ) | |
| end_time = time.time() # DEBUG TIME | |
| # print(f"[DEBUG {end_time:.2f}] run: generate_data call for key '{key}' completed in {end_time - start_time:.2f} seconds.") # DEBUG | |
| # Ensure result is a list for OneToMany validation | |
| if not isinstance(result, list): | |
| # print(f"[DEBUG {time.time():.2f}] run: WARNING - Expected list result from generate_data for key {key}, got {type(result)}. Raising TypeError.") # DEBUG | |
| print( | |
| f"Warning: Expected list result from generate_data for key {key}, got {type(result)}. Treating as failed attempt." | |
| ) | |
| raise TypeError("Generator did not return a list") # Trigger retry | |
| # print(f"[DEBUG {time.time():.2f}] run: Generation successful for key '{key}'. Result is a list of length {len(result)}. Validating...") # DEBUG | |
| validation_result = self.validation_function(result, input_data) | |
| if validation_result["result"]: | |
| # print(f"[DEBUG {time.time():.2f}] run: Validation successful for key '{key}'.") # DEBUG | |
| complete = True | |
| else: | |
| # print(f"[DEBUG {time.time():.2f}] run: Validation FAILED for key {key}.") # DEBUG | |
| error_message = validation_result["message"] | |
| print(f"Validation failed for key {key}") | |
| except Exception as e: | |
| # print(f"[DEBUG {time.time():.2f}] run: Exception during generation/validation for key '{key}': {type(e).__name__}: {e}") # DEBUG | |
| print(e) | |
| error_message = str(e) | |
| traceback.print_exc() | |
| retries_left -= 1 | |
| if not complete and retries_left > 0: | |
| # print(f"[DEBUG {time.time():.2f}] run: Retrying generation for key '{key}'. {retries_left} retries remaining.") # DEBUG | |
| await asyncio.sleep(1) # Optional: Add a small delay before retrying | |
| elif not complete: | |
| # print(f"[DEBUG {time.time():.2f}] run: Generation failed for key '{key}' after exhausting retries.") # DEBUG | |
| pass # Added pass to fix potential indentation error | |
| if not complete: # consider raising here and catching in the actual pipeline. | |
| # print(f"[DEBUG {time.time():.2f}] run: Generation ultimately failed for key '{key}'. Returning None.") # DEBUG | |
| return | |
| # print(f"[DEBUG {time.time():.2f}] run: Generation complete for key '{key}'. Calling save.") # DEBUG | |
| save_result = self.save( | |
| result=result, | |
| key=key, | |
| output_dict=output_dict, # Pass the shared dictionary | |
| input_data=input_data, | |
| full_output=full_output, | |
| full_response=full_response, | |
| full_input=full_input, | |
| include_details=include_details, | |
| completion_mode=completion_mode, | |
| ) | |
| # print(f"[DEBUG {time.time():.2f}] run: Save call completed for key '{key}'. Returning result: {type(save_result)}") # DEBUG | |
| return save_result | |
| async def execute_pipeline( | |
| self, | |
| input_dict={}, | |
| engine_wrapper=None, | |
| rtwl=None, | |
| default_prompt_folder=None, | |
| prompt_folder=None, | |
| output_dir=None, | |
| completion_mode=None, | |
| use_stop=None, | |
| include_details=False, | |
| **kwargs, | |
| ): | |
| # print(f"[DEBUG {time.time():.2f}] execute_pipeline: Starting execution for step '{self.output_file}'. Input dict size: {len(input_dict)}.") # DEBUG | |
| output_dict = {} # Initialize the dictionary for this step's results | |
| # print(f"[DEBUG {time.time():.2f}] execute_pipeline: Loading dataset for '{self.output_file}'.") # DEBUG | |
| self.load_dataset(output_dict, output_dir) # Load existing data at the start | |
| # print(f"[DEBUG {time.time():.2f}] execute_pipeline: Dataset loaded. Current output_dict size: {len(output_dict)}.") # DEBUG | |
| try: | |
| # Note: input_dict here refers to the *previous* step's output | |
| # print(f"[DEBUG {time.time():.2f}] execute_pipeline: Preparing generation tasks from input_dict (size {len(input_dict)}).") # DEBUG | |
| data_generations_tasks = [] | |
| for i, (key, value) in enumerate(input_dict.items()): | |
| # print(f"[DEBUG {time.time():.2f}] execute_pipeline: Creating task {i+1}/{len(input_dict)} for key '{key}'.") # DEBUG | |
| task = self.run( | |
| input_data=value, | |
| engine_wrapper=engine_wrapper, | |
| key=key, | |
| default_prompt_folder=default_prompt_folder, | |
| prompt_folder=prompt_folder, | |
| completion_mode=completion_mode, | |
| use_stop=use_stop, | |
| output_dict=output_dict, # Pass the shared dict to run/save | |
| include_details=include_details, | |
| **kwargs, | |
| ) | |
| data_generations_tasks.append(task) | |
| # data_generations_tasks = [ | |
| # self.run( | |
| # input_data=value, | |
| # engine_wrapper=engine_wrapper, | |
| # key=key, | |
| # default_prompt_folder=default_prompt_folder, | |
| # prompt_folder=prompt_folder, | |
| # completion_mode=completion_mode, | |
| # use_stop=use_stop, | |
| # output_dict=output_dict, # Pass the shared dict to run/save | |
| # include_details=include_details, | |
| # **kwargs | |
| # ) for key, value in input_dict.items() # Iterate over inputs from previous step | |
| # ] | |
| if not data_generations_tasks: | |
| # print(f"[DEBUG {time.time():.2f}] execute_pipeline: Warning - No tasks created for step '{self.output_file}'.") # DEBUG | |
| print("Warning: No tasks to run for this step.") | |
| # print(f"[DEBUG {time.time():.2f}] execute_pipeline: Created {len(data_generations_tasks)} generation tasks. Wrapping with Rate-Limited Wrapper (rtwl).") # DEBUG | |
| coroutines = [rtwl(task) for task in data_generations_tasks] | |
| # print(f"[DEBUG {time.time():.2f}] execute_pipeline: Wrapped {len(coroutines)} tasks. Starting processing with tqdm.") # DEBUG | |
| print(f"Processing {len(coroutines)} items...") | |
| TASK_TIMEOUT_SECONDS = 600 # 10 minutes timeout | |
| processed_count = 0 | |
| total_tasks = len(coroutines) | |
| for future in tqdmasyncio.tqdm( | |
| tqdmasyncio.tqdm.as_completed(coroutines), | |
| total=len(coroutines), | |
| desc=f"Step: {self.output_file}", | |
| ): | |
| processed_count += 1 # DEBUG | |
| # print(f"[DEBUG {time.time():.2f}] execute_pipeline: Awaiting future {processed_count}/{len(coroutines)}.") # DEBUG - Can be very verbose | |
| try: | |
| # Wrap the await future with a timeout | |
| await asyncio.wait_for(future, timeout=TASK_TIMEOUT_SECONDS) | |
| # print(f"[DEBUG {time.time():.2f}] execute_pipeline: Future {processed_count}/{len(coroutines)} completed successfully.") # DEBUG | |
| except asyncio.TimeoutError: | |
| print( | |
| f"\nWARNING: Task {processed_count}/{total_tasks} for step '{self.output_file}' timed out after {TASK_TIMEOUT_SECONDS} seconds.", | |
| file=sys.stderr, | |
| ) | |
| # Task is cancelled by wait_for, loop continues | |
| except Exception as task_exc: | |
| # print(f"[DEBUG {time.time():.2f}] execute_pipeline: Error in awaited task {processed_count}/{len(coroutines)}: {task_exc}") # DEBUG | |
| # Log exceptions from individual run tasks if needed, but allow others to continue | |
| print(f"\nError in awaited task: {task_exc}") | |
| traceback.print_exc() # Optionally print traceback for task errors | |
| # print(f"[DEBUG {time.time():.2f}] execute_pipeline: Finished processing all {len(coroutines)} futures.") # DEBUG | |
| # Filter out entries where the generation might have failed and didn't add the result key | |
| # This is crucial for OneToMany as failed 'run' won't add keys to output_dict | |
| # but we still need to ensure consistency if partial results were loaded. | |
| # However, the primary filtering happens because failed `run` calls simply don't add anything to `output_dict`. | |
| # A check after loading might be useful for corrupted files, but less so for runtime failures. | |
| # Consider if filtering is needed based on how `load_dataset` handles errors. | |
| # print(f"[DEBUG {time.time():.2f}] execute_pipeline: Optional filtering step (commented out).") # DEBUG | |
| # filter_out_nonpresent_keys(output_dict, key_to_check=self.result_key) # Optional: clean loaded data | |
| except Exception as e: | |
| # print(f"[DEBUG {time.time():.2f}] execute_pipeline: CRITICAL EXCEPTION during execution for step '{self.output_file}': {e}") # DEBUG | |
| print( | |
| f"\nCritical exception during pipeline execution for step {self.output_file}: {e}" | |
| ) | |
| traceback.print_exc() | |
| # The finally block will still attempt to save. Re-raise if pipeline should stop. | |
| raise e | |
| finally: | |
| final_count = len(output_dict) | |
| # print(f"[DEBUG {time.time():.2f}] execute_pipeline: FINALLY block reached. Final item count: {final_count}. Saving dataset.") # DEBUG | |
| print( | |
| f"\nSaving results for step '{self.output_file}'. Final item count: {final_count}" | |
| ) | |
| # Robust save called in finally block | |
| filter_out_nonpresent_keys(output_dict, key_to_check=self.result_key) | |
| self.save_dataset(output_dict, output_dir) | |
| # print(f"[DEBUG {time.time():.2f}] execute_pipeline: FINALLY block complete after save_dataset call.") # DEBUG | |
| # print(f"[DEBUG {time.time():.2f}] execute_pipeline: Execution finished for step '{self.output_file}'. Returning output_dict with {len(output_dict)} items.") # DEBUG | |
| return output_dict # Return the completed dictionary | |